Bladeren bron

Merge dev into refactor/server-routes

- Resolved conflict in server.ts by keeping refactored route structure
- Added directory and roots query params to SessionRoutes from dev

Co-authored-by: Leka74 <[email protected]>
Aiden Cline 3 maanden geleden
bovenliggende
commit
6ada2968de
100 gewijzigde bestanden met toevoegingen van 9460 en 1499 verwijderingen
  1. 10 1
      .github/workflows/nix-desktop.yml
  2. 2 2
      .github/workflows/pr-standards.yml
  3. 1 1
      .github/workflows/publish.yml
  4. 105 15
      .github/workflows/update-nix-hashes.yml
  5. 1 0
      .gitignore
  6. 2 2
      .opencode/agent/triage.md
  7. 24 0
      .opencode/command/ai-deps.md
  8. 3 3
      AGENTS.md
  9. 1 1
      README.md
  10. 40 0
      SECURITY.md
  11. 202 195
      STATS.md
  12. 69 9
      STYLE_GUIDE.md
  13. 184 99
      bun.lock
  14. 3 3
      flake.lock
  15. 23 3
      flake.nix
  16. 6 5
      github/README.md
  17. 4 0
      infra/console.ts
  18. 1 1
      install
  19. 6 1
      nix/hashes.json
  20. 4 2
      nix/node-modules.nix
  21. 10 3
      nix/scripts/update-hashes.sh
  22. 3 2
      package.json
  23. 2 32
      packages/app/index.html
  24. 1 1
      packages/app/package.json
  25. 28 0
      packages/app/public/oc-theme-preload.js
  26. 12 15
      packages/app/src/app.tsx
  27. 99 0
      packages/app/src/components/dialog-fork.tsx
  28. 154 25
      packages/app/src/components/dialog-select-file.tsx
  29. 1 1
      packages/app/src/components/dialog-select-model.tsx
  30. 81 14
      packages/app/src/components/dialog-select-server.tsx
  31. 35 8
      packages/app/src/components/prompt-input.tsx
  32. 2 1
      packages/app/src/components/session-context-usage.tsx
  33. 178 228
      packages/app/src/components/session/session-header.tsx
  34. 6 3
      packages/app/src/components/terminal.tsx
  35. 118 0
      packages/app/src/components/titlebar.tsx
  36. 12 69
      packages/app/src/context/command.tsx
  37. 2 1
      packages/app/src/context/global-sdk.tsx
  38. 195 31
      packages/app/src/context/global-sync.tsx
  39. 94 26
      packages/app/src/context/layout.tsx
  40. 9 0
      packages/app/src/context/platform.tsx
  41. 1 4
      packages/app/src/context/server.tsx
  42. 1 1
      packages/app/src/context/sync.tsx
  43. 15 1
      packages/app/src/context/terminal.tsx
  44. 4 0
      packages/app/src/index.css
  45. 8 0
      packages/app/src/pages/directory-layout.tsx
  46. 793 434
      packages/app/src/pages/layout.tsx
  47. 36 11
      packages/app/src/pages/session.tsx
  48. 8 2
      packages/console/app/package.json
  49. 1 0
      packages/console/app/public/social-share-black.png
  50. BIN
      packages/console/app/src/asset/black/hero.png
  51. 3 0
      packages/console/app/src/component/footer.tsx
  52. 15 0
      packages/console/app/src/component/spotlight.css
  53. 820 0
      packages/console/app/src/component/spotlight.tsx
  54. 3 3
      packages/console/app/src/config.ts
  55. 0 24
      packages/console/app/src/context/auth.session.ts
  56. 26 1
      packages/console/app/src/context/auth.ts
  57. 7 6
      packages/console/app/src/lib/github.ts
  58. 3 2
      packages/console/app/src/routes/auth/[...callback].ts
  59. 4 1
      packages/console/app/src/routes/auth/authorize.ts
  60. 1 1
      packages/console/app/src/routes/auth/logout.ts
  61. 1 1
      packages/console/app/src/routes/auth/status.ts
  62. 828 0
      packages/console/app/src/routes/black.css
  63. 235 0
      packages/console/app/src/routes/black.tsx
  64. 62 0
      packages/console/app/src/routes/black/common.tsx
  65. 108 0
      packages/console/app/src/routes/black/index.tsx
  66. 451 0
      packages/console/app/src/routes/black/subscribe/[plan].tsx
  67. 214 0
      packages/console/app/src/routes/black/workspace.css
  68. 134 0
      packages/console/app/src/routes/black/workspace.tsx
  69. 477 0
      packages/console/app/src/routes/changelog/index.css
  70. 147 0
      packages/console/app/src/routes/changelog/index.tsx
  71. 1 1
      packages/console/app/src/routes/download/[platform].ts
  72. 0 1
      packages/console/app/src/routes/download/index.css
  73. 7 5
      packages/console/app/src/routes/download/index.tsx
  74. 1 1
      packages/console/app/src/routes/index.css
  75. 9 2
      packages/console/app/src/routes/index.tsx
  76. 200 69
      packages/console/app/src/routes/stripe/webhook.ts
  77. 3 0
      packages/console/app/src/routes/temp.tsx
  78. 1 1
      packages/console/app/src/routes/user-menu.tsx
  79. 54 0
      packages/console/app/src/routes/workspace/[id]/billing/black-section.module.css
  80. 76 1
      packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx
  81. 12 2
      packages/console/app/src/routes/workspace/common.tsx
  82. 38 28
      packages/console/app/src/routes/zen/util/handler.ts
  83. 161 56
      packages/console/app/src/routes/zen/util/provider/anthropic.ts
  84. 5 4
      packages/console/app/src/routes/zen/util/provider/google.ts
  85. 3 2
      packages/console/app/src/routes/zen/util/provider/openai-compatible.ts
  86. 3 2
      packages/console/app/src/routes/zen/util/provider/openai.ts
  87. 3 2
      packages/console/app/src/routes/zen/util/provider/provider.ts
  88. 1 1
      packages/console/app/src/routes/zen/util/stickyProviderTracker.ts
  89. 1 1
      packages/console/app/tsconfig.json
  90. 1 0
      packages/console/core/migrations/0051_jazzy_green_goblin.sql
  91. 1 0
      packages/console/core/migrations/0052_aromatic_agent_zero.sql
  92. 1228 0
      packages/console/core/migrations/meta/0051_snapshot.json
  93. 1235 0
      packages/console/core/migrations/meta/0052_snapshot.json
  94. 14 0
      packages/console/core/migrations/meta/_journal.json
  95. 2 1
      packages/console/core/package.json
  96. 41 0
      packages/console/core/script/black-cancel-waitlist.ts
  97. 163 0
      packages/console/core/script/black-transfer.ts
  98. 15 0
      packages/console/core/script/credit-workspace.ts
  99. 28 2
      packages/console/core/script/lookup-user.ts
  100. 14 22
      packages/console/core/script/promote-models.ts

+ 10 - 1
.github/workflows/nix-desktop.yml

@@ -9,6 +9,13 @@ on:
       - "nix/**"
       - "nix/**"
       - "packages/app/**"
       - "packages/app/**"
       - "packages/desktop/**"
       - "packages/desktop/**"
+  pull_request:
+    paths:
+      - "flake.nix"
+      - "flake.lock"
+      - "nix/**"
+      - "packages/app/**"
+      - "packages/desktop/**"
   workflow_dispatch:
   workflow_dispatch:
 
 
 jobs:
 jobs:
@@ -18,6 +25,8 @@ jobs:
       matrix:
       matrix:
         os:
         os:
           - blacksmith-4vcpu-ubuntu-2404
           - blacksmith-4vcpu-ubuntu-2404
+          - blacksmith-4vcpu-ubuntu-2404-arm
+          - macos-15
           - macos-latest
           - macos-latest
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     timeout-minutes: 60
     timeout-minutes: 60
@@ -26,7 +35,7 @@ jobs:
         uses: actions/checkout@v6
         uses: actions/checkout@v6
 
 
       - name: Setup Nix
       - name: Setup Nix
-        uses: DeterminateSystems/nix-installer-action@v21
+        uses: nixbuild/nix-quick-install-action@v34
 
 
       - name: Build desktop via flake
       - name: Build desktop via flake
         run: |
         run: |

+ 2 - 2
.github/workflows/pr-standards.yml

@@ -105,7 +105,7 @@ jobs:
               query($owner: String!, $repo: String!, $number: Int!) {
               query($owner: String!, $repo: String!, $number: Int!) {
                 repository(owner: $owner, name: $repo) {
                 repository(owner: $owner, name: $repo) {
                   pullRequest(number: $number) {
                   pullRequest(number: $number) {
-                    issuesReferences(first: 1) {
+                    closingIssuesReferences(first: 1) {
                       totalCount
                       totalCount
                     }
                     }
                   }
                   }
@@ -119,7 +119,7 @@ jobs:
               number: pr.number
               number: pr.number
             });
             });
 
 
-            const linkedIssues = result.repository.pullRequest.issuesReferences.totalCount;
+            const linkedIssues = result.repository.pullRequest.closingIssuesReferences.totalCount;
 
 
             if (linkedIssues === 0) {
             if (linkedIssues === 0) {
               await addLabel('needs:issue');
               await addLabel('needs:issue');

+ 1 - 1
.github/workflows/publish.yml

@@ -92,7 +92,7 @@ jobs:
 
 
   publish-tauri:
   publish-tauri:
     needs: publish
     needs: publish
-    continue-on-error: true
+    continue-on-error: false
     strategy:
     strategy:
       fail-fast: false
       fail-fast: false
       matrix:
       matrix:

+ 105 - 15
.github/workflows/update-nix-hashes.yml

@@ -17,11 +17,11 @@ on:
       - "packages/*/package.json"
       - "packages/*/package.json"
 
 
 jobs:
 jobs:
-  update:
+  update-flake:
     if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
     if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
     runs-on: blacksmith-4vcpu-ubuntu-2404
     runs-on: blacksmith-4vcpu-ubuntu-2404
     env:
     env:
-      SYSTEM: x86_64-linux
+      TITLE: flake.lock
 
 
     steps:
     steps:
       - name: Checkout repository
       - name: Checkout repository
@@ -33,39 +33,129 @@ jobs:
           repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
           repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
 
 
       - name: Setup Nix
       - name: Setup Nix
-        uses: DeterminateSystems/nix-installer-action@v20
+        uses: nixbuild/nix-quick-install-action@v34
 
 
       - name: Configure git
       - name: Configure git
         run: |
         run: |
           git config --global user.email "[email protected]"
           git config --global user.email "[email protected]"
           git config --global user.name "Github Action"
           git config --global user.name "Github Action"
 
 
-      - name: Update flake.lock
+      - name: Update ${{ env.TITLE }}
         run: |
         run: |
           set -euo pipefail
           set -euo pipefail
-          echo "📦 Updating flake.lock..."
+          echo "📦 Updating $TITLE..."
           nix flake update
           nix flake update
-          echo "✅ flake.lock updated successfully"
+          echo "✅ $TITLE updated successfully"
 
 
-      - name: Update node_modules hash
+      - name: Commit ${{ env.TITLE }} changes
+        env:
+          TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
+        run: |
+          set -euo pipefail
+
+          echo "🔍 Checking for changes in tracked files..."
+
+          summarize() {
+            local status="$1"
+            {
+              echo "### Nix $TITLE"
+              echo ""
+              echo "- ref: ${GITHUB_REF_NAME}"
+              echo "- status: ${status}"
+            } >> "$GITHUB_STEP_SUMMARY"
+            if [ -n "${GITHUB_SERVER_URL:-}" ] && [ -n "${GITHUB_REPOSITORY:-}" ] && [ -n "${GITHUB_RUN_ID:-}" ]; then
+              echo "- run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" >> "$GITHUB_STEP_SUMMARY"
+            fi
+            echo "" >> "$GITHUB_STEP_SUMMARY"
+          }
+          FILES=(flake.lock flake.nix)
+          STATUS="$(git status --short -- "${FILES[@]}" || true)"
+          if [ -z "$STATUS" ]; then
+            echo "✅ No changes detected."
+            summarize "no changes"
+            exit 0
+          fi
+
+          echo "📝 Changes detected:"
+          echo "$STATUS"
+          echo "🔗 Staging files..."
+          git add "${FILES[@]}"
+          echo "💾 Committing changes..."
+          git commit -m "Update $TITLE"
+          echo "✅ Changes committed"
+
+          BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
+          echo "🌳 Pulling latest from branch: $BRANCH"
+          git pull --rebase origin "$BRANCH"
+          echo "🚀 Pushing changes to branch: $BRANCH"
+          git push origin HEAD:"$BRANCH"
+          echo "✅ Changes pushed successfully"
+
+          summarize "committed $(git rev-parse --short HEAD)"
+
+  update-node-modules-hash:
+    needs: update-flake
+    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - system: x86_64-linux
+            host: blacksmith-4vcpu-ubuntu-2404
+          - system: aarch64-linux
+            host: blacksmith-4vcpu-ubuntu-2404-arm
+          - system: x86_64-darwin
+            host: macos-15-intel
+          - system: aarch64-darwin
+            host: macos-latest
+    runs-on: ${{ matrix.host }}
+    env:
+      SYSTEM: ${{ matrix.system }}
+      TITLE: node_modules hash (${{ matrix.system }})
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          fetch-depth: 0
+          ref: ${{ github.head_ref || github.ref_name }}
+          repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
+
+      - name: Setup Nix
+        uses: nixbuild/nix-quick-install-action@v34
+
+      - name: Configure git
+        run: |
+          git config --global user.email "[email protected]"
+          git config --global user.name "Github Action"
+
+      - name: Pull latest changes
+        env:
+          TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
+        run: |
+          BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
+          git pull origin "$BRANCH"
+
+      - name: Update ${{ env.TITLE }}
         run: |
         run: |
           set -euo pipefail
           set -euo pipefail
-          echo "🔄 Updating node_modules hash..."
+          echo "🔄 Updating $TITLE..."
           nix/scripts/update-hashes.sh
           nix/scripts/update-hashes.sh
-          echo "✅ node_modules hash updated successfully"
+          echo "✅ $TITLE updated successfully"
 
 
-      - name: Commit hash changes
+      - name: Commit ${{ env.TITLE }} changes
         env:
         env:
           TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
           TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
         run: |
         run: |
           set -euo pipefail
           set -euo pipefail
 
 
-          echo "🔍 Checking for changes in tracked Nix files..."
+          echo "🔍 Checking for changes in tracked files..."
 
 
           summarize() {
           summarize() {
             local status="$1"
             local status="$1"
             {
             {
-              echo "### Nix Hash Update"
+              echo "### Nix $TITLE"
               echo ""
               echo ""
               echo "- ref: ${GITHUB_REF_NAME}"
               echo "- ref: ${GITHUB_REF_NAME}"
               echo "- status: ${status}"
               echo "- status: ${status}"
@@ -76,10 +166,10 @@ jobs:
             echo "" >> "$GITHUB_STEP_SUMMARY"
             echo "" >> "$GITHUB_STEP_SUMMARY"
           }
           }
 
 
-          FILES=(flake.lock flake.nix nix/node-modules.nix nix/hashes.json)
+          FILES=(nix/hashes.json)
           STATUS="$(git status --short -- "${FILES[@]}" || true)"
           STATUS="$(git status --short -- "${FILES[@]}" || true)"
           if [ -z "$STATUS" ]; then
           if [ -z "$STATUS" ]; then
-            echo "✅ No changes detected. Hashes are already up to date."
+            echo "✅ No changes detected."
             summarize "no changes"
             summarize "no changes"
             exit 0
             exit 0
           fi
           fi
@@ -89,7 +179,7 @@ jobs:
           echo "🔗 Staging files..."
           echo "🔗 Staging files..."
           git add "${FILES[@]}"
           git add "${FILES[@]}"
           echo "💾 Committing changes..."
           echo "💾 Committing changes..."
-          git commit -m "Update Nix flake.lock and hashes"
+          git commit -m "Update $TITLE"
           echo "✅ Changes committed"
           echo "✅ Changes committed"
 
 
           BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
           BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"

+ 1 - 0
.gitignore

@@ -24,3 +24,4 @@ target
 # Local dev files
 # Local dev files
 opencode-dev
 opencode-dev
 logs/
 logs/
+*.bun-build

+ 2 - 2
.opencode/agent/triage.md

@@ -45,9 +45,9 @@ Desktop app issues:
 
 
 #### zen
 #### zen
 
 
-**Only** add if the issue mentions "zen" or "opencode zen". Zen is our gateway for coding models. **Do not** add for other gateways or inference providers.
+**Only** add if the issue mentions "zen" or "opencode zen" or "opencode black".
 
 
-If the issue doesn't have "zen" in it then don't add zen label
+If the issue doesn't have "zen" or "opencode black" in it then don't add zen label
 
 
 #### docs
 #### docs
 
 

+ 24 - 0
.opencode/command/ai-deps.md

@@ -0,0 +1,24 @@
+---
+description: "Bump AI sdk dependencies minor / patch versions only"
+---
+
+Please read @package.json and @packages/opencode/package.json.
+
+Your job is to look into AI SDK dependencies, figure out if they have versions that can be upgraded (minor or patch versions ONLY no major ignore major changes).
+
+I want a report of every dependency and the version that can be upgraded to.
+What would be even better is if you can give me links to the changelog for each dependency, or at least some reference info so I can see what bugs were fixed or new features were added.
+
+Consider using subagents for each dep to save your context window.
+
+Here is a short list of some deps (please be comprehensive tho):
+
+- "ai"
+- "@ai-sdk/openai"
+- "@ai-sdk/anthropic"
+- "@openrouter/ai-sdk-provider"
+- etc, etc
+
+DO NOT upgrade the dependencies yet, just make a list of all dependencies and their versions that can be upgraded to minor or patch versions only.
+
+Write up your findings to ai-sdk-updates.md

+ 3 - 3
AGENTS.md

@@ -1,4 +1,4 @@
-- To test opencode in the `packages/opencode` directory you can run `bun dev`
-- To regenerate the javascript SDK, run ./packages/sdk/js/script/build.ts
+- To test opencode in `packages/opencode`, run `bun dev`.
+- To regenerate the JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
 - ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
 - ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
-- the default branch in this repo is `dev`
+- The default branch in this repo is `dev`.

+ 1 - 1
README.md

@@ -29,7 +29,7 @@ npm i -g opencode-ai@latest        # or bun/pnpm/yarn
 scoop bucket add extras; scoop install extras/opencode  # Windows
 scoop bucket add extras; scoop install extras/opencode  # Windows
 choco install opencode             # Windows
 choco install opencode             # Windows
 brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)
 brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)
-brew install opencode              # macOS and Linux (official brew formula, updated less frequently)
+brew install opencode              # macOS and Linux (official brew formula, updated less)
 paru -S opencode-bin               # Arch Linux
 paru -S opencode-bin               # Arch Linux
 mise use -g opencode               # Any OS
 mise use -g opencode               # Any OS
 nix run nixpkgs#opencode           # or github:anomalyco/opencode for latest dev branch
 nix run nixpkgs#opencode           # or github:anomalyco/opencode for latest dev branch

+ 40 - 0
SECURITY.md

@@ -0,0 +1,40 @@
+# Security
+
+## Threat Model
+
+### Overview
+
+OpenCode is an AI-powered coding assistant that runs locally on your machine. It provides an agent system with access to powerful tools including shell execution, file operations, and web access.
+
+### No Sandbox
+
+OpenCode does **not** sandbox the agent. The permission system exists as a UX feature to help users stay aware of what actions the agent is taking - it prompts for confirmation before executing commands, writing files, etc. However, it is not designed to provide security isolation.
+
+If you need true isolation, run OpenCode inside a Docker container or VM.
+
+### Server Mode
+
+Server mode is opt-in only. When enabled, set `OPENCODE_SERVER_PASSWORD` to require HTTP Basic Auth. Without this, the server runs unauthenticated (with a warning). It is the end user's responsibility to secure the server - any functionality it provides is not a vulnerability.
+
+### Out of Scope
+
+| Category                        | Rationale                                                               |
+| ------------------------------- | ----------------------------------------------------------------------- |
+| **Server access when opted-in** | If you enable server mode, API access is expected behavior              |
+| **Sandbox escapes**             | The permission system is not a sandbox (see above)                      |
+| **LLM provider data handling**  | Data sent to your configured LLM provider is governed by their policies |
+| **MCP server behavior**         | External MCP servers you configure are outside our trust boundary       |
+
+---
+
+# Reporting Security Issues
+
+We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
+
+To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/anomalyco/opencode/security/advisories/new) tab.
+
+The team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
+
+## Escalation
+
+If you do not receive an acknowledgement of your report within 6 business days, you may send an email to [email protected]

+ 202 - 195
STATS.md

@@ -1,197 +1,204 @@
 # Download Stats
 # Download Stats
 
 
-| Date       | GitHub Downloads     | npm Downloads       | Total                |
-| ---------- | -------------------- | ------------------- | -------------------- |
-| 2025-06-29 | 18,789 (+0)          | 39,420 (+0)         | 58,209 (+0)          |
-| 2025-06-30 | 20,127 (+1,338)      | 41,059 (+1,639)     | 61,186 (+2,977)      |
-| 2025-07-01 | 22,108 (+1,981)      | 43,745 (+2,686)     | 65,853 (+4,667)      |
-| 2025-07-02 | 24,814 (+2,706)      | 46,168 (+2,423)     | 70,982 (+5,129)      |
-| 2025-07-03 | 27,834 (+3,020)      | 49,955 (+3,787)     | 77,789 (+6,807)      |
-| 2025-07-04 | 30,608 (+2,774)      | 54,758 (+4,803)     | 85,366 (+7,577)      |
-| 2025-07-05 | 32,524 (+1,916)      | 58,371 (+3,613)     | 90,895 (+5,529)      |
-| 2025-07-06 | 33,766 (+1,242)      | 59,694 (+1,323)     | 93,460 (+2,565)      |
-| 2025-07-08 | 38,052 (+4,286)      | 64,468 (+4,774)     | 102,520 (+9,060)     |
-| 2025-07-09 | 40,924 (+2,872)      | 67,935 (+3,467)     | 108,859 (+6,339)     |
-| 2025-07-10 | 43,796 (+2,872)      | 71,402 (+3,467)     | 115,198 (+6,339)     |
-| 2025-07-11 | 46,982 (+3,186)      | 77,462 (+6,060)     | 124,444 (+9,246)     |
-| 2025-07-12 | 49,302 (+2,320)      | 82,177 (+4,715)     | 131,479 (+7,035)     |
-| 2025-07-13 | 50,803 (+1,501)      | 86,394 (+4,217)     | 137,197 (+5,718)     |
-| 2025-07-14 | 53,283 (+2,480)      | 87,860 (+1,466)     | 141,143 (+3,946)     |
-| 2025-07-15 | 57,590 (+4,307)      | 91,036 (+3,176)     | 148,626 (+7,483)     |
-| 2025-07-16 | 62,313 (+4,723)      | 95,258 (+4,222)     | 157,571 (+8,945)     |
-| 2025-07-17 | 66,684 (+4,371)      | 100,048 (+4,790)    | 166,732 (+9,161)     |
-| 2025-07-18 | 70,379 (+3,695)      | 102,587 (+2,539)    | 172,966 (+6,234)     |
-| 2025-07-19 | 73,497 (+3,117)      | 105,904 (+3,317)    | 179,401 (+6,434)     |
-| 2025-07-20 | 76,453 (+2,956)      | 109,044 (+3,140)    | 185,497 (+6,096)     |
-| 2025-07-21 | 80,197 (+3,744)      | 113,537 (+4,493)    | 193,734 (+8,237)     |
-| 2025-07-22 | 84,251 (+4,054)      | 118,073 (+4,536)    | 202,324 (+8,590)     |
-| 2025-07-23 | 88,589 (+4,338)      | 121,436 (+3,363)    | 210,025 (+7,701)     |
-| 2025-07-24 | 92,469 (+3,880)      | 124,091 (+2,655)    | 216,560 (+6,535)     |
-| 2025-07-25 | 96,417 (+3,948)      | 126,985 (+2,894)    | 223,402 (+6,842)     |
-| 2025-07-26 | 100,646 (+4,229)     | 131,411 (+4,426)    | 232,057 (+8,655)     |
-| 2025-07-27 | 102,644 (+1,998)     | 134,736 (+3,325)    | 237,380 (+5,323)     |
-| 2025-07-28 | 105,446 (+2,802)     | 136,016 (+1,280)    | 241,462 (+4,082)     |
-| 2025-07-29 | 108,998 (+3,552)     | 137,542 (+1,526)    | 246,540 (+5,078)     |
-| 2025-07-30 | 113,544 (+4,546)     | 140,317 (+2,775)    | 253,861 (+7,321)     |
-| 2025-07-31 | 118,339 (+4,795)     | 143,344 (+3,027)    | 261,683 (+7,822)     |
-| 2025-08-01 | 123,539 (+5,200)     | 146,680 (+3,336)    | 270,219 (+8,536)     |
-| 2025-08-02 | 127,864 (+4,325)     | 149,236 (+2,556)    | 277,100 (+6,881)     |
-| 2025-08-03 | 131,397 (+3,533)     | 150,451 (+1,215)    | 281,848 (+4,748)     |
-| 2025-08-04 | 136,266 (+4,869)     | 153,260 (+2,809)    | 289,526 (+7,678)     |
-| 2025-08-05 | 141,596 (+5,330)     | 155,752 (+2,492)    | 297,348 (+7,822)     |
-| 2025-08-06 | 147,067 (+5,471)     | 158,309 (+2,557)    | 305,376 (+8,028)     |
-| 2025-08-07 | 152,591 (+5,524)     | 160,889 (+2,580)    | 313,480 (+8,104)     |
-| 2025-08-08 | 158,187 (+5,596)     | 163,448 (+2,559)    | 321,635 (+8,155)     |
-| 2025-08-09 | 162,770 (+4,583)     | 165,721 (+2,273)    | 328,491 (+6,856)     |
-| 2025-08-10 | 165,695 (+2,925)     | 167,109 (+1,388)    | 332,804 (+4,313)     |
-| 2025-08-11 | 169,297 (+3,602)     | 167,953 (+844)      | 337,250 (+4,446)     |
-| 2025-08-12 | 176,307 (+7,010)     | 171,876 (+3,923)    | 348,183 (+10,933)    |
-| 2025-08-13 | 182,997 (+6,690)     | 177,182 (+5,306)    | 360,179 (+11,996)    |
-| 2025-08-14 | 189,063 (+6,066)     | 179,741 (+2,559)    | 368,804 (+8,625)     |
-| 2025-08-15 | 193,608 (+4,545)     | 181,792 (+2,051)    | 375,400 (+6,596)     |
-| 2025-08-16 | 198,118 (+4,510)     | 184,558 (+2,766)    | 382,676 (+7,276)     |
-| 2025-08-17 | 201,299 (+3,181)     | 186,269 (+1,711)    | 387,568 (+4,892)     |
-| 2025-08-18 | 204,559 (+3,260)     | 187,399 (+1,130)    | 391,958 (+4,390)     |
-| 2025-08-19 | 209,814 (+5,255)     | 189,668 (+2,269)    | 399,482 (+7,524)     |
-| 2025-08-20 | 214,497 (+4,683)     | 191,481 (+1,813)    | 405,978 (+6,496)     |
-| 2025-08-21 | 220,465 (+5,968)     | 194,784 (+3,303)    | 415,249 (+9,271)     |
-| 2025-08-22 | 225,899 (+5,434)     | 197,204 (+2,420)    | 423,103 (+7,854)     |
-| 2025-08-23 | 229,005 (+3,106)     | 199,238 (+2,034)    | 428,243 (+5,140)     |
-| 2025-08-24 | 232,098 (+3,093)     | 201,157 (+1,919)    | 433,255 (+5,012)     |
-| 2025-08-25 | 236,607 (+4,509)     | 202,650 (+1,493)    | 439,257 (+6,002)     |
-| 2025-08-26 | 242,783 (+6,176)     | 205,242 (+2,592)    | 448,025 (+8,768)     |
-| 2025-08-27 | 248,409 (+5,626)     | 205,242 (+0)        | 453,651 (+5,626)     |
-| 2025-08-28 | 252,796 (+4,387)     | 205,242 (+0)        | 458,038 (+4,387)     |
-| 2025-08-29 | 256,045 (+3,249)     | 211,075 (+5,833)    | 467,120 (+9,082)     |
-| 2025-08-30 | 258,863 (+2,818)     | 212,397 (+1,322)    | 471,260 (+4,140)     |
-| 2025-08-31 | 262,004 (+3,141)     | 213,944 (+1,547)    | 475,948 (+4,688)     |
-| 2025-09-01 | 265,359 (+3,355)     | 215,115 (+1,171)    | 480,474 (+4,526)     |
-| 2025-09-02 | 270,483 (+5,124)     | 217,075 (+1,960)    | 487,558 (+7,084)     |
-| 2025-09-03 | 274,793 (+4,310)     | 219,755 (+2,680)    | 494,548 (+6,990)     |
-| 2025-09-04 | 280,430 (+5,637)     | 222,103 (+2,348)    | 502,533 (+7,985)     |
-| 2025-09-05 | 283,769 (+3,339)     | 223,793 (+1,690)    | 507,562 (+5,029)     |
-| 2025-09-06 | 286,245 (+2,476)     | 225,036 (+1,243)    | 511,281 (+3,719)     |
-| 2025-09-07 | 288,623 (+2,378)     | 225,866 (+830)      | 514,489 (+3,208)     |
-| 2025-09-08 | 293,341 (+4,718)     | 227,073 (+1,207)    | 520,414 (+5,925)     |
-| 2025-09-09 | 300,036 (+6,695)     | 229,788 (+2,715)    | 529,824 (+9,410)     |
-| 2025-09-10 | 307,287 (+7,251)     | 233,435 (+3,647)    | 540,722 (+10,898)    |
-| 2025-09-11 | 314,083 (+6,796)     | 237,356 (+3,921)    | 551,439 (+10,717)    |
-| 2025-09-12 | 321,046 (+6,963)     | 240,728 (+3,372)    | 561,774 (+10,335)    |
-| 2025-09-13 | 324,894 (+3,848)     | 245,539 (+4,811)    | 570,433 (+8,659)     |
-| 2025-09-14 | 328,876 (+3,982)     | 248,245 (+2,706)    | 577,121 (+6,688)     |
-| 2025-09-15 | 334,201 (+5,325)     | 250,983 (+2,738)    | 585,184 (+8,063)     |
-| 2025-09-16 | 342,609 (+8,408)     | 255,264 (+4,281)    | 597,873 (+12,689)    |
-| 2025-09-17 | 351,117 (+8,508)     | 260,970 (+5,706)    | 612,087 (+14,214)    |
-| 2025-09-18 | 358,717 (+7,600)     | 266,922 (+5,952)    | 625,639 (+13,552)    |
-| 2025-09-19 | 365,401 (+6,684)     | 271,859 (+4,937)    | 637,260 (+11,621)    |
-| 2025-09-20 | 372,092 (+6,691)     | 276,917 (+5,058)    | 649,009 (+11,749)    |
-| 2025-09-21 | 377,079 (+4,987)     | 280,261 (+3,344)    | 657,340 (+8,331)     |
-| 2025-09-22 | 382,492 (+5,413)     | 284,009 (+3,748)    | 666,501 (+9,161)     |
-| 2025-09-23 | 387,008 (+4,516)     | 289,129 (+5,120)    | 676,137 (+9,636)     |
-| 2025-09-24 | 393,325 (+6,317)     | 294,927 (+5,798)    | 688,252 (+12,115)    |
-| 2025-09-25 | 398,879 (+5,554)     | 301,663 (+6,736)    | 700,542 (+12,290)    |
-| 2025-09-26 | 404,334 (+5,455)     | 306,713 (+5,050)    | 711,047 (+10,505)    |
-| 2025-09-27 | 411,618 (+7,284)     | 317,763 (+11,050)   | 729,381 (+18,334)    |
-| 2025-09-28 | 414,910 (+3,292)     | 322,522 (+4,759)    | 737,432 (+8,051)     |
-| 2025-09-29 | 419,919 (+5,009)     | 328,033 (+5,511)    | 747,952 (+10,520)    |
-| 2025-09-30 | 427,991 (+8,072)     | 336,472 (+8,439)    | 764,463 (+16,511)    |
-| 2025-10-01 | 433,591 (+5,600)     | 341,742 (+5,270)    | 775,333 (+10,870)    |
-| 2025-10-02 | 440,852 (+7,261)     | 348,099 (+6,357)    | 788,951 (+13,618)    |
-| 2025-10-03 | 446,829 (+5,977)     | 359,937 (+11,838)   | 806,766 (+17,815)    |
-| 2025-10-04 | 452,561 (+5,732)     | 370,386 (+10,449)   | 822,947 (+16,181)    |
-| 2025-10-05 | 455,559 (+2,998)     | 374,745 (+4,359)    | 830,304 (+7,357)     |
-| 2025-10-06 | 460,927 (+5,368)     | 379,489 (+4,744)    | 840,416 (+10,112)    |
-| 2025-10-07 | 467,336 (+6,409)     | 385,438 (+5,949)    | 852,774 (+12,358)    |
-| 2025-10-08 | 474,643 (+7,307)     | 394,139 (+8,701)    | 868,782 (+16,008)    |
-| 2025-10-09 | 479,203 (+4,560)     | 400,526 (+6,387)    | 879,729 (+10,947)    |
-| 2025-10-10 | 484,374 (+5,171)     | 406,015 (+5,489)    | 890,389 (+10,660)    |
-| 2025-10-11 | 488,427 (+4,053)     | 414,699 (+8,684)    | 903,126 (+12,737)    |
-| 2025-10-12 | 492,125 (+3,698)     | 418,745 (+4,046)    | 910,870 (+7,744)     |
-| 2025-10-14 | 505,130 (+13,005)    | 429,286 (+10,541)   | 934,416 (+23,546)    |
-| 2025-10-15 | 512,717 (+7,587)     | 439,290 (+10,004)   | 952,007 (+17,591)    |
-| 2025-10-16 | 517,719 (+5,002)     | 447,137 (+7,847)    | 964,856 (+12,849)    |
-| 2025-10-17 | 526,239 (+8,520)     | 457,467 (+10,330)   | 983,706 (+18,850)    |
-| 2025-10-18 | 531,564 (+5,325)     | 465,272 (+7,805)    | 996,836 (+13,130)    |
-| 2025-10-19 | 536,209 (+4,645)     | 469,078 (+3,806)    | 1,005,287 (+8,451)   |
-| 2025-10-20 | 541,264 (+5,055)     | 472,952 (+3,874)    | 1,014,216 (+8,929)   |
-| 2025-10-21 | 548,721 (+7,457)     | 479,703 (+6,751)    | 1,028,424 (+14,208)  |
-| 2025-10-22 | 557,949 (+9,228)     | 491,395 (+11,692)   | 1,049,344 (+20,920)  |
-| 2025-10-23 | 564,716 (+6,767)     | 498,736 (+7,341)    | 1,063,452 (+14,108)  |
-| 2025-10-24 | 572,692 (+7,976)     | 506,905 (+8,169)    | 1,079,597 (+16,145)  |
-| 2025-10-25 | 578,927 (+6,235)     | 516,129 (+9,224)    | 1,095,056 (+15,459)  |
-| 2025-10-26 | 584,409 (+5,482)     | 521,179 (+5,050)    | 1,105,588 (+10,532)  |
-| 2025-10-27 | 589,999 (+5,590)     | 526,001 (+4,822)    | 1,116,000 (+10,412)  |
-| 2025-10-28 | 595,776 (+5,777)     | 532,438 (+6,437)    | 1,128,214 (+12,214)  |
-| 2025-10-29 | 606,259 (+10,483)    | 542,064 (+9,626)    | 1,148,323 (+20,109)  |
-| 2025-10-30 | 613,746 (+7,487)     | 542,064 (+0)        | 1,155,810 (+7,487)   |
-| 2025-10-30 | 617,846 (+4,100)     | 555,026 (+12,962)   | 1,172,872 (+17,062)  |
-| 2025-10-31 | 626,612 (+8,766)     | 564,579 (+9,553)    | 1,191,191 (+18,319)  |
-| 2025-11-01 | 636,100 (+9,488)     | 581,806 (+17,227)   | 1,217,906 (+26,715)  |
-| 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)  |
-| 2025-11-11 | 729,769 (+7,481)     | 677,501 (+9,276)    | 1,407,270 (+16,757)  |
-| 2025-11-12 | 740,180 (+10,411)    | 686,454 (+8,953)    | 1,426,634 (+19,364)  |
-| 2025-11-13 | 749,905 (+9,725)     | 696,157 (+9,703)    | 1,446,062 (+19,428)  |
-| 2025-11-14 | 759,928 (+10,023)    | 705,237 (+9,080)    | 1,465,165 (+19,103)  |
-| 2025-11-15 | 765,955 (+6,027)     | 712,870 (+7,633)    | 1,478,825 (+13,660)  |
-| 2025-11-16 | 771,069 (+5,114)     | 716,596 (+3,726)    | 1,487,665 (+8,840)   |
-| 2025-11-17 | 780,161 (+9,092)     | 723,339 (+6,743)    | 1,503,500 (+15,835)  |
-| 2025-11-18 | 791,563 (+11,402)    | 732,544 (+9,205)    | 1,524,107 (+20,607)  |
-| 2025-11-19 | 804,409 (+12,846)    | 747,624 (+15,080)   | 1,552,033 (+27,926)  |
-| 2025-11-20 | 814,620 (+10,211)    | 757,907 (+10,283)   | 1,572,527 (+20,494)  |
-| 2025-11-21 | 826,309 (+11,689)    | 769,307 (+11,400)   | 1,595,616 (+23,089)  |
-| 2025-11-22 | 837,269 (+10,960)    | 780,996 (+11,689)   | 1,618,265 (+22,649)  |
-| 2025-11-23 | 846,609 (+9,340)     | 795,069 (+14,073)   | 1,641,678 (+23,413)  |
-| 2025-11-24 | 856,733 (+10,124)    | 804,033 (+8,964)    | 1,660,766 (+19,088)  |
-| 2025-11-25 | 869,423 (+12,690)    | 817,339 (+13,306)   | 1,686,762 (+25,996)  |
-| 2025-11-26 | 881,414 (+11,991)    | 832,518 (+15,179)   | 1,713,932 (+27,170)  |
-| 2025-11-27 | 893,960 (+12,546)    | 846,180 (+13,662)   | 1,740,140 (+26,208)  |
-| 2025-11-28 | 901,741 (+7,781)     | 856,482 (+10,302)   | 1,758,223 (+18,083)  |
-| 2025-11-29 | 908,689 (+6,948)     | 863,361 (+6,879)    | 1,772,050 (+13,827)  |
-| 2025-11-30 | 916,116 (+7,427)     | 870,194 (+6,833)    | 1,786,310 (+14,260)  |
-| 2025-12-01 | 925,898 (+9,782)     | 876,500 (+6,306)    | 1,802,398 (+16,088)  |
-| 2025-12-02 | 939,250 (+13,352)    | 890,919 (+14,419)   | 1,830,169 (+27,771)  |
-| 2025-12-03 | 952,249 (+12,999)    | 903,713 (+12,794)   | 1,855,962 (+25,793)  |
-| 2025-12-04 | 965,611 (+13,362)    | 916,471 (+12,758)   | 1,882,082 (+26,120)  |
-| 2025-12-05 | 977,996 (+12,385)    | 930,616 (+14,145)   | 1,908,612 (+26,530)  |
-| 2025-12-06 | 987,884 (+9,888)     | 943,773 (+13,157)   | 1,931,657 (+23,045)  |
-| 2025-12-07 | 994,046 (+6,162)     | 951,425 (+7,652)    | 1,945,471 (+13,814)  |
-| 2025-12-08 | 1,000,898 (+6,852)   | 957,149 (+5,724)    | 1,958,047 (+12,576)  |
-| 2025-12-09 | 1,011,488 (+10,590)  | 973,922 (+16,773)   | 1,985,410 (+27,363)  |
-| 2025-12-10 | 1,025,891 (+14,403)  | 991,708 (+17,786)   | 2,017,599 (+32,189)  |
-| 2025-12-11 | 1,045,110 (+19,219)  | 1,010,559 (+18,851) | 2,055,669 (+38,070)  |
-| 2025-12-12 | 1,061,340 (+16,230)  | 1,030,838 (+20,279) | 2,092,178 (+36,509)  |
-| 2025-12-13 | 1,073,561 (+12,221)  | 1,044,608 (+13,770) | 2,118,169 (+25,991)  |
-| 2025-12-14 | 1,082,042 (+8,481)   | 1,052,425 (+7,817)  | 2,134,467 (+16,298)  |
-| 2025-12-15 | 1,093,632 (+11,590)  | 1,059,078 (+6,653)  | 2,152,710 (+18,243)  |
-| 2025-12-16 | 1,120,477 (+26,845)  | 1,078,022 (+18,944) | 2,198,499 (+45,789)  |
-| 2025-12-17 | 1,151,067 (+30,590)  | 1,097,661 (+19,639) | 2,248,728 (+50,229)  |
-| 2025-12-18 | 1,178,658 (+27,591)  | 1,113,418 (+15,757) | 2,292,076 (+43,348)  |
-| 2025-12-19 | 1,203,485 (+24,827)  | 1,129,698 (+16,280) | 2,333,183 (+41,107)  |
-| 2025-12-20 | 1,223,000 (+19,515)  | 1,146,258 (+16,560) | 2,369,258 (+36,075)  |
-| 2025-12-21 | 1,242,675 (+19,675)  | 1,158,909 (+12,651) | 2,401,584 (+32,326)  |
-| 2025-12-22 | 1,262,522 (+19,847)  | 1,169,121 (+10,212) | 2,431,643 (+30,059)  |
-| 2025-12-23 | 1,286,548 (+24,026)  | 1,186,439 (+17,318) | 2,472,987 (+41,344)  |
-| 2025-12-24 | 1,309,323 (+22,775)  | 1,203,767 (+17,328) | 2,513,090 (+40,103)  |
-| 2025-12-25 | 1,333,032 (+23,709)  | 1,217,283 (+13,516) | 2,550,315 (+37,225)  |
-| 2025-12-26 | 1,352,411 (+19,379)  | 1,227,615 (+10,332) | 2,580,026 (+29,711)  |
-| 2025-12-27 | 1,371,771 (+19,360)  | 1,238,236 (+10,621) | 2,610,007 (+29,981)  |
-| 2025-12-28 | 1,390,388 (+18,617)  | 1,245,690 (+7,454)  | 2,636,078 (+26,071)  |
-| 2025-12-29 | 1,415,560 (+25,172)  | 1,257,101 (+11,411) | 2,672,661 (+36,583)  |
-| 2025-12-30 | 1,445,450 (+29,890)  | 1,272,689 (+15,588) | 2,718,139 (+45,478)  |
-| 2025-12-31 | 1,479,598 (+34,148)  | 1,293,235 (+20,546) | 2,772,833 (+54,694)  |
-| 2026-01-01 | 1,508,883 (+29,285)  | 1,309,874 (+16,639) | 2,818,757 (+45,924)  |
-| 2026-01-02 | 1,563,474 (+54,591)  | 1,320,959 (+11,085) | 2,884,433 (+65,676)  |
-| 2026-01-03 | 1,618,065 (+54,591)  | 1,331,914 (+10,955) | 2,949,979 (+65,546)  |
-| 2026-01-04 | 1,672,656 (+39,702)  | 1,339,883 (+7,969)  | 3,012,539 (+62,560)  |
-| 2026-01-05 | 1,738,171 (+65,515)  | 1,353,043 (+13,160) | 3,091,214 (+78,675)  |
-| 2026-01-06 | 1,960,988 (+222,817) | 1,377,377 (+24,334) | 3,338,365 (+247,151) |
-| 2026-01-07 | 2,123,239 (+162,251) | 1,398,648 (+21,271) | 3,521,887 (+183,522) |
-| 2026-01-08 | 2,272,630 (+149,391) | 1,432,480 (+33,832) | 3,705,110 (+183,223) |
+| Date       | GitHub Downloads     | npm Downloads        | Total                |
+| ---------- | -------------------- | -------------------- | -------------------- |
+| 2025-06-29 | 18,789 (+0)          | 39,420 (+0)          | 58,209 (+0)          |
+| 2025-06-30 | 20,127 (+1,338)      | 41,059 (+1,639)      | 61,186 (+2,977)      |
+| 2025-07-01 | 22,108 (+1,981)      | 43,745 (+2,686)      | 65,853 (+4,667)      |
+| 2025-07-02 | 24,814 (+2,706)      | 46,168 (+2,423)      | 70,982 (+5,129)      |
+| 2025-07-03 | 27,834 (+3,020)      | 49,955 (+3,787)      | 77,789 (+6,807)      |
+| 2025-07-04 | 30,608 (+2,774)      | 54,758 (+4,803)      | 85,366 (+7,577)      |
+| 2025-07-05 | 32,524 (+1,916)      | 58,371 (+3,613)      | 90,895 (+5,529)      |
+| 2025-07-06 | 33,766 (+1,242)      | 59,694 (+1,323)      | 93,460 (+2,565)      |
+| 2025-07-08 | 38,052 (+4,286)      | 64,468 (+4,774)      | 102,520 (+9,060)     |
+| 2025-07-09 | 40,924 (+2,872)      | 67,935 (+3,467)      | 108,859 (+6,339)     |
+| 2025-07-10 | 43,796 (+2,872)      | 71,402 (+3,467)      | 115,198 (+6,339)     |
+| 2025-07-11 | 46,982 (+3,186)      | 77,462 (+6,060)      | 124,444 (+9,246)     |
+| 2025-07-12 | 49,302 (+2,320)      | 82,177 (+4,715)      | 131,479 (+7,035)     |
+| 2025-07-13 | 50,803 (+1,501)      | 86,394 (+4,217)      | 137,197 (+5,718)     |
+| 2025-07-14 | 53,283 (+2,480)      | 87,860 (+1,466)      | 141,143 (+3,946)     |
+| 2025-07-15 | 57,590 (+4,307)      | 91,036 (+3,176)      | 148,626 (+7,483)     |
+| 2025-07-16 | 62,313 (+4,723)      | 95,258 (+4,222)      | 157,571 (+8,945)     |
+| 2025-07-17 | 66,684 (+4,371)      | 100,048 (+4,790)     | 166,732 (+9,161)     |
+| 2025-07-18 | 70,379 (+3,695)      | 102,587 (+2,539)     | 172,966 (+6,234)     |
+| 2025-07-19 | 73,497 (+3,117)      | 105,904 (+3,317)     | 179,401 (+6,434)     |
+| 2025-07-20 | 76,453 (+2,956)      | 109,044 (+3,140)     | 185,497 (+6,096)     |
+| 2025-07-21 | 80,197 (+3,744)      | 113,537 (+4,493)     | 193,734 (+8,237)     |
+| 2025-07-22 | 84,251 (+4,054)      | 118,073 (+4,536)     | 202,324 (+8,590)     |
+| 2025-07-23 | 88,589 (+4,338)      | 121,436 (+3,363)     | 210,025 (+7,701)     |
+| 2025-07-24 | 92,469 (+3,880)      | 124,091 (+2,655)     | 216,560 (+6,535)     |
+| 2025-07-25 | 96,417 (+3,948)      | 126,985 (+2,894)     | 223,402 (+6,842)     |
+| 2025-07-26 | 100,646 (+4,229)     | 131,411 (+4,426)     | 232,057 (+8,655)     |
+| 2025-07-27 | 102,644 (+1,998)     | 134,736 (+3,325)     | 237,380 (+5,323)     |
+| 2025-07-28 | 105,446 (+2,802)     | 136,016 (+1,280)     | 241,462 (+4,082)     |
+| 2025-07-29 | 108,998 (+3,552)     | 137,542 (+1,526)     | 246,540 (+5,078)     |
+| 2025-07-30 | 113,544 (+4,546)     | 140,317 (+2,775)     | 253,861 (+7,321)     |
+| 2025-07-31 | 118,339 (+4,795)     | 143,344 (+3,027)     | 261,683 (+7,822)     |
+| 2025-08-01 | 123,539 (+5,200)     | 146,680 (+3,336)     | 270,219 (+8,536)     |
+| 2025-08-02 | 127,864 (+4,325)     | 149,236 (+2,556)     | 277,100 (+6,881)     |
+| 2025-08-03 | 131,397 (+3,533)     | 150,451 (+1,215)     | 281,848 (+4,748)     |
+| 2025-08-04 | 136,266 (+4,869)     | 153,260 (+2,809)     | 289,526 (+7,678)     |
+| 2025-08-05 | 141,596 (+5,330)     | 155,752 (+2,492)     | 297,348 (+7,822)     |
+| 2025-08-06 | 147,067 (+5,471)     | 158,309 (+2,557)     | 305,376 (+8,028)     |
+| 2025-08-07 | 152,591 (+5,524)     | 160,889 (+2,580)     | 313,480 (+8,104)     |
+| 2025-08-08 | 158,187 (+5,596)     | 163,448 (+2,559)     | 321,635 (+8,155)     |
+| 2025-08-09 | 162,770 (+4,583)     | 165,721 (+2,273)     | 328,491 (+6,856)     |
+| 2025-08-10 | 165,695 (+2,925)     | 167,109 (+1,388)     | 332,804 (+4,313)     |
+| 2025-08-11 | 169,297 (+3,602)     | 167,953 (+844)       | 337,250 (+4,446)     |
+| 2025-08-12 | 176,307 (+7,010)     | 171,876 (+3,923)     | 348,183 (+10,933)    |
+| 2025-08-13 | 182,997 (+6,690)     | 177,182 (+5,306)     | 360,179 (+11,996)    |
+| 2025-08-14 | 189,063 (+6,066)     | 179,741 (+2,559)     | 368,804 (+8,625)     |
+| 2025-08-15 | 193,608 (+4,545)     | 181,792 (+2,051)     | 375,400 (+6,596)     |
+| 2025-08-16 | 198,118 (+4,510)     | 184,558 (+2,766)     | 382,676 (+7,276)     |
+| 2025-08-17 | 201,299 (+3,181)     | 186,269 (+1,711)     | 387,568 (+4,892)     |
+| 2025-08-18 | 204,559 (+3,260)     | 187,399 (+1,130)     | 391,958 (+4,390)     |
+| 2025-08-19 | 209,814 (+5,255)     | 189,668 (+2,269)     | 399,482 (+7,524)     |
+| 2025-08-20 | 214,497 (+4,683)     | 191,481 (+1,813)     | 405,978 (+6,496)     |
+| 2025-08-21 | 220,465 (+5,968)     | 194,784 (+3,303)     | 415,249 (+9,271)     |
+| 2025-08-22 | 225,899 (+5,434)     | 197,204 (+2,420)     | 423,103 (+7,854)     |
+| 2025-08-23 | 229,005 (+3,106)     | 199,238 (+2,034)     | 428,243 (+5,140)     |
+| 2025-08-24 | 232,098 (+3,093)     | 201,157 (+1,919)     | 433,255 (+5,012)     |
+| 2025-08-25 | 236,607 (+4,509)     | 202,650 (+1,493)     | 439,257 (+6,002)     |
+| 2025-08-26 | 242,783 (+6,176)     | 205,242 (+2,592)     | 448,025 (+8,768)     |
+| 2025-08-27 | 248,409 (+5,626)     | 205,242 (+0)         | 453,651 (+5,626)     |
+| 2025-08-28 | 252,796 (+4,387)     | 205,242 (+0)         | 458,038 (+4,387)     |
+| 2025-08-29 | 256,045 (+3,249)     | 211,075 (+5,833)     | 467,120 (+9,082)     |
+| 2025-08-30 | 258,863 (+2,818)     | 212,397 (+1,322)     | 471,260 (+4,140)     |
+| 2025-08-31 | 262,004 (+3,141)     | 213,944 (+1,547)     | 475,948 (+4,688)     |
+| 2025-09-01 | 265,359 (+3,355)     | 215,115 (+1,171)     | 480,474 (+4,526)     |
+| 2025-09-02 | 270,483 (+5,124)     | 217,075 (+1,960)     | 487,558 (+7,084)     |
+| 2025-09-03 | 274,793 (+4,310)     | 219,755 (+2,680)     | 494,548 (+6,990)     |
+| 2025-09-04 | 280,430 (+5,637)     | 222,103 (+2,348)     | 502,533 (+7,985)     |
+| 2025-09-05 | 283,769 (+3,339)     | 223,793 (+1,690)     | 507,562 (+5,029)     |
+| 2025-09-06 | 286,245 (+2,476)     | 225,036 (+1,243)     | 511,281 (+3,719)     |
+| 2025-09-07 | 288,623 (+2,378)     | 225,866 (+830)       | 514,489 (+3,208)     |
+| 2025-09-08 | 293,341 (+4,718)     | 227,073 (+1,207)     | 520,414 (+5,925)     |
+| 2025-09-09 | 300,036 (+6,695)     | 229,788 (+2,715)     | 529,824 (+9,410)     |
+| 2025-09-10 | 307,287 (+7,251)     | 233,435 (+3,647)     | 540,722 (+10,898)    |
+| 2025-09-11 | 314,083 (+6,796)     | 237,356 (+3,921)     | 551,439 (+10,717)    |
+| 2025-09-12 | 321,046 (+6,963)     | 240,728 (+3,372)     | 561,774 (+10,335)    |
+| 2025-09-13 | 324,894 (+3,848)     | 245,539 (+4,811)     | 570,433 (+8,659)     |
+| 2025-09-14 | 328,876 (+3,982)     | 248,245 (+2,706)     | 577,121 (+6,688)     |
+| 2025-09-15 | 334,201 (+5,325)     | 250,983 (+2,738)     | 585,184 (+8,063)     |
+| 2025-09-16 | 342,609 (+8,408)     | 255,264 (+4,281)     | 597,873 (+12,689)    |
+| 2025-09-17 | 351,117 (+8,508)     | 260,970 (+5,706)     | 612,087 (+14,214)    |
+| 2025-09-18 | 358,717 (+7,600)     | 266,922 (+5,952)     | 625,639 (+13,552)    |
+| 2025-09-19 | 365,401 (+6,684)     | 271,859 (+4,937)     | 637,260 (+11,621)    |
+| 2025-09-20 | 372,092 (+6,691)     | 276,917 (+5,058)     | 649,009 (+11,749)    |
+| 2025-09-21 | 377,079 (+4,987)     | 280,261 (+3,344)     | 657,340 (+8,331)     |
+| 2025-09-22 | 382,492 (+5,413)     | 284,009 (+3,748)     | 666,501 (+9,161)     |
+| 2025-09-23 | 387,008 (+4,516)     | 289,129 (+5,120)     | 676,137 (+9,636)     |
+| 2025-09-24 | 393,325 (+6,317)     | 294,927 (+5,798)     | 688,252 (+12,115)    |
+| 2025-09-25 | 398,879 (+5,554)     | 301,663 (+6,736)     | 700,542 (+12,290)    |
+| 2025-09-26 | 404,334 (+5,455)     | 306,713 (+5,050)     | 711,047 (+10,505)    |
+| 2025-09-27 | 411,618 (+7,284)     | 317,763 (+11,050)    | 729,381 (+18,334)    |
+| 2025-09-28 | 414,910 (+3,292)     | 322,522 (+4,759)     | 737,432 (+8,051)     |
+| 2025-09-29 | 419,919 (+5,009)     | 328,033 (+5,511)     | 747,952 (+10,520)    |
+| 2025-09-30 | 427,991 (+8,072)     | 336,472 (+8,439)     | 764,463 (+16,511)    |
+| 2025-10-01 | 433,591 (+5,600)     | 341,742 (+5,270)     | 775,333 (+10,870)    |
+| 2025-10-02 | 440,852 (+7,261)     | 348,099 (+6,357)     | 788,951 (+13,618)    |
+| 2025-10-03 | 446,829 (+5,977)     | 359,937 (+11,838)    | 806,766 (+17,815)    |
+| 2025-10-04 | 452,561 (+5,732)     | 370,386 (+10,449)    | 822,947 (+16,181)    |
+| 2025-10-05 | 455,559 (+2,998)     | 374,745 (+4,359)     | 830,304 (+7,357)     |
+| 2025-10-06 | 460,927 (+5,368)     | 379,489 (+4,744)     | 840,416 (+10,112)    |
+| 2025-10-07 | 467,336 (+6,409)     | 385,438 (+5,949)     | 852,774 (+12,358)    |
+| 2025-10-08 | 474,643 (+7,307)     | 394,139 (+8,701)     | 868,782 (+16,008)    |
+| 2025-10-09 | 479,203 (+4,560)     | 400,526 (+6,387)     | 879,729 (+10,947)    |
+| 2025-10-10 | 484,374 (+5,171)     | 406,015 (+5,489)     | 890,389 (+10,660)    |
+| 2025-10-11 | 488,427 (+4,053)     | 414,699 (+8,684)     | 903,126 (+12,737)    |
+| 2025-10-12 | 492,125 (+3,698)     | 418,745 (+4,046)     | 910,870 (+7,744)     |
+| 2025-10-14 | 505,130 (+13,005)    | 429,286 (+10,541)    | 934,416 (+23,546)    |
+| 2025-10-15 | 512,717 (+7,587)     | 439,290 (+10,004)    | 952,007 (+17,591)    |
+| 2025-10-16 | 517,719 (+5,002)     | 447,137 (+7,847)     | 964,856 (+12,849)    |
+| 2025-10-17 | 526,239 (+8,520)     | 457,467 (+10,330)    | 983,706 (+18,850)    |
+| 2025-10-18 | 531,564 (+5,325)     | 465,272 (+7,805)     | 996,836 (+13,130)    |
+| 2025-10-19 | 536,209 (+4,645)     | 469,078 (+3,806)     | 1,005,287 (+8,451)   |
+| 2025-10-20 | 541,264 (+5,055)     | 472,952 (+3,874)     | 1,014,216 (+8,929)   |
+| 2025-10-21 | 548,721 (+7,457)     | 479,703 (+6,751)     | 1,028,424 (+14,208)  |
+| 2025-10-22 | 557,949 (+9,228)     | 491,395 (+11,692)    | 1,049,344 (+20,920)  |
+| 2025-10-23 | 564,716 (+6,767)     | 498,736 (+7,341)     | 1,063,452 (+14,108)  |
+| 2025-10-24 | 572,692 (+7,976)     | 506,905 (+8,169)     | 1,079,597 (+16,145)  |
+| 2025-10-25 | 578,927 (+6,235)     | 516,129 (+9,224)     | 1,095,056 (+15,459)  |
+| 2025-10-26 | 584,409 (+5,482)     | 521,179 (+5,050)     | 1,105,588 (+10,532)  |
+| 2025-10-27 | 589,999 (+5,590)     | 526,001 (+4,822)     | 1,116,000 (+10,412)  |
+| 2025-10-28 | 595,776 (+5,777)     | 532,438 (+6,437)     | 1,128,214 (+12,214)  |
+| 2025-10-29 | 606,259 (+10,483)    | 542,064 (+9,626)     | 1,148,323 (+20,109)  |
+| 2025-10-30 | 613,746 (+7,487)     | 542,064 (+0)         | 1,155,810 (+7,487)   |
+| 2025-10-30 | 617,846 (+4,100)     | 555,026 (+12,962)    | 1,172,872 (+17,062)  |
+| 2025-10-31 | 626,612 (+8,766)     | 564,579 (+9,553)     | 1,191,191 (+18,319)  |
+| 2025-11-01 | 636,100 (+9,488)     | 581,806 (+17,227)    | 1,217,906 (+26,715)  |
+| 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)  |
+| 2025-11-11 | 729,769 (+7,481)     | 677,501 (+9,276)     | 1,407,270 (+16,757)  |
+| 2025-11-12 | 740,180 (+10,411)    | 686,454 (+8,953)     | 1,426,634 (+19,364)  |
+| 2025-11-13 | 749,905 (+9,725)     | 696,157 (+9,703)     | 1,446,062 (+19,428)  |
+| 2025-11-14 | 759,928 (+10,023)    | 705,237 (+9,080)     | 1,465,165 (+19,103)  |
+| 2025-11-15 | 765,955 (+6,027)     | 712,870 (+7,633)     | 1,478,825 (+13,660)  |
+| 2025-11-16 | 771,069 (+5,114)     | 716,596 (+3,726)     | 1,487,665 (+8,840)   |
+| 2025-11-17 | 780,161 (+9,092)     | 723,339 (+6,743)     | 1,503,500 (+15,835)  |
+| 2025-11-18 | 791,563 (+11,402)    | 732,544 (+9,205)     | 1,524,107 (+20,607)  |
+| 2025-11-19 | 804,409 (+12,846)    | 747,624 (+15,080)    | 1,552,033 (+27,926)  |
+| 2025-11-20 | 814,620 (+10,211)    | 757,907 (+10,283)    | 1,572,527 (+20,494)  |
+| 2025-11-21 | 826,309 (+11,689)    | 769,307 (+11,400)    | 1,595,616 (+23,089)  |
+| 2025-11-22 | 837,269 (+10,960)    | 780,996 (+11,689)    | 1,618,265 (+22,649)  |
+| 2025-11-23 | 846,609 (+9,340)     | 795,069 (+14,073)    | 1,641,678 (+23,413)  |
+| 2025-11-24 | 856,733 (+10,124)    | 804,033 (+8,964)     | 1,660,766 (+19,088)  |
+| 2025-11-25 | 869,423 (+12,690)    | 817,339 (+13,306)    | 1,686,762 (+25,996)  |
+| 2025-11-26 | 881,414 (+11,991)    | 832,518 (+15,179)    | 1,713,932 (+27,170)  |
+| 2025-11-27 | 893,960 (+12,546)    | 846,180 (+13,662)    | 1,740,140 (+26,208)  |
+| 2025-11-28 | 901,741 (+7,781)     | 856,482 (+10,302)    | 1,758,223 (+18,083)  |
+| 2025-11-29 | 908,689 (+6,948)     | 863,361 (+6,879)     | 1,772,050 (+13,827)  |
+| 2025-11-30 | 916,116 (+7,427)     | 870,194 (+6,833)     | 1,786,310 (+14,260)  |
+| 2025-12-01 | 925,898 (+9,782)     | 876,500 (+6,306)     | 1,802,398 (+16,088)  |
+| 2025-12-02 | 939,250 (+13,352)    | 890,919 (+14,419)    | 1,830,169 (+27,771)  |
+| 2025-12-03 | 952,249 (+12,999)    | 903,713 (+12,794)    | 1,855,962 (+25,793)  |
+| 2025-12-04 | 965,611 (+13,362)    | 916,471 (+12,758)    | 1,882,082 (+26,120)  |
+| 2025-12-05 | 977,996 (+12,385)    | 930,616 (+14,145)    | 1,908,612 (+26,530)  |
+| 2025-12-06 | 987,884 (+9,888)     | 943,773 (+13,157)    | 1,931,657 (+23,045)  |
+| 2025-12-07 | 994,046 (+6,162)     | 951,425 (+7,652)     | 1,945,471 (+13,814)  |
+| 2025-12-08 | 1,000,898 (+6,852)   | 957,149 (+5,724)     | 1,958,047 (+12,576)  |
+| 2025-12-09 | 1,011,488 (+10,590)  | 973,922 (+16,773)    | 1,985,410 (+27,363)  |
+| 2025-12-10 | 1,025,891 (+14,403)  | 991,708 (+17,786)    | 2,017,599 (+32,189)  |
+| 2025-12-11 | 1,045,110 (+19,219)  | 1,010,559 (+18,851)  | 2,055,669 (+38,070)  |
+| 2025-12-12 | 1,061,340 (+16,230)  | 1,030,838 (+20,279)  | 2,092,178 (+36,509)  |
+| 2025-12-13 | 1,073,561 (+12,221)  | 1,044,608 (+13,770)  | 2,118,169 (+25,991)  |
+| 2025-12-14 | 1,082,042 (+8,481)   | 1,052,425 (+7,817)   | 2,134,467 (+16,298)  |
+| 2025-12-15 | 1,093,632 (+11,590)  | 1,059,078 (+6,653)   | 2,152,710 (+18,243)  |
+| 2025-12-16 | 1,120,477 (+26,845)  | 1,078,022 (+18,944)  | 2,198,499 (+45,789)  |
+| 2025-12-17 | 1,151,067 (+30,590)  | 1,097,661 (+19,639)  | 2,248,728 (+50,229)  |
+| 2025-12-18 | 1,178,658 (+27,591)  | 1,113,418 (+15,757)  | 2,292,076 (+43,348)  |
+| 2025-12-19 | 1,203,485 (+24,827)  | 1,129,698 (+16,280)  | 2,333,183 (+41,107)  |
+| 2025-12-20 | 1,223,000 (+19,515)  | 1,146,258 (+16,560)  | 2,369,258 (+36,075)  |
+| 2025-12-21 | 1,242,675 (+19,675)  | 1,158,909 (+12,651)  | 2,401,584 (+32,326)  |
+| 2025-12-22 | 1,262,522 (+19,847)  | 1,169,121 (+10,212)  | 2,431,643 (+30,059)  |
+| 2025-12-23 | 1,286,548 (+24,026)  | 1,186,439 (+17,318)  | 2,472,987 (+41,344)  |
+| 2025-12-24 | 1,309,323 (+22,775)  | 1,203,767 (+17,328)  | 2,513,090 (+40,103)  |
+| 2025-12-25 | 1,333,032 (+23,709)  | 1,217,283 (+13,516)  | 2,550,315 (+37,225)  |
+| 2025-12-26 | 1,352,411 (+19,379)  | 1,227,615 (+10,332)  | 2,580,026 (+29,711)  |
+| 2025-12-27 | 1,371,771 (+19,360)  | 1,238,236 (+10,621)  | 2,610,007 (+29,981)  |
+| 2025-12-28 | 1,390,388 (+18,617)  | 1,245,690 (+7,454)   | 2,636,078 (+26,071)  |
+| 2025-12-29 | 1,415,560 (+25,172)  | 1,257,101 (+11,411)  | 2,672,661 (+36,583)  |
+| 2025-12-30 | 1,445,450 (+29,890)  | 1,272,689 (+15,588)  | 2,718,139 (+45,478)  |
+| 2025-12-31 | 1,479,598 (+34,148)  | 1,293,235 (+20,546)  | 2,772,833 (+54,694)  |
+| 2026-01-01 | 1,508,883 (+29,285)  | 1,309,874 (+16,639)  | 2,818,757 (+45,924)  |
+| 2026-01-02 | 1,563,474 (+54,591)  | 1,320,959 (+11,085)  | 2,884,433 (+65,676)  |
+| 2026-01-03 | 1,618,065 (+54,591)  | 1,331,914 (+10,955)  | 2,949,979 (+65,546)  |
+| 2026-01-04 | 1,672,656 (+39,702)  | 1,339,883 (+7,969)   | 3,012,539 (+62,560)  |
+| 2026-01-05 | 1,738,171 (+65,515)  | 1,353,043 (+13,160)  | 3,091,214 (+78,675)  |
+| 2026-01-06 | 1,960,988 (+222,817) | 1,377,377 (+24,334)  | 3,338,365 (+247,151) |
+| 2026-01-07 | 2,123,239 (+162,251) | 1,398,648 (+21,271)  | 3,521,887 (+183,522) |
+| 2026-01-08 | 2,272,630 (+149,391) | 1,432,480 (+33,832)  | 3,705,110 (+183,223) |
+| 2026-01-09 | 2,443,565 (+170,935) | 1,469,451 (+36,971)  | 3,913,016 (+207,906) |
+| 2026-01-10 | 2,632,023 (+188,458) | 1,503,670 (+34,219)  | 4,135,693 (+222,677) |
+| 2026-01-11 | 2,836,394 (+204,371) | 1,530,479 (+26,809)  | 4,366,873 (+231,180) |
+| 2026-01-12 | 3,053,594 (+217,200) | 1,553,671 (+23,192)  | 4,607,265 (+240,392) |
+| 2026-01-13 | 3,297,078 (+243,484) | 1,595,062 (+41,391)  | 4,892,140 (+284,875) |
+| 2026-01-14 | 3,568,928 (+271,850) | 1,645,362 (+50,300)  | 5,214,290 (+322,150) |
+| 2026-01-16 | 4,121,550 (+552,622) | 1,754,418 (+109,056) | 5,875,968 (+661,678) |

+ 69 - 9
STYLE_GUIDE.md

@@ -1,11 +1,71 @@
 ## Style Guide
 ## Style Guide
 
 
-- Try to keep things in one function unless composable or reusable
-- AVOID unnecessary destructuring of variables. instead of doing `const { a, b }
-= obj` just reference it as obj.a and obj.b. this preserves context
-- AVOID `try`/`catch` where possible
-- AVOID `else` statements
-- AVOID using `any` type
-- AVOID `let` statements
-- PREFER single word variable names where possible
-- Use as many bun apis as possible like Bun.file()
+- Keep things in one function unless composable or reusable
+- Avoid unnecessary destructuring. Instead of `const { a, b } = obj`, use `obj.a` and `obj.b` to preserve context
+- Avoid `try`/`catch` where possible
+- Avoid using the `any` type
+- Prefer single word variable names where possible
+- Use Bun APIs when possible, like `Bun.file()`
+
+# Avoid let statements
+
+We don't like `let` statements, especially combined with if/else statements.
+Prefer `const`.
+
+Good:
+
+```ts
+const foo = condition ? 1 : 2
+```
+
+Bad:
+
+```ts
+let foo
+
+if (condition) foo = 1
+else foo = 2
+```
+
+# Avoid else statements
+
+Prefer early returns or using an `iife` to avoid else statements.
+
+Good:
+
+```ts
+function foo() {
+  if (condition) return 1
+  return 2
+}
+```
+
+Bad:
+
+```ts
+function foo() {
+  if (condition) return 1
+  else return 2
+}
+```
+
+# Prefer single word naming
+
+Try your best to find a single word name for your variables, functions, etc.
+Only use multiple words if you cannot.
+
+Good:
+
+```ts
+const foo = 1
+const bar = 2
+const baz = 3
+```
+
+Bad:
+
+```ts
+const fooBar = 1
+const barBaz = 2
+const bazFoo = 3
+```

+ 184 - 99
bun.lock

@@ -22,7 +22,7 @@
     },
     },
     "packages/app": {
     "packages/app": {
       "name": "@opencode-ai/app",
       "name": "@opencode-ai/app",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
@@ -70,7 +70,7 @@
     },
     },
     "packages/console/app": {
     "packages/console/app": {
       "name": "@opencode-ai/console-app",
       "name": "@opencode-ai/console-app",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "@cloudflare/vite-plugin": "1.15.2",
         "@cloudflare/vite-plugin": "1.15.2",
         "@ibm/plex": "6.4.1",
         "@ibm/plex": "6.4.1",
@@ -81,24 +81,30 @@
         "@opencode-ai/console-mail": "workspace:*",
         "@opencode-ai/console-mail": "workspace:*",
         "@opencode-ai/console-resource": "workspace:*",
         "@opencode-ai/console-resource": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
+        "@smithy/eventstream-codec": "4.2.7",
+        "@smithy/util-utf8": "4.2.0",
         "@solidjs/meta": "catalog:",
         "@solidjs/meta": "catalog:",
         "@solidjs/router": "catalog:",
         "@solidjs/router": "catalog:",
         "@solidjs/start": "catalog:",
         "@solidjs/start": "catalog:",
+        "@stripe/stripe-js": "8.6.1",
         "chart.js": "4.5.1",
         "chart.js": "4.5.1",
         "nitro": "3.0.1-alpha.1",
         "nitro": "3.0.1-alpha.1",
         "solid-js": "catalog:",
         "solid-js": "catalog:",
+        "solid-list": "0.3.0",
+        "solid-stripe": "0.8.1",
         "vite": "catalog:",
         "vite": "catalog:",
         "zod": "catalog:",
         "zod": "catalog:",
       },
       },
       "devDependencies": {
       "devDependencies": {
         "@typescript/native-preview": "catalog:",
         "@typescript/native-preview": "catalog:",
+        "@webgpu/types": "0.1.54",
         "typescript": "catalog:",
         "typescript": "catalog:",
         "wrangler": "4.50.0",
         "wrangler": "4.50.0",
       },
       },
     },
     },
     "packages/console/core": {
     "packages/console/core": {
       "name": "@opencode-ai/console-core",
       "name": "@opencode-ai/console-core",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "@aws-sdk/client-sts": "3.782.0",
         "@aws-sdk/client-sts": "3.782.0",
         "@jsx-email/render": "1.1.1",
         "@jsx-email/render": "1.1.1",
@@ -125,7 +131,7 @@
     },
     },
     "packages/console/function": {
     "packages/console/function": {
       "name": "@opencode-ai/console-function",
       "name": "@opencode-ai/console-function",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "@ai-sdk/anthropic": "2.0.0",
         "@ai-sdk/anthropic": "2.0.0",
         "@ai-sdk/openai": "2.0.2",
         "@ai-sdk/openai": "2.0.2",
@@ -149,7 +155,7 @@
     },
     },
     "packages/console/mail": {
     "packages/console/mail": {
       "name": "@opencode-ai/console-mail",
       "name": "@opencode-ai/console-mail",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "@jsx-email/all": "2.2.3",
         "@jsx-email/all": "2.2.3",
         "@jsx-email/cli": "1.4.3",
         "@jsx-email/cli": "1.4.3",
@@ -173,7 +179,7 @@
     },
     },
     "packages/desktop": {
     "packages/desktop": {
       "name": "@opencode-ai/desktop",
       "name": "@opencode-ai/desktop",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "@opencode-ai/app": "workspace:*",
         "@opencode-ai/app": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
@@ -202,7 +208,7 @@
     },
     },
     "packages/enterprise": {
     "packages/enterprise": {
       "name": "@opencode-ai/enterprise",
       "name": "@opencode-ai/enterprise",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "@opencode-ai/ui": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
         "@opencode-ai/util": "workspace:*",
         "@opencode-ai/util": "workspace:*",
@@ -231,7 +237,7 @@
     },
     },
     "packages/function": {
     "packages/function": {
       "name": "@opencode-ai/function",
       "name": "@opencode-ai/function",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "@octokit/auth-app": "8.0.1",
         "@octokit/auth-app": "8.0.1",
         "@octokit/rest": "catalog:",
         "@octokit/rest": "catalog:",
@@ -247,7 +253,7 @@
     },
     },
     "packages/opencode": {
     "packages/opencode": {
       "name": "opencode",
       "name": "opencode",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "bin": {
       "bin": {
         "opencode": "./bin/opencode",
         "opencode": "./bin/opencode",
       },
       },
@@ -255,26 +261,27 @@
         "@actions/core": "1.11.1",
         "@actions/core": "1.11.1",
         "@actions/github": "6.0.1",
         "@actions/github": "6.0.1",
         "@agentclientprotocol/sdk": "0.5.1",
         "@agentclientprotocol/sdk": "0.5.1",
-        "@ai-sdk/amazon-bedrock": "3.0.57",
-        "@ai-sdk/anthropic": "2.0.56",
-        "@ai-sdk/azure": "2.0.82",
-        "@ai-sdk/cerebras": "1.0.33",
-        "@ai-sdk/cohere": "2.0.21",
-        "@ai-sdk/deepinfra": "1.0.30",
-        "@ai-sdk/gateway": "2.0.23",
-        "@ai-sdk/google": "2.0.49",
-        "@ai-sdk/google-vertex": "3.0.81",
-        "@ai-sdk/groq": "2.0.33",
-        "@ai-sdk/mistral": "2.0.26",
-        "@ai-sdk/openai": "2.0.71",
-        "@ai-sdk/openai-compatible": "1.0.29",
-        "@ai-sdk/perplexity": "2.0.22",
-        "@ai-sdk/provider": "2.0.0",
-        "@ai-sdk/provider-utils": "3.0.19",
-        "@ai-sdk/togetherai": "1.0.30",
+        "@ai-sdk/amazon-bedrock": "3.0.73",
+        "@ai-sdk/anthropic": "2.0.57",
+        "@ai-sdk/azure": "2.0.91",
+        "@ai-sdk/cerebras": "1.0.34",
+        "@ai-sdk/cohere": "2.0.22",
+        "@ai-sdk/deepinfra": "1.0.31",
+        "@ai-sdk/gateway": "2.0.25",
+        "@ai-sdk/google": "2.0.52",
+        "@ai-sdk/google-vertex": "3.0.97",
+        "@ai-sdk/groq": "2.0.34",
+        "@ai-sdk/mistral": "2.0.27",
+        "@ai-sdk/openai": "2.0.89",
+        "@ai-sdk/openai-compatible": "1.0.30",
+        "@ai-sdk/perplexity": "2.0.23",
+        "@ai-sdk/provider": "2.0.1",
+        "@ai-sdk/provider-utils": "3.0.20",
+        "@ai-sdk/togetherai": "1.0.31",
         "@ai-sdk/vercel": "1.0.31",
         "@ai-sdk/vercel": "1.0.31",
-        "@ai-sdk/xai": "2.0.42",
+        "@ai-sdk/xai": "2.0.51",
         "@clack/prompts": "1.0.0-alpha.1",
         "@clack/prompts": "1.0.0-alpha.1",
+        "@gitlab/gitlab-ai-provider": "3.1.1",
         "@hono/standard-validator": "0.1.5",
         "@hono/standard-validator": "0.1.5",
         "@hono/zod-validator": "catalog:",
         "@hono/zod-validator": "catalog:",
         "@modelcontextprotocol/sdk": "1.25.2",
         "@modelcontextprotocol/sdk": "1.25.2",
@@ -286,8 +293,8 @@
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/util": "workspace:*",
         "@opencode-ai/util": "workspace:*",
         "@openrouter/ai-sdk-provider": "1.5.2",
         "@openrouter/ai-sdk-provider": "1.5.2",
-        "@opentui/core": "0.1.70",
-        "@opentui/solid": "0.1.70",
+        "@opentui/core": "0.1.74",
+        "@opentui/solid": "0.1.74",
         "@parcel/watcher": "2.5.1",
         "@parcel/watcher": "2.5.1",
         "@pierre/diffs": "catalog:",
         "@pierre/diffs": "catalog:",
         "@solid-primitives/event-bus": "1.1.2",
         "@solid-primitives/event-bus": "1.1.2",
@@ -350,7 +357,7 @@
     },
     },
     "packages/plugin": {
     "packages/plugin": {
       "name": "@opencode-ai/plugin",
       "name": "@opencode-ai/plugin",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
         "zod": "catalog:",
         "zod": "catalog:",
@@ -370,9 +377,9 @@
     },
     },
     "packages/sdk/js": {
     "packages/sdk/js": {
       "name": "@opencode-ai/sdk",
       "name": "@opencode-ai/sdk",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "devDependencies": {
       "devDependencies": {
-        "@hey-api/openapi-ts": "0.88.1",
+        "@hey-api/openapi-ts": "0.90.4",
         "@tsconfig/node22": "catalog:",
         "@tsconfig/node22": "catalog:",
         "@types/node": "catalog:",
         "@types/node": "catalog:",
         "@typescript/native-preview": "catalog:",
         "@typescript/native-preview": "catalog:",
@@ -381,7 +388,7 @@
     },
     },
     "packages/slack": {
     "packages/slack": {
       "name": "@opencode-ai/slack",
       "name": "@opencode-ai/slack",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
         "@slack/bolt": "^3.17.1",
         "@slack/bolt": "^3.17.1",
@@ -394,7 +401,7 @@
     },
     },
     "packages/ui": {
     "packages/ui": {
       "name": "@opencode-ai/ui",
       "name": "@opencode-ai/ui",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
@@ -406,6 +413,7 @@
         "@solid-primitives/resize-observer": "2.1.3",
         "@solid-primitives/resize-observer": "2.1.3",
         "@solidjs/meta": "catalog:",
         "@solidjs/meta": "catalog:",
         "@typescript/native-preview": "catalog:",
         "@typescript/native-preview": "catalog:",
+        "dompurify": "3.3.1",
         "fuzzysort": "catalog:",
         "fuzzysort": "catalog:",
         "katex": "0.16.27",
         "katex": "0.16.27",
         "luxon": "catalog:",
         "luxon": "catalog:",
@@ -433,7 +441,7 @@
     },
     },
     "packages/util": {
     "packages/util": {
       "name": "@opencode-ai/util",
       "name": "@opencode-ai/util",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "zod": "catalog:",
         "zod": "catalog:",
       },
       },
@@ -444,7 +452,7 @@
     },
     },
     "packages/web": {
     "packages/web": {
       "name": "@opencode-ai/web",
       "name": "@opencode-ai/web",
-      "version": "1.1.8",
+      "version": "1.1.24",
       "dependencies": {
       "dependencies": {
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/markdown-remark": "6.3.1",
         "@astrojs/markdown-remark": "6.3.1",
@@ -500,12 +508,13 @@
     "@tailwindcss/vite": "4.1.11",
     "@tailwindcss/vite": "4.1.11",
     "@tsconfig/bun": "1.0.9",
     "@tsconfig/bun": "1.0.9",
     "@tsconfig/node22": "22.0.2",
     "@tsconfig/node22": "22.0.2",
-    "@types/bun": "1.3.4",
+    "@types/bun": "1.3.5",
     "@types/luxon": "3.7.1",
     "@types/luxon": "3.7.1",
     "@types/node": "22.13.9",
     "@types/node": "22.13.9",
     "@typescript/native-preview": "7.0.0-dev.20251207.1",
     "@typescript/native-preview": "7.0.0-dev.20251207.1",
-    "ai": "5.0.97",
+    "ai": "5.0.119",
     "diff": "8.0.2",
     "diff": "8.0.2",
+    "dompurify": "3.3.1",
     "fuzzysort": "3.1.0",
     "fuzzysort": "3.1.0",
     "hono": "4.10.7",
     "hono": "4.10.7",
     "hono-openapi": "1.1.2",
     "hono-openapi": "1.1.2",
@@ -541,48 +550,52 @@
 
 
     "@agentclientprotocol/sdk": ["@agentclientprotocol/[email protected]", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg=="],
     "@agentclientprotocol/sdk": ["@agentclientprotocol/[email protected]", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg=="],
 
 
-    "@ai-sdk/amazon-bedrock": ["@ai-sdk/[email protected].57", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.45", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-mOUSLe+RgZzx0rtL1p9QXmSd/08z1EkBR+vQ1ydpd1t5P0Nx2kB8afiukEgM8nuDvmO9eYQlp7VTy1n5ffPs2g=="],
+    "@ai-sdk/amazon-bedrock": ["@ai-sdk/[email protected]3", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.57", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EAAGJ/dfbAZaqIhK3w52hq6cftSLZwXdC6uHKh8Cls1T0N4MxS6ykDf54UyFO3bZWkQxR+Mdw1B3qireGOxtJQ=="],
 
 
     "@ai-sdk/anthropic": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
     "@ai-sdk/anthropic": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
 
 
-    "@ai-sdk/azure": ["@ai-sdk/[email protected].82", "", { "dependencies": { "@ai-sdk/openai": "2.0.80", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Bpab51ETBB4adZC1xGMYsryL/CB8j1sA+t5aDqhRv3t3WRLTxhaBDcFKtQTIuxiEQTFosz9Q2xQqdfBvQm5jHw=="],
+    "@ai-sdk/azure": ["@ai-sdk/[email protected].91", "", { "dependencies": { "@ai-sdk/openai": "2.0.89", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9tznVSs6LGQNKKxb8pKd7CkBV9yk+a/ENpFicHCj2CmBUKefxzwJ9JbUqrlK3VF6dGZw3LXq0dWxt7/Yekaj1w=="],
 
 
-    "@ai-sdk/cerebras": ["@ai-sdk/[email protected]3", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2gSSS/7kunIwMdC4td5oWsUAzoLw84ccGpz6wQbxVnrb1iWnrEnKa5tRBduaP6IXpzLWsu8wME3+dQhZy+gT7w=="],
+    "@ai-sdk/cerebras": ["@ai-sdk/[email protected]4", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XOK0dJsAGoPYi/lfR4KFBi8xhvJ46oCpAxUD6FmJAuJ4eh0qlj5zDt+myvzM8gvN7S6K7zHD+mdWlOPKGQT8Vg=="],
 
 
-    "@ai-sdk/cohere": ["@ai-sdk/[email protected]1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZjaZFvJlc5XOPi3QwTLEFZbHIgTJc6YGvxz+8zIMGVZi/hdynR8/f/C1A9x6mhzmBtAqi/dZ2h11oouAQH5z4g=="],
+    "@ai-sdk/cohere": ["@ai-sdk/[email protected]2", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yJ9kP5cEDJwo8qpITq5TQFD8YNfNtW+HbyvWwrKMbFzmiMvIZuk95HIaFXE7PCTuZsqMA05yYu+qX/vQ3rNKjA=="],
 
 
-    "@ai-sdk/deepinfra": ["@ai-sdk/[email protected]0", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XK8oRZFApzo6xnS5C+FhWUUkB2itA5Nfon3pU9dJVM0goViq8GwdleZTBRqhu4DE4KJURo5DGWpJr2hfV54cEg=="],
+    "@ai-sdk/deepinfra": ["@ai-sdk/[email protected]1", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-87qFcYNvDF/89hB//MQjYTb3tlsAfmgeZrZ34RESeBTZpSgs0EzYOMqPMwFTHUNp4wteoifikDJbaS/9Da8cfw=="],
 
 
-    "@ai-sdk/gateway": ["@ai-sdk/[email protected]3", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-qmX7afPRszUqG5hryHF3UN8ITPIRSGmDW6VYCmByzjoUkgm3MekzSx2hMV1wr0P+llDeuXb378SjqUfpvWJulg=="],
+    "@ai-sdk/gateway": ["@ai-sdk/[email protected]5", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Rq+FX55ne7lMiqai7NcvvDZj4HLsr+hg77WayqmySqc6zhw3tIOLxd4Ty6OpwNj0C0bVMi3iCl2zvJIEirh9XA=="],
 
 
-    "@ai-sdk/google": ["@ai-sdk/[email protected].49", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-efwKk4mOV0SpumUaQskeYABk37FJPmEYwoDJQEjyLRmGSjtHRe9P5Cwof5ffLvaFav2IaJpBGEz98pyTs7oNWA=="],
+    "@ai-sdk/google": ["@ai-sdk/[email protected].52", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2XUnGi3f7TV4ujoAhA+Fg3idUoG/+Y2xjCRg70a1/m0DH1KSQqYaCboJ1C19y6ZHGdf5KNT20eJdswP6TvrY2g=="],
 
 
-    "@ai-sdk/google-vertex": ["@ai-sdk/[email protected].81", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.50", "@ai-sdk/google": "2.0.44", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18", "google-auth-library": "^9.15.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yrl5Ug0Mqwo9ya45oxczgy2RWgpEA/XQQCSFYP+3NZMQ4yA3Iim1vkOjVCsGaZZ8rjVk395abi1ZMZV0/6rqVA=="],
+    "@ai-sdk/google-vertex": ["@ai-sdk/[email protected].97", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.57", "@ai-sdk/google": "2.0.52", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-s4tI7Z15i6FlbtCvS4SBRal8wRfkOXJzKxlS6cU4mJW/QfUfoVy4b22836NVNJwDvkG/HkDSfzwm/X8mn46MhA=="],
 
 
-    "@ai-sdk/groq": ["@ai-sdk/[email protected]3", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FWGl7xNr88NBveao3y9EcVWYUt9ABPrwLFY7pIutSNgaTf32vgvyhREobaMrLU4Scr5G/2tlNqOPZ5wkYMaZig=="],
+    "@ai-sdk/groq": ["@ai-sdk/[email protected]4", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wfCYkVgmVjxNA32T57KbLabVnv9aFUflJ4urJ7eWgTwbnmGQHElCTu+rJ3ydxkXSqxOkXPwMOttDm7XNrvPjmg=="],
 
 
-    "@ai-sdk/mistral": ["@ai-sdk/[email protected]6", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jxDB++4WI1wEx5ONNBI+VbkmYJOYIuS8UQY13/83UGRaiW7oB/WHiH4ETe6KzbKpQPB3XruwTJQjUMsMfKyTXA=="],
+    "@ai-sdk/mistral": ["@ai-sdk/[email protected]7", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-gaptHgaXjMw3+eA0Q4FABcsj5nQNP6EpFaGUR+Pj5WJy7Kn6mApl975/x57224MfeJIShNpt8wFKK3tvh5ewKg=="],
 
 
     "@ai-sdk/openai": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="],
     "@ai-sdk/openai": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="],
 
 
     "@ai-sdk/openai-compatible": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-luHVcU+yKzwv3ekKgbP3v+elUVxb2Rt+8c6w9qi7g2NYG2/pEL21oIrnaEnc6UtTZLLZX9EFBcpq2N1FQKDIMw=="],
     "@ai-sdk/openai-compatible": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-luHVcU+yKzwv3ekKgbP3v+elUVxb2Rt+8c6w9qi7g2NYG2/pEL21oIrnaEnc6UtTZLLZX9EFBcpq2N1FQKDIMw=="],
 
 
-    "@ai-sdk/perplexity": ["@ai-sdk/[email protected]2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zwzcnk08R2J3mZcQPn4Ifl4wYGrvANR7jsBB0hCTUSbb+Rx3ybpikSWiGuXQXxdiRc1I5MWXgj70m+bZaLPvHw=="],
+    "@ai-sdk/perplexity": ["@ai-sdk/[email protected]3", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-aiaRvnc6mhQZKhTTSXPCjPH8Iqr5D/PfCN1hgVP/3RGTBbJtsd9HemIBSABeSdAKbsMH/PwJxgnqH75HEamcBA=="],
 
 
-    "@ai-sdk/provider": ["@ai-sdk/[email protected].0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
+    "@ai-sdk/provider": ["@ai-sdk/[email protected].1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="],
 
 
-    "@ai-sdk/provider-utils": ["@ai-sdk/[email protected].19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="],
+    "@ai-sdk/provider-utils": ["@ai-sdk/[email protected].20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
 
 
-    "@ai-sdk/togetherai": ["@ai-sdk/[email protected]0", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9bxQbIXnWSN4bNismrza3NvIo+ui/Y3pj3UN6e9vCszCWFCN45RgISi4oDe10RqmzaJ/X8cfO/Tem+K8MT3wGQ=="],
+    "@ai-sdk/togetherai": ["@ai-sdk/[email protected]1", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RlYubjStoZQxna4Ng91Vvo8YskvL7lW9zj68IwZfCnaDBSAp1u6Nhc5BR4ZtKnY6PA3XEtu4bATIQl7yiiQ+Lw=="],
 
 
     "@ai-sdk/vercel": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ggvwAMt/KsbqcdR6ILQrjwrRONLV/8aG6rOLbjcOGvV0Ai+WdZRRKQj5nOeQ06PvwVQtKdkp7S4IinpXIhCiHg=="],
     "@ai-sdk/vercel": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ggvwAMt/KsbqcdR6ILQrjwrRONLV/8aG6rOLbjcOGvV0Ai+WdZRRKQj5nOeQ06PvwVQtKdkp7S4IinpXIhCiHg=="],
 
 
-    "@ai-sdk/xai": ["@ai-sdk/[email protected].42", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wlwO4yRoZ/d+ca29vN8SDzxus7POdnL7GBTyRdSrt6icUF0hooLesauC8qRUC4aLxtqvMEc1YHtJOU7ZnLWbTQ=="],
+    "@ai-sdk/xai": ["@ai-sdk/[email protected].51", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-AI3le03qiegkZvn9hpnpDwez49lOvQLj4QUBT8H41SMbrdTYOxn3ktTwrsSu90cNDdzKGMvoH0u2GHju1EdnCg=="],
 
 
     "@alloc/quick-lru": ["@alloc/[email protected]", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
     "@alloc/quick-lru": ["@alloc/[email protected]", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
 
 
     "@ampproject/remapping": ["@ampproject/[email protected]", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
     "@ampproject/remapping": ["@ampproject/[email protected]", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
 
 
+    "@anthropic-ai/sdk": ["@anthropic-ai/[email protected]", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ=="],
+
+    "@anycable/core": ["@anycable/[email protected]", "", { "dependencies": { "nanoevents": "^7.0.1" } }, "sha512-x5ZXDcW/N4cxWl93CnbHs/u7qq4793jS2kNPWm+duPrXlrva+ml2ZGT7X9tuOBKzyIHf60zWCdIK7TUgMPAwXA=="],
+
     "@astrojs/cloudflare": ["@astrojs/[email protected]", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "@astrojs/underscore-redirects": "1.0.0", "@cloudflare/workers-types": "^4.20250507.0", "tinyglobby": "^0.2.13", "vite": "^6.3.5", "wrangler": "^4.14.1" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-xhJptF5tU2k5eo70nIMyL1Udma0CqmUEnGSlGyFflLqSY82CRQI6nWZ/xZt0ZvmXuErUjIx0YYQNfZsz5CNjLQ=="],
     "@astrojs/cloudflare": ["@astrojs/[email protected]", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "@astrojs/underscore-redirects": "1.0.0", "@cloudflare/workers-types": "^4.20250507.0", "tinyglobby": "^0.2.13", "vite": "^6.3.5", "wrangler": "^4.14.1" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-xhJptF5tU2k5eo70nIMyL1Udma0CqmUEnGSlGyFflLqSY82CRQI6nWZ/xZt0ZvmXuErUjIx0YYQNfZsz5CNjLQ=="],
 
 
     "@astrojs/compiler": ["@astrojs/[email protected]", "", {}, "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw=="],
     "@astrojs/compiler": ["@astrojs/[email protected]", "", {}, "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw=="],
@@ -903,13 +916,17 @@
 
 
     "@fontsource/inter": ["@fontsource/[email protected]", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
     "@fontsource/inter": ["@fontsource/[email protected]", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
 
 
+    "@gitlab/gitlab-ai-provider": ["@gitlab/[email protected]", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-7AtFrCflq2NzC99bj7YaqbQDCZyaScM1+L4ujllV5syiRTFE239Uhnd/yEkPXa7sUAnNRfN3CWusCkQ2zK/q9g=="],
+
+    "@graphql-typed-document-node/core": ["@graphql-typed-document-node/[email protected]", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="],
+
     "@happy-dom/global-registrator": ["@happy-dom/[email protected]", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
     "@happy-dom/global-registrator": ["@happy-dom/[email protected]", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
 
 
-    "@hey-api/codegen-core": ["@hey-api/[email protected]", "", { "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-vArVDtrvdzFewu1hnjUm4jX1NBITlSCeO81EdWq676MxQbyxsGcDPAgohaSA+Wvr4HjPSvsg2/1s2zYxUtXebg=="],
+    "@hey-api/codegen-core": ["@hey-api/codegen-core@0.5.2", "", { "dependencies": { "ansi-colors": "4.1.3", "color-support": "1.1.3" }, "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-88cqrrB2cLXN8nMOHidQTcVOnZsJ5kebEbBefjMCifaUCwTA30ouSSWvTZqrOX4O104zjJyu7M8Gcv/NNYQuaA=="],
 
 
     "@hey-api/json-schema-ref-parser": ["@hey-api/[email protected]", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.1", "lodash": "^4.17.21" } }, "sha512-oS+5yAdwnK20lSeFO1d53Ku+yaGCsY8PcrmSq2GtSs3bsBfRnHAbpPKSVzQcaxAOrzj5NB+f34WhZglVrNayBA=="],
     "@hey-api/json-schema-ref-parser": ["@hey-api/[email protected]", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.1", "lodash": "^4.17.21" } }, "sha512-oS+5yAdwnK20lSeFO1d53Ku+yaGCsY8PcrmSq2GtSs3bsBfRnHAbpPKSVzQcaxAOrzj5NB+f34WhZglVrNayBA=="],
 
 
-    "@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.88.1", "", { "dependencies": { "@hey-api/codegen-core": "^0.3.3", "@hey-api/json-schema-ref-parser": "1.2.2", "ansi-colors": "4.1.3", "c12": "3.3.2", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.2" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-x/nDTupOnV9VuSeNIiJpgIpc915GHduhyseJeMTnI0JMsXaObmpa0rgPr3ASVEYMLgpvqozIEG1RTOOnal6zLQ=="],
+    "@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.90.4", "", { "dependencies": { "@hey-api/codegen-core": "^0.5.2", "@hey-api/json-schema-ref-parser": "1.2.2", "ansi-colors": "4.1.3", "c12": "3.3.3", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.3" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-9l++kjcb0ui4JqPlueZ6OZ9zKn6eK/8//Z2jHcIXb5MRwDRgubOOSpTU5llEv3uvWfT10VzcMp99dySWq0AASw=="],
 
 
     "@hono/node-server": ["@hono/[email protected]", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw=="],
     "@hono/node-server": ["@hono/[email protected]", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw=="],
 
 
@@ -1201,21 +1218,21 @@
 
 
     "@opentelemetry/api": ["@opentelemetry/[email protected]", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
     "@opentelemetry/api": ["@opentelemetry/[email protected]", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
 
 
-    "@opentui/core": ["@opentui/[email protected]0", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.70", "@opentui/core-darwin-x64": "0.1.70", "@opentui/core-linux-arm64": "0.1.70", "@opentui/core-linux-x64": "0.1.70", "@opentui/core-win32-arm64": "0.1.70", "@opentui/core-win32-x64": "0.1.70", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-6cPAlbCnaiUUtQtvZNpkr0Xv8AdVAgJuy2VAwIsDN1pIv0zMpa0ZG+mr7afCGygw1eeDRveefrjfgFAB1r0SVw=="],
+    "@opentui/core": ["@opentui/[email protected]4", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.74", "@opentui/core-darwin-x64": "0.1.74", "@opentui/core-linux-arm64": "0.1.74", "@opentui/core-linux-x64": "0.1.74", "@opentui/core-win32-arm64": "0.1.74", "@opentui/core-win32-x64": "0.1.74", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-g4W16ymv12JdgZ+9B4t7mpIICvzWy2+eHERfmDf80ALduOQCUedKQdULcBFhVCYUXIkDRtIy6CID5thMAah3FA=="],
 
 
-    "@opentui/core-darwin-arm64": ["@opentui/[email protected]0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rM8EnvW1tOAXWnp2Iy2M82I+ViSmRwUagx3v1/ni6N8GCcw/3mE0C6eB3sVlYNXVMwBEgiKpWFn85RCe4+qXQw=="],
+    "@opentui/core-darwin-arm64": ["@opentui/[email protected]4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rfmlDLtm/u17CnuhJgCxPeYMvOST+A2MOdVOk46IurtHO849bdYqK6iudKNlFRs1FOrymgSKF9GlWBHAOKeRjg=="],
 
 
-    "@opentui/core-darwin-x64": ["@opentui/[email protected]0", "", { "os": "darwin", "cpu": "x64" }, "sha512-XdBgW+em8J+YGSUpaKF8/NxPjikJygK3dIkeMAw5xQ2lt7jXKxeM5MMmN/V4MfK3pLMtO56rLJlXaLH/h50uQA=="],
+    "@opentui/core-darwin-x64": ["@opentui/[email protected]4", "", { "os": "darwin", "cpu": "x64" }, "sha512-WAD8orsDV0ZdW/5GwjOOB4FY96772xbkz+rcV7WRzEFUVaqoBaC04IuqYzS9d5s+cjkbT5Cpj47hrVYkkVQKng=="],
 
 
-    "@opentui/core-linux-arm64": ["@opentui/[email protected]0", "", { "os": "linux", "cpu": "arm64" }, "sha512-oSVWNMSOx0Na0M0LCqtWCxeh4SuLSK5lg8ZwVzsEoimIAxh0snp9nRUo/Qi8yD9BP0DSDmXuM/B3ONtzFaf0dw=="],
+    "@opentui/core-linux-arm64": ["@opentui/[email protected]4", "", { "os": "linux", "cpu": "arm64" }, "sha512-lgmHzrzLy4e+rgBS+lhtsMLLgIMLbtLNMm6EzVPyYVDlLDGjM7+ulXMem7AtpaRrWrUUl4REiG9BoQUsCFDwYA=="],
 
 
-    "@opentui/core-linux-x64": ["@opentui/[email protected]0", "", { "os": "linux", "cpu": "x64" }, "sha512-WUrhukefMghcZ7sAjkxEy50vA6ii0X21xh7m8c4omXyYYfQXyDs25pNExB8cwoCrZEaC8RTlF4lRSNPIXsZKhA=="],
+    "@opentui/core-linux-x64": ["@opentui/[email protected]4", "", { "os": "linux", "cpu": "x64" }, "sha512-8Mn2WbdBQ29xCThuPZezjDhd1N3+fXwKkGvCBOdTI0le6h2A/vCNbfUVjwfr/EGZSRXxCG+Yapol34BAULGpOA=="],
 
 
-    "@opentui/core-win32-arm64": ["@opentui/[email protected]0", "", { "os": "win32", "cpu": "arm64" }, "sha512-p1K2VJXGmZqSV7mR61v7KJpT1Zth7DS99wEtaqqfK68OWt33K2XxLmGO0KD142R2JLfXu32NnRmBHxmVx8IjBA=="],
+    "@opentui/core-win32-arm64": ["@opentui/[email protected]4", "", { "os": "win32", "cpu": "arm64" }, "sha512-dvYUXz03avnI6ZluyLp00HPmR0UT/IE/6QS97XBsgJlUTtpnbKkBtB5jD1NHwWkElaRj1Qv2QP36ngFoJqbl9g=="],
 
 
-    "@opentui/core-win32-x64": ["@opentui/[email protected]0", "", { "os": "win32", "cpu": "x64" }, "sha512-G6b8te1twMeDhjg1oZa0IcUjhOJZFCSdlQt+q5gu5vVtjCrIwAn9o7m5EwNMPakc31pDWUZ7v0ktgv0Xw1AQVA=="],
+    "@opentui/core-win32-x64": ["@opentui/[email protected]4", "", { "os": "win32", "cpu": "x64" }, "sha512-3wfWXaAKOIlDQz6ZZIESf2M+YGZ7uFHijjTEM8w/STRlLw8Y6+QyGYi1myHSM4d6RSO+/s2EMDxvjDf899W9vQ=="],
 
 
-    "@opentui/solid": ["@opentui/[email protected]0", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.70", "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-8Cw/w4Of2OJhsFhcp/Wdj8cJRVaGvVsIiUoYiFtyToM01J4en0bg/vnbeZteyuZWeEtA4iz1/rSEQf7Dp+2FIQ=="],
+    "@opentui/solid": ["@opentui/[email protected]4", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.74", "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-Vz82cI8T9YeJjGsVg4ULp6ral4N+xyt1j9A6Tbu3aaQgEKiB74LW03EXREehfjPr1irOFxtKfWPbx5NKH0Upag=="],
 
 
     "@oslojs/asn1": ["@oslojs/[email protected]", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
     "@oslojs/asn1": ["@oslojs/[email protected]", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
 
 
@@ -1507,7 +1524,7 @@
 
 
     "@smithy/credential-provider-imds": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="],
     "@smithy/credential-provider-imds": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="],
 
 
-    "@smithy/eventstream-codec": ["@smithy/[email protected].5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="],
+    "@smithy/eventstream-codec": ["@smithy/[email protected].7", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ=="],
 
 
     "@smithy/eventstream-serde-browser": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw=="],
     "@smithy/eventstream-serde-browser": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw=="],
 
 
@@ -1597,6 +1614,8 @@
 
 
     "@smithy/uuid": ["@smithy/[email protected]", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
     "@smithy/uuid": ["@smithy/[email protected]", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
 
 
+    "@socket.io/component-emitter": ["@socket.io/[email protected]", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
+
     "@solid-primitives/active-element": ["@solid-primitives/[email protected]", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9t5K4aR2naVDj950XU8OjnLgOg94a8k5wr6JNOPK+N5ESLsJDq42c1ZP8UKpewi1R+wplMMxiM6OPKRzbxJY7A=="],
     "@solid-primitives/active-element": ["@solid-primitives/[email protected]", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9t5K4aR2naVDj950XU8OjnLgOg94a8k5wr6JNOPK+N5ESLsJDq42c1ZP8UKpewi1R+wplMMxiM6OPKRzbxJY7A=="],
 
 
     "@solid-primitives/audio": ["@solid-primitives/[email protected]", "", { "dependencies": { "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-UMD3ORQfI5Ky8yuKPxidDiEazsjv/dsoiKK5yZxLnsgaeNR1Aym3/77h/qT1jBYeXUgj4DX6t7NMpFUSVr14OQ=="],
     "@solid-primitives/audio": ["@solid-primitives/[email protected]", "", { "dependencies": { "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-UMD3ORQfI5Ky8yuKPxidDiEazsjv/dsoiKK5yZxLnsgaeNR1Aym3/77h/qT1jBYeXUgj4DX6t7NMpFUSVr14OQ=="],
@@ -1649,6 +1668,8 @@
 
 
     "@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
     "@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
 
 
+    "@stripe/stripe-js": ["@stripe/[email protected]", "", {}, "sha512-UJ05U2062XDgydbUcETH1AoRQLNhigQ2KmDn1BG8sC3xfzu6JKg95Qt6YozdzFpxl1Npii/02m2LEWFt1RYjVA=="],
+
     "@swc/helpers": ["@swc/[email protected]", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
     "@swc/helpers": ["@swc/[email protected]", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
 
 
     "@tailwindcss/node": ["@tailwindcss/[email protected]", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
     "@tailwindcss/node": ["@tailwindcss/[email protected]", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
@@ -1755,7 +1776,7 @@
 
 
     "@types/braces": ["@types/[email protected]", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="],
     "@types/braces": ["@types/[email protected]", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="],
 
 
-    "@types/bun": ["@types/[email protected].4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
+    "@types/bun": ["@types/[email protected].5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
 
 
     "@types/chai": ["@types/[email protected]", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
     "@types/chai": ["@types/[email protected]", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
 
 
@@ -1827,6 +1848,8 @@
 
 
     "@types/serve-static": ["@types/[email protected]", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
     "@types/serve-static": ["@types/[email protected]", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
 
 
+    "@types/trusted-types": ["@types/[email protected]", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
+
     "@types/tsscmp": ["@types/[email protected]", "", {}, "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g=="],
     "@types/tsscmp": ["@types/[email protected]", "", {}, "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g=="],
 
 
     "@types/tunnel": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA=="],
     "@types/tunnel": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA=="],
@@ -1883,7 +1906,7 @@
 
 
     "@vitest/utils": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="],
     "@vitest/utils": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="],
 
 
-    "@webgpu/types": ["@webgpu/[email protected].66", "", {}, "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA=="],
+    "@webgpu/types": ["@webgpu/[email protected].54", "", {}, "sha512-81oaalC8LFrXjhsczomEQ0u3jG+TqE6V9QHLA8GNZq/Rnot0KDugu3LhSYSlie8tSdooAN1Hov05asrUUp9qgg=="],
 
 
     "@zip.js/zip.js": ["@zip.js/[email protected]", "", {}, "sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA=="],
     "@zip.js/zip.js": ["@zip.js/[email protected]", "", {}, "sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA=="],
 
 
@@ -1903,7 +1926,7 @@
 
 
     "agentkeepalive": ["[email protected]", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
     "agentkeepalive": ["[email protected]", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
 
 
-    "ai": ["[email protected]7", "", { "dependencies": { "@ai-sdk/gateway": "2.0.12", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8zBx0b/owis4eJI2tAlV8a1Rv0BANmLxontcAelkLNwEHhgfgXeKpDkhNB6OgV+BJSwboIUDkgd9312DdJnCOQ=="],
+    "ai": ["[email protected].119", "", { "dependencies": { "@ai-sdk/gateway": "2.0.25", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HUOwhc17fl2SZTJGZyA/99aNu706qKfXaUBCy9vgZiXBwrxg2eTzn2BCz7kmYDsfx6Fg2ACBy2icm41bsDXCTw=="],
 
 
     "ajv": ["[email protected]", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
     "ajv": ["[email protected]", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
 
 
@@ -2055,7 +2078,7 @@
 
 
     "bun-pty": ["[email protected]", "", {}, "sha512-WK4G6uWsZgu1v4hKIlw6G1q2AOf8Rbga2Yr7RnxArVjjyb+mtVa/CFc9GOJf+OYSJSH8k7LonAtQOVeNAddRyg=="],
     "bun-pty": ["[email protected]", "", {}, "sha512-WK4G6uWsZgu1v4hKIlw6G1q2AOf8Rbga2Yr7RnxArVjjyb+mtVa/CFc9GOJf+OYSJSH8k7LonAtQOVeNAddRyg=="],
 
 
-    "bun-types": ["[email protected].4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
+    "bun-types": ["[email protected].5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
 
 
     "bun-webgpu": ["[email protected]", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="],
     "bun-webgpu": ["[email protected]", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="],
 
 
@@ -2071,7 +2094,7 @@
 
 
     "bytes": ["[email protected]", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
     "bytes": ["[email protected]", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
 
 
-    "c12": ["[email protected].2", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A=="],
+    "c12": ["[email protected].3", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q=="],
 
 
     "call-bind": ["[email protected]", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
     "call-bind": ["[email protected]", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
 
 
@@ -2205,6 +2228,8 @@
 
 
     "csstype": ["[email protected]", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
     "csstype": ["[email protected]", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
 
 
+    "data-uri-to-buffer": ["[email protected]", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
+
     "data-view-buffer": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
     "data-view-buffer": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
 
 
     "data-view-byte-length": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
     "data-view-byte-length": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
@@ -2277,6 +2302,8 @@
 
 
     "domhandler": ["[email protected]", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
     "domhandler": ["[email protected]", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
 
 
+    "dompurify": ["[email protected]", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="],
+
     "domutils": ["[email protected]", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
     "domutils": ["[email protected]", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
 
 
     "dot-case": ["[email protected]", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
     "dot-case": ["[email protected]", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
@@ -2309,6 +2336,10 @@
 
 
     "encodeurl": ["[email protected]", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
     "encodeurl": ["[email protected]", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
 
 
+    "engine.io-client": ["[email protected]", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw=="],
+
+    "engine.io-parser": ["[email protected]", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
+
     "enhanced-resolve": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
     "enhanced-resolve": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
 
 
     "entities": ["[email protected]", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
     "entities": ["[email protected]", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
@@ -2419,6 +2450,8 @@
 
 
     "fdir": ["[email protected]", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
     "fdir": ["[email protected]", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
 
 
+    "fetch-blob": ["[email protected]", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
+
     "file-type": ["[email protected]", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
     "file-type": ["[email protected]", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
 
 
     "fill-range": ["[email protected]", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
     "fill-range": ["[email protected]", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
@@ -2449,6 +2482,8 @@
 
 
     "formdata-node": ["[email protected]", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
     "formdata-node": ["[email protected]", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
 
 
+    "formdata-polyfill": ["[email protected]", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
+
     "forwarded": ["[email protected]", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
     "forwarded": ["[email protected]", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
 
 
     "fraction.js": ["[email protected]", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
     "fraction.js": ["[email protected]", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
@@ -2471,9 +2506,9 @@
 
 
     "fuzzysort": ["[email protected]", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="],
     "fuzzysort": ["[email protected]", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="],
 
 
-    "gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="],
+    "gaxios": ["[email protected].3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="],
 
 
-    "gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="],
+    "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
 
 
     "gel": ["[email protected]", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ=="],
     "gel": ["[email protected]", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ=="],
 
 
@@ -2519,17 +2554,21 @@
 
 
     "globby": ["[email protected]", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.1.1", "ignore": "^5.1.4", "merge2": "^1.3.0", "slash": "^3.0.0" } }, "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg=="],
     "globby": ["[email protected]", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.1.1", "ignore": "^5.1.4", "merge2": "^1.3.0", "slash": "^3.0.0" } }, "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg=="],
 
 
-    "google-auth-library": ["google-auth-library@9.15.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", "jws": "^4.0.0" } }, "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng=="],
+    "google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="],
 
 
-    "google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
+    "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
 
 
     "gopd": ["[email protected]", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
     "gopd": ["[email protected]", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
 
 
     "graceful-fs": ["[email protected]", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
     "graceful-fs": ["[email protected]", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
 
 
+    "graphql": ["[email protected]", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="],
+
+    "graphql-request": ["[email protected]", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", "cross-fetch": "^3.1.5" }, "peerDependencies": { "graphql": "14 - 16" } }, "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw=="],
+
     "gray-matter": ["[email protected]", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
     "gray-matter": ["[email protected]", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
 
 
-    "gtoken": ["[email protected]", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="],
+    "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="],
 
 
     "h3": ["[email protected]", "", { "dependencies": { "rou3": "^0.7.8", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-vZq8pEUp6THsXKXrUXX44eOqfChic2wVQ1GlSzQCBr7DeFBkfIZAo2WyNND4GSv54TAa0E4LYIK73WSPdgKUgw=="],
     "h3": ["[email protected]", "", { "dependencies": { "rou3": "^0.7.8", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-vZq8pEUp6THsXKXrUXX44eOqfChic2wVQ1GlSzQCBr7DeFBkfIZAo2WyNND4GSv54TAa0E4LYIK73WSPdgKUgw=="],
 
 
@@ -2755,6 +2794,8 @@
 
 
     "isexe": ["[email protected]", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
     "isexe": ["[email protected]", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
 
 
+    "isomorphic-ws": ["[email protected]", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
+
     "iterate-iterator": ["[email protected]", "", {}, "sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw=="],
     "iterate-iterator": ["[email protected]", "", {}, "sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw=="],
 
 
     "iterate-value": ["[email protected]", "", { "dependencies": { "es-get-iterator": "^1.0.2", "iterate-iterator": "^1.0.1" } }, "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ=="],
     "iterate-value": ["[email protected]", "", { "dependencies": { "es-get-iterator": "^1.0.2", "iterate-iterator": "^1.0.1" } }, "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ=="],
@@ -2787,6 +2828,8 @@
 
 
     "json-schema": ["[email protected]", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
     "json-schema": ["[email protected]", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
 
 
+    "json-schema-to-ts": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
+
     "json-schema-traverse": ["[email protected]", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
     "json-schema-traverse": ["[email protected]", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
 
 
     "json-schema-typed": ["[email protected]", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
     "json-schema-typed": ["[email protected]", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
@@ -3063,6 +3106,8 @@
 
 
     "named-placeholders": ["[email protected]", "", { "dependencies": { "lru-cache": "^7.14.1" } }, "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w=="],
     "named-placeholders": ["[email protected]", "", { "dependencies": { "lru-cache": "^7.14.1" } }, "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w=="],
 
 
+    "nanoevents": ["[email protected]", "", {}, "sha512-o6lpKiCxLeijK4hgsqfR6CNToPyRU3keKyyI6uwuHRvpRTbZ0wXw51WRgyldVugZqoJfkGFrjrIenYH3bfEO3Q=="],
+
     "nanoid": ["[email protected]", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
     "nanoid": ["[email protected]", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
 
 
     "negotiator": ["[email protected]", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
     "negotiator": ["[email protected]", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
@@ -3417,6 +3462,8 @@
 
 
     "reusify": ["[email protected]", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
     "reusify": ["[email protected]", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
 
 
+    "rimraf": ["[email protected]", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
+
     "rollup": ["[email protected]", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="],
     "rollup": ["[email protected]", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="],
 
 
     "rou3": ["[email protected]", "", {}, "sha512-aoFj6f7MJZ5muJ+Of79nrhs9N3oLGqi2VEMe94Zbkjb6Wupha46EuoYgpWSOZlXww3bbd8ojgXTAA2mzimX5Ww=="],
     "rou3": ["[email protected]", "", {}, "sha512-aoFj6f7MJZ5muJ+Of79nrhs9N3oLGqi2VEMe94Zbkjb6Wupha46EuoYgpWSOZlXww3bbd8ojgXTAA2mzimX5Ww=="],
@@ -3447,7 +3494,7 @@
 
 
     "selderee": ["[email protected]", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
     "selderee": ["[email protected]", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
 
 
-    "semver": ["[email protected].2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
+    "semver": ["[email protected].3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
 
 
     "send": ["[email protected]", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
     "send": ["[email protected]", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
 
 
@@ -3503,6 +3550,10 @@
 
 
     "smol-toml": ["[email protected]", "", {}, "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ=="],
     "smol-toml": ["[email protected]", "", {}, "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ=="],
 
 
+    "socket.io-client": ["[email protected]", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="],
+
+    "socket.io-parser": ["[email protected]", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ=="],
+
     "solid-js": ["[email protected]", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="],
     "solid-js": ["[email protected]", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="],
 
 
     "solid-list": ["[email protected]", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="],
     "solid-list": ["[email protected]", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="],
@@ -3513,6 +3564,8 @@
 
 
     "solid-refresh": ["[email protected]", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="],
     "solid-refresh": ["[email protected]", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="],
 
 
+    "solid-stripe": ["[email protected]", "", { "peerDependencies": { "@stripe/stripe-js": ">=1.44.1 <8.0.0", "solid-js": "^1.6.0" } }, "sha512-l2SkWoe51rsvk9u1ILBRWyCHODZebChSGMR6zHYJTivTRC0XWrRnNNKs5x1PYXsaIU71KYI6ov5CZB5cOtGLWw=="],
+
     "solid-use": ["[email protected]", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="],
     "solid-use": ["[email protected]", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="],
 
 
     "source-map": ["[email protected]", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
     "source-map": ["[email protected]", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
@@ -3667,6 +3720,8 @@
 
 
     "trough": ["[email protected]", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
     "trough": ["[email protected]", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
 
 
+    "ts-algebra": ["[email protected]", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
+
     "ts-interface-checker": ["[email protected]", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
     "ts-interface-checker": ["[email protected]", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
 
 
     "tsconfck": ["[email protected]", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
     "tsconfck": ["[email protected]", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
@@ -3859,6 +3914,8 @@
 
 
     "xmlbuilder": ["[email protected]", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
     "xmlbuilder": ["[email protected]", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
 
 
+    "xmlhttprequest-ssl": ["[email protected]", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
+
     "xxhash-wasm": ["[email protected]", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
     "xxhash-wasm": ["[email protected]", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
 
 
     "y18n": ["[email protected]", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
     "y18n": ["[email protected]", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
@@ -3909,35 +3966,35 @@
 
 
     "@agentclientprotocol/sdk/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
     "@agentclientprotocol/sdk/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
 
 
-    "@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/[email protected].45", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Ipv62vavDCmrV/oE/lXehL9FzwQuZOnnlhPEftWizx464Wb6lvnBTJx8uhmEYruFSzOWTI95Z33ncZ4tA8E6RQ=="],
+    "@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/[email protected]7", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
 
 
-    "@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
+    "@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/[email protected]", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="],
+
+    "@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
 
 
     "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
     "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
 
 
-    "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/[email protected]0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tNHuraF11db+8xJEDBoU9E3vMcpnHFKRhnLQ3DQX2LnEzfPB9DksZ8rE+yVuDN1WRW9cm2OWAhgHFgVKs7ICuw=="],
+    "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/[email protected]9", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
 
 
-    "@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected].29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
+    "@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected].30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
 
 
-    "@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected].29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
+    "@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected].30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
 
 
-    "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/[email protected]0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="],
+    "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/[email protected]7", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
 
 
-    "@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="],
+    "@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
 
 
     "@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
     "@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
 
 
+    "@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
+
     "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
     "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
 
 
-    "@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected].29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
+    "@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected].30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
 
 
     "@ai-sdk/vercel/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
     "@ai-sdk/vercel/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
 
 
-    "@ai-sdk/vercel/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="],
-
-    "@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
-
-    "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
+    "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
 
 
     "@astrojs/cloudflare/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=="],
     "@astrojs/cloudflare/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=="],
 
 
@@ -4011,6 +4068,8 @@
 
 
     "@expressive-code/plugin-shiki/shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
     "@expressive-code/plugin-shiki/shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
 
 
+    "@gitlab/gitlab-ai-provider/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+
     "@hey-api/json-schema-ref-parser/js-yaml": ["[email protected]", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
     "@hey-api/json-schema-ref-parser/js-yaml": ["[email protected]", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
 
 
     "@hey-api/openapi-ts/open": ["[email protected]", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
     "@hey-api/openapi-ts/open": ["[email protected]", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
@@ -4181,6 +4240,10 @@
 
 
     "@slack/web-api/p-queue": ["[email protected]", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
     "@slack/web-api/p-queue": ["[email protected]", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
 
 
+    "@smithy/eventstream-codec/@smithy/types": ["@smithy/[email protected]", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA=="],
+
+    "@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/[email protected]", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="],
+
     "@solidjs/start/esbuild": ["[email protected]", "", { "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=="],
     "@solidjs/start/esbuild": ["[email protected]", "", { "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=="],
 
 
     "@solidjs/start/path-to-regexp": ["[email protected]", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
     "@solidjs/start/path-to-regexp": ["[email protected]", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
@@ -4205,10 +4268,6 @@
 
 
     "accepts/mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
     "accepts/mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
 
 
-    "ai/@ai-sdk/gateway": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W+cB1sOWvPcz9qiIsNtD+HxUrBUva2vWv2K1EFukuImX+HA0uZx3EyyOjhYQ9gtf/teqEG80M6OvJ7xx/VLV2A=="],
-
-    "ai/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
-
     "ansi-align/string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
     "ansi-align/string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
 
 
     "anymatch/picomatch": ["[email protected]", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
     "anymatch/picomatch": ["[email protected]", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
@@ -4221,6 +4280,8 @@
 
 
     "astro/diff": ["[email protected]", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="],
     "astro/diff": ["[email protected]", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="],
 
 
+    "astro/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
+
     "astro/shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
     "astro/shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
 
 
     "astro/unstorage": ["[email protected]", "", { "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.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-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q=="],
     "astro/unstorage": ["[email protected]", "", { "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.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-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q=="],
@@ -4241,6 +4302,10 @@
 
 
     "body-parser/qs": ["[email protected]", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
     "body-parser/qs": ["[email protected]", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
 
 
+    "bun-webgpu/@webgpu/types": ["@webgpu/[email protected]", "", {}, "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA=="],
+
+    "c12/chokidar": ["[email protected]", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
+
     "clean-css/source-map": ["[email protected]", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
     "clean-css/source-map": ["[email protected]", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
 
 
     "compress-commons/is-stream": ["[email protected]", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
     "compress-commons/is-stream": ["[email protected]", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
@@ -4257,6 +4322,10 @@
 
 
     "editorconfig/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="],
     "editorconfig/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="],
 
 
+    "editorconfig/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
+
+    "engine.io-client/ws": ["[email protected]", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
+
     "es-get-iterator/isarray": ["[email protected]", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
     "es-get-iterator/isarray": ["[email protected]", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
 
 
     "esbuild-plugin-copy/chalk": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
     "esbuild-plugin-copy/chalk": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -4273,13 +4342,15 @@
 
 
     "express/qs": ["[email protected]", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
     "express/qs": ["[email protected]", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
 
 
+    "fetch-blob/web-streams-polyfill": ["[email protected]", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
+
     "finalhandler/debug": ["[email protected]", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
     "finalhandler/debug": ["[email protected]", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
 
 
     "form-data/mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
     "form-data/mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
 
 
-    "gaxios/is-stream": ["[email protected]", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
+    "gaxios/node-fetch": ["[email protected]", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
 
 
-    "gaxios/uuid": ["[email protected]", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
+    "gel/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
 
 
     "glob/minimatch": ["[email protected]", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
     "glob/minimatch": ["[email protected]", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
 
 
@@ -4295,6 +4366,8 @@
 
 
     "jsonwebtoken/jws": ["[email protected]", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
     "jsonwebtoken/jws": ["[email protected]", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
 
 
+    "jsonwebtoken/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
+
     "katex/commander": ["[email protected]", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
     "katex/commander": ["[email protected]", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
 
 
     "lazystream/readable-stream": ["[email protected]", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
     "lazystream/readable-stream": ["[email protected]", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
@@ -4321,11 +4394,11 @@
 
 
     "nypm/tinyexec": ["[email protected]", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
     "nypm/tinyexec": ["[email protected]", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
 
 
-    "opencode/@ai-sdk/anthropic": ["@ai-sdk/[email protected]6", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ=="],
+    "opencode/@ai-sdk/anthropic": ["@ai-sdk/[email protected]7", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
 
 
-    "opencode/@ai-sdk/openai": ["@ai-sdk/[email protected].71", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tg+gj+R0z/On9P4V7hy7/7o04cQPjKGayMCL3gzWD/aNGjAKkhEnaocuNDidSnghizt8g2zJn16cAuAolnW+qQ=="],
+    "opencode/@ai-sdk/openai": ["@ai-sdk/[email protected].89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
 
 
-    "opencode/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected].29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
+    "opencode/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected].30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
 
 
     "opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/[email protected]", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
     "opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/[email protected]", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
 
 
@@ -4363,6 +4436,8 @@
 
 
     "readdir-glob/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
     "readdir-glob/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
 
 
+    "rimraf/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-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
+
     "router/path-to-regexp": ["[email protected]", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
     "router/path-to-regexp": ["[email protected]", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
 
 
     "safe-array-concat/isarray": ["[email protected]", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
     "safe-array-concat/isarray": ["[email protected]", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
@@ -4377,6 +4452,8 @@
 
 
     "sharp/detect-libc": ["[email protected]", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
     "sharp/detect-libc": ["[email protected]", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
 
 
+    "sharp/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
+
     "shiki/@shikijs/core": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
     "shiki/@shikijs/core": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
 
 
     "shiki/@shikijs/types": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
     "shiki/@shikijs/types": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
@@ -4855,6 +4932,8 @@
 
 
     "body-parser/debug/ms": ["[email protected]", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
     "body-parser/debug/ms": ["[email protected]", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
 
 
+    "c12/chokidar/readdirp": ["[email protected]", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
+
     "cross-spawn/which/isexe": ["[email protected]", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
     "cross-spawn/which/isexe": ["[email protected]", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
 
 
     "drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
     "drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
@@ -4925,8 +5004,6 @@
 
 
     "lazystream/readable-stream/string_decoder": ["[email protected]", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
     "lazystream/readable-stream/string_decoder": ["[email protected]", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
 
 
-    "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
-
     "opencontrol/@modelcontextprotocol/sdk/express": ["[email protected]", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
     "opencontrol/@modelcontextprotocol/sdk/express": ["[email protected]", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
 
 
     "opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["[email protected]", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
     "opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["[email protected]", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
@@ -4943,6 +5020,12 @@
 
 
     "readable-stream/buffer/ieee754": ["[email protected]", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
     "readable-stream/buffer/ieee754": ["[email protected]", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
 
 
+    "rimraf/glob/jackspeak": ["[email protected]", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
+
+    "rimraf/glob/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+
+    "rimraf/glob/path-scurry": ["[email protected]", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
+
     "send/debug/ms": ["[email protected]", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
     "send/debug/ms": ["[email protected]", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
 
 
     "string-width-cjs/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
     "string-width-cjs/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
@@ -5125,6 +5208,8 @@
 
 
     "pkg-up/find-up/locate-path/path-exists": ["[email protected]", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
     "pkg-up/find-up/locate-path/path-exists": ["[email protected]", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
 
 
+    "rimraf/glob/path-scurry/lru-cache": ["[email protected]", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+
     "tw-to-css/tailwindcss/chokidar/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
     "tw-to-css/tailwindcss/chokidar/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
 
 
     "tw-to-css/tailwindcss/chokidar/readdirp": ["[email protected]", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
     "tw-to-css/tailwindcss/chokidar/readdirp": ["[email protected]", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],

+ 3 - 3
flake.lock

@@ -2,11 +2,11 @@
   "nodes": {
   "nodes": {
     "nixpkgs": {
     "nixpkgs": {
       "locked": {
       "locked": {
-        "lastModified": 1767364772,
-        "narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
+        "lastModified": 1768395095,
+        "narHash": "sha256-ZhuYJbwbZT32QA95tSkXd9zXHcdZj90EzHpEXBMabaw=",
         "owner": "NixOS",
         "owner": "NixOS",
         "repo": "nixpkgs",
         "repo": "nixpkgs",
-        "rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
+        "rev": "13868c071cc73a5e9f610c47d7bb08e5da64fdd5",
         "type": "github"
         "type": "github"
       },
       },
       "original": {
       "original": {

+ 23 - 3
flake.nix

@@ -27,11 +27,28 @@
         "aarch64-darwin" = "bun-darwin-arm64";
         "aarch64-darwin" = "bun-darwin-arm64";
         "x86_64-darwin" = "bun-darwin-x64";
         "x86_64-darwin" = "bun-darwin-x64";
       };
       };
-      defaultNodeModules = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+
+      # Parse "bun-{os}-{cpu}" to {os, cpu}
+      parseBunTarget =
+        target:
+        let
+          parts = lib.splitString "-" target;
+        in
+        {
+          os = builtins.elemAt parts 1;
+          cpu = builtins.elemAt parts 2;
+        };
+
       hashesFile = "${./nix}/hashes.json";
       hashesFile = "${./nix}/hashes.json";
       hashesData =
       hashesData =
         if builtins.pathExists hashesFile then builtins.fromJSON (builtins.readFile hashesFile) else { };
         if builtins.pathExists hashesFile then builtins.fromJSON (builtins.readFile hashesFile) else { };
-      nodeModulesHash = hashesData.nodeModules or defaultNodeModules;
+      # Lookup hash: supports per-system ({system: hash}) or legacy single hash
+      nodeModulesHashFor =
+        system:
+        if builtins.isAttrs hashesData.nodeModules then
+          hashesData.nodeModules.${system}
+        else
+          hashesData.nodeModules;
       modelsDev = forEachSystem (
       modelsDev = forEachSystem (
         system:
         system:
         let
         let
@@ -63,8 +80,11 @@
         system:
         system:
         let
         let
           pkgs = pkgsFor system;
           pkgs = pkgsFor system;
+          bunPlatform = parseBunTarget bunTarget.${system};
           mkNodeModules = pkgs.callPackage ./nix/node-modules.nix {
           mkNodeModules = pkgs.callPackage ./nix/node-modules.nix {
-            hash = nodeModulesHash;
+            hash = nodeModulesHashFor system;
+            bunCpu = bunPlatform.cpu;
+            bunOs = bunPlatform.os;
           };
           };
           mkOpencode = pkgs.callPackage ./nix/opencode.nix { };
           mkOpencode = pkgs.callPackage ./nix/opencode.nix { };
           mkDesktop = pkgs.callPackage ./nix/desktop.nix { };
           mkDesktop = pkgs.callPackage ./nix/desktop.nix { };

+ 6 - 5
github/README.md

@@ -81,12 +81,13 @@ This will walk you through installing the GitHub app, creating the workflow, and
        permissions:
        permissions:
          id-token: write
          id-token: write
        steps:
        steps:
-         - name: Checkout repository
-           uses: actions/checkout@v6
-           with:
-             fetch-depth: 1
+          - name: Checkout repository
+            uses: actions/checkout@v6
+            with:
+              fetch-depth: 1
+              persist-credentials: false
 
 
-         - name: Run opencode
+          - name: Run opencode
            uses: anomalyco/opencode/github@latest
            uses: anomalyco/opencode/github@latest
            env:
            env:
              ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
              ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

+ 4 - 0
infra/console.ts

@@ -119,9 +119,11 @@ const ZEN_MODELS = [
   new sst.Secret("ZEN_MODELS5"),
   new sst.Secret("ZEN_MODELS5"),
   new sst.Secret("ZEN_MODELS6"),
   new sst.Secret("ZEN_MODELS6"),
   new sst.Secret("ZEN_MODELS7"),
   new sst.Secret("ZEN_MODELS7"),
+  new sst.Secret("ZEN_MODELS8"),
 ]
 ]
 const ZEN_BLACK = new sst.Secret("ZEN_BLACK")
 const ZEN_BLACK = new sst.Secret("ZEN_BLACK")
 const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
 const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
+const STRIPE_PUBLISHABLE_KEY = new sst.Secret("STRIPE_PUBLISHABLE_KEY")
 const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
 const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
   properties: { value: auth.url.apply((url) => url!) },
   properties: { value: auth.url.apply((url) => url!) },
 })
 })
@@ -163,6 +165,7 @@ new sst.cloudflare.x.SolidStart("Console", {
     AWS_SES_ACCESS_KEY_ID,
     AWS_SES_ACCESS_KEY_ID,
     AWS_SES_SECRET_ACCESS_KEY,
     AWS_SES_SECRET_ACCESS_KEY,
     ZEN_BLACK,
     ZEN_BLACK,
+    new sst.Secret("ZEN_SESSION_SECRET"),
     ...ZEN_MODELS,
     ...ZEN_MODELS,
     ...($dev
     ...($dev
       ? [
       ? [
@@ -176,6 +179,7 @@ new sst.cloudflare.x.SolidStart("Console", {
     //VITE_DOCS_URL: web.url.apply((url) => url!),
     //VITE_DOCS_URL: web.url.apply((url) => url!),
     //VITE_API_URL: gateway.url.apply((url) => url!),
     //VITE_API_URL: gateway.url.apply((url) => url!),
     VITE_AUTH_URL: auth.url.apply((url) => url!),
     VITE_AUTH_URL: auth.url.apply((url) => url!),
+    VITE_STRIPE_PUBLISHABLE_KEY: STRIPE_PUBLISHABLE_KEY.value,
   },
   },
   transform: {
   transform: {
     server: {
     server: {

+ 1 - 1
install

@@ -369,7 +369,7 @@ case $current_shell in
         config_files="$HOME/.config/fish/config.fish"
         config_files="$HOME/.config/fish/config.fish"
     ;;
     ;;
     zsh)
     zsh)
-        config_files="$HOME/.zshrc $HOME/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
+        config_files="${ZDOTDIR:-$HOME}/.zshrc ${ZDOTDIR:-$HOME}/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
     ;;
     ;;
     bash)
     bash)
         config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
         config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"

+ 6 - 1
nix/hashes.json

@@ -1,3 +1,8 @@
 {
 {
-  "nodeModules": "sha256-KjBAaI9Kv6huOmPvUbtyYsMhbScI91w1lOZyXpIWqI0="
+  "nodeModules": {
+    "x86_64-linux": "sha256-07XxcHLuToM4QfWVyaPLACxjPZ93ZM7gtpX2o08Lp18=",
+    "aarch64-linux": "sha256-0Im52dLeZ0ZtaPJr/U4m7+IRtOfziHNJI/Bu/V6cPho=",
+    "aarch64-darwin": "sha256-U2UvE70nM0OI0VhIku8qnX+ptPbA+Q/y1BGXbFMcyt4=",
+    "x86_64-darwin": "sha256-CpZFHBMPJSib2Vqs6oC8HQjQtviPUMa/qezHAe22N/A="
+  }
 }
 }

+ 4 - 2
nix/node-modules.nix

@@ -5,6 +5,8 @@
   bun,
   bun,
   cacert,
   cacert,
   curl,
   curl,
+  bunCpu,
+  bunOs,
 }:
 }:
 args:
 args:
 stdenvNoCC.mkDerivation {
 stdenvNoCC.mkDerivation {
@@ -29,8 +31,8 @@ stdenvNoCC.mkDerivation {
     export HOME=$(mktemp -d)
     export HOME=$(mktemp -d)
     export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
     export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
     bun install \
     bun install \
-      --cpu="*" \
-      --os="*" \
+      --cpu="${bunCpu}" \
+      --os="${bunOs}" \
       --frozen-lockfile \
       --frozen-lockfile \
       --ignore-scripts \
       --ignore-scripts \
       --no-progress \
       --no-progress \

+ 10 - 3
nix/scripts/update-hashes.sh

@@ -10,7 +10,7 @@ HASH_FILE=${HASH_FILE:-$DEFAULT_HASH_FILE}
 if [ ! -f "$HASH_FILE" ]; then
 if [ ! -f "$HASH_FILE" ]; then
   cat >"$HASH_FILE" <<EOF
   cat >"$HASH_FILE" <<EOF
 {
 {
-  "nodeModules": "$DUMMY"
+  "nodeModules": {}
 }
 }
 EOF
 EOF
 fi
 fi
@@ -33,9 +33,16 @@ trap cleanup EXIT
 
 
 write_node_modules_hash() {
 write_node_modules_hash() {
   local value="$1"
   local value="$1"
+  local system="${2:-$SYSTEM}"
   local temp
   local temp
   temp=$(mktemp)
   temp=$(mktemp)
-  jq --arg value "$value" '.nodeModules = $value' "$HASH_FILE" >"$temp"
+  
+  if jq -e '.nodeModules | type == "object"' "$HASH_FILE" >/dev/null 2>&1; then
+    jq --arg system "$system" --arg value "$value" '.nodeModules[$system] = $value' "$HASH_FILE" >"$temp"
+  else
+    jq --arg system "$system" --arg value "$value" '.nodeModules = {($system): $value}' "$HASH_FILE" >"$temp"
+  fi
+  
   mv "$temp" "$HASH_FILE"
   mv "$temp" "$HASH_FILE"
 }
 }
 
 
@@ -104,7 +111,7 @@ fi
 
 
 write_node_modules_hash "$CORRECT_HASH"
 write_node_modules_hash "$CORRECT_HASH"
 
 
-jq -e --arg hash "$CORRECT_HASH" '.nodeModules == $hash' "$HASH_FILE" >/dev/null
+jq -e --arg system "$SYSTEM" --arg hash "$CORRECT_HASH" '.nodeModules[$system] == $hash' "$HASH_FILE" >/dev/null
 
 
 echo "node_modules hash updated for ${SYSTEM}: $CORRECT_HASH"
 echo "node_modules hash updated for ${SYSTEM}: $CORRECT_HASH"
 
 

+ 3 - 2
package.json

@@ -21,7 +21,7 @@
       "packages/slack"
       "packages/slack"
     ],
     ],
     "catalog": {
     "catalog": {
-      "@types/bun": "1.3.4",
+      "@types/bun": "1.3.5",
       "@octokit/rest": "22.0.0",
       "@octokit/rest": "22.0.0",
       "@hono/zod-validator": "0.4.2",
       "@hono/zod-validator": "0.4.2",
       "ulid": "3.0.1",
       "ulid": "3.0.1",
@@ -36,7 +36,8 @@
       "@solid-primitives/storage": "4.3.3",
       "@solid-primitives/storage": "4.3.3",
       "@tailwindcss/vite": "4.1.11",
       "@tailwindcss/vite": "4.1.11",
       "diff": "8.0.2",
       "diff": "8.0.2",
-      "ai": "5.0.97",
+      "dompurify": "3.3.1",
+      "ai": "5.0.119",
       "hono": "4.10.7",
       "hono": "4.10.7",
       "hono-openapi": "1.1.2",
       "hono-openapi": "1.1.2",
       "fuzzysort": "3.1.0",
       "fuzzysort": "3.1.0",

+ 2 - 32
packages/app/index.html

@@ -13,41 +13,11 @@
     <meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
     <meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
     <meta property="og:image" content="/social-share.png" />
     <meta property="og:image" content="/social-share.png" />
     <meta property="twitter:image" content="/social-share.png" />
     <meta property="twitter:image" content="/social-share.png" />
-    <!-- Theme preload script - applies cached theme to avoid FOUC -->
-    <script id="oc-theme-preload-script">
-      ;(function () {
-        var themeId = localStorage.getItem("opencode-theme-id")
-        if (!themeId) return
-
-        var scheme = localStorage.getItem("opencode-color-scheme") || "system"
-        var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)
-        var mode = isDark ? "dark" : "light"
-
-        document.documentElement.dataset.theme = themeId
-        document.documentElement.dataset.colorScheme = mode
-
-        if (themeId === "oc-1") return
-
-        var css = localStorage.getItem("opencode-theme-css-" + themeId + "-" + mode)
-        if (css) {
-          var style = document.createElement("style")
-          style.id = "oc-theme-preload"
-          style.textContent =
-            ":root{color-scheme:" +
-            mode +
-            ";--text-mix-blend-mode:" +
-            (isDark ? "plus-lighter" : "multiply") +
-            ";" +
-            css +
-            "}"
-          document.head.appendChild(style)
-        }
-      })()
-    </script>
+    <script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
   </head>
   </head>
   <body class="antialiased overscroll-none text-12-regular overflow-hidden">
   <body class="antialiased overscroll-none text-12-regular overflow-hidden">
     <noscript>You need to enable JavaScript to run this app.</noscript>
     <noscript>You need to enable JavaScript to run this app.</noscript>
-    <div id="root" class="flex flex-col h-dvh"></div>
+    <div id="root" class="flex flex-col h-dvh p-px"></div>
     <script src="/src/entry.tsx" type="module"></script>
     <script src="/src/entry.tsx" type="module"></script>
   </body>
   </body>
 </html>
 </html>

+ 1 - 1
packages/app/package.json

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

+ 28 - 0
packages/app/public/oc-theme-preload.js

@@ -0,0 +1,28 @@
+;(function () {
+  var themeId = localStorage.getItem("opencode-theme-id")
+  if (!themeId) return
+
+  var scheme = localStorage.getItem("opencode-color-scheme") || "system"
+  var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)
+  var mode = isDark ? "dark" : "light"
+
+  document.documentElement.dataset.theme = themeId
+  document.documentElement.dataset.colorScheme = mode
+
+  if (themeId === "oc-1") return
+
+  var css = localStorage.getItem("opencode-theme-css-" + themeId + "-" + mode)
+  if (css) {
+    var style = document.createElement("style")
+    style.id = "oc-theme-preload"
+    style.textContent =
+      ":root{color-scheme:" +
+      mode +
+      ";--text-mix-blend-mode:" +
+      (isDark ? "plus-lighter" : "multiply") +
+      ";" +
+      css +
+      "}"
+    document.head.appendChild(style)
+  }
+})()

+ 12 - 15
packages/app/src/app.tsx

@@ -33,22 +33,10 @@ const Loading = () => <div class="size-full flex items-center justify-center tex
 
 
 declare global {
 declare global {
   interface Window {
   interface Window {
-    __OPENCODE__?: { updaterEnabled?: boolean; port?: number; serverReady?: boolean }
+    __OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string }
   }
   }
 }
 }
 
 
-const defaultServerUrl = iife(() => {
-  const param = new URLSearchParams(document.location.search).get("url")
-  if (param) return param
-
-  if (location.hostname.includes("opencode.ai")) return "http://localhost:4096"
-  if (window.__OPENCODE__) return `http://127.0.0.1:${window.__OPENCODE__.port}`
-  if (import.meta.env.DEV)
-    return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}`
-
-  return window.location.origin
-})
-
 export function AppBaseProviders(props: ParentProps) {
 export function AppBaseProviders(props: ParentProps) {
   return (
   return (
     <MetaProvider>
     <MetaProvider>
@@ -77,9 +65,18 @@ function ServerKey(props: ParentProps) {
   )
   )
 }
 }
 
 
-export function AppInterface() {
+export function AppInterface(props: { defaultUrl?: string }) {
+  const defaultServerUrl = () => {
+    if (props.defaultUrl) return props.defaultUrl
+    if (location.hostname.includes("opencode.ai")) return "http://localhost:4096"
+    if (import.meta.env.DEV)
+      return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}`
+
+    return window.location.origin
+  }
+
   return (
   return (
-    <ServerProvider defaultUrl={defaultServerUrl}>
+    <ServerProvider defaultUrl={defaultServerUrl()}>
       <ServerKey>
       <ServerKey>
         <GlobalSDKProvider>
         <GlobalSDKProvider>
           <GlobalSyncProvider>
           <GlobalSyncProvider>

+ 99 - 0
packages/app/src/components/dialog-fork.tsx

@@ -0,0 +1,99 @@
+import { Component, createMemo } from "solid-js"
+import { useNavigate, useParams } from "@solidjs/router"
+import { useSync } from "@/context/sync"
+import { useSDK } from "@/context/sdk"
+import { usePrompt } from "@/context/prompt"
+import { useDialog } from "@opencode-ai/ui/context/dialog"
+import { Dialog } from "@opencode-ai/ui/dialog"
+import { List } from "@opencode-ai/ui/list"
+import { extractPromptFromParts } from "@/utils/prompt"
+import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client"
+import { base64Encode } from "@opencode-ai/util/encode"
+
+interface ForkableMessage {
+  id: string
+  text: string
+  time: string
+}
+
+function formatTime(date: Date): string {
+  return date.toLocaleTimeString(undefined, { timeStyle: "short" })
+}
+
+export const DialogFork: Component = () => {
+  const params = useParams()
+  const navigate = useNavigate()
+  const sync = useSync()
+  const sdk = useSDK()
+  const prompt = usePrompt()
+  const dialog = useDialog()
+
+  const messages = createMemo((): ForkableMessage[] => {
+    const sessionID = params.id
+    if (!sessionID) return []
+
+    const msgs = sync.data.message[sessionID] ?? []
+    const result: ForkableMessage[] = []
+
+    for (const message of msgs) {
+      if (message.role !== "user") continue
+
+      const parts = sync.data.part[message.id] ?? []
+      const textPart = parts.find((x): x is SDKTextPart => x.type === "text" && !x.synthetic && !x.ignored)
+      if (!textPart) continue
+
+      result.push({
+        id: message.id,
+        text: textPart.text.replace(/\n/g, " ").slice(0, 200),
+        time: formatTime(new Date(message.time.created)),
+      })
+    }
+
+    return result.reverse()
+  })
+
+  const handleSelect = (item: ForkableMessage | undefined) => {
+    if (!item) return
+
+    const sessionID = params.id
+    if (!sessionID) return
+
+    const parts = sync.data.part[item.id] ?? []
+    const restored = extractPromptFromParts(parts, { directory: sdk.directory })
+
+    dialog.close()
+
+    sdk.client.session.fork({ sessionID, messageID: item.id }).then((forked) => {
+      if (!forked.data) return
+      navigate(`/${base64Encode(sdk.directory)}/session/${forked.data.id}`)
+      requestAnimationFrame(() => {
+        prompt.set(restored)
+      })
+    })
+  }
+
+  return (
+    <Dialog title="Fork from message">
+      <List
+        class="flex-1 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0"
+        search={{ placeholder: "Search", autofocus: true }}
+        emptyMessage="No messages to fork from"
+        key={(x) => x.id}
+        items={messages}
+        filterKeys={["text"]}
+        onSelect={handleSelect}
+      >
+        {(item) => (
+          <div class="w-full flex items-center gap-2">
+            <span class="truncate flex-1 min-w-0 text-left" style={{ "font-weight": "400" }}>
+              {item.text}
+            </span>
+            <span class="text-text-weak shrink-0" style={{ "font-weight": "400" }}>
+              {item.time}
+            </span>
+          </div>
+        )}
+      </List>
+    </Dialog>
+  )
+}

+ 154 - 25
packages/app/src/components/dialog-select-file.tsx

@@ -4,46 +4,175 @@ import { FileIcon } from "@opencode-ai/ui/file-icon"
 import { List } from "@opencode-ai/ui/list"
 import { List } from "@opencode-ai/ui/list"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
 import { useParams } from "@solidjs/router"
 import { useParams } from "@solidjs/router"
-import { createMemo } from "solid-js"
+import { createMemo, createSignal, onCleanup, Show } from "solid-js"
+import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
 import { useLayout } from "@/context/layout"
 import { useLayout } from "@/context/layout"
 import { useFile } from "@/context/file"
 import { useFile } from "@/context/file"
 
 
+type EntryType = "command" | "file"
+
+type Entry = {
+  id: string
+  type: EntryType
+  title: string
+  description?: string
+  keybind?: string
+  category: "Commands" | "Files"
+  option?: CommandOption
+  path?: string
+}
+
 export function DialogSelectFile() {
 export function DialogSelectFile() {
+  const command = useCommand()
   const layout = useLayout()
   const layout = useLayout()
   const file = useFile()
   const file = useFile()
   const dialog = useDialog()
   const dialog = useDialog()
   const params = useParams()
   const params = useParams()
   const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
   const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
   const tabs = createMemo(() => layout.tabs(sessionKey()))
   const tabs = createMemo(() => layout.tabs(sessionKey()))
+  const view = createMemo(() => layout.view(sessionKey()))
+  const state = { cleanup: undefined as (() => void) | void, committed: false }
+  const [grouped, setGrouped] = createSignal(false)
+  const common = ["session.new", "session.previous", "session.next", "terminal.toggle", "review.toggle"]
+  const limit = 5
+
+  const allowed = createMemo(() =>
+    command.options.filter(
+      (option) => !option.disabled && !option.id.startsWith("suggested.") && option.id !== "file.open",
+    ),
+  )
+
+  const commandItem = (option: CommandOption): Entry => ({
+    id: "command:" + option.id,
+    type: "command",
+    title: option.title,
+    description: option.description,
+    keybind: option.keybind,
+    category: "Commands",
+    option,
+  })
+
+  const fileItem = (path: string): Entry => ({
+    id: "file:" + path,
+    type: "file",
+    title: path,
+    category: "Files",
+    path,
+  })
+
+  const list = createMemo(() => allowed().map(commandItem))
+
+  const picks = createMemo(() => {
+    const all = allowed()
+    const order = new Map(common.map((id, index) => [id, index]))
+    const picked = all.filter((option) => order.has(option.id))
+    const base = picked.length ? picked : all.slice(0, limit)
+    const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
+    return sorted.map(commandItem)
+  })
+
+  const recent = createMemo(() => {
+    const all = tabs().all()
+    const active = tabs().active()
+    const order = active ? [active, ...all.filter((item) => item !== active)] : all
+    const seen = new Set<string>()
+    const items: Entry[] = []
+
+    for (const item of order) {
+      const path = file.pathFromTab(item)
+      if (!path) continue
+      if (seen.has(path)) continue
+      seen.add(path)
+      items.push(fileItem(path))
+    }
+
+    return items.slice(0, limit)
+  })
+
+  const items = async (filter: string) => {
+    const query = filter.trim()
+    setGrouped(query.length > 0)
+    if (!query) return [...picks(), ...recent()]
+    const files = await file.searchFiles(query)
+    const entries = files.map(fileItem)
+    return [...list(), ...entries]
+  }
+
+  const handleMove = (item: Entry | undefined) => {
+    state.cleanup?.()
+    if (!item) return
+    if (item.type !== "command") return
+    state.cleanup = item.option?.onHighlight?.()
+  }
+
+  const open = (path: string) => {
+    const value = file.tab(path)
+    tabs().open(value)
+    file.load(path)
+    view().reviewPanel.open()
+  }
+
+  const handleSelect = (item: Entry | undefined) => {
+    if (!item) return
+    state.committed = true
+    state.cleanup = undefined
+    dialog.close()
+
+    if (item.type === "command") {
+      item.option?.onSelect?.("palette")
+      return
+    }
+
+    if (!item.path) return
+    open(item.path)
+  }
+
+  onCleanup(() => {
+    if (state.committed) return
+    state.cleanup?.()
+  })
+
   return (
   return (
-    <Dialog title="Select file">
+    <Dialog title="Search">
       <List
       <List
-        search={{ placeholder: "Search files", autofocus: true }}
-        emptyMessage="No files found"
-        items={file.searchFiles}
-        key={(x) => x}
-        onSelect={(path) => {
-          if (path) {
-            const value = file.tab(path)
-            tabs().open(value)
-            file.load(path)
-            layout.review.open()
-          }
-          dialog.close()
-        }}
+        search={{ placeholder: "Search files and commands", autofocus: true }}
+        emptyMessage="No results found"
+        items={items}
+        key={(item) => item.id}
+        filterKeys={["title", "description", "category"]}
+        groupBy={(item) => (grouped() ? item.category : "")}
+        onMove={handleMove}
+        onSelect={handleSelect}
       >
       >
-        {(i) => (
-          <div class="w-full flex items-center justify-between rounded-md">
-            <div class="flex items-center gap-x-3 grow min-w-0">
-              <FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
-              <div class="flex items-center text-14-regular">
-                <span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
-                  {getDirectory(i)}
-                </span>
-                <span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
+        {(item) => (
+          <Show
+            when={item.type === "command"}
+            fallback={
+              <div class="w-full flex items-center justify-between rounded-md">
+                <div class="flex items-center gap-x-3 grow min-w-0">
+                  <FileIcon node={{ path: item.path ?? "", type: "file" }} class="shrink-0 size-4" />
+                  <div class="flex items-center text-14-regular">
+                    <span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
+                      {getDirectory(item.path ?? "")}
+                    </span>
+                    <span class="text-text-strong whitespace-nowrap">{getFilename(item.path ?? "")}</span>
+                  </div>
+                </div>
+              </div>
+            }
+          >
+            <div class="w-full flex items-center justify-between gap-4">
+              <div class="flex items-center gap-2 min-w-0">
+                <span class="text-14-regular text-text-strong whitespace-nowrap">{item.title}</span>
+                <Show when={item.description}>
+                  <span class="text-14-regular text-text-weak truncate">{item.description}</span>
+                </Show>
               </div>
               </div>
+              <Show when={item.keybind}>
+                <span class="text-12-regular text-text-subtle shrink-0">{formatKeybind(item.keybind ?? "")}</span>
+              </Show>
             </div>
             </div>
-          </div>
+          </Show>
         )}
         )}
       </List>
       </List>
     </Dialog>
     </Dialog>

+ 1 - 1
packages/app/src/components/dialog-select-model.tsx

@@ -76,7 +76,7 @@ export const ModelSelectorPopover: Component<{
     <Kobalte open={open()} onOpenChange={setOpen} placement="top-start" gutter={8}>
     <Kobalte open={open()} onOpenChange={setOpen} placement="top-start" gutter={8}>
       <Kobalte.Trigger as="div">{props.children}</Kobalte.Trigger>
       <Kobalte.Trigger as="div">{props.children}</Kobalte.Trigger>
       <Kobalte.Portal>
       <Kobalte.Portal>
-        <Kobalte.Content class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none">
+        <Kobalte.Content class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden">
           <Kobalte.Title class="sr-only">Select model</Kobalte.Title>
           <Kobalte.Title class="sr-only">Select model</Kobalte.Title>
           <ModelList provider={props.provider} onSelect={() => setOpen(false)} class="p-1" />
           <ModelList provider={props.provider} onSelect={() => setOpen(false)} class="p-1" />
         </Kobalte.Content>
         </Kobalte.Content>

+ 81 - 14
packages/app/src/components/dialog-select-server.tsx

@@ -1,10 +1,11 @@
-import { createEffect, createMemo, onCleanup } from "solid-js"
+import { createResource, createEffect, createMemo, onCleanup, Show } from "solid-js"
 import { createStore, reconcile } from "solid-js/store"
 import { createStore, reconcile } from "solid-js/store"
 import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { Dialog } from "@opencode-ai/ui/dialog"
 import { Dialog } from "@opencode-ai/ui/dialog"
 import { List } from "@opencode-ai/ui/list"
 import { List } from "@opencode-ai/ui/list"
 import { TextField } from "@opencode-ai/ui/text-field"
 import { TextField } from "@opencode-ai/ui/text-field"
 import { Button } from "@opencode-ai/ui/button"
 import { Button } from "@opencode-ai/ui/button"
+import { IconButton } from "@opencode-ai/ui/icon-button"
 import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server"
 import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server"
 import { usePlatform } from "@/context/platform"
 import { usePlatform } from "@/context/platform"
 import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
 import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
@@ -35,6 +36,8 @@ export function DialogSelectServer() {
     error: "",
     error: "",
     status: {} as Record<string, ServerStatus | undefined>,
     status: {} as Record<string, ServerStatus | undefined>,
   })
   })
+  const [defaultUrl, defaultUrlActions] = createResource(() => platform.getDefaultServerUrl?.())
+  const isDesktop = platform.platform === "desktop"
 
 
   const items = createMemo(() => {
   const items = createMemo(() => {
     const current = server.url
     const current = server.url
@@ -114,6 +117,10 @@ export function DialogSelectServer() {
     select(value, true)
     select(value, true)
   }
   }
 
 
+  async function handleRemove(url: string) {
+    server.remove(url)
+  }
+
   return (
   return (
     <Dialog title="Servers" description="Switch which OpenCode server this app connects to.">
     <Dialog title="Servers" description="Switch which OpenCode server this app connects to.">
       <div class="flex flex-col gap-4 pb-4">
       <div class="flex flex-col gap-4 pb-4">
@@ -128,20 +135,33 @@ export function DialogSelectServer() {
           }}
           }}
         >
         >
           {(i) => (
           {(i) => (
-            <div
-              class="flex items-center gap-2 min-w-0 flex-1"
-              classList={{ "opacity-50": store.status[i]?.healthy === false }}
-            >
+            <div class="flex items-center gap-2 min-w-0 flex-1 group/item">
               <div
               <div
-                classList={{
-                  "size-1.5 rounded-full shrink-0": true,
-                  "bg-icon-success-base": store.status[i]?.healthy === true,
-                  "bg-icon-critical-base": store.status[i]?.healthy === false,
-                  "bg-border-weak-base": store.status[i] === undefined,
-                }}
-              />
-              <span class="truncate">{serverDisplayName(i)}</span>
-              <span class="text-text-weak">{store.status[i]?.version}</span>
+                class="flex items-center gap-2 min-w-0 flex-1"
+                classList={{ "opacity-50": store.status[i]?.healthy === false }}
+              >
+                <div
+                  classList={{
+                    "size-1.5 rounded-full shrink-0": true,
+                    "bg-icon-success-base": store.status[i]?.healthy === true,
+                    "bg-icon-critical-base": store.status[i]?.healthy === false,
+                    "bg-border-weak-base": store.status[i] === undefined,
+                  }}
+                />
+                <span class="truncate">{serverDisplayName(i)}</span>
+                <span class="text-text-weak">{store.status[i]?.version}</span>
+              </div>
+              <Show when={current() !== i && server.list.includes(i)}>
+                <IconButton
+                  icon="circle-x"
+                  variant="ghost"
+                  class="bg-transparent transition-opacity shrink-0 hover:scale-110"
+                  onClick={(e) => {
+                    e.stopPropagation()
+                    handleRemove(i)
+                  }}
+                />
+              </Show>
             </div>
             </div>
           )}
           )}
         </List>
         </List>
@@ -173,6 +193,53 @@ export function DialogSelectServer() {
             </div>
             </div>
           </form>
           </form>
         </div>
         </div>
+
+        <Show when={isDesktop}>
+          <div class="mt-6 px-3 flex flex-col gap-1.5">
+            <div class="px-3">
+              <h3 class="text-14-regular text-text-weak">Default server</h3>
+              <p class="text-12-regular text-text-weak mt-1">
+                Connect to this server on app launch instead of starting a local server. Requires restart.
+              </p>
+            </div>
+            <div class="flex items-center gap-2 px-3 py-2">
+              <Show
+                when={defaultUrl()}
+                fallback={
+                  <Show
+                    when={server.url}
+                    fallback={<span class="text-14-regular text-text-weak">No server selected</span>}
+                  >
+                    <Button
+                      variant="secondary"
+                      size="small"
+                      onClick={async () => {
+                        await platform.setDefaultServerUrl?.(server.url)
+                        defaultUrlActions.refetch(server.url)
+                      }}
+                    >
+                      Set current server as default
+                    </Button>
+                  </Show>
+                }
+              >
+                <div class="flex items-center gap-2 flex-1 min-w-0">
+                  <span class="truncate text-14-regular">{serverDisplayName(defaultUrl()!)}</span>
+                </div>
+                <Button
+                  variant="ghost"
+                  size="small"
+                  onClick={async () => {
+                    await platform.setDefaultServerUrl?.(null)
+                    defaultUrlActions.refetch()
+                  }}
+                >
+                  Clear
+                </Button>
+              </Show>
+            </div>
+          </div>
+        </Show>
       </div>
       </div>
     </Dialog>
     </Dialog>
   )
   )

+ 35 - 8
packages/app/src/components/prompt-input.tsx

@@ -33,11 +33,14 @@ import { useSync } from "@/context/sync"
 import { FileIcon } from "@opencode-ai/ui/file-icon"
 import { FileIcon } from "@opencode-ai/ui/file-icon"
 import { Button } from "@opencode-ai/ui/button"
 import { Button } from "@opencode-ai/ui/button"
 import { Icon } from "@opencode-ai/ui/icon"
 import { Icon } from "@opencode-ai/ui/icon"
+import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
+import type { IconName } from "@opencode-ai/ui/icons/provider"
 import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
 import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
 import { IconButton } from "@opencode-ai/ui/icon-button"
 import { IconButton } from "@opencode-ai/ui/icon-button"
 import { Select } from "@opencode-ai/ui/select"
 import { Select } from "@opencode-ai/ui/select"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
 import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { useDialog } from "@opencode-ai/ui/context/dialog"
+import { ImagePreview } from "@opencode-ai/ui/image-preview"
 import { ModelSelectorPopover } from "@/components/dialog-select-model"
 import { ModelSelectorPopover } from "@/components/dialog-select-model"
 import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
 import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
 import { useProviders } from "@/hooks/use-providers"
 import { useProviders } from "@/hooks/use-providers"
@@ -361,6 +364,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     if (!isFocused()) setStore("popover", null)
     if (!isFocused()) setStore("popover", null)
   })
   })
 
 
+  // Safety: reset composing state on focus change to prevent stuck state
+  // This handles edge cases where compositionend event may not fire
+  createEffect(() => {
+    if (!isFocused()) setComposing(false)
+  })
+
   type AtOption = { type: "agent"; name: string; display: string } | { type: "file"; path: string; display: string }
   type AtOption = { type: "agent"; name: string; display: string } | { type: "file"; path: string; display: string }
 
 
   const agentList = createMemo(() =>
   const agentList = createMemo(() =>
@@ -386,6 +395,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
   const {
   const {
     flat: atFlat,
     flat: atFlat,
     active: atActive,
     active: atActive,
+    setActive: setAtActive,
     onInput: atOnInput,
     onInput: atOnInput,
     onKeyDown: atOnKeyDown,
     onKeyDown: atOnKeyDown,
   } = useFilteredList<AtOption>({
   } = useFilteredList<AtOption>({
@@ -452,6 +462,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
   const {
   const {
     flat: slashFlat,
     flat: slashFlat,
     active: slashActive,
     active: slashActive,
+    setActive: setSlashActive,
     onInput: slashOnInput,
     onInput: slashOnInput,
     onKeyDown: slashOnKeyDown,
     onKeyDown: slashOnKeyDown,
     refetch: slashRefetch,
     refetch: slashRefetch,
@@ -876,6 +887,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       }
       }
     }
     }
 
 
+    // Handle Shift+Enter BEFORE IME check - Shift+Enter is never used for IME input
+    // and should always insert a newline regardless of composition state
+    if (event.key === "Enter" && event.shiftKey) {
+      addPart({ type: "text", content: "\n", start: 0, end: 0 })
+      event.preventDefault()
+      return
+    }
+
     if (event.key === "Enter" && isImeComposing(event)) {
     if (event.key === "Enter" && isImeComposing(event)) {
       return
       return
     }
     }
@@ -939,11 +958,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       return
       return
     }
     }
 
 
-    if (event.key === "Enter" && event.shiftKey) {
-      addPart({ type: "text", content: "\n", start: 0, end: 0 })
-      event.preventDefault()
-      return
-    }
+    // Note: Shift+Enter is handled earlier, before IME check
     if (event.key === "Enter" && !event.shiftKey) {
     if (event.key === "Enter" && !event.shiftKey) {
       handleSubmit(event)
       handleSubmit(event)
     }
     }
@@ -1299,6 +1314,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
           class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-80 min-h-10
           class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-80 min-h-10
                  overflow-auto no-scrollbar flex flex-col p-2 rounded-md
                  overflow-auto no-scrollbar flex flex-col p-2 rounded-md
                  border border-border-base bg-surface-raised-stronger-non-alpha shadow-md"
                  border border-border-base bg-surface-raised-stronger-non-alpha shadow-md"
+          onMouseDown={(e) => e.preventDefault()}
         >
         >
           <Switch>
           <Switch>
             <Match when={store.popover === "at"}>
             <Match when={store.popover === "at"}>
@@ -1314,6 +1330,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                         "bg-surface-raised-base-hover": atActive() === atKey(item),
                         "bg-surface-raised-base-hover": atActive() === atKey(item),
                       }}
                       }}
                       onClick={() => handleAtSelect(item)}
                       onClick={() => handleAtSelect(item)}
+                      onMouseEnter={() => setAtActive(atKey(item))}
                     >
                     >
                       <Show
                       <Show
                         when={item.type === "agent"}
                         when={item.type === "agent"}
@@ -1360,6 +1377,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                         "bg-surface-raised-base-hover": slashActive() === cmd.id,
                         "bg-surface-raised-base-hover": slashActive() === cmd.id,
                       }}
                       }}
                       onClick={() => handleSlashSelect(cmd)}
                       onClick={() => handleSlashSelect(cmd)}
+                      onMouseEnter={() => setSlashActive(cmd.id)}
                     >
                     >
                       <div class="flex items-center gap-2 min-w-0">
                       <div class="flex items-center gap-2 min-w-0">
                         <span class="text-14-regular text-text-strong whitespace-nowrap">/{cmd.trigger}</span>
                         <span class="text-14-regular text-text-strong whitespace-nowrap">/{cmd.trigger}</span>
@@ -1479,7 +1497,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                     <img
                     <img
                       src={attachment.dataUrl}
                       src={attachment.dataUrl}
                       alt={attachment.filename}
                       alt={attachment.filename}
-                      class="size-16 rounded-md object-cover border border-border-base"
+                      class="size-16 rounded-md object-cover border border-border-base hover:border-border-strong-base transition-colors"
+                      onClick={() =>
+                        dialog.show(() => <ImagePreview src={attachment.dataUrl} alt={attachment.filename} />)
+                      }
                     />
                     />
                   </Show>
                   </Show>
                   <button
                   <button
@@ -1551,6 +1572,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                   fallback={
                   fallback={
                     <TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
                     <TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
                       <Button as="div" variant="ghost" onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}>
                       <Button as="div" variant="ghost" onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}>
+                        <Show when={local.model.current()?.provider?.id}>
+                          <ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
+                        </Show>
                         {local.model.current()?.name ?? "Select model"}
                         {local.model.current()?.name ?? "Select model"}
                         <Icon name="chevron-down" size="small" />
                         <Icon name="chevron-down" size="small" />
                       </Button>
                       </Button>
@@ -1560,6 +1584,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                   <ModelSelectorPopover>
                   <ModelSelectorPopover>
                     <TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
                     <TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
                       <Button as="div" variant="ghost">
                       <Button as="div" variant="ghost">
+                        <Show when={local.model.current()?.provider?.id}>
+                          <ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
+                        </Show>
                         {local.model.current()?.name ?? "Select model"}
                         {local.model.current()?.name ?? "Select model"}
                         <Icon name="chevron-down" size="small" />
                         <Icon name="chevron-down" size="small" />
                       </Button>
                       </Button>
@@ -1574,10 +1601,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                   >
                   >
                     <Button
                     <Button
                       variant="ghost"
                       variant="ghost"
-                      class="text-text-base _hidden group-hover/prompt-input:inline-block"
+                      class="text-text-base _hidden group-hover/prompt-input:inline-block capitalize text-12-regular"
                       onClick={() => local.model.variant.cycle()}
                       onClick={() => local.model.variant.cycle()}
                     >
                     >
-                      <span class="capitalize text-12-regular">{local.model.variant.current() ?? "Default"}</span>
+                      {local.model.variant.current() ?? "Default"}
                     </Button>
                     </Button>
                   </TooltipKeybind>
                   </TooltipKeybind>
                 </Show>
                 </Show>

+ 2 - 1
packages/app/src/components/session-context-usage.tsx

@@ -20,6 +20,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
   const variant = createMemo(() => props.variant ?? "button")
   const variant = createMemo(() => props.variant ?? "button")
   const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
   const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
   const tabs = createMemo(() => layout.tabs(sessionKey()))
   const tabs = createMemo(() => layout.tabs(sessionKey()))
+  const view = createMemo(() => layout.view(sessionKey()))
   const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
   const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
 
 
   const cost = createMemo(() => {
   const cost = createMemo(() => {
@@ -48,7 +49,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
 
 
   const openContext = () => {
   const openContext = () => {
     if (!params.id) return
     if (!params.id) return
-    layout.review.open()
+    view().reviewPanel.open()
     tabs().open("context")
     tabs().open("context")
     tabs().setActive("context")
     tabs().setActive("context")
   }
   }

+ 178 - 228
packages/app/src/components/session/session-header.tsx

@@ -1,261 +1,211 @@
 import { createMemo, createResource, Show } from "solid-js"
 import { createMemo, createResource, Show } from "solid-js"
-import { A, useNavigate, useParams } from "@solidjs/router"
+import { Portal } from "solid-js/web"
+import { useParams } from "@solidjs/router"
 import { useLayout } from "@/context/layout"
 import { useLayout } from "@/context/layout"
 import { useCommand } from "@/context/command"
 import { useCommand } from "@/context/command"
-import { useServer } from "@/context/server"
-import { useDialog } from "@opencode-ai/ui/context/dialog"
+// import { useServer } from "@/context/server"
+// import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { useSync } from "@/context/sync"
 import { useSync } from "@/context/sync"
 import { useGlobalSDK } from "@/context/global-sdk"
 import { useGlobalSDK } from "@/context/global-sdk"
 import { getFilename } from "@opencode-ai/util/path"
 import { getFilename } from "@opencode-ai/util/path"
-import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
+import { base64Decode } from "@opencode-ai/util/encode"
 import { iife } from "@opencode-ai/util/iife"
 import { iife } from "@opencode-ai/util/iife"
 import { Icon } from "@opencode-ai/ui/icon"
 import { Icon } from "@opencode-ai/ui/icon"
 import { IconButton } from "@opencode-ai/ui/icon-button"
 import { IconButton } from "@opencode-ai/ui/icon-button"
 import { Button } from "@opencode-ai/ui/button"
 import { Button } from "@opencode-ai/ui/button"
 import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
 import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
-import { Select } from "@opencode-ai/ui/select"
 import { Popover } from "@opencode-ai/ui/popover"
 import { Popover } from "@opencode-ai/ui/popover"
 import { TextField } from "@opencode-ai/ui/text-field"
 import { TextField } from "@opencode-ai/ui/text-field"
-import { DialogSelectServer } from "@/components/dialog-select-server"
-import { SessionLspIndicator } from "@/components/session-lsp-indicator"
-import { SessionMcpIndicator } from "@/components/session-mcp-indicator"
-import type { Session } from "@opencode-ai/sdk/v2/client"
-import { same } from "@/utils/same"
 
 
 export function SessionHeader() {
 export function SessionHeader() {
   const globalSDK = useGlobalSDK()
   const globalSDK = useGlobalSDK()
   const layout = useLayout()
   const layout = useLayout()
   const params = useParams()
   const params = useParams()
-  const navigate = useNavigate()
   const command = useCommand()
   const command = useCommand()
-  const server = useServer()
-  const dialog = useDialog()
+  // const server = useServer()
+  // const dialog = useDialog()
   const sync = useSync()
   const sync = useSync()
 
 
   const projectDirectory = createMemo(() => base64Decode(params.dir ?? ""))
   const projectDirectory = createMemo(() => base64Decode(params.dir ?? ""))
+  const project = createMemo(() => {
+    const directory = projectDirectory()
+    if (!directory) return
+    return layout.projects.list().find((p) => p.worktree === directory || p.sandboxes?.includes(directory))
+  })
+  const name = createMemo(() => {
+    const current = project()
+    if (current) return current.name || getFilename(current.worktree)
+    return getFilename(projectDirectory())
+  })
+  const hotkey = createMemo(() => command.keybind("file.open"))
 
 
-  const sessions = createMemo(() => (sync.data.session ?? []).filter((s) => !s.parentID))
   const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id))
   const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id))
-  const parentSession = createMemo(() => {
-    const current = currentSession()
-    if (!current?.parentID) return undefined
-    return sync.data.session.find((s) => s.id === current.parentID)
-  })
   const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
   const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
-  const worktrees = createMemo(() => layout.projects.list().map((p) => p.worktree), [], { equals: same })
-
-  function navigateToProject(directory: string) {
-    navigate(`/${base64Encode(directory)}`)
-  }
+  const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
+  const view = createMemo(() => layout.view(sessionKey()))
 
 
-  function navigateToSession(session: Session | undefined) {
-    if (!session) return
-    // Only navigate if we're actually changing to a different session
-    if (session.id === params.id) return
-    navigate(`/${params.dir}/session/${session.id}`)
-  }
+  const centerMount = createMemo(() => document.getElementById("opencode-titlebar-center"))
+  const rightMount = createMemo(() => document.getElementById("opencode-titlebar-right"))
 
 
   return (
   return (
-    <header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex">
-      <button
-        type="button"
-        class="xl:hidden w-12 shrink-0 flex items-center justify-center border-r border-border-weak-base hover:bg-surface-raised-base-hover active:bg-surface-raised-base-active transition-colors"
-        onClick={layout.mobileSidebar.toggle}
-      >
-        <Icon name="menu" size="small" />
-      </button>
-      <div class="px-4 flex items-center justify-between gap-4 w-full">
-        <div class="flex items-center gap-3 min-w-0">
-          <div class="flex items-center gap-2 min-w-0">
-            <div class="hidden xl:flex items-center gap-2">
-              <Select
-                options={worktrees()}
-                current={sync.project?.worktree ?? projectDirectory()}
-                label={(x) => getFilename(x)}
-                onSelect={(x) => (x ? navigateToProject(x) : undefined)}
-                class="text-14-regular text-text-base"
-                variant="ghost"
-              >
-                {/* @ts-ignore */}
-                {(i) => (
-                  <div class="flex items-center gap-2">
-                    <Icon name="folder" size="small" />
-                    <div class="text-text-strong">{getFilename(i)}</div>
-                  </div>
-                )}
-              </Select>
-              <div class="text-text-weaker">/</div>
-            </div>
-            <Show
-              when={parentSession()}
-              fallback={
-                <>
-                  <Select
-                    options={sessions()}
-                    current={currentSession()}
-                    placeholder="New session"
-                    label={(x) => x.title}
-                    value={(x) => x.id}
-                    onSelect={navigateToSession}
-                    class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md"
-                    variant="ghost"
-                  />
-                </>
-              }
+    <>
+      <Show when={centerMount()}>
+        {(mount) => (
+          <Portal mount={mount()}>
+            <button
+              type="button"
+              class="hidden md:flex w-[320px] p-1 pl-1.5 items-center gap-2 justify-between rounded-md border border-border-weak-base bg-surface-raised-base transition-colors cursor-default hover:bg-surface-raised-base-hover focus:bg-surface-raised-base-hover active:bg-surface-raised-base-active"
+              onClick={() => command.trigger("file.open")}
             >
             >
-              <div class="flex items-center gap-2 min-w-0">
-                <Select
-                  options={sessions()}
-                  current={parentSession()}
-                  placeholder="Back to parent session"
-                  label={(x) => x.title}
-                  value={(x) => x.id}
-                  onSelect={(session) => {
-                    // Only navigate if selecting a different session than current parent
-                    const currentParent = parentSession()
-                    if (session && currentParent && session.id !== currentParent.id) {
-                      navigateToSession(session)
-                    }
-                  }}
-                  class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md"
-                  variant="ghost"
-                />
-                <div class="text-text-weaker">/</div>
-                <div class="flex items-center gap-1.5 min-w-0">
-                  <Tooltip value="Back to parent session">
-                    <button
-                      type="button"
-                      class="flex items-center justify-center gap-1 p-1 rounded hover:bg-surface-raised-base-hover active:bg-surface-raised-base-active transition-colors flex-shrink-0"
-                      onClick={() => navigateToSession(parentSession())}
+              <div class="flex items-center gap-2">
+                <Icon name="magnifying-glass" size="normal" class="icon-base" />
+                <span class="flex-1 min-w-0 text-14-regular text-text-weak truncate" style={{ "line-height": 1 }}>
+                  Search {name()}
+                </span>
+              </div>
+
+              <Show when={hotkey()}>
+                {(keybind) => (
+                  <span
+                    class="shrink-0 flex items-center justify-center h-5 px-2 rounded-[2px] bg-surface-base text-12-medium text-text-weak"
+                    style={{ "box-shadow": "var(--shadow-xxs-border)" }}
+                  >
+                    {keybind()}
+                  </span>
+                )}
+              </Show>
+            </button>
+          </Portal>
+        )}
+      </Show>
+      <Show when={rightMount()}>
+        {(mount) => (
+          <Portal mount={mount()}>
+            <div class="flex items-center gap-3">
+              {/* <div class="hidden md:flex items-center gap-1"> */}
+              {/*   <Button */}
+              {/*     size="small" */}
+              {/*     variant="ghost" */}
+              {/*     onClick={() => { */}
+              {/*       dialog.show(() => <DialogSelectServer />) */}
+              {/*     }} */}
+              {/*   > */}
+              {/*     <div */}
+              {/*       classList={{ */}
+              {/*         "size-1.5 rounded-full": true, */}
+              {/*         "bg-icon-success-base": server.healthy() === true, */}
+              {/*         "bg-icon-critical-base": server.healthy() === false, */}
+              {/*         "bg-border-weak-base": server.healthy() === undefined, */}
+              {/*       }} */}
+              {/*     /> */}
+              {/*     <Icon name="server" size="small" class="text-icon-weak" /> */}
+              {/*     <span class="text-12-regular text-text-weak truncate max-w-[200px]">{server.name}</span> */}
+              {/*   </Button> */}
+              {/*   <SessionLspIndicator /> */}
+              {/*   <SessionMcpIndicator /> */}
+              {/* </div> */}
+              <div class="flex items-center gap-1">
+                <Show when={currentSession()?.summary?.files}>
+                  <TooltipKeybind
+                    class="hidden md:block shrink-0"
+                    title="Toggle review"
+                    keybind={command.keybind("review.toggle")}
+                  >
+                    <Button
+                      variant="ghost"
+                      class="group/review-toggle size-6 p-0"
+                      onClick={() => view().reviewPanel.toggle()}
                     >
                     >
-                      <Icon name="arrow-left" size="small" class="text-icon-base" />
-                    </button>
-                  </Tooltip>
-                </div>
+                      <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
+                        <Icon
+                          name={view().reviewPanel.opened() ? "layout-right" : "layout-left"}
+                          size="small"
+                          class="group-hover/review-toggle:hidden"
+                        />
+                        <Icon
+                          name={view().reviewPanel.opened() ? "layout-right-partial" : "layout-left-partial"}
+                          size="small"
+                          class="hidden group-hover/review-toggle:inline-block"
+                        />
+                        <Icon
+                          name={view().reviewPanel.opened() ? "layout-right-full" : "layout-left-full"}
+                          size="small"
+                          class="hidden group-active/review-toggle:inline-block"
+                        />
+                      </div>
+                    </Button>
+                  </TooltipKeybind>
+                </Show>
+                <TooltipKeybind
+                  class="hidden md:block shrink-0"
+                  title="Toggle terminal"
+                  keybind={command.keybind("terminal.toggle")}
+                >
+                  <Button
+                    variant="ghost"
+                    class="group/terminal-toggle size-6 p-0"
+                    onClick={() => view().terminal.toggle()}
+                  >
+                    <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
+                      <Icon
+                        size="small"
+                        name={view().terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
+                        class="group-hover/terminal-toggle:hidden"
+                      />
+                      <Icon
+                        size="small"
+                        name="layout-bottom-partial"
+                        class="hidden group-hover/terminal-toggle:inline-block"
+                      />
+                      <Icon
+                        size="small"
+                        name={view().terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
+                        class="hidden group-active/terminal-toggle:inline-block"
+                      />
+                    </div>
+                  </Button>
+                </TooltipKeybind>
               </div>
               </div>
-            </Show>
-          </div>
-          <Show when={currentSession() && !parentSession()}>
-            <TooltipKeybind class="hidden xl:block" title="New session" keybind={command.keybind("session.new")}>
-              <IconButton as={A} href={`/${params.dir}/session`} icon="edit-small-2" variant="ghost" />
-            </TooltipKeybind>
-          </Show>
-        </div>
-        <div class="flex items-center gap-3">
-          <div class="hidden md:flex items-center gap-1">
-            <Button
-              size="small"
-              variant="ghost"
-              onClick={() => {
-                dialog.show(() => <DialogSelectServer />)
-              }}
-            >
-              <div
-                classList={{
-                  "size-1.5 rounded-full": true,
-                  "bg-icon-success-base": server.healthy() === true,
-                  "bg-icon-critical-base": server.healthy() === false,
-                  "bg-border-weak-base": server.healthy() === undefined,
-                }}
-              />
-              <Icon name="server" size="small" class="text-icon-weak" />
-              <span class="text-12-regular text-text-weak truncate max-w-[200px]">{server.name}</span>
-            </Button>
-            <SessionLspIndicator />
-            <SessionMcpIndicator />
-          </div>
-          <div class="flex items-center gap-1">
-            <Show when={currentSession()?.summary?.files}>
-              <TooltipKeybind
-                class="hidden md:block shrink-0"
-                title="Toggle review"
-                keybind={command.keybind("review.toggle")}
-              >
-                <Button variant="ghost" class="group/review-toggle size-6 p-0" onClick={layout.review.toggle}>
-                  <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
-                    <Icon
-                      name={layout.review.opened() ? "layout-right" : "layout-left"}
-                      size="small"
-                      class="group-hover/review-toggle:hidden"
-                    />
-                    <Icon
-                      name={layout.review.opened() ? "layout-right-partial" : "layout-left-partial"}
-                      size="small"
-                      class="hidden group-hover/review-toggle:inline-block"
-                    />
-                    <Icon
-                      name={layout.review.opened() ? "layout-right-full" : "layout-left-full"}
-                      size="small"
-                      class="hidden group-active/review-toggle:inline-block"
-                    />
-                  </div>
-                </Button>
-              </TooltipKeybind>
-            </Show>
-            <TooltipKeybind
-              class="hidden md:block shrink-0"
-              title="Toggle terminal"
-              keybind={command.keybind("terminal.toggle")}
-            >
-              <Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
-                <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
-                  <Icon
-                    size="small"
-                    name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
-                    class="group-hover/terminal-toggle:hidden"
-                  />
-                  <Icon
-                    size="small"
-                    name="layout-bottom-partial"
-                    class="hidden group-hover/terminal-toggle:inline-block"
-                  />
-                  <Icon
-                    size="small"
-                    name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
-                    class="hidden group-active/terminal-toggle:inline-block"
-                  />
-                </div>
-              </Button>
-            </TooltipKeybind>
-          </div>
-          <Show when={shareEnabled() && currentSession()}>
-            <Popover
-              title="Share session"
-              trigger={
-                <Tooltip class="shrink-0" value="Share session">
-                  <IconButton icon="share" variant="ghost" class="" />
-                </Tooltip>
-              }
-            >
-              {iife(() => {
-                const [url] = createResource(
-                  () => currentSession(),
-                  async (session) => {
-                    if (!session) return
-                    let shareURL = session.share?.url
-                    if (!shareURL) {
-                      shareURL = await globalSDK.client.session
-                        .share({ sessionID: session.id, directory: projectDirectory() })
-                        .then((r) => r.data?.share?.url)
-                        .catch((e) => {
-                          console.error("Failed to share session", e)
-                          return undefined
-                        })
-                    }
-                    return shareURL
-                  },
-                  { initialValue: "" },
-                )
-                return (
-                  <Show when={url.latest}>
-                    {(shareUrl) => <TextField value={shareUrl()} readOnly copyable class="w-72" />}
-                  </Show>
-                )
-              })}
-            </Popover>
-          </Show>
-        </div>
-      </div>
-    </header>
+              <Show when={shareEnabled() && currentSession()}>
+                <Popover
+                  title="Share session"
+                  trigger={
+                    <Tooltip class="shrink-0" value="Share session">
+                      <IconButton icon="share" variant="ghost" class="" />
+                    </Tooltip>
+                  }
+                >
+                  {iife(() => {
+                    const [url] = createResource(
+                      () => currentSession(),
+                      async (session) => {
+                        if (!session) return
+                        let shareURL = session.share?.url
+                        if (!shareURL) {
+                          shareURL = await globalSDK.client.session
+                            .share({ sessionID: session.id, directory: projectDirectory() })
+                            .then((r) => r.data?.share?.url)
+                            .catch((e) => {
+                              console.error("Failed to share session", e)
+                              return undefined
+                            })
+                        }
+                        return shareURL
+                      },
+                      { initialValue: "" },
+                    )
+                    return (
+                      <Show when={url.latest}>
+                        {(shareUrl) => <TextField value={shareUrl()} readOnly copyable class="w-72" />}
+                      </Show>
+                    )
+                  })}
+                </Popover>
+              </Show>
+            </div>
+          </Portal>
+        )}
+      </Show>
+    </>
   )
   )
 }
 }

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

@@ -100,9 +100,12 @@ export const Terminal = (props: TerminalProps) => {
     const mod = await import("ghostty-web")
     const mod = await import("ghostty-web")
     ghostty = await mod.Ghostty.load()
     ghostty = await mod.Ghostty.load()
 
 
-    const socket = new WebSocket(
-      sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`,
-    )
+    const url = new URL(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
+    if (window.__OPENCODE__?.serverPassword) {
+      url.username = "opencode"
+      url.password = window.__OPENCODE__?.serverPassword
+    }
+    const socket = new WebSocket(url)
     ws = socket
     ws = socket
 
 
     const t = new mod.Terminal({
     const t = new mod.Terminal({

+ 118 - 0
packages/app/src/components/titlebar.tsx

@@ -0,0 +1,118 @@
+import { createEffect, createMemo, Show } from "solid-js"
+import { IconButton } from "@opencode-ai/ui/icon-button"
+import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
+import { useTheme } from "@opencode-ai/ui/theme"
+
+import { useLayout } from "@/context/layout"
+import { usePlatform } from "@/context/platform"
+import { useCommand } from "@/context/command"
+
+export function Titlebar() {
+  const layout = useLayout()
+  const platform = usePlatform()
+  const command = useCommand()
+  const theme = useTheme()
+
+  const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
+  const reserve = createMemo(
+    () => platform.platform === "desktop" && (platform.os === "windows" || platform.os === "linux"),
+  )
+  const web = createMemo(() => platform.platform === "web")
+
+  const getWin = () => {
+    if (platform.platform !== "desktop") return
+
+    const tauri = (
+      window as unknown as {
+        __TAURI__?: { window?: { getCurrentWindow?: () => { startDragging?: () => Promise<void> } } }
+      }
+    ).__TAURI__
+    if (!tauri?.window?.getCurrentWindow) return
+
+    return tauri.window.getCurrentWindow()
+  }
+
+  createEffect(() => {
+    if (platform.platform !== "desktop") return
+
+    const scheme = theme.colorScheme()
+    const value = scheme === "system" ? null : scheme
+
+    const tauri = (window as unknown as { __TAURI__?: { webviewWindow?: { getCurrentWebviewWindow?: () => unknown } } })
+      .__TAURI__
+    const get = tauri?.webviewWindow?.getCurrentWebviewWindow
+    if (!get) return
+
+    const win = get() as { setTheme?: (theme?: "light" | "dark" | null) => Promise<void> }
+    if (!win.setTheme) return
+
+    void win.setTheme(value).catch(() => undefined)
+  })
+
+  const interactive = (target: EventTarget | null) => {
+    if (!(target instanceof Element)) return false
+
+    const selector =
+      "button, a, input, textarea, select, option, [role='button'], [role='menuitem'], [contenteditable='true'], [contenteditable='']"
+
+    return !!target.closest(selector)
+  }
+
+  const drag = (e: MouseEvent) => {
+    if (platform.platform !== "desktop") return
+    if (e.buttons !== 1) return
+    if (interactive(e.target)) return
+
+    const win = getWin()
+    if (!win?.startDragging) return
+
+    e.preventDefault()
+    void win.startDragging().catch(() => undefined)
+  }
+
+  return (
+    <header class="h-10 shrink-0 bg-background-base flex items-center relative">
+      <div
+        classList={{
+          "flex items-center w-full min-w-0 pr-2": true,
+          "pl-2": !mac(),
+        }}
+        onMouseDown={drag}
+      >
+        <Show when={mac()}>
+          <div class="w-[72px] h-full shrink-0" data-tauri-drag-region />
+          <div class="xl:hidden w-10 shrink-0 flex items-center justify-center">
+            <IconButton icon="menu" variant="ghost" class="size-8 rounded-md" onClick={layout.mobileSidebar.toggle} />
+          </div>
+        </Show>
+        <Show when={!mac()}>
+          <div class="xl:hidden w-[48px] shrink-0 flex items-center justify-center">
+            <IconButton icon="menu" variant="ghost" class="size-8 rounded-md" onClick={layout.mobileSidebar.toggle} />
+          </div>
+        </Show>
+        <TooltipKeybind
+          class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0"}
+          placement="bottom"
+          title="Toggle sidebar"
+          keybind={command.keybind("sidebar.toggle")}
+        >
+          <IconButton
+            icon={layout.sidebar.opened() ? "layout-left" : "layout-right"}
+            variant="ghost"
+            class="size-8 rounded-md"
+            onClick={layout.sidebar.toggle}
+          />
+        </TooltipKeybind>
+        <div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
+        <div class="flex-1 h-full" data-tauri-drag-region />
+        <div id="opencode-titlebar-right" class="flex items-center gap-3 shrink-0" />
+        <Show when={reserve()}>
+          <div class="w-[120px] h-full shrink-0" data-tauri-drag-region />
+        </Show>
+      </div>
+      <div class="absolute inset-0 flex items-center justify-center pointer-events-none">
+        <div id="opencode-titlebar-center" class="pointer-events-auto" />
+      </div>
+    </header>
+  )
+}

+ 12 - 69
packages/app/src/context/command.tsx

@@ -1,8 +1,5 @@
-import { createMemo, createSignal, onCleanup, onMount, Show, type Accessor } from "solid-js"
+import { createMemo, createSignal, onCleanup, onMount, type Accessor } from "solid-js"
 import { createSimpleContext } from "@opencode-ai/ui/context"
 import { createSimpleContext } from "@opencode-ai/ui/context"
-import { useDialog } from "@opencode-ai/ui/context/dialog"
-import { Dialog } from "@opencode-ai/ui/dialog"
-import { List } from "@opencode-ai/ui/list"
 
 
 const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
 const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
 
 
@@ -114,67 +111,11 @@ export function formatKeybind(config: string): string {
   return IS_MAC ? parts.join("") : parts.join("+")
   return IS_MAC ? parts.join("") : parts.join("+")
 }
 }
 
 
-function DialogCommand(props: { options: CommandOption[] }) {
-  const dialog = useDialog()
-  let cleanup: (() => void) | void
-  let committed = false
-
-  const handleMove = (option: CommandOption | undefined) => {
-    cleanup?.()
-    cleanup = option?.onHighlight?.()
-  }
-
-  const handleSelect = (option: CommandOption | undefined) => {
-    if (option) {
-      committed = true
-      cleanup = undefined
-      dialog.close()
-      option.onSelect?.("palette")
-    }
-  }
-
-  onCleanup(() => {
-    if (!committed) {
-      cleanup?.()
-    }
-  })
-
-  return (
-    <Dialog title="Commands">
-      <List
-        search={{ placeholder: "Search commands", autofocus: true }}
-        emptyMessage="No commands found"
-        items={() => props.options.filter((x) => !x.id.startsWith("suggested.") || !x.disabled)}
-        key={(x) => x?.id}
-        filterKeys={["title", "description", "category"]}
-        groupBy={(x) => x.category ?? ""}
-        onMove={handleMove}
-        onSelect={handleSelect}
-      >
-        {(option) => (
-          <div class="w-full flex items-center justify-between gap-4">
-            <div class="flex items-center gap-2 min-w-0">
-              <span class="text-14-regular text-text-strong whitespace-nowrap">{option.title}</span>
-              <Show when={option.description}>
-                <span class="text-14-regular text-text-weak truncate">{option.description}</span>
-              </Show>
-            </div>
-            <Show when={option.keybind}>
-              <span class="text-12-regular text-text-subtle shrink-0">{formatKeybind(option.keybind!)}</span>
-            </Show>
-          </div>
-        )}
-      </List>
-    </Dialog>
-  )
-}
-
 export const { use: useCommand, provider: CommandProvider } = createSimpleContext({
 export const { use: useCommand, provider: CommandProvider } = createSimpleContext({
   name: "Command",
   name: "Command",
   init: () => {
   init: () => {
     const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
     const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
     const [suspendCount, setSuspendCount] = createSignal(0)
     const [suspendCount, setSuspendCount] = createSignal(0)
-    const dialog = useDialog()
 
 
     const options = createMemo(() => {
     const options = createMemo(() => {
       const seen = new Set<string>()
       const seen = new Set<string>()
@@ -202,12 +143,19 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
 
 
     const suspended = () => suspendCount() > 0
     const suspended = () => suspendCount() > 0
 
 
-    const showPalette = () => {
-      if (!dialog.active) {
-        dialog.show(() => <DialogCommand options={options().filter((x) => !x.disabled)} />)
+    const run = (id: string, source?: "palette" | "keybind" | "slash") => {
+      for (const option of options()) {
+        if (option.id === id || option.id === "suggested." + id) {
+          option.onSelect?.(source)
+          return
+        }
       }
       }
     }
     }
 
 
+    const showPalette = () => {
+      run("file.open", "palette")
+    }
+
     const handleKeyDown = (event: KeyboardEvent) => {
     const handleKeyDown = (event: KeyboardEvent) => {
       if (suspended()) return
       if (suspended()) return
 
 
@@ -248,12 +196,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
         })
         })
       },
       },
       trigger(id: string, source?: "palette" | "keybind" | "slash") {
       trigger(id: string, source?: "palette" | "keybind" | "slash") {
-        for (const option of options()) {
-          if (option.id === id || option.id === "suggested." + id) {
-            option.onSelect?.(source)
-            return
-          }
-        }
+        run(id, source)
       },
       },
       keybind(id: string) {
       keybind(id: string) {
         const option = options().find((x) => x.id === id || x.id === "suggested." + id)
         const option = options().find((x) => x.id === id || x.id === "suggested." + id)

+ 2 - 1
packages/app/src/context/global-sdk.tsx

@@ -9,11 +9,13 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
   name: "GlobalSDK",
   name: "GlobalSDK",
   init: () => {
   init: () => {
     const server = useServer()
     const server = useServer()
+    const platform = usePlatform()
     const abort = new AbortController()
     const abort = new AbortController()
 
 
     const eventSdk = createOpencodeClient({
     const eventSdk = createOpencodeClient({
       baseUrl: server.url,
       baseUrl: server.url,
       signal: abort.signal,
       signal: abort.signal,
+      fetch: platform.fetch,
     })
     })
     const emitter = createGlobalEmitter<{
     const emitter = createGlobalEmitter<{
       [key: string]: Event
       [key: string]: Event
@@ -93,7 +95,6 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
       stop()
       stop()
     })
     })
 
 
-    const platform = usePlatform()
     const sdk = createOpencodeClient({
     const sdk = createOpencodeClient({
       baseUrl: server.url,
       baseUrl: server.url,
       fetch: platform.fetch,
       fetch: platform.fetch,

+ 195 - 31
packages/app/src/context/global-sync.tsx

@@ -16,16 +16,32 @@ import {
   type LspStatus,
   type LspStatus,
   type VcsInfo,
   type VcsInfo,
   type PermissionRequest,
   type PermissionRequest,
+  type QuestionRequest,
   createOpencodeClient,
   createOpencodeClient,
 } from "@opencode-ai/sdk/v2/client"
 } from "@opencode-ai/sdk/v2/client"
-import { createStore, produce, reconcile } from "solid-js/store"
+import { createStore, produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store"
 import { Binary } from "@opencode-ai/util/binary"
 import { Binary } from "@opencode-ai/util/binary"
 import { retry } from "@opencode-ai/util/retry"
 import { retry } from "@opencode-ai/util/retry"
 import { useGlobalSDK } from "./global-sdk"
 import { useGlobalSDK } from "./global-sdk"
 import { ErrorPage, type InitError } from "../pages/error"
 import { ErrorPage, type InitError } from "../pages/error"
-import { batch, createContext, useContext, onCleanup, onMount, type ParentProps, Switch, Match } from "solid-js"
+import {
+  batch,
+  createContext,
+  createEffect,
+  getOwner,
+  runWithOwner,
+  useContext,
+  onCleanup,
+  onMount,
+  type Accessor,
+  type ParentProps,
+  Switch,
+  Match,
+} from "solid-js"
 import { showToast } from "@opencode-ai/ui/toast"
 import { showToast } from "@opencode-ai/ui/toast"
 import { getFilename } from "@opencode-ai/util/path"
 import { getFilename } from "@opencode-ai/util/path"
+import { usePlatform } from "./platform"
+import { Persist, persisted } from "@/utils/persist"
 
 
 type State = {
 type State = {
   status: "loading" | "partial" | "complete"
   status: "loading" | "partial" | "complete"
@@ -36,6 +52,7 @@ type State = {
   config: Config
   config: Config
   path: Path
   path: Path
   session: Session[]
   session: Session[]
+  sessionTotal: number
   session_status: {
   session_status: {
     [sessionID: string]: SessionStatus
     [sessionID: string]: SessionStatus
   }
   }
@@ -48,6 +65,9 @@ type State = {
   permission: {
   permission: {
     [sessionID: string]: PermissionRequest[]
     [sessionID: string]: PermissionRequest[]
   }
   }
+  question: {
+    [sessionID: string]: QuestionRequest[]
+  }
   mcp: {
   mcp: {
     [name: string]: McpStatus
     [name: string]: McpStatus
   }
   }
@@ -62,8 +82,18 @@ type State = {
   }
   }
 }
 }
 
 
+type VcsCache = {
+  store: Store<{ value: VcsInfo | undefined }>
+  setStore: SetStoreFunction<{ value: VcsInfo | undefined }>
+  ready: Accessor<boolean>
+}
+
 function createGlobalSync() {
 function createGlobalSync() {
   const globalSDK = useGlobalSDK()
   const globalSDK = useGlobalSDK()
+  const platform = usePlatform()
+  const owner = getOwner()
+  if (!owner) throw new Error("GlobalSync must be created within owner")
+  const vcsCache = new Map<string, VcsCache>()
   const [globalStore, setGlobalStore] = createStore<{
   const [globalStore, setGlobalStore] = createStore<{
     ready: boolean
     ready: boolean
     error?: InitError
     error?: InitError
@@ -79,52 +109,81 @@ function createGlobalSync() {
     provider_auth: {},
     provider_auth: {},
   })
   })
 
 
-  const children: Record<string, ReturnType<typeof createStore<State>>> = {}
+  const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
+
   function child(directory: string) {
   function child(directory: string) {
     if (!directory) console.error("No directory provided")
     if (!directory) console.error("No directory provided")
     if (!children[directory]) {
     if (!children[directory]) {
-      children[directory] = createStore<State>({
-        project: "",
-        provider: { all: [], connected: [], default: {} },
-        config: {},
-        path: { state: "", config: "", worktree: "", directory: "", home: "" },
-        status: "loading" as const,
-        agent: [],
-        command: [],
-        session: [],
-        session_status: {},
-        session_diff: {},
-        todo: {},
-        permission: {},
-        mcp: {},
-        lsp: [],
-        vcs: undefined,
-        limit: 5,
-        message: {},
-        part: {},
-      })
-      bootstrapInstance(directory)
+      const cache = runWithOwner(owner, () =>
+        persisted(
+          Persist.workspace(directory, "vcs", ["vcs.v1"]),
+          createStore({ value: undefined as VcsInfo | undefined }),
+        ),
+      )
+      if (!cache) throw new Error("Failed to create persisted cache")
+      vcsCache.set(directory, { store: cache[0], setStore: cache[1], ready: cache[3] })
+
+      const init = () => {
+        children[directory] = createStore<State>({
+          project: "",
+          provider: { all: [], connected: [], default: {} },
+          config: {},
+          path: { state: "", config: "", worktree: "", directory: "", home: "" },
+          status: "loading" as const,
+          agent: [],
+          command: [],
+          session: [],
+          sessionTotal: 0,
+          session_status: {},
+          session_diff: {},
+          todo: {},
+          permission: {},
+          question: {},
+          mcp: {},
+          lsp: [],
+          vcs: cache[0].value,
+          limit: 5,
+          message: {},
+          part: {},
+        })
+        bootstrapInstance(directory)
+      }
+
+      runWithOwner(owner, init)
     }
     }
-    return children[directory]
+    const childStore = children[directory]
+    if (!childStore) throw new Error("Failed to create store")
+    return childStore
   }
   }
 
 
   async function loadSessions(directory: string) {
   async function loadSessions(directory: string) {
     const [store, setStore] = child(directory)
     const [store, setStore] = child(directory)
-    globalSDK.client.session
-      .list({ directory })
+    const limit = store.limit
+
+    return globalSDK.client.session
+      .list({ directory, roots: true })
       .then((x) => {
       .then((x) => {
-        const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
         const nonArchived = (x.data ?? [])
         const nonArchived = (x.data ?? [])
           .filter((s) => !!s?.id)
           .filter((s) => !!s?.id)
           .filter((s) => !s.time?.archived)
           .filter((s) => !s.time?.archived)
           .slice()
           .slice()
           .sort((a, b) => a.id.localeCompare(b.id))
           .sort((a, b) => a.id.localeCompare(b.id))
+
+        const sandboxWorkspace = globalStore.project.some((p) => (p.sandboxes ?? []).includes(directory))
+        if (sandboxWorkspace) {
+          setStore("session", reconcile(nonArchived, { key: "id" }))
+          return
+        }
+
+        const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
         // Include up to the limit, plus any updated in the last 4 hours
         // Include up to the limit, plus any updated in the last 4 hours
         const sessions = nonArchived.filter((s, i) => {
         const sessions = nonArchived.filter((s, i) => {
-          if (i < store.limit) return true
+          if (i < limit) return true
           const updated = new Date(s.time?.updated ?? s.time?.created).getTime()
           const updated = new Date(s.time?.updated ?? s.time?.created).getTime()
           return updated > fourHoursAgo
           return updated > fourHoursAgo
         })
         })
+        // Store total session count (used for "load more" pagination)
+        setStore("sessionTotal", nonArchived.length)
         setStore("session", reconcile(sessions, { key: "id" }))
         setStore("session", reconcile(sessions, { key: "id" }))
       })
       })
       .catch((err) => {
       .catch((err) => {
@@ -137,12 +196,22 @@ function createGlobalSync() {
   async function bootstrapInstance(directory: string) {
   async function bootstrapInstance(directory: string) {
     if (!directory) return
     if (!directory) return
     const [store, setStore] = child(directory)
     const [store, setStore] = child(directory)
+    const cache = vcsCache.get(directory)
+    if (!cache) return
     const sdk = createOpencodeClient({
     const sdk = createOpencodeClient({
       baseUrl: globalSDK.url,
       baseUrl: globalSDK.url,
+      fetch: platform.fetch,
       directory,
       directory,
       throwOnError: true,
       throwOnError: true,
     })
     })
 
 
+    createEffect(() => {
+      if (!cache.ready()) return
+      const cached = cache.store.value
+      if (!cached?.branch) return
+      setStore("vcs", (value) => value ?? cached)
+    })
+
     const blockingRequests = {
     const blockingRequests = {
       project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
       project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
       provider: () =>
       provider: () =>
@@ -172,7 +241,11 @@ function createGlobalSync() {
           loadSessions(directory),
           loadSessions(directory),
           sdk.mcp.status().then((x) => setStore("mcp", x.data!)),
           sdk.mcp.status().then((x) => setStore("mcp", x.data!)),
           sdk.lsp.status().then((x) => setStore("lsp", x.data!)),
           sdk.lsp.status().then((x) => setStore("lsp", x.data!)),
-          sdk.vcs.get().then((x) => setStore("vcs", x.data)),
+          sdk.vcs.get().then((x) => {
+            const next = x.data ?? store.vcs
+            setStore("vcs", next)
+            if (next?.branch) cache.setStore("value", next)
+          }),
           sdk.permission.list().then((x) => {
           sdk.permission.list().then((x) => {
             const grouped: Record<string, PermissionRequest[]> = {}
             const grouped: Record<string, PermissionRequest[]> = {}
             for (const perm of x.data ?? []) {
             for (const perm of x.data ?? []) {
@@ -205,6 +278,38 @@ function createGlobalSync() {
               }
               }
             })
             })
           }),
           }),
+          sdk.question.list().then((x) => {
+            const grouped: Record<string, QuestionRequest[]> = {}
+            for (const question of x.data ?? []) {
+              if (!question?.id || !question.sessionID) continue
+              const existing = grouped[question.sessionID]
+              if (existing) {
+                existing.push(question)
+                continue
+              }
+              grouped[question.sessionID] = [question]
+            }
+
+            batch(() => {
+              for (const sessionID of Object.keys(store.question)) {
+                if (grouped[sessionID]) continue
+                setStore("question", sessionID, [])
+              }
+              for (const [sessionID, questions] of Object.entries(grouped)) {
+                setStore(
+                  "question",
+                  sessionID,
+                  reconcile(
+                    questions
+                      .filter((q) => !!q?.id)
+                      .slice()
+                      .sort((a, b) => a.id.localeCompare(b.id)),
+                    { key: "id" },
+                  ),
+                )
+              }
+            })
+          }),
         ]).then(() => {
         ]).then(() => {
           setStore("status", "complete")
           setStore("status", "complete")
         })
         })
@@ -246,6 +351,23 @@ function createGlobalSync() {
         bootstrapInstance(directory)
         bootstrapInstance(directory)
         break
         break
       }
       }
+      case "session.created": {
+        const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
+        if (result.found) {
+          setStore("session", result.index, reconcile(event.properties.info))
+          break
+        }
+        setStore(
+          "session",
+          produce((draft) => {
+            draft.splice(result.index, 0, event.properties.info)
+          }),
+        )
+        if (!event.properties.info.parentID) {
+          setStore("sessionTotal", store.sessionTotal + 1)
+        }
+        break
+      }
       case "session.updated": {
       case "session.updated": {
         const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
         const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
         if (event.properties.info.time.archived) {
         if (event.properties.info.time.archived) {
@@ -353,7 +475,10 @@ function createGlobalSync() {
         break
         break
       }
       }
       case "vcs.branch.updated": {
       case "vcs.branch.updated": {
-        setStore("vcs", { branch: event.properties.branch })
+        const next = { branch: event.properties.branch }
+        setStore("vcs", next)
+        const cache = vcsCache.get(directory)
+        if (cache) cache.setStore("value", next)
         break
         break
       }
       }
       case "permission.asked": {
       case "permission.asked": {
@@ -393,9 +518,48 @@ function createGlobalSync() {
         )
         )
         break
         break
       }
       }
+      case "question.asked": {
+        const sessionID = event.properties.sessionID
+        const questions = store.question[sessionID]
+        if (!questions) {
+          setStore("question", sessionID, [event.properties])
+          break
+        }
+
+        const result = Binary.search(questions, event.properties.id, (q) => q.id)
+        if (result.found) {
+          setStore("question", sessionID, result.index, reconcile(event.properties))
+          break
+        }
+
+        setStore(
+          "question",
+          sessionID,
+          produce((draft) => {
+            draft.splice(result.index, 0, event.properties)
+          }),
+        )
+        break
+      }
+      case "question.replied":
+      case "question.rejected": {
+        const questions = store.question[event.properties.sessionID]
+        if (!questions) break
+        const result = Binary.search(questions, event.properties.requestID, (q) => q.id)
+        if (!result.found) break
+        setStore(
+          "question",
+          event.properties.sessionID,
+          produce((draft) => {
+            draft.splice(result.index, 1)
+          }),
+        )
+        break
+      }
       case "lsp.updated": {
       case "lsp.updated": {
         const sdk = createOpencodeClient({
         const sdk = createOpencodeClient({
           baseUrl: globalSDK.url,
           baseUrl: globalSDK.url,
+          fetch: platform.fetch,
           directory,
           directory,
           throwOnError: true,
           throwOnError: true,
         })
         })

+ 94 - 26
packages/app/src/context/layout.tsx

@@ -33,6 +33,8 @@ type SessionTabs = {
 type SessionView = {
 type SessionView = {
   scroll: Record<string, SessionScroll>
   scroll: Record<string, SessionScroll>
   reviewOpen?: string[]
   reviewOpen?: string[]
+  terminalOpened?: boolean
+  reviewPanelOpened?: boolean
 }
 }
 
 
 export type LocalProject = Partial<Project> & { worktree: string; expanded: boolean }
 export type LocalProject = Partial<Project> & { worktree: string; expanded: boolean }
@@ -45,19 +47,39 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
     const globalSdk = useGlobalSDK()
     const globalSdk = useGlobalSDK()
     const globalSync = useGlobalSync()
     const globalSync = useGlobalSync()
     const server = useServer()
     const server = useServer()
+
+    const isRecord = (value: unknown): value is Record<string, unknown> =>
+      typeof value === "object" && value !== null && !Array.isArray(value)
+
+    const migrate = (value: unknown) => {
+      if (!isRecord(value)) return value
+      const sidebar = value.sidebar
+      if (!isRecord(sidebar)) return value
+      if (typeof sidebar.workspaces !== "boolean") return value
+      return {
+        ...value,
+        sidebar: {
+          ...sidebar,
+          workspaces: {},
+          workspacesDefault: sidebar.workspaces,
+        },
+      }
+    }
+
+    const target = Persist.global("layout", ["layout.v6"])
     const [store, setStore, _, ready] = persisted(
     const [store, setStore, _, ready] = persisted(
-      Persist.global("layout", ["layout.v6"]),
+      { ...target, migrate },
       createStore({
       createStore({
         sidebar: {
         sidebar: {
           opened: false,
           opened: false,
-          width: 280,
+          width: 344,
+          workspaces: {} as Record<string, boolean>,
+          workspacesDefault: false,
         },
         },
         terminal: {
         terminal: {
-          opened: false,
           height: 280,
           height: 280,
         },
         },
         review: {
         review: {
-          opened: true,
           diffStyle: "split" as ReviewDiffStyle,
           diffStyle: "split" as ReviewDiffStyle,
         },
         },
         session: {
         session: {
@@ -150,7 +172,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
         const current = store.sessionView[sessionKey]
         const current = store.sessionView[sessionKey]
         const keep = meta.active ?? sessionKey
         const keep = meta.active ?? sessionKey
         if (!current) {
         if (!current) {
-          setStore("sessionView", sessionKey, { scroll: next })
+          setStore("sessionView", sessionKey, { scroll: next, terminalOpened: false, reviewPanelOpened: true })
           prune(keep)
           prune(keep)
           return
           return
         }
         }
@@ -304,42 +326,32 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
         resize(width: number) {
         resize(width: number) {
           setStore("sidebar", "width", width)
           setStore("sidebar", "width", width)
         },
         },
-      },
-      terminal: {
-        opened: createMemo(() => store.terminal.opened),
-        open() {
-          setStore("terminal", "opened", true)
+        workspaces(directory: string) {
+          return createMemo(() => store.sidebar.workspaces[directory] ?? store.sidebar.workspacesDefault ?? false)
         },
         },
-        close() {
-          setStore("terminal", "opened", false)
+        setWorkspaces(directory: string, value: boolean) {
+          setStore("sidebar", "workspaces", directory, value)
         },
         },
-        toggle() {
-          setStore("terminal", "opened", (x) => !x)
+        toggleWorkspaces(directory: string) {
+          const current = store.sidebar.workspaces[directory] ?? store.sidebar.workspacesDefault ?? false
+          setStore("sidebar", "workspaces", directory, !current)
         },
         },
+      },
+      terminal: {
         height: createMemo(() => store.terminal.height),
         height: createMemo(() => store.terminal.height),
         resize(height: number) {
         resize(height: number) {
           setStore("terminal", "height", height)
           setStore("terminal", "height", height)
         },
         },
       },
       },
       review: {
       review: {
-        opened: createMemo(() => store.review?.opened ?? true),
         diffStyle: createMemo(() => store.review?.diffStyle ?? "split"),
         diffStyle: createMemo(() => store.review?.diffStyle ?? "split"),
         setDiffStyle(diffStyle: ReviewDiffStyle) {
         setDiffStyle(diffStyle: ReviewDiffStyle) {
           if (!store.review) {
           if (!store.review) {
-            setStore("review", { opened: true, diffStyle })
+            setStore("review", { diffStyle })
             return
             return
           }
           }
           setStore("review", "diffStyle", diffStyle)
           setStore("review", "diffStyle", diffStyle)
         },
         },
-        open() {
-          setStore("review", "opened", true)
-        },
-        close() {
-          setStore("review", "opened", false)
-        },
-        toggle() {
-          setStore("review", "opened", (x) => !x)
-        },
       },
       },
       session: {
       session: {
         width: createMemo(() => store.session?.width ?? 600),
         width: createMemo(() => store.session?.width ?? 600),
@@ -367,6 +379,33 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
         touch(sessionKey)
         touch(sessionKey)
         scroll.seed(sessionKey)
         scroll.seed(sessionKey)
         const s = createMemo(() => store.sessionView[sessionKey] ?? { scroll: {} })
         const s = createMemo(() => store.sessionView[sessionKey] ?? { scroll: {} })
+        const terminalOpened = createMemo(() => s().terminalOpened ?? false)
+        const reviewPanelOpened = createMemo(() => s().reviewPanelOpened ?? true)
+
+        function setTerminalOpened(next: boolean) {
+          const current = store.sessionView[sessionKey]
+          if (!current) {
+            setStore("sessionView", sessionKey, { scroll: {}, terminalOpened: next, reviewPanelOpened: true })
+            return
+          }
+
+          const value = current.terminalOpened ?? false
+          if (value === next) return
+          setStore("sessionView", sessionKey, "terminalOpened", next)
+        }
+
+        function setReviewPanelOpened(next: boolean) {
+          const current = store.sessionView[sessionKey]
+          if (!current) {
+            setStore("sessionView", sessionKey, { scroll: {}, terminalOpened: false, reviewPanelOpened: next })
+            return
+          }
+
+          const value = current.reviewPanelOpened ?? true
+          if (value === next) return
+          setStore("sessionView", sessionKey, "reviewPanelOpened", next)
+        }
+
         return {
         return {
           scroll(tab: string) {
           scroll(tab: string) {
             return scroll.scroll(sessionKey, tab)
             return scroll.scroll(sessionKey, tab)
@@ -374,12 +413,41 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
           setScroll(tab: string, pos: SessionScroll) {
           setScroll(tab: string, pos: SessionScroll) {
             scroll.setScroll(sessionKey, tab, pos)
             scroll.setScroll(sessionKey, tab, pos)
           },
           },
+          terminal: {
+            opened: terminalOpened,
+            open() {
+              setTerminalOpened(true)
+            },
+            close() {
+              setTerminalOpened(false)
+            },
+            toggle() {
+              setTerminalOpened(!terminalOpened())
+            },
+          },
+          reviewPanel: {
+            opened: reviewPanelOpened,
+            open() {
+              setReviewPanelOpened(true)
+            },
+            close() {
+              setReviewPanelOpened(false)
+            },
+            toggle() {
+              setReviewPanelOpened(!reviewPanelOpened())
+            },
+          },
           review: {
           review: {
             open: createMemo(() => s().reviewOpen),
             open: createMemo(() => s().reviewOpen),
             setOpen(open: string[]) {
             setOpen(open: string[]) {
               const current = store.sessionView[sessionKey]
               const current = store.sessionView[sessionKey]
               if (!current) {
               if (!current) {
-                setStore("sessionView", sessionKey, { scroll: {}, reviewOpen: open })
+                setStore("sessionView", sessionKey, {
+                  scroll: {},
+                  terminalOpened: false,
+                  reviewPanelOpened: true,
+                  reviewOpen: open,
+                })
                 return
                 return
               }
               }
 
 

+ 9 - 0
packages/app/src/context/platform.tsx

@@ -5,6 +5,9 @@ export type Platform = {
   /** Platform discriminator */
   /** Platform discriminator */
   platform: "web" | "desktop"
   platform: "web" | "desktop"
 
 
+  /** Desktop OS (Tauri only) */
+  os?: "macos" | "windows" | "linux"
+
   /** App version */
   /** App version */
   version?: string
   version?: string
 
 
@@ -37,6 +40,12 @@ export type Platform = {
 
 
   /** Fetch override */
   /** Fetch override */
   fetch?: typeof fetch
   fetch?: typeof fetch
+
+  /** Get the configured default server URL (desktop only) */
+  getDefaultServerUrl?(): Promise<string | null>
+
+  /** Set the default server URL to use on app startup (desktop only) */
+  setDefaultServerUrl?(url: string | null): Promise<void>
 }
 }
 
 
 export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({
 export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({

+ 1 - 4
packages/app/src/context/server.tsx

@@ -16,10 +16,7 @@ export function normalizeServerUrl(input: string) {
 
 
 export function serverDisplayName(url: string) {
 export function serverDisplayName(url: string) {
   if (!url) return ""
   if (!url) return ""
-  return url
-    .replace(/^https?:\/\//, "")
-    .replace(/\/+$/, "")
-    .split("/")[0]
+  return url.replace(/^https?:\/\//, "").replace(/\/+$/, "")
 }
 }
 
 
 function projectsKey(url: string) {
 function projectsKey(url: string) {

+ 1 - 1
packages/app/src/context/sync.tsx

@@ -14,7 +14,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
     const sdk = useSDK()
     const sdk = useSDK()
     const [store, setStore] = globalSync.child(sdk.directory)
     const [store, setStore] = globalSync.child(sdk.directory)
     const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/")
     const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/")
-    const chunk = 200
+    const chunk = 400
     const inflight = new Map<string, Promise<void>>()
     const inflight = new Map<string, Promise<void>>()
     const inflightDiff = new Map<string, Promise<void>>()
     const inflightDiff = new Map<string, Promise<void>>()
     const inflightTodo = new Map<string, Promise<void>>()
     const inflightTodo = new Map<string, Promise<void>>()

+ 15 - 1
packages/app/src/context/terminal.tsx

@@ -8,6 +8,7 @@ import { Persist, persisted } from "@/utils/persist"
 export type LocalPTY = {
 export type LocalPTY = {
   id: string
   id: string
   title: string
   title: string
+  titleNumber: number
   rows?: number
   rows?: number
   cols?: number
   cols?: number
   buffer?: string
   buffer?: string
@@ -42,8 +43,20 @@ function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, id:
     all: createMemo(() => Object.values(store.all)),
     all: createMemo(() => Object.values(store.all)),
     active: createMemo(() => store.active),
     active: createMemo(() => store.active),
     new() {
     new() {
+      const existingTitleNumbers = new Set(
+        store.all.map((pty) => {
+          const match = pty.titleNumber
+          return match
+        }),
+      )
+
+      let nextNumber = 1
+      while (existingTitleNumbers.has(nextNumber)) {
+        nextNumber++
+      }
+
       sdk.client.pty
       sdk.client.pty
-        .create({ title: `Terminal ${store.all.length + 1}` })
+        .create({ title: `Terminal ${nextNumber}` })
         .then((pty) => {
         .then((pty) => {
           const id = pty.data?.id
           const id = pty.data?.id
           if (!id) return
           if (!id) return
@@ -52,6 +65,7 @@ function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, id:
             {
             {
               id,
               id,
               title: pty.data?.title ?? "Terminal",
               title: pty.data?.title ?? "Terminal",
+              titleNumber: nextNumber,
             },
             },
           ])
           ])
           setStore("active", id)
           setStore("active", id)

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

@@ -5,3 +5,7 @@
     cursor: default;
     cursor: default;
   }
   }
 }
 }
+
+*[data-tauri-drag-region] {
+  app-region: drag;
+}

+ 8 - 0
packages/app/src/pages/directory-layout.tsx

@@ -7,6 +7,7 @@ import { LocalProvider } from "@/context/local"
 import { base64Decode } from "@opencode-ai/util/encode"
 import { base64Decode } from "@opencode-ai/util/encode"
 import { DataProvider } from "@opencode-ai/ui/context"
 import { DataProvider } from "@opencode-ai/ui/context"
 import { iife } from "@opencode-ai/util/iife"
 import { iife } from "@opencode-ai/util/iife"
+import type { QuestionAnswer } from "@opencode-ai/sdk/v2"
 
 
 export default function Layout(props: ParentProps) {
 export default function Layout(props: ParentProps) {
   const params = useParams()
   const params = useParams()
@@ -27,6 +28,11 @@ export default function Layout(props: ParentProps) {
               response: "once" | "always" | "reject"
               response: "once" | "always" | "reject"
             }) => sdk.client.permission.respond(input)
             }) => sdk.client.permission.respond(input)
 
 
+            const replyToQuestion = (input: { requestID: string; answers: QuestionAnswer[] }) =>
+              sdk.client.question.reply(input)
+
+            const rejectQuestion = (input: { requestID: string }) => sdk.client.question.reject(input)
+
             const navigateToSession = (sessionID: string) => {
             const navigateToSession = (sessionID: string) => {
               navigate(`/${params.dir}/session/${sessionID}`)
               navigate(`/${params.dir}/session/${sessionID}`)
             }
             }
@@ -36,6 +42,8 @@ export default function Layout(props: ParentProps) {
                 data={sync.data}
                 data={sync.data}
                 directory={directory()}
                 directory={directory()}
                 onPermissionRespond={respond}
                 onPermissionRespond={respond}
+                onQuestionReply={replyToQuestion}
+                onQuestionReject={rejectQuestion}
                 onNavigateToSession={navigateToSession}
                 onNavigateToSession={navigateToSession}
               >
               >
                 <LocalProvider>{props.children}</LocalProvider>
                 <LocalProvider>{props.children}</LocalProvider>

File diff suppressed because it is too large
+ 793 - 434
packages/app/src/pages/layout.tsx


+ 36 - 11
packages/app/src/pages/session.tsx

@@ -31,6 +31,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { DialogSelectFile } from "@/components/dialog-select-file"
 import { DialogSelectFile } from "@/components/dialog-select-file"
 import { DialogSelectModel } from "@/components/dialog-select-model"
 import { DialogSelectModel } from "@/components/dialog-select-model"
 import { DialogSelectMcp } from "@/components/dialog-select-mcp"
 import { DialogSelectMcp } from "@/components/dialog-select-mcp"
+import { DialogFork } from "@/components/dialog-fork"
 import { useCommand } from "@/context/command"
 import { useCommand } from "@/context/command"
 import { useNavigate, useParams } from "@solidjs/router"
 import { useNavigate, useParams } from "@solidjs/router"
 import { UserMessage } from "@opencode-ai/sdk/v2"
 import { UserMessage } from "@opencode-ai/sdk/v2"
@@ -377,7 +378,7 @@ export default function Page() {
   })
   })
 
 
   createEffect(() => {
   createEffect(() => {
-    if (!layout.terminal.opened()) return
+    if (!view().terminal.opened()) return
     if (!terminal.ready()) return
     if (!terminal.ready()) return
     if (terminal.all().length !== 0) return
     if (terminal.all().length !== 0) return
     terminal.new()
     terminal.new()
@@ -427,7 +428,7 @@ export default function Page() {
     {
     {
       id: "file.open",
       id: "file.open",
       title: "Open file",
       title: "Open file",
-      description: "Search and open a file",
+      description: "Search files and commands",
       category: "File",
       category: "File",
       keybind: "mod+p",
       keybind: "mod+p",
       slash: "open",
       slash: "open",
@@ -440,7 +441,7 @@ export default function Page() {
       category: "View",
       category: "View",
       keybind: "ctrl+`",
       keybind: "ctrl+`",
       slash: "terminal",
       slash: "terminal",
-      onSelect: () => layout.terminal.toggle(),
+      onSelect: () => view().terminal.toggle(),
     },
     },
     {
     {
       id: "review.toggle",
       id: "review.toggle",
@@ -448,7 +449,7 @@ export default function Page() {
       description: "Show or hide the review panel",
       description: "Show or hide the review panel",
       category: "View",
       category: "View",
       keybind: "mod+shift+r",
       keybind: "mod+shift+r",
-      onSelect: () => layout.review.toggle(),
+      onSelect: () => view().reviewPanel.toggle(),
     },
     },
     {
     {
       id: "terminal.new",
       id: "terminal.new",
@@ -645,6 +646,15 @@ export default function Page() {
         })
         })
       },
       },
     },
     },
+    {
+      id: "session.fork",
+      title: "Fork from message",
+      description: "Create a new session from a previous message",
+      category: "Session",
+      slash: "fork",
+      disabled: !params.id || visibleUserMessages().length === 0,
+      onSelect: () => dialog.show(() => <DialogFork />),
+    },
   ])
   ])
 
 
   const handleKeyDown = (event: KeyboardEvent) => {
   const handleKeyDown = (event: KeyboardEvent) => {
@@ -720,7 +730,9 @@ export default function Page() {
   const reviewTab = createMemo(() => hasReview() || tabs().active() === "review")
   const reviewTab = createMemo(() => hasReview() || tabs().active() === "review")
   const mobileReview = createMemo(() => !isDesktop() && hasReview() && store.mobileTab === "review")
   const mobileReview = createMemo(() => !isDesktop() && hasReview() && store.mobileTab === "review")
 
 
-  const showTabs = createMemo(() => layout.review.opened() && (hasReview() || tabs().all().length > 0 || contextOpen()))
+  const showTabs = createMemo(
+    () => view().reviewPanel.opened() && (hasReview() || tabs().all().length > 0 || contextOpen()),
+  )
 
 
   const activeTab = createMemo(() => {
   const activeTab = createMemo(() => {
     const active = tabs().active()
     const active = tabs().active()
@@ -745,7 +757,7 @@ export default function Page() {
     if (!id) return
     if (!id) return
     if (!hasReview()) return
     if (!hasReview()) return
 
 
-    const wants = isDesktop() ? layout.review.opened() && activeTab() === "review" : store.mobileTab === "review"
+    const wants = isDesktop() ? view().reviewPanel.opened() && activeTab() === "review" : store.mobileTab === "review"
     if (!wants) return
     if (!wants) return
     if (diffsReady()) return
     if (diffsReady()) return
 
 
@@ -873,6 +885,19 @@ export default function Page() {
     window.history.replaceState(null, "", `#${anchor(id)}`)
     window.history.replaceState(null, "", `#${anchor(id)}`)
   }
   }
 
 
+  const scrollToElement = (el: HTMLElement, behavior: ScrollBehavior) => {
+    const root = scroller
+    if (!root) {
+      el.scrollIntoView({ behavior, block: "start" })
+      return
+    }
+
+    const a = el.getBoundingClientRect()
+    const b = root.getBoundingClientRect()
+    const top = a.top - b.top + root.scrollTop
+    root.scrollTo({ top, behavior })
+  }
+
   const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
   const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
     setActiveMessage(message)
     setActiveMessage(message)
 
 
@@ -884,7 +909,7 @@ export default function Page() {
 
 
       requestAnimationFrame(() => {
       requestAnimationFrame(() => {
         const el = document.getElementById(anchor(message.id))
         const el = document.getElementById(anchor(message.id))
-        if (el) el.scrollIntoView({ behavior, block: "start" })
+        if (el) scrollToElement(el, behavior)
       })
       })
 
 
       updateHash(message.id)
       updateHash(message.id)
@@ -892,7 +917,7 @@ export default function Page() {
     }
     }
 
 
     const el = document.getElementById(anchor(message.id))
     const el = document.getElementById(anchor(message.id))
-    if (el) el.scrollIntoView({ behavior, block: "start" })
+    if (el) scrollToElement(el, behavior)
     updateHash(message.id)
     updateHash(message.id)
   }
   }
 
 
@@ -944,7 +969,7 @@ export default function Page() {
 
 
       const hashTarget = document.getElementById(hash)
       const hashTarget = document.getElementById(hash)
       if (hashTarget) {
       if (hashTarget) {
-        hashTarget.scrollIntoView({ behavior: "auto", block: "start" })
+        scrollToElement(hashTarget, "auto")
         return
         return
       }
       }
 
 
@@ -1600,7 +1625,7 @@ export default function Page() {
         </Show>
         </Show>
       </div>
       </div>
 
 
-      <Show when={isDesktop() && layout.terminal.opened()}>
+      <Show when={isDesktop() && view().terminal.opened()}>
         <div
         <div
           class="relative w-full flex-col shrink-0 border-t border-border-weak-base"
           class="relative w-full flex-col shrink-0 border-t border-border-weak-base"
           style={{ height: `${layout.terminal.height()}px` }}
           style={{ height: `${layout.terminal.height()}px` }}
@@ -1612,7 +1637,7 @@ export default function Page() {
             max={window.innerHeight * 0.6}
             max={window.innerHeight * 0.6}
             collapseThreshold={50}
             collapseThreshold={50}
             onResize={layout.terminal.resize}
             onResize={layout.terminal.resize}
-            onCollapse={layout.terminal.close}
+            onCollapse={view().terminal.close}
           />
           />
           <Show
           <Show
             when={terminal.ready()}
             when={terminal.ready()}

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

@@ -1,12 +1,12 @@
 {
 {
   "name": "@opencode-ai/console-app",
   "name": "@opencode-ai/console-app",
-  "version": "1.1.8",
+  "version": "1.1.24",
   "type": "module",
   "type": "module",
   "license": "MIT",
   "license": "MIT",
   "scripts": {
   "scripts": {
     "typecheck": "tsgo --noEmit",
     "typecheck": "tsgo --noEmit",
     "dev": "vite dev --host 0.0.0.0",
     "dev": "vite dev --host 0.0.0.0",
-    "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
+    "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai VITE_STRIPE_PUBLISHABLE_KEY=pk_test_51RtuLNE7fOCwHSD4mewwzFejyytjdGoSDK7CAvhbffwaZnPbNb2rwJICw6LTOXCmWO320fSNXvb5NzI08RZVkAxd00syfqrW7t bun sst shell --stage=dev bun dev",
     "build": "./script/generate-sitemap.ts && vite build && ../../opencode/script/schema.ts ./.output/public/config.json",
     "build": "./script/generate-sitemap.ts && vite build && ../../opencode/script/schema.ts ./.output/public/config.json",
     "start": "vite start"
     "start": "vite start"
   },
   },
@@ -20,17 +20,23 @@
     "@opencode-ai/console-mail": "workspace:*",
     "@opencode-ai/console-mail": "workspace:*",
     "@opencode-ai/console-resource": "workspace:*",
     "@opencode-ai/console-resource": "workspace:*",
     "@opencode-ai/ui": "workspace:*",
     "@opencode-ai/ui": "workspace:*",
+    "@smithy/eventstream-codec": "4.2.7",
+    "@smithy/util-utf8": "4.2.0",
     "@solidjs/meta": "catalog:",
     "@solidjs/meta": "catalog:",
     "@solidjs/router": "catalog:",
     "@solidjs/router": "catalog:",
     "@solidjs/start": "catalog:",
     "@solidjs/start": "catalog:",
+    "@stripe/stripe-js": "8.6.1",
     "chart.js": "4.5.1",
     "chart.js": "4.5.1",
     "nitro": "3.0.1-alpha.1",
     "nitro": "3.0.1-alpha.1",
     "solid-js": "catalog:",
     "solid-js": "catalog:",
+    "solid-list": "0.3.0",
+    "solid-stripe": "0.8.1",
     "vite": "catalog:",
     "vite": "catalog:",
     "zod": "catalog:"
     "zod": "catalog:"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@typescript/native-preview": "catalog:",
     "@typescript/native-preview": "catalog:",
+    "@webgpu/types": "0.1.54",
     "typescript": "catalog:",
     "typescript": "catalog:",
     "wrangler": "4.50.0"
     "wrangler": "4.50.0"
   },
   },

+ 1 - 0
packages/console/app/public/social-share-black.png

@@ -0,0 +1 @@
+../../../ui/src/assets/images/social-share-black.png

BIN
packages/console/app/src/asset/black/hero.png


+ 3 - 0
packages/console/app/src/component/footer.tsx

@@ -24,6 +24,9 @@ export function Footer() {
       <div data-slot="cell">
       <div data-slot="cell">
         <a href="/docs">Docs</a>
         <a href="/docs">Docs</a>
       </div>
       </div>
+      <div data-slot="cell">
+        <a href="/changelog">Changelog</a>
+      </div>
       <div data-slot="cell">
       <div data-slot="cell">
         <a href="/discord">Discord</a>
         <a href="/discord">Discord</a>
       </div>
       </div>

+ 15 - 0
packages/console/app/src/component/spotlight.css

@@ -0,0 +1,15 @@
+.spotlight-container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 50dvh;
+  pointer-events: none;
+  overflow: hidden;
+}
+
+.spotlight-container canvas {
+  display: block;
+  width: 100%;
+  height: 100%;
+}

+ 820 - 0
packages/console/app/src/component/spotlight.tsx

@@ -0,0 +1,820 @@
+import { createSignal, createEffect, onMount, onCleanup, Accessor } from "solid-js"
+import "./spotlight.css"
+
+export interface ParticlesConfig {
+  enabled: boolean
+  amount: number
+  size: [number, number]
+  speed: number
+  opacity: number
+  drift: number
+}
+
+export interface SpotlightConfig {
+  placement: [number, number]
+  color: string
+  speed: number
+  spread: number
+  length: number
+  width: number
+  pulsating: false | [number, number]
+  distance: number
+  saturation: number
+  noiseAmount: number
+  distortion: number
+  opacity: number
+  particles: ParticlesConfig
+}
+
+export const defaultConfig: SpotlightConfig = {
+  placement: [0.5, -0.15],
+  color: "#ffffff",
+  speed: 0.8,
+  spread: 0.5,
+  length: 4.0,
+  width: 0.15,
+  pulsating: [0.95, 1.1],
+  distance: 3.5,
+  saturation: 0.35,
+  noiseAmount: 0.15,
+  distortion: 0.05,
+  opacity: 0.325,
+  particles: {
+    enabled: true,
+    amount: 70,
+    size: [1.25, 1.5],
+    speed: 0.75,
+    opacity: 0.9,
+    drift: 1.5,
+  },
+}
+
+export interface SpotlightAnimationState {
+  time: number
+  intensity: number
+  pulseValue: number
+}
+
+interface SpotlightProps {
+  config: Accessor<SpotlightConfig>
+  class?: string
+  onAnimationFrame?: (state: SpotlightAnimationState) => void
+}
+
+const hexToRgb = (hex: string): [number, number, number] => {
+  const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
+  return m ? [parseInt(m[1], 16) / 255, parseInt(m[2], 16) / 255, parseInt(m[3], 16) / 255] : [1, 1, 1]
+}
+
+const getAnchorAndDir = (
+  placement: [number, number],
+  w: number,
+  h: number,
+): { anchor: [number, number]; dir: [number, number] } => {
+  const [px, py] = placement
+  const outside = 0.2
+
+  let anchorX = px * w
+  let anchorY = py * h
+  let dirX = 0
+  let dirY = 0
+
+  const centerX = 0.5
+  const centerY = 0.5
+
+  if (py <= 0.25) {
+    anchorY = -outside * h + py * h
+    dirY = 1
+    dirX = (centerX - px) * 0.5
+  } else if (py >= 0.75) {
+    anchorY = (1 + outside) * h - (1 - py) * h
+    dirY = -1
+    dirX = (centerX - px) * 0.5
+  } else if (px <= 0.25) {
+    anchorX = -outside * w + px * w
+    dirX = 1
+    dirY = (centerY - py) * 0.5
+  } else if (px >= 0.75) {
+    anchorX = (1 + outside) * w - (1 - px) * w
+    dirX = -1
+    dirY = (centerY - py) * 0.5
+  } else {
+    dirY = 1
+  }
+
+  const len = Math.sqrt(dirX * dirX + dirY * dirY)
+  if (len > 0) {
+    dirX /= len
+    dirY /= len
+  }
+
+  return { anchor: [anchorX, anchorY], dir: [dirX, dirY] }
+}
+
+interface UniformData {
+  iTime: number
+  iResolution: [number, number]
+  lightPos: [number, number]
+  lightDir: [number, number]
+  color: [number, number, number]
+  speed: number
+  lightSpread: number
+  lightLength: number
+  sourceWidth: number
+  pulsating: number
+  pulsatingMin: number
+  pulsatingMax: number
+  fadeDistance: number
+  saturation: number
+  noiseAmount: number
+  distortion: number
+  particlesEnabled: number
+  particleAmount: number
+  particleSizeMin: number
+  particleSizeMax: number
+  particleSpeed: number
+  particleOpacity: number
+  particleDrift: number
+}
+
+const WGSL_SHADER = `
+  struct Uniforms {
+    iTime: f32,
+    _pad0: f32,
+    iResolution: vec2<f32>,
+    lightPos: vec2<f32>,
+    lightDir: vec2<f32>,
+    color: vec3<f32>, 
+    speed: f32,
+    lightSpread: f32,
+    lightLength: f32,
+    sourceWidth: f32,
+    pulsating: f32,
+    pulsatingMin: f32,
+    pulsatingMax: f32,
+    fadeDistance: f32,
+    saturation: f32,
+    noiseAmount: f32,
+    distortion: f32,
+    particlesEnabled: f32,
+    particleAmount: f32,
+    particleSizeMin: f32,
+    particleSizeMax: f32,
+    particleSpeed: f32,
+    particleOpacity: f32,
+    particleDrift: f32,
+    _pad1: f32,
+    _pad2: f32,
+  };
+
+  @group(0) @binding(0) var<uniform> uniforms: Uniforms;
+
+  struct VertexOutput {
+    @builtin(position) position: vec4<f32>,
+    @location(0) vUv: vec2<f32>,
+  };
+
+  @vertex
+  fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
+    var positions = array<vec2<f32>, 3>(
+      vec2<f32>(-1.0, -1.0),
+      vec2<f32>(3.0, -1.0),
+      vec2<f32>(-1.0, 3.0)
+    );
+    
+    var output: VertexOutput;
+    let pos = positions[vertexIndex];
+    output.position = vec4<f32>(pos, 0.0, 1.0);
+    output.vUv = pos * 0.5 + 0.5;
+    return output;
+  }
+
+  fn hash(p: vec2<f32>) -> f32 {
+    let p3 = fract(p.xyx * 0.1031);
+    return fract((p3.x + p3.y) * p3.z + dot(p3, p3.yzx + 33.33));
+  }
+
+  fn hash2(p: vec2<f32>) -> vec2<f32> {
+    let n = sin(dot(p, vec2<f32>(41.0, 289.0)));
+    return fract(vec2<f32>(n * 262144.0, n * 32768.0));
+  }
+
+  fn fastNoise(st: vec2<f32>) -> f32 {
+    return fract(sin(dot(st, vec2<f32>(12.9898, 78.233))) * 43758.5453);
+  }
+
+  fn lightStrengthCombined(lightSource: vec2<f32>, lightRefDirection: vec2<f32>, coord: vec2<f32>) -> f32 {
+    let sourceToCoord = coord - lightSource;
+    let distSq = dot(sourceToCoord, sourceToCoord);
+    let distance = sqrt(distSq);
+    
+    let baseSize = min(uniforms.iResolution.x, uniforms.iResolution.y);
+    let maxDistance = max(baseSize * uniforms.lightLength, 0.001);
+    if (distance > maxDistance) {
+      return 0.0;
+    }
+    
+    let invDist = 1.0 / max(distance, 0.001);
+    let dirNorm = sourceToCoord * invDist;
+    let cosAngle = dot(dirNorm, lightRefDirection);
+    
+    if (cosAngle < 0.0) {
+      return 0.0;
+    }
+
+    let side = dot(dirNorm, vec2<f32>(-lightRefDirection.y, lightRefDirection.x));
+    let time = uniforms.iTime;
+    let speed = uniforms.speed;
+    
+    let asymNoise = fastNoise(vec2<f32>(side * 6.0 + time * 0.12, distance * 0.004 + cosAngle * 2.0));
+    let asymShift = (asymNoise - 0.5) * uniforms.distortion * 0.6;
+    
+    let distortPhase = time * 1.4 + distance * 0.006 + cosAngle * 4.5 + side * 1.7;
+    let distortedAngle = cosAngle + uniforms.distortion * sin(distortPhase) * 0.22 + asymShift;
+    
+    let flickerSeed = cosAngle * 9.0 + side * 4.0 + time * speed * 0.35;
+    let flicker = 0.86 + fastNoise(vec2<f32>(flickerSeed, distance * 0.01)) * 0.28;
+    
+    let asymSpread = max(uniforms.lightSpread * (0.9 + (asymNoise - 0.5) * 0.25), 0.001);
+    let spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / asymSpread);
+    let lengthFalloff = clamp(1.0 - distance / maxDistance, 0.0, 1.0);
+    
+    let fadeMaxDist = max(baseSize * uniforms.fadeDistance, 0.001);
+    let fadeFalloff = clamp((fadeMaxDist - distance) / fadeMaxDist, 0.0, 1.0);
+    
+    var pulse: f32 = 1.0;
+    if (uniforms.pulsating > 0.5) {
+      let pulseCenter = (uniforms.pulsatingMin + uniforms.pulsatingMax) * 0.5;
+      let pulseAmplitude = (uniforms.pulsatingMax - uniforms.pulsatingMin) * 0.5;
+      pulse = pulseCenter + pulseAmplitude * sin(time * speed * 3.0);
+    }
+
+    let timeSpeed = time * speed;
+    let wave = 0.5
+      + 0.25 * sin(cosAngle * 28.0 + side * 8.0 + timeSpeed * 1.2)
+      + 0.18 * cos(cosAngle * 22.0 - timeSpeed * 0.95 + side * 6.0)
+      + 0.12 * sin(cosAngle * 35.0 + timeSpeed * 1.6 + asymNoise * 3.0);
+    let minStrength = 0.14 + asymNoise * 0.06;
+    let baseStrength = max(clamp(wave * (0.85 + asymNoise * 0.3), 0.0, 1.0), minStrength);
+
+    let lightStrength = baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse * flicker;
+    let ambientLight = (0.06 + asymNoise * 0.04) * lengthFalloff * fadeFalloff * spreadFactor;
+
+    return max(lightStrength, ambientLight);
+  }
+
+  fn particle(coord: vec2<f32>, particlePos: vec2<f32>, size: f32) -> f32 {
+    let delta = coord - particlePos;
+    let distSq = dot(delta, delta);
+    let sizeSq = size * size;
+    
+    if (distSq > sizeSq * 9.0) {
+      return 0.0;
+    }
+    
+    let d = sqrt(distSq);
+    let core = smoothstep(size, size * 0.35, d);
+    let glow = smoothstep(size * 3.0, 0.0, d) * 0.55;
+    return core + glow;
+  }
+
+  fn renderParticles(coord: vec2<f32>, lightSource: vec2<f32>, lightDir: vec2<f32>) -> f32 {
+    if (uniforms.particlesEnabled < 0.5 || uniforms.particleAmount < 1.0) {
+      return 0.0;
+    }
+
+    var particleSum: f32 = 0.0;
+    let particleCount = i32(uniforms.particleAmount);
+    let time = uniforms.iTime * uniforms.particleSpeed;
+    let perpDir = vec2<f32>(-lightDir.y, lightDir.x);
+    let baseSize = min(uniforms.iResolution.x, uniforms.iResolution.y);
+    let maxDist = max(baseSize * uniforms.lightLength, 1.0);
+    let spreadScale = uniforms.lightSpread * baseSize * 0.65;
+    let coneHalfWidth = uniforms.lightSpread * baseSize * 0.55;
+    
+    for (var i: i32 = 0; i < particleCount; i = i + 1) {
+      let fi = f32(i);
+      let seed = vec2<f32>(fi * 127.1, fi * 311.7);
+      let rnd = hash2(seed);
+      
+      let lifeDuration = 2.0 + hash(seed + vec2<f32>(19.0, 73.0)) * 3.0;
+      let lifeOffset = hash(seed + vec2<f32>(91.0, 37.0)) * lifeDuration;
+      let lifeProgress = fract((time + lifeOffset) / lifeDuration);
+      
+      let fadeIn = smoothstep(0.0, 0.2, lifeProgress);
+      let fadeOut = 1.0 - smoothstep(0.8, 1.0, lifeProgress);
+      let lifeFade = fadeIn * fadeOut;
+      if (lifeFade < 0.01) {
+        continue;
+      }
+      
+      let alongLight = rnd.x * maxDist * 0.8;
+      let perpOffset = (rnd.y - 0.5) * spreadScale;
+      
+      let floatPhase = rnd.y * 6.28318 + fi * 0.37;
+      let floatSpeed = 0.35 + rnd.x * 0.9;
+      let drift = vec2<f32>(
+        sin(time * floatSpeed + floatPhase),
+        cos(time * floatSpeed * 0.85 + floatPhase * 1.3)
+      ) * uniforms.particleDrift * baseSize * 0.08;
+      
+      let wobble = vec2<f32>(
+        sin(time * 1.4 + floatPhase * 2.1),
+        cos(time * 1.1 + floatPhase * 1.6)
+      ) * uniforms.particleDrift * baseSize * 0.03;
+      
+      let flowOffset = (rnd.x - 0.5) * baseSize * 0.12 + fract(time * 0.06 + rnd.y) * baseSize * 0.1;
+      
+      let basePos = lightSource + lightDir * (alongLight + flowOffset) + perpDir * perpOffset + drift + wobble;
+      
+      let toParticle = basePos - lightSource;
+      let projLen = dot(toParticle, lightDir);
+      if (projLen < 0.0 || projLen > maxDist) {
+        continue;
+      }
+      
+      let sideDist = abs(dot(toParticle, perpDir));
+      if (sideDist > coneHalfWidth) {
+        continue;
+      }
+      
+      let size = mix(uniforms.particleSizeMin, uniforms.particleSizeMax, rnd.x);
+      let twinkle = 0.7 + 0.3 * sin(time * (1.5 + rnd.y * 2.0) + floatPhase);
+      let distFade = 1.0 - smoothstep(maxDist * 0.2, maxDist * 0.95, projLen);
+      if (distFade < 0.01) {
+        continue;
+      }
+      
+      let p = particle(coord, basePos, size);
+      if (p > 0.0) {
+        particleSum = particleSum + p * lifeFade * twinkle * distFade * uniforms.particleOpacity;
+        if (particleSum >= 1.0) {
+          break;
+        }
+      }
+    }
+    
+    return min(particleSum, 1.0);
+  }
+
+  @fragment
+  fn fragmentMain(@builtin(position) fragCoord: vec4<f32>, @location(0) vUv: vec2<f32>) -> @location(0) vec4<f32> {
+    let coord = vec2<f32>(fragCoord.x, fragCoord.y);
+    
+    let normalizedX = (coord.x / uniforms.iResolution.x) - 0.5;
+    let widthOffset = -normalizedX * uniforms.sourceWidth * uniforms.iResolution.x;
+    
+    let perpDir = vec2<f32>(-uniforms.lightDir.y, uniforms.lightDir.x);
+    let adjustedLightPos = uniforms.lightPos + perpDir * widthOffset;
+    
+    let lightValue = lightStrengthCombined(adjustedLightPos, uniforms.lightDir, coord);
+    
+    if (lightValue < 0.001) {
+      let particles = renderParticles(coord, adjustedLightPos, uniforms.lightDir);
+      if (particles < 0.001) {
+        return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+      }
+      let particleBrightness = particles * 1.8;
+      return vec4<f32>(uniforms.color * particleBrightness, particles * 0.9);
+    }
+
+    var fragColor = vec4<f32>(lightValue, lightValue, lightValue, lightValue);
+
+    if (uniforms.noiseAmount > 0.01) {
+      let n = fastNoise(coord * 0.5 + uniforms.iTime * 0.5);
+      let grain = mix(1.0, n, uniforms.noiseAmount * 0.5);
+      fragColor = vec4<f32>(fragColor.rgb * grain, fragColor.a);
+    }
+
+    let brightness = 1.0 - (coord.y / uniforms.iResolution.y);
+    fragColor = vec4<f32>(
+      fragColor.x * (0.15 + brightness * 0.85),
+      fragColor.y * (0.35 + brightness * 0.65),
+      fragColor.z * (0.55 + brightness * 0.45),
+      fragColor.a
+    );
+
+    if (abs(uniforms.saturation - 1.0) > 0.01) {
+      let gray = dot(fragColor.rgb, vec3<f32>(0.299, 0.587, 0.114));
+      fragColor = vec4<f32>(mix(vec3<f32>(gray), fragColor.rgb, uniforms.saturation), fragColor.a);
+    }
+
+    fragColor = vec4<f32>(fragColor.rgb * uniforms.color, fragColor.a);
+    
+    let particles = renderParticles(coord, adjustedLightPos, uniforms.lightDir);
+    if (particles > 0.001) {
+      let particleBrightness = particles * 1.8;
+      fragColor = vec4<f32>(fragColor.rgb + uniforms.color * particleBrightness, max(fragColor.a, particles * 0.9));
+    }
+    
+    return fragColor;
+  }
+`
+
+const UNIFORM_BUFFER_SIZE = 144
+
+function updateUniformBuffer(buffer: Float32Array, data: UniformData): void {
+  buffer[0] = data.iTime
+  buffer[2] = data.iResolution[0]
+  buffer[3] = data.iResolution[1]
+  buffer[4] = data.lightPos[0]
+  buffer[5] = data.lightPos[1]
+  buffer[6] = data.lightDir[0]
+  buffer[7] = data.lightDir[1]
+  buffer[8] = data.color[0]
+  buffer[9] = data.color[1]
+  buffer[10] = data.color[2]
+  buffer[11] = data.speed
+  buffer[12] = data.lightSpread
+  buffer[13] = data.lightLength
+  buffer[14] = data.sourceWidth
+  buffer[15] = data.pulsating
+  buffer[16] = data.pulsatingMin
+  buffer[17] = data.pulsatingMax
+  buffer[18] = data.fadeDistance
+  buffer[19] = data.saturation
+  buffer[20] = data.noiseAmount
+  buffer[21] = data.distortion
+  buffer[22] = data.particlesEnabled
+  buffer[23] = data.particleAmount
+  buffer[24] = data.particleSizeMin
+  buffer[25] = data.particleSizeMax
+  buffer[26] = data.particleSpeed
+  buffer[27] = data.particleOpacity
+  buffer[28] = data.particleDrift
+}
+
+export default function Spotlight(props: SpotlightProps) {
+  let containerRef: HTMLDivElement | undefined
+  let canvasRef: HTMLCanvasElement | null = null
+  let deviceRef: GPUDevice | null = null
+  let contextRef: GPUCanvasContext | null = null
+  let pipelineRef: GPURenderPipeline | null = null
+  let uniformBufferRef: GPUBuffer | null = null
+  let bindGroupRef: GPUBindGroup | null = null
+  let animationIdRef: number | null = null
+  let cleanupFunctionRef: (() => void) | null = null
+  let uniformDataRef: UniformData | null = null
+  let uniformArrayRef: Float32Array | null = null
+  let configRef: SpotlightConfig = props.config()
+  let frameCount = 0
+
+  const [isVisible, setIsVisible] = createSignal(false)
+
+  createEffect(() => {
+    configRef = props.config()
+  })
+
+  onMount(() => {
+    if (!containerRef) return
+
+    const observer = new IntersectionObserver(
+      (entries) => {
+        const entry = entries[0]
+        setIsVisible(entry.isIntersecting)
+      },
+      { threshold: 0.1 },
+    )
+
+    observer.observe(containerRef)
+
+    onCleanup(() => {
+      observer.disconnect()
+    })
+  })
+
+  createEffect(() => {
+    const visible = isVisible()
+    const config = props.config()
+    if (!visible || !containerRef) {
+      return
+    }
+
+    if (cleanupFunctionRef) {
+      cleanupFunctionRef()
+      cleanupFunctionRef = null
+    }
+
+    const initializeWebGPU = async () => {
+      if (!containerRef) {
+        return
+      }
+
+      await new Promise((resolve) => setTimeout(resolve, 10))
+
+      if (!containerRef) {
+        return
+      }
+
+      if (!navigator.gpu) {
+        console.warn("WebGPU is not supported in this browser")
+        return
+      }
+
+      const adapter = await navigator.gpu.requestAdapter({
+        powerPreference: "high-performance",
+      })
+      if (!adapter) {
+        console.warn("Failed to get WebGPU adapter")
+        return
+      }
+
+      const device = await adapter.requestDevice()
+      deviceRef = device
+
+      const canvas = document.createElement("canvas")
+      canvas.style.width = "100%"
+      canvas.style.height = "100%"
+      canvasRef = canvas
+
+      while (containerRef.firstChild) {
+        containerRef.removeChild(containerRef.firstChild)
+      }
+      containerRef.appendChild(canvas)
+
+      const context = canvas.getContext("webgpu")
+      if (!context) {
+        console.warn("Failed to get WebGPU context")
+        return
+      }
+      contextRef = context
+
+      const presentationFormat = navigator.gpu.getPreferredCanvasFormat()
+      context.configure({
+        device,
+        format: presentationFormat,
+        alphaMode: "premultiplied",
+      })
+
+      const shaderModule = device.createShaderModule({
+        code: WGSL_SHADER,
+      })
+
+      const uniformBuffer = device.createBuffer({
+        size: UNIFORM_BUFFER_SIZE,
+        usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
+      })
+      uniformBufferRef = uniformBuffer
+
+      const bindGroupLayout = device.createBindGroupLayout({
+        entries: [
+          {
+            binding: 0,
+            visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
+            buffer: { type: "uniform" },
+          },
+        ],
+      })
+
+      const bindGroup = device.createBindGroup({
+        layout: bindGroupLayout,
+        entries: [
+          {
+            binding: 0,
+            resource: { buffer: uniformBuffer },
+          },
+        ],
+      })
+      bindGroupRef = bindGroup
+
+      const pipelineLayout = device.createPipelineLayout({
+        bindGroupLayouts: [bindGroupLayout],
+      })
+
+      const pipeline = device.createRenderPipeline({
+        layout: pipelineLayout,
+        vertex: {
+          module: shaderModule,
+          entryPoint: "vertexMain",
+        },
+        fragment: {
+          module: shaderModule,
+          entryPoint: "fragmentMain",
+          targets: [
+            {
+              format: presentationFormat,
+              blend: {
+                color: {
+                  srcFactor: "src-alpha",
+                  dstFactor: "one-minus-src-alpha",
+                  operation: "add",
+                },
+                alpha: {
+                  srcFactor: "one",
+                  dstFactor: "one-minus-src-alpha",
+                  operation: "add",
+                },
+              },
+            },
+          ],
+        },
+        primitive: {
+          topology: "triangle-list",
+        },
+      })
+      pipelineRef = pipeline
+
+      const { clientWidth: wCSS, clientHeight: hCSS } = containerRef
+      const dpr = Math.min(window.devicePixelRatio, 2)
+      const w = wCSS * dpr
+      const h = hCSS * dpr
+      const { anchor, dir } = getAnchorAndDir(config.placement, w, h)
+
+      uniformDataRef = {
+        iTime: 0,
+        iResolution: [w, h],
+        lightPos: anchor,
+        lightDir: dir,
+        color: hexToRgb(config.color),
+        speed: config.speed,
+        lightSpread: config.spread,
+        lightLength: config.length,
+        sourceWidth: config.width,
+        pulsating: config.pulsating !== false ? 1.0 : 0.0,
+        pulsatingMin: config.pulsating !== false ? config.pulsating[0] : 1.0,
+        pulsatingMax: config.pulsating !== false ? config.pulsating[1] : 1.0,
+        fadeDistance: config.distance,
+        saturation: config.saturation,
+        noiseAmount: config.noiseAmount,
+        distortion: config.distortion,
+        particlesEnabled: config.particles.enabled ? 1.0 : 0.0,
+        particleAmount: config.particles.amount,
+        particleSizeMin: config.particles.size[0],
+        particleSizeMax: config.particles.size[1],
+        particleSpeed: config.particles.speed,
+        particleOpacity: config.particles.opacity,
+        particleDrift: config.particles.drift,
+      }
+
+      const updatePlacement = () => {
+        if (!containerRef || !canvasRef || !uniformDataRef) {
+          return
+        }
+
+        const dpr = Math.min(window.devicePixelRatio, 2)
+        const { clientWidth: wCSS, clientHeight: hCSS } = containerRef
+        const w = Math.floor(wCSS * dpr)
+        const h = Math.floor(hCSS * dpr)
+
+        canvasRef.width = w
+        canvasRef.height = h
+
+        uniformDataRef.iResolution = [w, h]
+
+        const { anchor, dir } = getAnchorAndDir(configRef.placement, w, h)
+        uniformDataRef.lightPos = anchor
+        uniformDataRef.lightDir = dir
+      }
+
+      const loop = (t: number) => {
+        if (!deviceRef || !contextRef || !pipelineRef || !uniformBufferRef || !bindGroupRef || !uniformDataRef) {
+          return
+        }
+
+        const timeSeconds = t * 0.001
+        uniformDataRef.iTime = timeSeconds
+        frameCount++
+
+        if (props.onAnimationFrame && frameCount % 2 === 0) {
+          const pulsatingMin = configRef.pulsating !== false ? configRef.pulsating[0] : 1.0
+          const pulsatingMax = configRef.pulsating !== false ? configRef.pulsating[1] : 1.0
+          const pulseCenter = (pulsatingMin + pulsatingMax) * 0.5
+          const pulseAmplitude = (pulsatingMax - pulsatingMin) * 0.5
+          const pulseValue =
+            configRef.pulsating !== false
+              ? pulseCenter + pulseAmplitude * Math.sin(timeSeconds * configRef.speed * 3.0)
+              : 1.0
+
+          const baseIntensity1 = 0.45 + 0.15 * Math.sin(timeSeconds * configRef.speed * 1.5)
+          const baseIntensity2 = 0.3 + 0.2 * Math.cos(timeSeconds * configRef.speed * 1.1)
+          const intensity = Math.max((baseIntensity1 + baseIntensity2) * pulseValue, 0.55)
+
+          props.onAnimationFrame({
+            time: timeSeconds,
+            intensity,
+            pulseValue: Math.max(pulseValue, 0.9),
+          })
+        }
+
+        try {
+          if (!uniformArrayRef) {
+            uniformArrayRef = new Float32Array(36)
+          }
+          updateUniformBuffer(uniformArrayRef, uniformDataRef)
+          deviceRef.queue.writeBuffer(uniformBufferRef, 0, uniformArrayRef.buffer)
+
+          const commandEncoder = deviceRef.createCommandEncoder()
+
+          const textureView = contextRef.getCurrentTexture().createView()
+
+          const renderPass = commandEncoder.beginRenderPass({
+            colorAttachments: [
+              {
+                view: textureView,
+                clearValue: { r: 0, g: 0, b: 0, a: 0 },
+                loadOp: "clear",
+                storeOp: "store",
+              },
+            ],
+          })
+
+          renderPass.setPipeline(pipelineRef)
+          renderPass.setBindGroup(0, bindGroupRef)
+          renderPass.draw(3)
+          renderPass.end()
+
+          deviceRef.queue.submit([commandEncoder.finish()])
+
+          animationIdRef = requestAnimationFrame(loop)
+        } catch (error) {
+          console.warn("WebGPU rendering error:", error)
+          return
+        }
+      }
+
+      window.addEventListener("resize", updatePlacement)
+      updatePlacement()
+      animationIdRef = requestAnimationFrame(loop)
+
+      cleanupFunctionRef = () => {
+        if (animationIdRef) {
+          cancelAnimationFrame(animationIdRef)
+          animationIdRef = null
+        }
+
+        window.removeEventListener("resize", updatePlacement)
+
+        if (uniformBufferRef) {
+          uniformBufferRef.destroy()
+          uniformBufferRef = null
+        }
+
+        if (deviceRef) {
+          deviceRef.destroy()
+          deviceRef = null
+        }
+
+        if (canvasRef && canvasRef.parentNode) {
+          canvasRef.parentNode.removeChild(canvasRef)
+        }
+
+        canvasRef = null
+        contextRef = null
+        pipelineRef = null
+        bindGroupRef = null
+        uniformDataRef = null
+      }
+    }
+
+    initializeWebGPU()
+
+    onCleanup(() => {
+      if (cleanupFunctionRef) {
+        cleanupFunctionRef()
+        cleanupFunctionRef = null
+      }
+    })
+  })
+
+  createEffect(() => {
+    if (!uniformDataRef || !containerRef) {
+      return
+    }
+
+    const config = props.config()
+
+    uniformDataRef.color = hexToRgb(config.color)
+    uniformDataRef.speed = config.speed
+    uniformDataRef.lightSpread = config.spread
+    uniformDataRef.lightLength = config.length
+    uniformDataRef.sourceWidth = config.width
+    uniformDataRef.pulsating = config.pulsating !== false ? 1.0 : 0.0
+    uniformDataRef.pulsatingMin = config.pulsating !== false ? config.pulsating[0] : 1.0
+    uniformDataRef.pulsatingMax = config.pulsating !== false ? config.pulsating[1] : 1.0
+    uniformDataRef.fadeDistance = config.distance
+    uniformDataRef.saturation = config.saturation
+    uniformDataRef.noiseAmount = config.noiseAmount
+    uniformDataRef.distortion = config.distortion
+    uniformDataRef.particlesEnabled = config.particles.enabled ? 1.0 : 0.0
+    uniformDataRef.particleAmount = config.particles.amount
+    uniformDataRef.particleSizeMin = config.particles.size[0]
+    uniformDataRef.particleSizeMax = config.particles.size[1]
+    uniformDataRef.particleSpeed = config.particles.speed
+    uniformDataRef.particleOpacity = config.particles.opacity
+    uniformDataRef.particleDrift = config.particles.drift
+
+    const dpr = Math.min(window.devicePixelRatio, 2)
+    const { clientWidth: wCSS, clientHeight: hCSS } = containerRef
+    const { anchor, dir } = getAnchorAndDir(config.placement, wCSS * dpr, hCSS * dpr)
+    uniformDataRef.lightPos = anchor
+    uniformDataRef.lightDir = dir
+  })
+
+  return (
+    <div
+      ref={containerRef}
+      class={`spotlight-container ${props.class ?? ""}`.trim()}
+      style={{ opacity: props.config().opacity }}
+    />
+  )
+}

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

@@ -9,8 +9,8 @@ export const config = {
   github: {
   github: {
     repoUrl: "https://github.com/anomalyco/opencode",
     repoUrl: "https://github.com/anomalyco/opencode",
     starsFormatted: {
     starsFormatted: {
-      compact: "50K",
-      full: "50,000",
+      compact: "70K",
+      full: "70,000",
     },
     },
   },
   },
 
 
@@ -23,7 +23,7 @@ export const config = {
   // Static stats (used on landing page)
   // Static stats (used on landing page)
   stats: {
   stats: {
     contributors: "500",
     contributors: "500",
-    commits: "6,500",
+    commits: "7,000",
     monthlyUsers: "650,000",
     monthlyUsers: "650,000",
   },
   },
 } as const
 } as const

+ 0 - 24
packages/console/app/src/context/auth.session.ts

@@ -1,24 +0,0 @@
-import { useSession } from "@solidjs/start/http"
-
-export interface AuthSession {
-  account?: Record<
-    string,
-    {
-      id: string
-      email: string
-    }
-  >
-  current?: string
-}
-
-export function useAuthSession() {
-  return useSession<AuthSession>({
-    password: "0".repeat(32),
-    name: "auth",
-    maxAge: 60 * 60 * 24 * 365,
-    cookie: {
-      secure: false,
-      httpOnly: true,
-    },
-  })
-}

+ 26 - 1
packages/console/app/src/context/auth.ts

@@ -5,13 +5,38 @@ import { redirect } from "@solidjs/router"
 import { Actor } from "@opencode-ai/console-core/actor.js"
 import { Actor } from "@opencode-ai/console-core/actor.js"
 
 
 import { createClient } from "@openauthjs/openauth/client"
 import { createClient } from "@openauthjs/openauth/client"
-import { useAuthSession } from "./auth.session"
 
 
 export const AuthClient = createClient({
 export const AuthClient = createClient({
   clientID: "app",
   clientID: "app",
   issuer: import.meta.env.VITE_AUTH_URL,
   issuer: import.meta.env.VITE_AUTH_URL,
 })
 })
 
 
+import { useSession } from "@solidjs/start/http"
+import { Resource } from "@opencode-ai/console-resource"
+
+export interface AuthSession {
+  account?: Record<
+    string,
+    {
+      id: string
+      email: string
+    }
+  >
+  current?: string
+}
+
+export function useAuthSession() {
+  return useSession<AuthSession>({
+    password: Resource.ZEN_SESSION_SECRET.value,
+    name: "auth",
+    maxAge: 60 * 60 * 24 * 365,
+    cookie: {
+      secure: false,
+      httpOnly: true,
+    },
+  })
+}
+
 export const getActor = async (workspace?: string): Promise<Actor.Info> => {
 export const getActor = async (workspace?: string): Promise<Actor.Info> => {
   "use server"
   "use server"
   const evt = getRequestEvent()
   const evt = getRequestEvent()

+ 7 - 6
packages/console/app/src/lib/github.ts

@@ -14,13 +14,14 @@ export const github = query(async () => {
       fetch(`${apiBaseUrl}/releases`, { headers }).then((res) => res.json()),
       fetch(`${apiBaseUrl}/releases`, { headers }).then((res) => res.json()),
       fetch(`${apiBaseUrl}/contributors?per_page=1`, { headers }),
       fetch(`${apiBaseUrl}/contributors?per_page=1`, { headers }),
     ])
     ])
+    if (!Array.isArray(releases) || releases.length === 0) {
+      return undefined
+    }
     const [release] = releases
     const [release] = releases
-    const contributorCount = Number.parseInt(
-      contributors.headers
-        .get("Link")!
-        .match(/&page=(\d+)>; rel="last"/)!
-        .at(1)!,
-    )
+    const linkHeader = contributors.headers.get("Link")
+    const contributorCount = linkHeader
+      ? Number.parseInt(linkHeader.match(/&page=(\d+)>; rel="last"/)?.at(1) ?? "0")
+      : 0
     return {
     return {
       stars: meta.stargazers_count,
       stars: meta.stargazers_count,
       release: {
       release: {

+ 3 - 2
packages/console/app/src/routes/auth/callback.ts → packages/console/app/src/routes/auth/[...callback].ts

@@ -1,10 +1,11 @@
 import { redirect } from "@solidjs/router"
 import { redirect } from "@solidjs/router"
 import type { APIEvent } from "@solidjs/start/server"
 import type { APIEvent } from "@solidjs/start/server"
 import { AuthClient } from "~/context/auth"
 import { AuthClient } from "~/context/auth"
-import { useAuthSession } from "~/context/auth.session"
+import { useAuthSession } from "~/context/auth"
 
 
 export async function GET(input: APIEvent) {
 export async function GET(input: APIEvent) {
   const url = new URL(input.request.url)
   const url = new URL(input.request.url)
+
   try {
   try {
     const code = url.searchParams.get("code")
     const code = url.searchParams.get("code")
     if (!code) throw new Error("No code found")
     if (!code) throw new Error("No code found")
@@ -27,7 +28,7 @@ export async function GET(input: APIEvent) {
         current: id,
         current: id,
       }
       }
     })
     })
-    return redirect("/auth")
+    return redirect(url.pathname === "/auth/callback" ? "/auth" : url.pathname.replace("/auth/callback", ""))
   } catch (e: any) {
   } catch (e: any) {
     return new Response(
     return new Response(
       JSON.stringify({
       JSON.stringify({

+ 4 - 1
packages/console/app/src/routes/auth/authorize.ts

@@ -2,6 +2,9 @@ import type { APIEvent } from "@solidjs/start/server"
 import { AuthClient } from "~/context/auth"
 import { AuthClient } from "~/context/auth"
 
 
 export async function GET(input: APIEvent) {
 export async function GET(input: APIEvent) {
-  const result = await AuthClient.authorize(new URL("./callback", input.request.url).toString(), "code")
+  const url = new URL(input.request.url)
+  const cont = url.searchParams.get("continue") ?? ""
+  const callbackUrl = new URL(`./callback${cont}`, input.request.url)
+  const result = await AuthClient.authorize(callbackUrl.toString(), "code")
   return Response.redirect(result.url, 302)
   return Response.redirect(result.url, 302)
 }
 }

+ 1 - 1
packages/console/app/src/routes/auth/logout.ts

@@ -1,6 +1,6 @@
 import { redirect } from "@solidjs/router"
 import { redirect } from "@solidjs/router"
 import { APIEvent } from "@solidjs/start"
 import { APIEvent } from "@solidjs/start"
-import { useAuthSession } from "~/context/auth.session"
+import { useAuthSession } from "~/context/auth"
 
 
 export async function GET(event: APIEvent) {
 export async function GET(event: APIEvent) {
   const auth = await useAuthSession()
   const auth = await useAuthSession()

+ 1 - 1
packages/console/app/src/routes/auth/status.ts

@@ -1,5 +1,5 @@
 import { APIEvent } from "@solidjs/start"
 import { APIEvent } from "@solidjs/start"
-import { useAuthSession } from "~/context/auth.session"
+import { useAuthSession } from "~/context/auth"
 
 
 export async function GET(input: APIEvent) {
 export async function GET(input: APIEvent) {
   const session = await useAuthSession()
   const session = await useAuthSession()

+ 828 - 0
packages/console/app/src/routes/black.css

@@ -0,0 +1,828 @@
+::view-transition-group(*) {
+  animation-duration: 250ms;
+  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+::view-transition-old(root),
+::view-transition-new(root) {
+  animation-duration: 250ms;
+  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+::view-transition-image-pair(root) {
+  isolation: isolate;
+}
+
+::view-transition-old(root) {
+  animation: none;
+  mix-blend-mode: normal;
+}
+
+::view-transition-new(root) {
+  animation: none;
+  mix-blend-mode: normal;
+}
+
+@keyframes fade-in {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+@keyframes fade-out {
+  from {
+    opacity: 1;
+  }
+  to {
+    opacity: 0;
+  }
+}
+
+@keyframes fade-in-up {
+  from {
+    opacity: 0;
+    transform: translateY(8px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+@keyframes reveal-terms {
+  from {
+    mask-position: 0% 200%;
+  }
+  to {
+    mask-position: 0% 50%;
+  }
+}
+
+@keyframes hide-terms {
+  from {
+    mask-position: 0% 50%;
+  }
+  to {
+    mask-position: 0% 200%;
+  }
+}
+
+::view-transition-old(terms-20),
+::view-transition-old(terms-100),
+::view-transition-old(terms-200) {
+  mask-image: linear-gradient(to bottom, transparent, black 25% 75%, transparent);
+  mask-repeat: no-repeat;
+  mask-size: 100% 200%;
+  animation: hide-terms 200ms cubic-bezier(0.25, 0, 0.5, 1) forwards;
+}
+
+::view-transition-new(terms-20),
+::view-transition-new(terms-100),
+::view-transition-new(terms-200) {
+  mask-image: linear-gradient(to bottom, transparent, black 25% 75%, transparent);
+  mask-repeat: no-repeat;
+  mask-position: 0% 200%;
+  mask-size: 100% 200%;
+  animation: reveal-terms 300ms cubic-bezier(0.25, 0, 0.5, 1) 50ms forwards;
+}
+
+::view-transition-old(actions-20),
+::view-transition-old(actions-100),
+::view-transition-old(actions-200) {
+  animation: fade-out 80ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+::view-transition-new(actions-20),
+::view-transition-new(actions-100),
+::view-transition-new(actions-200) {
+  animation: fade-in-up 300ms cubic-bezier(0.16, 1, 0.3, 1) 300ms forwards;
+  opacity: 0;
+}
+
+::view-transition-group(card-20),
+::view-transition-group(card-100),
+::view-transition-group(card-200) {
+  animation-duration: 250ms;
+  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+[data-page="black"] {
+  background: #000;
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: stretch;
+  font-family: var(--font-mono);
+  color: #fff;
+
+  [data-component="header-logo"] {
+    filter: drop-shadow(0 8px 24px rgba(0, 0, 0, 0.25)) drop-shadow(0 4px 16px rgba(0, 0, 0, 0.1));
+    position: relative;
+    z-index: 1;
+  }
+
+  .header-light-rays {
+    position: absolute;
+    inset: 0 0 auto 0;
+    height: 30dvh;
+    pointer-events: none;
+    z-index: 0;
+  }
+
+  [data-component="header"] {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding-top: 40px;
+    flex-shrink: 0;
+  }
+
+  [data-component="content"] {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    width: 100%;
+    flex-grow: 1;
+
+    [data-slot="hero"] {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      text-align: center;
+      gap: 8px;
+      margin-top: 40px;
+      padding: 0 20px;
+
+      @media (min-width: 768px) {
+        margin-top: 60px;
+      }
+
+      h1 {
+        color: rgba(255, 255, 255, 0.92);
+        font-size: 16px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: 1.45;
+        margin: 0;
+
+        @media (min-width: 768px) {
+          font-size: 20px;
+        }
+
+        @media (max-width: 480px) {
+          font-size: 14px;
+        }
+      }
+
+      p {
+        color: rgba(255, 255, 255, 0.59);
+        font-size: 16px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: 1.45;
+        margin: 0;
+
+        @media (min-width: 768px) {
+          font-size: 20px;
+        }
+
+        @media (max-width: 480px) {
+          font-size: 14px;
+        }
+      }
+    }
+
+    [data-slot="hero-black"] {
+      margin-top: 40px;
+      padding: 0 20px;
+      position: relative;
+
+      @media (min-width: 768px) {
+        margin-top: 60px;
+      }
+
+      svg {
+        width: 100%;
+        max-width: 590px;
+        height: auto;
+        overflow: visible;
+        filter: drop-shadow(0 0 20px rgba(255, 255, 255, calc(0.1 + var(--hero-black-glow-intensity, 0) * 0.15)))
+          drop-shadow(0 -5px 30px rgba(255, 255, 255, calc(var(--hero-black-glow-intensity, 0) * 0.2)));
+        mask-image: linear-gradient(to bottom, black, transparent);
+        stroke-width: 1.5;
+
+        [data-slot="black-base"] {
+          fill: url(#hero-black-fill-gradient);
+          stroke: url(#hero-black-stroke-gradient);
+        }
+
+        [data-slot="black-glow"] {
+          fill: url(#hero-black-top-glow);
+          pointer-events: none;
+        }
+
+        [data-slot="black-shimmer"] {
+          fill: url(#hero-black-shimmer-gradient);
+          pointer-events: none;
+          mix-blend-mode: overlay;
+        }
+      }
+    }
+
+    [data-slot="cta"] {
+      display: flex;
+      flex-direction: column;
+      gap: 16px;
+      align-items: center;
+      text-align: center;
+      margin-top: -40px;
+      width: 100%;
+
+      @media (min-width: 768px) {
+        margin-top: -20px;
+      }
+
+      [data-slot="heading"] {
+        color: rgba(255, 255, 255, 0.92);
+        text-align: center;
+        font-size: 18px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: 160%;
+
+        span {
+          display: inline-block;
+        }
+      }
+      [data-slot="subheading"] {
+        color: rgba(255, 255, 255, 0.59);
+        font-size: 15px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: 160%;
+
+        @media (min-width: 768px) {
+          font-size: 18px;
+          line-height: 160%;
+        }
+      }
+      [data-slot="button"] {
+        display: inline-flex;
+        height: 40px;
+        padding: 0 12px;
+        justify-content: center;
+        align-items: center;
+        gap: 8px;
+        border-radius: 4px;
+        background: rgba(255, 255, 255, 0.92);
+        text-decoration: none;
+        color: #000;
+        font-family: "JetBrains Mono Nerd Font";
+        font-size: 16px;
+        font-style: normal;
+        font-weight: 500;
+        line-height: normal;
+
+        &:hover {
+          background: #e0e0e0;
+        }
+
+        &:active {
+          transform: scale(0.98);
+        }
+      }
+      [data-slot="back-soon"] {
+        color: rgba(255, 255, 255, 0.59);
+        text-align: center;
+        font-size: 13px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: 160%; /* 20.8px */
+      }
+      [data-slot="follow-us"] {
+        display: inline-flex;
+        height: 40px;
+        padding: 0 12px;
+        justify-content: center;
+        align-items: center;
+        gap: 8px;
+        border-radius: 4px;
+        border: 1px solid rgba(255, 255, 255, 0.17);
+        color: rgba(255, 255, 255, 0.59);
+        font-family: var(--font-mono);
+        font-size: 14px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: normal;
+        text-decoration: none;
+      }
+
+      [data-slot="pricing"] {
+        display: flex;
+        flex-direction: column;
+        gap: 16px;
+        width: 100%;
+        max-width: 660px;
+        padding: 0 20px;
+
+        @media (min-width: 768px) {
+          padding: 0;
+        }
+      }
+
+      [data-slot="pricing-card"] {
+        display: flex;
+        flex-direction: column;
+        gap: 12px;
+        padding: 24px;
+        border: 1px solid rgba(255, 255, 255, 0.17);
+        background: black;
+        background-clip: padding-box;
+        border-radius: 4px;
+        text-decoration: none;
+        transition: border-color 0.15s ease;
+        cursor: pointer;
+        text-align: left;
+
+        @media (max-width: 480px) {
+          padding: 16px;
+        }
+
+        &:hover:not(:active) {
+          border-color: rgba(255, 255, 255, 0.35);
+        }
+
+        [data-slot="icon"] {
+          color: rgba(255, 255, 255, 0.59);
+        }
+
+        [data-slot="price"] {
+          display: flex;
+          flex-wrap: wrap;
+          align-items: baseline;
+          gap: 8px;
+        }
+
+        [data-slot="amount"] {
+          color: rgba(255, 255, 255, 0.92);
+          font-size: 24px;
+          font-weight: 500;
+        }
+
+        [data-slot="period"] {
+          color: rgba(255, 255, 255, 0.59);
+          font-size: 14px;
+        }
+
+        [data-slot="multiplier"] {
+          color: rgba(255, 255, 255, 0.39);
+          font-size: 14px;
+
+          &::before {
+            content: "·";
+            margin-right: 8px;
+          }
+        }
+      }
+
+      [data-slot="selected-plan"] {
+        display: flex;
+        flex-direction: column;
+        gap: 32px;
+        width: 100%;
+        max-width: 660px;
+        margin: 0 auto;
+        position: relative;
+        background-color: rgba(0, 0, 0, 0.75);
+        z-index: 1;
+
+        @media (max-width: 480px) {
+          margin: 0 20px;
+          width: calc(100% - 40px);
+        }
+      }
+
+      [data-slot="selected-card"] {
+        display: flex;
+        flex-direction: column;
+        gap: 12px;
+        padding: 24px;
+        border: 1px solid rgba(255, 255, 255, 0.17);
+        border-radius: 4px;
+        width: 100%;
+
+        [data-slot="icon"] {
+          color: rgba(255, 255, 255, 0.59);
+        }
+
+        [data-slot="price"] {
+          display: flex;
+          flex-wrap: wrap;
+          align-items: baseline;
+          gap: 8px;
+        }
+
+        [data-slot="amount"] {
+          color: rgba(255, 255, 255, 0.92);
+          font-size: 24px;
+          font-weight: 500;
+        }
+
+        [data-slot="period"] {
+          color: rgba(255, 255, 255, 0.59);
+          font-size: 14px;
+        }
+
+        [data-slot="multiplier"] {
+          color: rgba(255, 255, 255, 0.39);
+          font-size: 14px;
+
+          &::before {
+            content: "·";
+            margin-right: 8px;
+          }
+        }
+
+        [data-slot="terms"] {
+          list-style: none;
+          padding: 0;
+          margin: 0;
+          display: flex;
+          flex-direction: column;
+          gap: 8px;
+          text-align: left;
+
+          li {
+            color: rgba(255, 255, 255, 0.59);
+            font-size: 14px;
+            line-height: 1.5;
+            padding-left: 16px;
+            position: relative;
+
+            &::before {
+              content: "▪";
+              position: absolute;
+              left: 0;
+              color: rgba(255, 255, 255, 0.39);
+            }
+
+            @media (max-width: 768px) {
+              font-size: 12px;
+            }
+          }
+        }
+
+        [data-slot="actions"] {
+          display: flex;
+          gap: 16px;
+          margin-top: 8px;
+
+          button,
+          a {
+            flex: 1;
+            display: inline-flex;
+            height: 48px;
+            padding: 0 16px;
+            justify-content: center;
+            align-items: center;
+            border-radius: 4px;
+            font-family: var(--font-mono);
+            font-size: 16px;
+            font-weight: 400;
+            text-decoration: none;
+            cursor: pointer;
+          }
+
+          [data-slot="cancel"] {
+            background: rgba(255, 255, 255, 0.05);
+            border: 1px solid rgba(255, 255, 255, 0.17);
+            color: rgba(255, 255, 255, 0.92);
+            transition-property: background-color, border-color;
+            transition-duration: 150ms;
+            transition-timing-function: cubic-bezier(0.25, 0, 0.5, 1);
+
+            &:hover {
+              background-color: rgba(255, 255, 255, 0.08);
+              border-color: rgba(255, 255, 255, 0.25);
+            }
+          }
+
+          [data-slot="continue"] {
+            background: rgb(255, 255, 255);
+            color: rgb(0, 0, 0);
+            transition: background-color 150ms cubic-bezier(0.25, 0, 0.5, 1);
+
+            &:hover {
+              background: rgba(255, 255, 255, 0.9);
+            }
+          }
+        }
+      }
+
+      [data-slot="fine-print"] {
+        color: rgba(255, 255, 255, 0.39);
+        text-align: center;
+        font-size: 13px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: 160%; /* 20.8px */
+        font-style: italic;
+
+        a {
+          color: rgba(255, 255, 255, 0.39);
+          text-decoration: underline;
+        }
+      }
+    }
+
+    /* Subscribe page styles */
+    [data-slot="subscribe-form"] {
+      display: flex;
+      flex-direction: column;
+      gap: 32px;
+      align-items: center;
+      margin-top: -18px;
+      width: 100%;
+      max-width: 660px;
+      padding: 0 20px;
+
+      @media (min-width: 768px) {
+        margin-top: 40px;
+        padding: 0;
+      }
+
+      [data-slot="form-card"] {
+        width: 100%;
+        border: 1px solid rgba(255, 255, 255, 0.17);
+        border-radius: 4px;
+        padding: 24px;
+        display: flex;
+        flex-direction: column;
+        gap: 20px;
+      }
+
+      [data-slot="plan-header"] {
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+      }
+
+      [data-slot="title"] {
+        color: rgba(255, 255, 255, 0.92);
+        font-size: 16px;
+        font-weight: 400;
+        margin-bottom: 8px;
+      }
+
+      [data-slot="icon"] {
+        color: rgba(255, 255, 255, 0.59);
+        isolation: isolate;
+        transform: translateZ(0);
+      }
+
+      [data-slot="price"] {
+        display: flex;
+        flex-wrap: wrap;
+        align-items: baseline;
+        gap: 8px;
+      }
+
+      [data-slot="amount"] {
+        color: rgba(255, 255, 255, 0.92);
+        font-size: 24px;
+        font-weight: 500;
+      }
+
+      [data-slot="period"] {
+        color: rgba(255, 255, 255, 0.59);
+        font-size: 14px;
+      }
+
+      [data-slot="multiplier"] {
+        color: rgba(255, 255, 255, 0.39);
+        font-size: 14px;
+
+        &::before {
+          content: "·";
+          margin: 0 8px;
+        }
+      }
+
+      [data-slot="divider"] {
+        height: 1px;
+        background: rgba(255, 255, 255, 0.17);
+      }
+
+      [data-slot="section-title"] {
+        color: rgba(255, 255, 255, 0.92);
+        font-size: 16px;
+        font-weight: 400;
+      }
+
+      [data-slot="checkout-form"] {
+        display: flex;
+        flex-direction: column;
+        gap: 20px;
+      }
+
+      [data-slot="error"] {
+        color: #ff6b6b;
+        font-size: 14px;
+      }
+
+      [data-slot="submit-button"] {
+        width: 100%;
+        height: 48px;
+        background: rgba(255, 255, 255, 0.92);
+        border: none;
+        border-radius: 4px;
+        color: #000;
+        font-family: var(--font-mono);
+        font-size: 16px;
+        font-weight: 500;
+        cursor: pointer;
+        transition: background 0.15s ease;
+
+        &:hover:not(:disabled) {
+          background: #e0e0e0;
+        }
+
+        &:disabled {
+          opacity: 0.5;
+          cursor: not-allowed;
+        }
+      }
+
+      [data-slot="charge-notice"] {
+        color: #d4a500;
+        font-size: 14px;
+        text-align: center;
+      }
+
+      [data-slot="loading"] {
+        display: flex;
+        justify-content: center;
+        padding: 40px 0;
+
+        p {
+          color: rgba(255, 255, 255, 0.59);
+          font-size: 14px;
+        }
+      }
+
+      [data-slot="fine-print"] {
+        color: rgba(255, 255, 255, 0.39);
+        text-align: center;
+        font-size: 13px;
+        font-style: italic;
+        view-transition-name: fine-print;
+
+        a {
+          color: rgba(255, 255, 255, 0.39);
+          text-decoration: underline;
+        }
+      }
+
+      [data-slot="workspace-picker"] {
+        [data-slot="workspace-list"] {
+          width: 100%;
+          padding: 0;
+          margin: 0;
+          list-style: none;
+          display: flex;
+          flex-direction: column;
+          align-items: flex-start;
+          gap: 8px;
+          align-self: stretch;
+          outline: none;
+          overflow-y: auto;
+          max-height: 240px;
+          scrollbar-width: none;
+
+          &::-webkit-scrollbar {
+            display: none;
+          }
+
+          [data-slot="workspace-item"] {
+            width: 100%;
+            display: flex;
+            padding: 8px 12px;
+            align-items: center;
+            gap: 8px;
+            align-self: stretch;
+            cursor: pointer;
+
+            [data-slot="selected-icon"] {
+              visibility: hidden;
+              color: rgba(255, 255, 255, 0.39);
+              font-family: "IBM Plex Mono", monospace;
+              font-size: 16px;
+              font-style: normal;
+              font-weight: 400;
+              line-height: 160%;
+            }
+
+            span:last-child {
+              color: rgba(255, 255, 255, 0.92);
+              font-size: 16px;
+              font-style: normal;
+              font-weight: 400;
+              line-height: 160%;
+            }
+
+            &:hover,
+            &[data-active="true"] {
+              background: #161616;
+
+              [data-slot="selected-icon"] {
+                visibility: visible;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  [data-component="footer"] {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    justify-content: center;
+    align-items: center;
+    gap: 24px;
+    flex-shrink: 0;
+
+    @media (min-width: 768px) {
+      height: 120px;
+    }
+
+    [data-slot="footer-content"] {
+      display: flex;
+      gap: 24px;
+      align-items: center;
+      justify-content: center;
+
+      @media (min-width: 768px) {
+        gap: 40px;
+      }
+
+      span,
+      a {
+        color: rgba(255, 255, 255, 0.39);
+        font-family: "JetBrains Mono Nerd Font";
+        font-size: 16px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: normal;
+        text-decoration: none;
+      }
+
+      [data-slot="github-stars"] {
+        color: rgba(255, 255, 255, 0.25);
+        font-family: "JetBrains Mono Nerd Font";
+        font-size: 16px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: normal;
+      }
+
+      [data-slot="anomaly"] {
+        display: none;
+
+        @media (min-width: 768px) {
+          display: block;
+        }
+      }
+    }
+    [data-slot="anomaly-alt"] {
+      color: rgba(255, 255, 255, 0.39);
+      font-family: "JetBrains Mono Nerd Font";
+      font-size: 16px;
+      font-style: normal;
+      font-weight: 400;
+      line-height: normal;
+      text-decoration: none;
+      margin-bottom: 24px;
+
+      a {
+        color: rgba(255, 255, 255, 0.39);
+        font-family: "JetBrains Mono Nerd Font";
+        font-size: 16px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: normal;
+        text-decoration: none;
+      }
+
+      @media (min-width: 768px) {
+        display: none;
+      }
+    }
+  }
+}

File diff suppressed because it is too large
+ 235 - 0
packages/console/app/src/routes/black.tsx


+ 62 - 0
packages/console/app/src/routes/black/common.tsx

@@ -0,0 +1,62 @@
+import { Match, Switch } from "solid-js"
+
+export const plans = [
+  { id: "20", multiplier: null },
+  { id: "100", multiplier: "5x more usage than Black 20" },
+  { id: "200", multiplier: "20x more usage than Black 20" },
+] as const
+
+export type PlanID = (typeof plans)[number]["id"]
+export type Plan = (typeof plans)[number]
+
+export function PlanIcon(props: { plan: string }) {
+  return (
+    <Switch>
+      <Match when={props.plan === "20"}>
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <title>Black 20 plan</title>
+          <rect x="0.5" y="0.5" width="23" height="23" stroke="currentColor" />
+        </svg>
+      </Match>
+      <Match when={props.plan === "100"}>
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <title>Black 100 plan</title>
+          <rect x="0.5" y="0.5" width="9" height="9" stroke="currentColor" />
+          <rect x="0.5" y="14.5" width="9" height="9" stroke="currentColor" />
+          <rect x="14.5" y="0.5" width="9" height="9" stroke="currentColor" />
+          <rect x="14.5" y="14.5" width="9" height="9" stroke="currentColor" />
+        </svg>
+      </Match>
+      <Match when={props.plan === "200"}>
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <title>Black 200 plan</title>
+          <rect x="0.5" y="0.5" width="3" height="3" stroke="currentColor" />
+          <rect x="0.5" y="5.5" width="3" height="3" stroke="currentColor" />
+          <rect x="0.5" y="10.5" width="3" height="3" stroke="currentColor" />
+          <rect x="0.5" y="15.5" width="3" height="3" stroke="currentColor" />
+          <rect x="0.5" y="20.5" width="3" height="3" stroke="currentColor" />
+          <rect x="5.5" y="0.5" width="3" height="3" stroke="currentColor" />
+          <rect x="5.5" y="5.5" width="3" height="3" stroke="currentColor" />
+          <rect x="5.5" y="10.5" width="3" height="3" stroke="currentColor" />
+          <rect x="5.5" y="15.5" width="3" height="3" stroke="currentColor" />
+          <rect x="5.5" y="20.5" width="3" height="3" stroke="currentColor" />
+          <rect x="10.5" y="0.5" width="3" height="3" stroke="currentColor" />
+          <rect x="10.5" y="5.5" width="3" height="3" stroke="currentColor" />
+          <rect x="10.5" y="10.5" width="3" height="3" stroke="currentColor" />
+          <rect x="10.5" y="15.5" width="3" height="3" stroke="currentColor" />
+          <rect x="10.5" y="20.5" width="3" height="3" stroke="currentColor" />
+          <rect x="15.5" y="0.5" width="3" height="3" stroke="currentColor" />
+          <rect x="15.5" y="5.5" width="3" height="3" stroke="currentColor" />
+          <rect x="15.5" y="10.5" width="3" height="3" stroke="currentColor" />
+          <rect x="15.5" y="15.5" width="3" height="3" stroke="currentColor" />
+          <rect x="15.5" y="20.5" width="3" height="3" stroke="currentColor" />
+          <rect x="20.5" y="0.5" width="3" height="3" stroke="currentColor" />
+          <rect x="20.5" y="5.5" width="3" height="3" stroke="currentColor" />
+          <rect x="20.5" y="10.5" width="3" height="3" stroke="currentColor" />
+          <rect x="20.5" y="15.5" width="3" height="3" stroke="currentColor" />
+          <rect x="20.5" y="20.5" width="3" height="3" stroke="currentColor" />
+        </svg>
+      </Match>
+    </Switch>
+  )
+}

+ 108 - 0
packages/console/app/src/routes/black/index.tsx

@@ -0,0 +1,108 @@
+import { A, useSearchParams } from "@solidjs/router"
+import { Title } from "@solidjs/meta"
+import { createMemo, createSignal, For, Match, onMount, Show, Switch } from "solid-js"
+import { PlanIcon, plans } from "./common"
+
+export default function Black() {
+  const [params] = useSearchParams()
+  const [selected, setSelected] = createSignal<string | null>((params.plan as string) || null)
+  const [mounted, setMounted] = createSignal(false)
+  const selectedPlan = createMemo(() => plans.find((p) => p.id === selected()))
+
+  onMount(() => {
+    requestAnimationFrame(() => setMounted(true))
+  })
+
+  const transition = (action: () => void) => {
+    if (mounted() && "startViewTransition" in document) {
+      ;(document as any).startViewTransition(action)
+      return
+    }
+
+    action()
+  }
+
+  const select = (planId: string) => {
+    if (selected() === planId) {
+      return
+    }
+
+    transition(() => setSelected(planId))
+  }
+
+  const cancel = () => {
+    transition(() => setSelected(null))
+  }
+
+  return (
+    <>
+      <Title>opencode</Title>
+      <section data-slot="cta">
+        <Switch>
+          <Match when={!selected()}>
+            <div data-slot="pricing">
+              <For each={plans}>
+                {(plan) => (
+                  <button
+                    type="button"
+                    onClick={() => select(plan.id)}
+                    data-slot="pricing-card"
+                    style={{ "view-transition-name": `card-${plan.id}` }}
+                  >
+                    <div data-slot="icon">
+                      <PlanIcon plan={plan.id} />
+                    </div>
+                    <p data-slot="price">
+                      <span data-slot="amount">${plan.id}</span> <span data-slot="period">per month</span>
+                      <Show when={plan.multiplier}>
+                        <span data-slot="multiplier">{plan.multiplier}</span>
+                      </Show>
+                    </p>
+                  </button>
+                )}
+              </For>
+            </div>
+          </Match>
+          <Match when={selectedPlan()}>
+            {(plan) => (
+              <div data-slot="selected-plan">
+                <div data-slot="selected-card" style={{ "view-transition-name": `card-${plan().id}` }}>
+                  <div data-slot="icon">
+                    <PlanIcon plan={plan().id} />
+                  </div>
+                  <p data-slot="price">
+                    <span data-slot="amount">${plan().id}</span>{" "}
+                    <span data-slot="period">per person billed monthly</span>
+                    <Show when={plan().multiplier}>
+                      <span data-slot="multiplier">{plan().multiplier}</span>
+                    </Show>
+                  </p>
+                  <ul data-slot="terms" style={{ "view-transition-name": `terms-${plan().id}` }}>
+                    <li>Your subscription will not start immediately</li>
+                    <li>You will be added to the waitlist and activated soon</li>
+                    <li>Your card will be only charged when your subscription is activated</li>
+                    <li>Usage limits apply, heavily automated use may reach limits sooner</li>
+                    <li>Subscriptions for individuals, contact Enterprise for teams</li>
+                    <li>Limits may be adjusted and plans may be discontinued in the future</li>
+                    <li>Cancel your subscription at anytime</li>
+                  </ul>
+                  <div data-slot="actions" style={{ "view-transition-name": `actions-${plan().id}` }}>
+                    <button type="button" onClick={() => cancel()} data-slot="cancel">
+                      Cancel
+                    </button>
+                    <a href={`/black/subscribe/${plan().id}`} data-slot="continue">
+                      Continue
+                    </a>
+                  </div>
+                </div>
+              </div>
+            )}
+          </Match>
+        </Switch>
+        <p data-slot="fine-print" style={{ "view-transition-name": "fine-print" }}>
+          Prices shown don't include applicable tax · <A href="/legal/terms-of-service">Terms of Service</A>
+        </p>
+      </section>
+    </>
+  )
+}

+ 451 - 0
packages/console/app/src/routes/black/subscribe/[plan].tsx

@@ -0,0 +1,451 @@
+import { A, createAsync, query, redirect, useParams } from "@solidjs/router"
+import { Title } from "@solidjs/meta"
+import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js"
+import { type Stripe, type PaymentMethod, loadStripe } from "@stripe/stripe-js"
+import { Elements, PaymentElement, useStripe, useElements, AddressElement } from "solid-stripe"
+import { PlanID, plans } from "../common"
+import { getActor, useAuthSession } from "~/context/auth"
+import { withActor } from "~/context/auth.withActor"
+import { Actor } from "@opencode-ai/console-core/actor.js"
+import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js"
+import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
+import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
+import { createList } from "solid-list"
+import { Modal } from "~/component/modal"
+import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js"
+import { Billing } from "@opencode-ai/console-core/billing.js"
+
+const plansMap = Object.fromEntries(plans.map((p) => [p.id, p])) as Record<PlanID, (typeof plans)[number]>
+const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY!)
+
+const getWorkspaces = query(async (plan: string) => {
+  "use server"
+  const actor = await getActor()
+  if (actor.type === "public") throw redirect("/auth/authorize?continue=/black/subscribe/" + plan)
+  return withActor(async () => {
+    return Database.use((tx) =>
+      tx
+        .select({
+          id: WorkspaceTable.id,
+          name: WorkspaceTable.name,
+          slug: WorkspaceTable.slug,
+          billing: {
+            customerID: BillingTable.customerID,
+            paymentMethodID: BillingTable.paymentMethodID,
+            paymentMethodType: BillingTable.paymentMethodType,
+            paymentMethodLast4: BillingTable.paymentMethodLast4,
+            subscriptionID: BillingTable.subscriptionID,
+            timeSubscriptionBooked: BillingTable.timeSubscriptionBooked,
+          },
+        })
+        .from(UserTable)
+        .innerJoin(WorkspaceTable, eq(UserTable.workspaceID, WorkspaceTable.id))
+        .innerJoin(BillingTable, eq(WorkspaceTable.id, BillingTable.workspaceID))
+        .where(
+          and(
+            eq(UserTable.accountID, Actor.account()),
+            isNull(WorkspaceTable.timeDeleted),
+            isNull(UserTable.timeDeleted),
+          ),
+        ),
+    )
+  })
+}, "black.subscribe.workspaces")
+
+const createSetupIntent = async (input: { plan: string; workspaceID: string }) => {
+  "use server"
+  const { plan, workspaceID } = input
+
+  if (!plan || !["20", "100", "200"].includes(plan)) return { error: "Invalid plan" }
+  if (!workspaceID) return { error: "Workspace ID is required" }
+
+  return withActor(async () => {
+    const session = await useAuthSession()
+    const account = session.data.account?.[session.data.current ?? ""]
+    const email = account?.email
+
+    const customer = await Database.use((tx) =>
+      tx
+        .select({
+          customerID: BillingTable.customerID,
+          subscriptionID: BillingTable.subscriptionID,
+        })
+        .from(BillingTable)
+        .where(eq(BillingTable.workspaceID, workspaceID))
+        .then((rows) => rows[0]),
+    )
+    if (customer?.subscriptionID) {
+      return { error: "This workspace already has a subscription" }
+    }
+
+    let customerID = customer?.customerID
+    if (!customerID) {
+      const customer = await Billing.stripe().customers.create({
+        email,
+        metadata: {
+          workspaceID,
+        },
+      })
+      customerID = customer.id
+      await Database.use((tx) =>
+        tx
+          .update(BillingTable)
+          .set({
+            customerID,
+          })
+          .where(eq(BillingTable.workspaceID, workspaceID)),
+      )
+    }
+
+    const intent = await Billing.stripe().setupIntents.create({
+      customer: customerID,
+      payment_method_types: ["card"],
+      metadata: {
+        workspaceID,
+      },
+    })
+
+    return { clientSecret: intent.client_secret ?? undefined }
+  }, workspaceID)
+}
+
+const bookSubscription = async (input: {
+  workspaceID: string
+  plan: PlanID
+  paymentMethodID: string
+  paymentMethodType: string
+  paymentMethodLast4?: string
+}) => {
+  "use server"
+  return withActor(
+    () =>
+      Database.use((tx) =>
+        tx
+          .update(BillingTable)
+          .set({
+            paymentMethodID: input.paymentMethodID,
+            paymentMethodType: input.paymentMethodType,
+            paymentMethodLast4: input.paymentMethodLast4,
+            subscriptionPlan: input.plan,
+            timeSubscriptionBooked: new Date(),
+          })
+          .where(eq(BillingTable.workspaceID, input.workspaceID)),
+      ),
+    input.workspaceID,
+  )
+}
+
+interface SuccessData {
+  plan: string
+  paymentMethodType: string
+  paymentMethodLast4?: string
+}
+
+function Failure(props: { message: string }) {
+  return (
+    <div data-slot="failure">
+      <p data-slot="message">Uh oh! {props.message}</p>
+    </div>
+  )
+}
+
+function Success(props: SuccessData) {
+  return (
+    <div data-slot="success">
+      <p data-slot="title">You're on the OpenCode Black waitlist</p>
+      <dl data-slot="details">
+        <div>
+          <dt>Subscription plan</dt>
+          <dd>OpenCode Black {props.plan}</dd>
+        </div>
+        <div>
+          <dt>Amount</dt>
+          <dd>${props.plan} per month</dd>
+        </div>
+        <div>
+          <dt>Payment method</dt>
+          <dd>
+            <Show when={props.paymentMethodLast4} fallback={<span>{props.paymentMethodType}</span>}>
+              <span>
+                {props.paymentMethodType} - {props.paymentMethodLast4}
+              </span>
+            </Show>
+          </dd>
+        </div>
+        <div>
+          <dt>Date joined</dt>
+          <dd>{new Date().toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}</dd>
+        </div>
+      </dl>
+      <p data-slot="charge-notice">Your card will be charged when your subscription is activated</p>
+    </div>
+  )
+}
+
+function IntentForm(props: { plan: PlanID; workspaceID: string; onSuccess: (data: SuccessData) => void }) {
+  const stripe = useStripe()
+  const elements = useElements()
+  const [error, setError] = createSignal<string | undefined>(undefined)
+  const [loading, setLoading] = createSignal(false)
+
+  const handleSubmit = async (e: Event) => {
+    e.preventDefault()
+    if (!stripe() || !elements()) return
+
+    setLoading(true)
+    setError(undefined)
+
+    const result = await elements()!.submit()
+    if (result.error) {
+      setError(result.error.message ?? "An error occurred")
+      setLoading(false)
+      return
+    }
+
+    const { error: confirmError, setupIntent } = await stripe()!.confirmSetup({
+      elements: elements()!,
+      confirmParams: {
+        expand: ["payment_method"],
+        payment_method_data: {
+          allow_redisplay: "always",
+        },
+      },
+      redirect: "if_required",
+    })
+
+    if (confirmError) {
+      setError(confirmError.message ?? "An error occurred")
+      setLoading(false)
+      return
+    }
+
+    // TODO
+    console.log(setupIntent)
+    if (setupIntent?.status === "succeeded") {
+      const pm = setupIntent.payment_method as PaymentMethod
+
+      await bookSubscription({
+        workspaceID: props.workspaceID,
+        plan: props.plan,
+        paymentMethodID: pm.id,
+        paymentMethodType: pm.type,
+        paymentMethodLast4: pm.card?.last4,
+      })
+
+      props.onSuccess({
+        plan: props.plan,
+        paymentMethodType: pm.type,
+        paymentMethodLast4: pm.card?.last4,
+      })
+    }
+
+    setLoading(false)
+  }
+
+  return (
+    <form onSubmit={handleSubmit} data-slot="checkout-form">
+      <PaymentElement />
+      <AddressElement options={{ mode: "billing" }} />
+      <Show when={error()}>
+        <p data-slot="error">{error()}</p>
+      </Show>
+      <button type="submit" disabled={loading() || !stripe() || !elements()} data-slot="submit-button">
+        {loading() ? "Processing..." : `Subscribe $${props.plan}`}
+      </button>
+      <p data-slot="charge-notice">You will only be charged when your subscription is activated</p>
+    </form>
+  )
+}
+
+export default function BlackSubscribe() {
+  const params = useParams()
+  const planData = plansMap[(params.plan as PlanID) ?? "20"] ?? plansMap["20"]
+  const plan = planData.id
+
+  const workspaces = createAsync(() => getWorkspaces(plan))
+  const [selectedWorkspace, setSelectedWorkspace] = createSignal<string | undefined>(undefined)
+  const [success, setSuccess] = createSignal<SuccessData | undefined>(undefined)
+  const [failure, setFailure] = createSignal<string | undefined>(undefined)
+  const [clientSecret, setClientSecret] = createSignal<string | undefined>(undefined)
+  const [stripe, setStripe] = createSignal<Stripe | undefined>(undefined)
+
+  // Resolve stripe promise once
+  createEffect(() => {
+    stripePromise.then((s) => {
+      if (s) setStripe(s)
+    })
+  })
+
+  // Auto-select if only one workspace
+  createEffect(() => {
+    const ws = workspaces()
+    if (ws?.length === 1 && !selectedWorkspace()) {
+      setSelectedWorkspace(ws[0].id)
+    }
+  })
+
+  // Fetch setup intent when workspace is selected (unless workspace already has payment method)
+  createEffect(async () => {
+    const id = selectedWorkspace()
+    if (!id) return
+
+    const ws = workspaces()?.find((w) => w.id === id)
+    if (ws?.billing?.subscriptionID) {
+      setFailure("This workspace already has a subscription")
+      return
+    }
+    if (ws?.billing?.paymentMethodID) {
+      if (!ws?.billing?.timeSubscriptionBooked) {
+        await bookSubscription({
+          workspaceID: id,
+          plan: planData.id,
+          paymentMethodID: ws.billing.paymentMethodID!,
+          paymentMethodType: ws.billing.paymentMethodType!,
+          paymentMethodLast4: ws.billing.paymentMethodLast4 ?? undefined,
+        })
+      }
+      setSuccess({
+        plan: planData.id,
+        paymentMethodType: ws.billing.paymentMethodType!,
+        paymentMethodLast4: ws.billing.paymentMethodLast4 ?? undefined,
+      })
+      return
+    }
+
+    const result = await createSetupIntent({ plan, workspaceID: id })
+    if (result.error) {
+      setFailure(result.error)
+    } else if ("clientSecret" in result) {
+      setClientSecret(result.clientSecret)
+    }
+  })
+
+  // Keyboard navigation for workspace picker
+  const { active, setActive, onKeyDown } = createList({
+    items: () => workspaces()?.map((w) => w.id) ?? [],
+    initialActive: null,
+  })
+
+  const handleSelectWorkspace = (id: string) => {
+    setSelectedWorkspace(id)
+  }
+
+  let listRef: HTMLUListElement | undefined
+
+  // Show workspace picker if multiple workspaces and none selected
+  const showWorkspacePicker = () => {
+    const ws = workspaces()
+    return ws && ws.length > 1 && !selectedWorkspace()
+  }
+
+  return (
+    <>
+      <Title>Subscribe to OpenCode Black</Title>
+      <section data-slot="subscribe-form">
+        <div data-slot="form-card">
+          <Switch>
+            <Match when={success()}>{(data) => <Success {...data()} />}</Match>
+            <Match when={failure()}>{(data) => <Failure message={data()} />}</Match>
+            <Match when={true}>
+              <>
+                <div data-slot="plan-header">
+                  <p data-slot="title">Subscribe to OpenCode Black</p>
+                  <p data-slot="price">
+                    <span data-slot="amount">${planData.id}</span> <span data-slot="period">per month</span>
+                    <Show when={planData.multiplier}>
+                      <span data-slot="multiplier">{planData.multiplier}</span>
+                    </Show>
+                  </p>
+                </div>
+                <div data-slot="divider" />
+                <p data-slot="section-title">Payment method</p>
+
+                <Show
+                  when={clientSecret() && selectedWorkspace() && stripe()}
+                  fallback={
+                    <div data-slot="loading">
+                      <p>{selectedWorkspace() ? "Loading payment form..." : "Select a workspace to continue"}</p>
+                    </div>
+                  }
+                >
+                  <Elements
+                    stripe={stripe()!}
+                    options={{
+                      clientSecret: clientSecret()!,
+                      appearance: {
+                        theme: "night",
+                        variables: {
+                          colorPrimary: "#ffffff",
+                          colorBackground: "#1a1a1a",
+                          colorText: "#ffffff",
+                          colorTextSecondary: "#999999",
+                          colorDanger: "#ff6b6b",
+                          fontFamily: "JetBrains Mono, monospace",
+                          borderRadius: "4px",
+                          spacingUnit: "4px",
+                        },
+                        rules: {
+                          ".Input": {
+                            backgroundColor: "#1a1a1a",
+                            border: "1px solid rgba(255, 255, 255, 0.17)",
+                            color: "#ffffff",
+                          },
+                          ".Input:focus": {
+                            borderColor: "rgba(255, 255, 255, 0.35)",
+                            boxShadow: "none",
+                          },
+                          ".Label": {
+                            color: "rgba(255, 255, 255, 0.59)",
+                            fontSize: "14px",
+                            marginBottom: "8px",
+                          },
+                        },
+                      },
+                    }}
+                  >
+                    <IntentForm plan={plan} workspaceID={selectedWorkspace()!} onSuccess={setSuccess} />
+                  </Elements>
+                </Show>
+              </>
+            </Match>
+          </Switch>
+        </div>
+
+        {/* Workspace picker modal */}
+        <Modal open={showWorkspacePicker() ?? false} onClose={() => {}} title="Select a workspace for this plan">
+          <div data-slot="workspace-picker">
+            <ul
+              ref={listRef}
+              data-slot="workspace-list"
+              tabIndex={0}
+              onKeyDown={(e) => {
+                if (e.key === "Enter" && active()) {
+                  handleSelectWorkspace(active()!)
+                } else {
+                  onKeyDown(e)
+                }
+              }}
+            >
+              <For each={workspaces()}>
+                {(workspace) => (
+                  <li
+                    data-slot="workspace-item"
+                    data-active={active() === workspace.id}
+                    onMouseEnter={() => setActive(workspace.id)}
+                    onClick={() => handleSelectWorkspace(workspace.id)}
+                  >
+                    <span data-slot="selected-icon">[*]</span>
+                    <span>{workspace.name || workspace.slug}</span>
+                  </li>
+                )}
+              </For>
+            </ul>
+          </div>
+        </Modal>
+        <p data-slot="fine-print">
+          Prices shown don't include applicable tax · <A href="/legal/terms-of-service">Terms of Service</A>
+        </p>
+      </section>
+    </>
+  )
+}

+ 214 - 0
packages/console/app/src/routes/black/workspace.css

@@ -0,0 +1,214 @@
+[data-page="black"] {
+  background: #000;
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: stretch;
+  font-family: var(--font-mono);
+  color: #fff;
+
+  [data-component="header-gradient"] {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 288px;
+    background: linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(0, 0, 0, 0) 100%);
+  }
+
+  [data-component="header"] {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding-top: 40px;
+    flex-shrink: 0;
+
+    /* [data-component="header-logo"] { */
+    /* } */
+  }
+
+  [data-component="content"] {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    width: 100%;
+    flex-grow: 1;
+
+    [data-slot="hero-black"] {
+      margin-top: 110px;
+
+      @media (min-width: 768px) {
+        margin-top: 150px;
+      }
+    }
+
+    [data-slot="select-workspace"] {
+      display: flex;
+      margin-top: -24px;
+      width: 100%;
+      max-width: 480px;
+      height: 305px;
+      padding: 32px 20px 0 20px;
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 24px;
+
+      border: 1px solid #303030;
+      background: #0a0a0a;
+      box-shadow:
+        0 100px 80px 0 rgba(0, 0, 0, 0.04),
+        0 41.778px 33.422px 0 rgba(0, 0, 0, 0.05),
+        0 22.336px 17.869px 0 rgba(0, 0, 0, 0.06),
+        0 12.522px 10.017px 0 rgba(0, 0, 0, 0.08),
+        0 6.65px 5.32px 0 rgba(0, 0, 0, 0.09),
+        0 2.767px 2.214px 0 rgba(0, 0, 0, 0.13);
+
+      [data-slot="select-workspace-title"] {
+        flex-shrink: 0;
+        align-self: stretch;
+        color: rgba(255, 255, 255, 0.59);
+        text-align: center;
+        font-size: 16px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: 160%; /* 25.6px */
+      }
+
+      [data-slot="workspaces"] {
+        width: 100%;
+        padding: 0;
+        display: flex;
+        flex-direction: column;
+        align-items: flex-start;
+        gap: 8px;
+        align-self: stretch;
+        outline: none;
+        overflow-y: auto;
+        flex: 1;
+        min-height: 0;
+
+        scrollbar-width: none;
+        &::-webkit-scrollbar {
+          display: none;
+        }
+
+        [data-slot="workspace"] {
+          width: 100%;
+          display: flex;
+          padding: 8px 12px;
+          align-items: center;
+          gap: 8px;
+          align-self: stretch;
+          cursor: pointer;
+
+          [data-slot="selected-icon"] {
+            visibility: hidden;
+            color: rgba(255, 255, 255, 0.39);
+            font-family: "IBM Plex Mono";
+            font-size: 16px;
+            font-style: normal;
+            font-weight: 400;
+            line-height: 160%; /* 25.6px */
+          }
+
+          a {
+            color: rgba(255, 255, 255, 0.92);
+            font-size: 16px;
+            font-style: normal;
+            font-weight: 400;
+            line-height: 160%; /* 25.6px */
+            text-decoration: none;
+          }
+        }
+
+        [data-slot="workspace"]:hover,
+        [data-slot="workspace"][data-active="true"] {
+          background: #161616;
+
+          [data-slot="selected-icon"] {
+            visibility: visible;
+          }
+        }
+      }
+    }
+  }
+
+  [data-component="footer"] {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    justify-content: center;
+    align-items: center;
+    gap: 24px;
+    flex-shrink: 0;
+
+    @media (min-width: 768px) {
+      height: 120px;
+    }
+
+    [data-slot="footer-content"] {
+      display: flex;
+      gap: 24px;
+      align-items: center;
+      justify-content: center;
+
+      @media (min-width: 768px) {
+        gap: 40px;
+      }
+
+      span,
+      a {
+        color: rgba(255, 255, 255, 0.39);
+        font-family: "JetBrains Mono Nerd Font";
+        font-size: 16px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: normal;
+        text-decoration: none;
+      }
+
+      [data-slot="github-stars"] {
+        color: rgba(255, 255, 255, 0.25);
+        font-family: "JetBrains Mono Nerd Font";
+        font-size: 16px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: normal;
+      }
+
+      [data-slot="anomaly"] {
+        display: none;
+
+        @media (min-width: 768px) {
+          display: block;
+        }
+      }
+    }
+    [data-slot="anomaly-alt"] {
+      color: rgba(255, 255, 255, 0.39);
+      font-family: "JetBrains Mono Nerd Font";
+      font-size: 16px;
+      font-style: normal;
+      font-weight: 400;
+      line-height: normal;
+      text-decoration: none;
+      margin-bottom: 24px;
+
+      a {
+        color: rgba(255, 255, 255, 0.39);
+        font-family: "JetBrains Mono Nerd Font";
+        font-size: 16px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: normal;
+        text-decoration: none;
+      }
+
+      @media (min-width: 768px) {
+        display: none;
+      }
+    }
+  }
+}

File diff suppressed because it is too large
+ 134 - 0
packages/console/app/src/routes/black/workspace.tsx


+ 477 - 0
packages/console/app/src/routes/changelog/index.css

@@ -0,0 +1,477 @@
+::selection {
+  background: var(--color-background-interactive);
+  color: var(--color-text-strong);
+
+  @media (prefers-color-scheme: dark) {
+    background: var(--color-background-interactive);
+    color: var(--color-text-inverted);
+  }
+}
+
+[data-page="changelog"] {
+  --color-background: hsl(0, 20%, 99%);
+  --color-background-weak: hsl(0, 8%, 97%);
+  --color-background-weak-hover: hsl(0, 8%, 94%);
+  --color-background-strong: hsl(0, 5%, 12%);
+  --color-background-strong-hover: hsl(0, 5%, 18%);
+  --color-background-interactive: hsl(62, 84%, 88%);
+  --color-background-interactive-weaker: hsl(64, 74%, 95%);
+
+  --color-text: hsl(0, 1%, 39%);
+  --color-text-weak: hsl(0, 1%, 60%);
+  --color-text-weaker: hsl(30, 2%, 81%);
+  --color-text-strong: hsl(0, 5%, 12%);
+  --color-text-inverted: hsl(0, 20%, 99%);
+
+  --color-border: hsl(30, 2%, 81%);
+  --color-border-weak: hsl(0, 1%, 85%);
+
+  --color-icon: hsl(0, 1%, 55%);
+
+  background: var(--color-background);
+  font-family: var(--font-mono);
+  color: var(--color-text);
+  padding-bottom: 5rem;
+
+  @media (prefers-color-scheme: dark) {
+    --color-background: hsl(0, 9%, 7%);
+    --color-background-weak: hsl(0, 6%, 10%);
+    --color-background-weak-hover: hsl(0, 6%, 15%);
+    --color-background-strong: hsl(0, 15%, 94%);
+    --color-background-strong-hover: hsl(0, 15%, 97%);
+    --color-background-interactive: hsl(62, 100%, 90%);
+    --color-background-interactive-weaker: hsl(60, 20%, 8%);
+
+    --color-text: hsl(0, 4%, 71%);
+    --color-text-weak: hsl(0, 2%, 49%);
+    --color-text-weaker: hsl(0, 3%, 28%);
+    --color-text-strong: hsl(0, 15%, 94%);
+    --color-text-inverted: hsl(0, 9%, 7%);
+
+    --color-border: hsl(0, 3%, 28%);
+    --color-border-weak: hsl(0, 4%, 23%);
+
+    --color-icon: hsl(10, 3%, 43%);
+  }
+
+  /* Header styles - copied from download */
+  [data-component="top"] {
+    padding: 24px 5rem;
+    height: 80px;
+    position: sticky;
+    top: 0;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    background: var(--color-background);
+    border-bottom: 1px solid var(--color-border-weak);
+    z-index: 10;
+
+    @media (max-width: 60rem) {
+      padding: 24px 1.5rem;
+    }
+
+    img {
+      height: 34px;
+      width: auto;
+    }
+
+    [data-component="nav-desktop"] {
+      ul {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        gap: 48px;
+
+        @media (max-width: 55rem) {
+          gap: 32px;
+        }
+
+        @media (max-width: 48rem) {
+          gap: 24px;
+        }
+        li {
+          display: inline-block;
+          a {
+            text-decoration: none;
+            span {
+              color: var(--color-text-weak);
+            }
+          }
+          a:hover {
+            text-decoration: underline;
+            text-underline-offset: 2px;
+            text-decoration-thickness: 1px;
+          }
+          [data-slot="cta-button"] {
+            background: var(--color-background-strong);
+            color: var(--color-text-inverted);
+            padding: 8px 16px;
+            border-radius: 4px;
+            font-weight: 500;
+            text-decoration: none;
+
+            @media (max-width: 55rem) {
+              display: none;
+            }
+          }
+          [data-slot="cta-button"]:hover {
+            background: var(--color-background-strong-hover);
+            text-decoration: none;
+          }
+        }
+      }
+
+      @media (max-width: 40rem) {
+        display: none;
+      }
+    }
+
+    [data-component="nav-mobile"] {
+      button > svg {
+        color: var(--color-icon);
+      }
+    }
+
+    [data-component="nav-mobile-toggle"] {
+      border: none;
+      background: none;
+      outline: none;
+      height: 40px;
+      width: 40px;
+      cursor: pointer;
+      margin-right: -8px;
+    }
+
+    [data-component="nav-mobile-toggle"]:hover {
+      background: var(--color-background-weak);
+    }
+
+    [data-component="nav-mobile"] {
+      display: none;
+
+      @media (max-width: 40rem) {
+        display: block;
+
+        [data-component="nav-mobile-icon"] {
+          cursor: pointer;
+          height: 40px;
+          width: 40px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+        }
+
+        [data-component="nav-mobile-menu-list"] {
+          position: fixed;
+          background: var(--color-background);
+          top: 80px;
+          left: 0;
+          right: 0;
+          height: 100vh;
+
+          ul {
+            list-style: none;
+            padding: 20px 0;
+
+            li {
+              a {
+                text-decoration: none;
+                padding: 20px;
+                display: block;
+
+                span {
+                  color: var(--color-text-weak);
+                }
+              }
+
+              a:hover {
+                background: var(--color-background-weak);
+              }
+            }
+          }
+        }
+      }
+    }
+
+    [data-slot="logo dark"] {
+      display: none;
+    }
+
+    @media (prefers-color-scheme: dark) {
+      [data-slot="logo light"] {
+        display: none;
+      }
+      [data-slot="logo dark"] {
+        display: block;
+      }
+    }
+  }
+
+  [data-component="footer"] {
+    border-top: 1px solid var(--color-border-weak);
+    display: flex;
+    flex-direction: row;
+    margin-top: 4rem;
+
+    @media (max-width: 65rem) {
+      border-bottom: 1px solid var(--color-border-weak);
+    }
+
+    [data-slot="cell"] {
+      flex: 1;
+      text-align: center;
+
+      a {
+        text-decoration: none;
+        padding: 2rem 0;
+        width: 100%;
+        display: block;
+
+        span {
+          color: var(--color-text-weak);
+
+          @media (max-width: 40rem) {
+            display: none;
+          }
+        }
+      }
+
+      a:hover {
+        background: var(--color-background-weak);
+        text-decoration: underline;
+        text-underline-offset: 2px;
+        text-decoration-thickness: 1px;
+      }
+    }
+
+    [data-slot="cell"] + [data-slot="cell"] {
+      border-left: 1px solid var(--color-border-weak);
+
+      @media (max-width: 40rem) {
+        border-left: none;
+      }
+    }
+
+    @media (max-width: 25rem) {
+      flex-wrap: wrap;
+
+      [data-slot="cell"] {
+        flex: 1 0 100%;
+        border-left: none;
+        border-top: 1px solid var(--color-border-weak);
+      }
+
+      [data-slot="cell"]:nth-child(1) {
+        border-top: none;
+      }
+    }
+  }
+
+  [data-component="container"] {
+    max-width: 67.5rem;
+    margin: 0 auto;
+    border: 1px solid var(--color-border-weak);
+    border-top: none;
+
+    @media (max-width: 65rem) {
+      border: none;
+    }
+  }
+
+  [data-component="content"] {
+    padding: 6rem 5rem;
+
+    @media (max-width: 60rem) {
+      padding: 4rem 1.5rem;
+    }
+  }
+
+  [data-component="legal"] {
+    color: var(--color-text-weak);
+    text-align: center;
+    padding: 2rem 5rem;
+    display: flex;
+    gap: 32px;
+    justify-content: center;
+
+    @media (max-width: 60rem) {
+      padding: 2rem 1.5rem;
+    }
+
+    a {
+      color: var(--color-text-weak);
+      text-decoration: none;
+    }
+
+    a:hover {
+      color: var(--color-text);
+      text-decoration: underline;
+    }
+  }
+
+  /* Changelog Hero */
+  [data-component="changelog-hero"] {
+    margin-bottom: 4rem;
+    padding-bottom: 2rem;
+    border-bottom: 1px solid var(--color-border-weak);
+
+    @media (max-width: 50rem) {
+      margin-bottom: 2rem;
+      padding-bottom: 1.5rem;
+    }
+
+    h1 {
+      font-size: 1.5rem;
+      font-weight: 700;
+      color: var(--color-text-strong);
+      margin-bottom: 8px;
+    }
+
+    p {
+      color: var(--color-text);
+    }
+  }
+
+  /* Releases */
+  [data-component="releases"] {
+    display: flex;
+    flex-direction: column;
+    gap: 0;
+  }
+
+  [data-component="release"] {
+    display: grid;
+    grid-template-columns: 180px 1fr;
+    gap: 3rem;
+    padding: 2rem 0;
+    border-bottom: 1px solid var(--color-border-weak);
+
+    @media (max-width: 50rem) {
+      grid-template-columns: 1fr;
+      gap: 1rem;
+    }
+
+    &:first-child {
+      padding-top: 0;
+    }
+
+    &:last-child {
+      border-bottom: none;
+    }
+
+    header {
+      display: flex;
+      flex-direction: column;
+      gap: 4px;
+
+      @media (max-width: 50rem) {
+        flex-direction: row;
+        align-items: center;
+        gap: 12px;
+      }
+
+      [data-slot="version"] {
+        a {
+          font-weight: 600;
+          color: var(--color-text-strong);
+          text-decoration: none;
+
+          &:hover {
+            text-decoration: underline;
+            text-underline-offset: 2px;
+            text-decoration-thickness: 1px;
+          }
+        }
+      }
+
+      time {
+        color: var(--color-text-weak);
+        font-size: 14px;
+      }
+    }
+
+    [data-slot="content"] {
+      display: flex;
+      flex-direction: column;
+      gap: 1.5rem;
+    }
+
+    [data-component="section"] {
+      h3 {
+        font-size: 14px;
+        font-weight: 600;
+        color: var(--color-text-strong);
+        margin-bottom: 8px;
+      }
+
+      ul {
+        list-style: none;
+        padding: 0;
+        margin: 0;
+        display: flex;
+        flex-direction: column;
+        gap: 6px;
+
+        li {
+          color: var(--color-text);
+          line-height: 1.5;
+          padding-left: 16px;
+          position: relative;
+
+          &::before {
+            content: "-";
+            position: absolute;
+            left: 0;
+            color: var(--color-text-weak);
+          }
+
+          [data-slot="author"] {
+            color: var(--color-text-weak);
+            font-size: 13px;
+            margin-left: 4px;
+            text-decoration: none;
+
+            &:hover {
+              text-decoration: underline;
+              text-underline-offset: 2px;
+              text-decoration-thickness: 1px;
+            }
+          }
+        }
+      }
+    }
+
+    [data-component="contributors"] {
+      font-size: 13px;
+      color: var(--color-text-weak);
+      padding-top: 0.5rem;
+
+      span {
+        color: var(--color-text-weak);
+      }
+
+      a {
+        color: var(--color-text);
+        text-decoration: none;
+
+        &:hover {
+          text-decoration: underline;
+          text-underline-offset: 2px;
+          text-decoration-thickness: 1px;
+        }
+      }
+    }
+  }
+
+  a {
+    color: var(--color-text-strong);
+    text-decoration: underline;
+    text-underline-offset: 2px;
+    text-decoration-thickness: 1px;
+
+    &:hover {
+      text-decoration-thickness: 2px;
+    }
+  }
+}

+ 147 - 0
packages/console/app/src/routes/changelog/index.tsx

@@ -0,0 +1,147 @@
+import "./index.css"
+import { Title, Meta, Link } from "@solidjs/meta"
+import { createAsync, query } from "@solidjs/router"
+import { Header } from "~/component/header"
+import { Footer } from "~/component/footer"
+import { Legal } from "~/component/legal"
+import { config } from "~/config"
+import { For, Show } from "solid-js"
+
+type Release = {
+  tag_name: string
+  name: string
+  body: string
+  published_at: string
+  html_url: string
+}
+
+const getReleases = query(async () => {
+  "use server"
+  const response = await fetch("https://api.github.com/repos/anomalyco/opencode/releases?per_page=20", {
+    headers: {
+      Accept: "application/vnd.github.v3+json",
+      "User-Agent": "OpenCode-Console",
+    },
+    cf: {
+      cacheTtl: 60 * 5,
+      cacheEverything: true,
+    },
+  } as any)
+  if (!response.ok) return []
+  return response.json() as Promise<Release[]>
+}, "releases.get")
+
+function formatDate(dateString: string) {
+  const date = new Date(dateString)
+  return date.toLocaleDateString("en-US", {
+    year: "numeric",
+    month: "short",
+    day: "numeric",
+  })
+}
+
+function parseMarkdown(body: string) {
+  const lines = body.split("\n")
+  const sections: { title: string; items: string[] }[] = []
+  let current: { title: string; items: string[] } | null = null
+  let skip = false
+
+  for (const line of lines) {
+    if (line.startsWith("## ")) {
+      if (current) sections.push(current)
+      const title = line.slice(3).trim()
+      current = { title, items: [] }
+      skip = false
+    } else if (line.startsWith("**Thank you")) {
+      skip = true
+    } else if (line.startsWith("- ") && !skip) {
+      current?.items.push(line.slice(2).trim())
+    }
+  }
+  if (current) sections.push(current)
+
+  return { sections }
+}
+
+function ReleaseItem(props: { item: string }) {
+  const parts = () => {
+    const match = props.item.match(/^(.+?)(\s*\(@([\w-]+)\))?$/)
+    if (match) {
+      return {
+        text: match[1],
+        username: match[3],
+      }
+    }
+    return { text: props.item, username: undefined }
+  }
+
+  return (
+    <li>
+      <span>{parts().text}</span>
+      <Show when={parts().username}>
+        <a data-slot="author" href={`https://github.com/${parts().username}`} target="_blank" rel="noopener noreferrer">
+          (@{parts().username})
+        </a>
+      </Show>
+    </li>
+  )
+}
+
+export default function Changelog() {
+  const releases = createAsync(() => getReleases())
+
+  return (
+    <main data-page="changelog">
+      <Title>OpenCode | Changelog</Title>
+      <Link rel="canonical" href={`${config.baseUrl}/changelog`} />
+      <Meta name="description" content="OpenCode release notes and changelog" />
+
+      <div data-component="container">
+        <Header hideGetStarted />
+
+        <div data-component="content">
+          <section data-component="changelog-hero">
+            <h1>Changelog</h1>
+            <p>New updates and improvements to OpenCode</p>
+          </section>
+
+          <section data-component="releases">
+            <For each={releases()}>
+              {(release) => {
+                const parsed = () => parseMarkdown(release.body || "")
+                return (
+                  <article data-component="release">
+                    <header>
+                      <div data-slot="version">
+                        <a href={release.html_url} target="_blank" rel="noopener noreferrer">
+                          {release.tag_name}
+                        </a>
+                      </div>
+                      <time dateTime={release.published_at}>{formatDate(release.published_at)}</time>
+                    </header>
+                    <div data-slot="content">
+                      <For each={parsed().sections}>
+                        {(section) => (
+                          <div data-component="section">
+                            <h3>{section.title}</h3>
+                            <ul>
+                              <For each={section.items}>{(item) => <ReleaseItem item={item} />}</For>
+                            </ul>
+                          </div>
+                        )}
+                      </For>
+                    </div>
+                  </article>
+                )
+              }}
+            </For>
+          </section>
+
+          <Footer />
+        </div>
+      </div>
+
+      <Legal />
+    </main>
+  )
+}

+ 1 - 1
packages/console/app/src/routes/download/[platform].ts

@@ -24,7 +24,7 @@ export async function GET({ params: { platform } }: APIEvent) {
   const resp = await fetch(`https://github.com/anomalyco/opencode/releases/latest/download/${assetName}`, {
   const resp = await fetch(`https://github.com/anomalyco/opencode/releases/latest/download/${assetName}`, {
     cf: {
     cf: {
       // in case gh releases has rate limits
       // in case gh releases has rate limits
-      cacheTtl: 60 * 60 * 24,
+      cacheTtl: 60 * 5,
       cacheEverything: true,
       cacheEverything: true,
     },
     },
   } as any)
   } as any)

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

@@ -34,7 +34,6 @@
   font-family: var(--font-mono);
   font-family: var(--font-mono);
   color: var(--color-text);
   color: var(--color-text);
   padding-bottom: 5rem;
   padding-bottom: 5rem;
-  overflow-x: hidden;
 
 
   @media (prefers-color-scheme: dark) {
   @media (prefers-color-scheme: dark) {
     --color-background: hsl(0, 9%, 7%);
     --color-background: hsl(0, 9%, 7%);

+ 7 - 5
packages/console/app/src/routes/download/index.tsx

@@ -129,9 +129,9 @@ export default function Download() {
                 </code>
                 </code>
                 <CopyStatus />
                 <CopyStatus />
               </button>
               </button>
-              <button data-component="cli-row" onClick={handleCopyClick("brew install opencode")}>
+              <button data-component="cli-row" onClick={handleCopyClick("brew install anomalyco/tap/opencode")}>
                 <code>
                 <code>
-                  brew install <strong>opencode</strong>
+                  brew install <strong>anomalyco/tap/opencode</strong>
                 </code>
                 </code>
                 <CopyStatus />
                 <CopyStatus />
               </button>
               </button>
@@ -244,7 +244,8 @@ export default function Download() {
                   Download
                   Download
                 </a>
                 </a>
               </div>
               </div>
-              <div data-component="download-row">
+              {/* Disabled temporarily as it doesn't work */}
+              {/*<div data-component="download-row">
                 <div data-component="download-info">
                 <div data-component="download-info">
                   <span data-slot="icon">
                   <span data-slot="icon">
                     <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                     <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -259,7 +260,7 @@ export default function Download() {
                 <a href={getDownloadHref("linux-x64-appimage")} data-component="action-button">
                 <a href={getDownloadHref("linux-x64-appimage")} data-component="action-button">
                   Download
                   Download
                 </a>
                 </a>
-              </div>
+              </div>*/}
             </div>
             </div>
           </section>
           </section>
 
 
@@ -440,7 +441,8 @@ export default function Download() {
             </li>
             </li>
             <li>
             <li>
               <Faq question="Can I only use OpenCode in the terminal?">
               <Faq question="Can I only use OpenCode in the terminal?">
-                Not anymore! OpenCode is now available as an app for your desktop.
+                Not anymore! OpenCode is now available as an app for your <a href="/download">desktop</a> and{" "}
+                <a href="/docs/cli/#web">web</a>!
               </Faq>
               </Faq>
             </li>
             </li>
             <li>
             <li>

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

@@ -522,7 +522,7 @@ body {
     [data-slot="content"] {
     [data-slot="content"] {
       display: flex;
       display: flex;
       align-items: center;
       align-items: center;
-      gap: 4px;
+      gap: 1ch;
     }
     }
 
 
     [data-slot="text"] {
     [data-slot="text"] {

+ 9 - 2
packages/console/app/src/routes/index.tsx

@@ -140,7 +140,7 @@ export default function Home() {
                     <button data-copy data-slot="command" onClick={handleCopyClick}>
                     <button data-copy data-slot="command" onClick={handleCopyClick}>
                       <span>
                       <span>
                         <span data-slot="protocol">brew install </span>
                         <span data-slot="protocol">brew install </span>
-                        <span data-slot="highlight">opencode</span>
+                        <span data-slot="highlight">anomalyco/tap/opencode</span>
                       </span>
                       </span>
                       <CopyStatus />
                       <CopyStatus />
                     </button>
                     </button>
@@ -195,6 +195,12 @@ export default function Home() {
                   <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
                 </div>
                 </div>
               </li>
               </li>
+              <li>
+                <span>[*]</span>
+                <div>
+                  <strong>ChatGPT Plus/Pro</strong> Log in with OpenAI to use your ChatGPT Plus or Pro account
+                </div>
+              </li>
               <li>
               <li>
                 <span>[*]</span>
                 <span>[*]</span>
                 <div>
                 <div>
@@ -686,7 +692,8 @@ export default function Home() {
               </li>
               </li>
               <li>
               <li>
                 <Faq question="Can I only use OpenCode in the terminal?">
                 <Faq question="Can I only use OpenCode in the terminal?">
-                  Not anymore! OpenCode is now available as an app for your desktop.
+                  Not anymore! OpenCode is now available as an app for your <a href="/download">desktop</a> and{" "}
+                  <a href="/docs/web">web</a>!
                 </Faq>
                 </Faq>
               </li>
               </li>
               <li>
               <li>

+ 200 - 69
packages/console/app/src/routes/stripe/webhook.ts

@@ -1,11 +1,13 @@
 import { Billing } from "@opencode-ai/console-core/billing.js"
 import { Billing } from "@opencode-ai/console-core/billing.js"
 import type { APIEvent } from "@solidjs/start/server"
 import type { APIEvent } from "@solidjs/start/server"
-import { and, Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js"
+import { and, Database, eq, isNull, sql } from "@opencode-ai/console-core/drizzle/index.js"
 import { BillingTable, PaymentTable, SubscriptionTable } from "@opencode-ai/console-core/schema/billing.sql.js"
 import { BillingTable, PaymentTable, SubscriptionTable } from "@opencode-ai/console-core/schema/billing.sql.js"
 import { Identifier } from "@opencode-ai/console-core/identifier.js"
 import { Identifier } from "@opencode-ai/console-core/identifier.js"
 import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
 import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
 import { Actor } from "@opencode-ai/console-core/actor.js"
 import { Actor } from "@opencode-ai/console-core/actor.js"
 import { Resource } from "@opencode-ai/console-resource"
 import { Resource } from "@opencode-ai/console-resource"
+import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
+import { AuthTable } from "@opencode-ai/console-core/schema/auth.sql.js"
 
 
 export async function POST(input: APIEvent) {
 export async function POST(input: APIEvent) {
   const body = await Billing.stripe().webhooks.constructEventAsync(
   const body = await Billing.stripe().webhooks.constructEventAsync(
@@ -39,7 +41,7 @@ export async function POST(input: APIEvent) {
           .where(eq(BillingTable.customerID, customerID))
           .where(eq(BillingTable.customerID, customerID))
       })
       })
     }
     }
-    if (body.type === "checkout.session.completed") {
+    if (body.type === "checkout.session.completed" && body.data.object.mode === "payment") {
       const workspaceID = body.data.object.metadata?.workspaceID
       const workspaceID = body.data.object.metadata?.workspaceID
       const amountInCents = body.data.object.metadata?.amount && parseInt(body.data.object.metadata?.amount)
       const amountInCents = body.data.object.metadata?.amount && parseInt(body.data.object.metadata?.amount)
       const customerID = body.data.object.customer as string
       const customerID = body.data.object.customer as string
@@ -102,85 +104,112 @@ export async function POST(input: APIEvent) {
         })
         })
       })
       })
     }
     }
-    if (body.type === "charge.refunded") {
+    if (body.type === "checkout.session.completed" && body.data.object.mode === "subscription") {
+      const workspaceID = body.data.object.custom_fields.find((f) => f.key === "workspaceid")?.text?.value
+      const amountInCents = body.data.object.amount_total as number
       const customerID = body.data.object.customer as string
       const 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 customerEmail = body.data.object.customer_details?.email as string
+      const invoiceID = body.data.object.invoice as string
+      const subscriptionID = body.data.object.subscription as string
+      const promoCode = body.data.object.discounts?.[0]?.promotion_code as string
 
 
-      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")
       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} - ${amount}`,
-          })
-          .where(eq(BillingTable.workspaceID, workspaceID))
-      })
-    }
-    if (body.type === "invoice.payment_succeeded" && body.data.object.billing_reason === "subscription_cycle") {
-      const invoiceID = body.data.object.id as string
-      const amountInCents = body.data.object.amount_paid
-      const customerID = body.data.object.customer as string
-      const subscriptionID = body.data.object.parent?.subscription_details?.subscription as string
-
       if (!customerID) throw new Error("Customer ID not found")
       if (!customerID) throw new Error("Customer ID not found")
+      if (!amountInCents) throw new Error("Amount not found")
       if (!invoiceID) throw new Error("Invoice ID not found")
       if (!invoiceID) throw new Error("Invoice ID not found")
       if (!subscriptionID) throw new Error("Subscription ID not found")
       if (!subscriptionID) throw new Error("Subscription ID not found")
 
 
+      // get payment id from invoice
       const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
       const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
         expand: ["payments"],
         expand: ["payments"],
       })
       })
       const paymentID = invoice.payments?.data[0].payment.payment_intent as string
       const paymentID = invoice.payments?.data[0].payment.payment_intent as string
       if (!paymentID) throw new Error("Payment ID not found")
       if (!paymentID) 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 for customer")
-
-      await Database.use((tx) =>
-        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")
+
+      // get coupon id from promotion code
+      const couponID = await (async () => {
+        if (!promoCode) return
+        const coupon = await Billing.stripe().promotionCodes.retrieve(promoCode)
+        const couponID = coupon.coupon.id
+        if (!couponID) throw new Error("Coupon not found for promotion code")
+        return couponID
+      })()
+
+      // get user
+
+      await Actor.provide("system", { workspaceID }, async () => {
+        // look up current billing
+        const billing = await Billing.get()
+        if (!billing) throw new Error(`Workspace with ID ${workspaceID} not found`)
+
+        // Temporarily skip this check because during Black drop, user can checkout
+        // as a new customer
+        //if (billing.customerID !== customerID) throw new Error("Customer ID mismatch")
+
+        // Temporarily check the user to apply to. After Black drop, we will allow
+        // look up the user to apply to
+        const users = await Database.use((tx) =>
+          tx
+            .select({ id: UserTable.id, email: AuthTable.subject })
+            .from(UserTable)
+            .innerJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email")))
+            .where(and(eq(UserTable.workspaceID, workspaceID), isNull(UserTable.timeDeleted))),
+        )
+        const user = users.find((u) => u.email === customerEmail) ?? users[0]
+        if (!user) {
+          console.error(`Error: User with email ${customerEmail} not found in workspace ${workspaceID}`)
+          process.exit(1)
+        }
+
+        // set customer metadata
+        if (!billing?.customerID) {
+          await Billing.stripe().customers.update(customerID, {
+            metadata: {
+              workspaceID,
+            },
+          })
+        }
+
+        await Database.transaction(async (tx) => {
+          await tx
+            .update(BillingTable)
+            .set({
+              customerID,
+              subscriptionID,
+              subscriptionCouponID: couponID,
+              paymentMethodID: paymentMethod.id,
+              paymentMethodLast4: paymentMethod.card?.last4 ?? null,
+              paymentMethodType: paymentMethod.type,
+            })
+            .where(eq(BillingTable.workspaceID, workspaceID))
+
+          await tx.insert(SubscriptionTable).values({
+            workspaceID,
+            id: Identifier.create("subscription"),
+            userID: user.id,
+          })
+
+          await tx.insert(PaymentTable).values({
+            workspaceID,
+            id: Identifier.create("payment"),
+            amount: centsToMicroCents(amountInCents),
+            paymentID,
+            invoiceID,
+            customerID,
+            enrichment: {
+              type: "subscription",
+              couponID,
+            },
+          })
+        })
+      })
     }
     }
     if (body.type === "customer.subscription.created") {
     if (body.type === "customer.subscription.created") {
       const data = {
       const data = {
@@ -377,11 +406,113 @@ export async function POST(input: APIEvent) {
       if (!workspaceID) throw new Error("Workspace ID not found for subscription")
       if (!workspaceID) throw new Error("Workspace ID not found for subscription")
 
 
       await Database.transaction(async (tx) => {
       await Database.transaction(async (tx) => {
-        await tx.update(BillingTable).set({ subscriptionID: null }).where(eq(BillingTable.workspaceID, workspaceID))
+        await tx
+          .update(BillingTable)
+          .set({ subscriptionID: null, subscriptionCouponID: null })
+          .where(eq(BillingTable.workspaceID, workspaceID))
 
 
         await tx.delete(SubscriptionTable).where(eq(SubscriptionTable.workspaceID, workspaceID))
         await tx.delete(SubscriptionTable).where(eq(SubscriptionTable.workspaceID, workspaceID))
       })
       })
     }
     }
+    if (body.type === "invoice.payment_succeeded") {
+      if (body.data.object.billing_reason === "subscription_cycle") {
+        const invoiceID = body.data.object.id as string
+        const amountInCents = body.data.object.amount_paid
+        const customerID = body.data.object.customer as string
+        const subscriptionID = body.data.object.parent?.subscription_details?.subscription as string
+
+        if (!customerID) throw new Error("Customer ID not found")
+        if (!invoiceID) throw new Error("Invoice ID not found")
+        if (!subscriptionID) throw new Error("Subscription ID not found")
+
+        // get coupon id from subscription
+        const subscriptionData = await Billing.stripe().subscriptions.retrieve(subscriptionID, {
+          expand: ["discounts"],
+        })
+        const couponID =
+          typeof subscriptionData.discounts[0] === "string"
+            ? subscriptionData.discounts[0]
+            : subscriptionData.discounts[0]?.coupon?.id
+
+        // get payment id from invoice
+        const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
+          expand: ["payments"],
+        })
+        const paymentID = invoice.payments?.data[0].payment.payment_intent as string
+        if (!paymentID) {
+          // payment id can be undefined when using coupon
+          if (!couponID) 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 for customer")
+
+        await Database.use((tx) =>
+          tx.insert(PaymentTable).values({
+            workspaceID,
+            id: Identifier.create("payment"),
+            amount: centsToMicroCents(amountInCents),
+            paymentID,
+            invoiceID,
+            customerID,
+            enrichment: {
+              type: "subscription",
+              couponID,
+            },
+          }),
+        )
+      }
+    }
+    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")
+
+      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} - ${amount}`,
+          })
+          .where(eq(BillingTable.workspaceID, workspaceID))
+      })
+    }
   })()
   })()
     .then((message) => {
     .then((message) => {
       return Response.json({ message: message ?? "done" }, { status: 200 })
       return Response.json({ message: message ?? "done" }, { status: 200 })

+ 3 - 0
packages/console/app/src/routes/temp.tsx

@@ -91,6 +91,9 @@ export default function Home() {
             <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>
+            <li>
+              <strong>ChatGPT Plus/Pro</strong> Log in with OpenAI to use your ChatGPT Plus or Pro account
+            </li>
             <li>
             <li>
               <strong>Use any model</strong> Supports 75+ LLM providers through{" "}
               <strong>Use any model</strong> Supports 75+ LLM providers through{" "}
               <a href="https://models.dev">Models.dev</a>, including local models
               <a href="https://models.dev">Models.dev</a>, including local models

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

@@ -1,6 +1,6 @@
 import { action } from "@solidjs/router"
 import { action } from "@solidjs/router"
 import { getRequestEvent } from "solid-js/web"
 import { getRequestEvent } from "solid-js/web"
-import { useAuthSession } from "~/context/auth.session"
+import { useAuthSession } from "~/context/auth"
 import { Dropdown } from "~/component/dropdown"
 import { Dropdown } from "~/component/dropdown"
 import "./user-menu.css"
 import "./user-menu.css"
 
 

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

@@ -5,4 +5,58 @@
     align-items: center;
     align-items: center;
     gap: var(--space-4);
     gap: var(--space-4);
   }
   }
+
+  [data-slot="usage"] {
+    display: flex;
+    gap: var(--space-6);
+    margin-top: var(--space-4);
+
+    @media (max-width: 40rem) {
+      flex-direction: column;
+      gap: var(--space-4);
+    }
+  }
+
+  [data-slot="usage-item"] {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    gap: var(--space-2);
+  }
+
+  [data-slot="usage-header"] {
+    display: flex;
+    justify-content: space-between;
+    align-items: baseline;
+  }
+
+  [data-slot="usage-label"] {
+    font-size: var(--font-size-md);
+    font-weight: 500;
+    color: var(--color-text);
+  }
+
+  [data-slot="usage-value"] {
+    font-size: var(--font-size-sm);
+    color: var(--color-text-muted);
+  }
+
+  [data-slot="progress"] {
+    height: 8px;
+    background-color: var(--color-bg-surface);
+    border-radius: var(--border-radius-sm);
+    overflow: hidden;
+  }
+
+  [data-slot="progress-bar"] {
+    height: 100%;
+    background-color: var(--color-accent);
+    border-radius: var(--border-radius-sm);
+    transition: width 0.3s ease;
+  }
+
+  [data-slot="reset-time"] {
+    font-size: var(--font-size-sm);
+    color: var(--color-text-muted);
+  }
 }
 }

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

@@ -1,10 +1,58 @@
-import { action, useParams, useAction, useSubmission, json } from "@solidjs/router"
+import { action, useParams, useAction, useSubmission, json, query, createAsync } from "@solidjs/router"
 import { createStore } from "solid-js/store"
 import { createStore } from "solid-js/store"
+import { Show } from "solid-js"
 import { Billing } from "@opencode-ai/console-core/billing.js"
 import { Billing } from "@opencode-ai/console-core/billing.js"
+import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js"
+import { SubscriptionTable } from "@opencode-ai/console-core/schema/billing.sql.js"
+import { Actor } from "@opencode-ai/console-core/actor.js"
+import { Black } from "@opencode-ai/console-core/black.js"
 import { withActor } from "~/context/auth.withActor"
 import { withActor } from "~/context/auth.withActor"
 import { queryBillingInfo } from "../../common"
 import { queryBillingInfo } from "../../common"
 import styles from "./black-section.module.css"
 import styles from "./black-section.module.css"
 
 
+const querySubscription = query(async (workspaceID: string) => {
+  "use server"
+  return withActor(async () => {
+    const row = await Database.use((tx) =>
+      tx
+        .select({
+          rollingUsage: SubscriptionTable.rollingUsage,
+          fixedUsage: SubscriptionTable.fixedUsage,
+          timeRollingUpdated: SubscriptionTable.timeRollingUpdated,
+          timeFixedUpdated: SubscriptionTable.timeFixedUpdated,
+        })
+        .from(SubscriptionTable)
+        .where(and(eq(SubscriptionTable.workspaceID, Actor.workspace()), isNull(SubscriptionTable.timeDeleted)))
+        .then((r) => r[0]),
+    )
+    if (!row) return null
+
+    return {
+      rollingUsage: Black.analyzeRollingUsage({
+        usage: row.rollingUsage ?? 0,
+        timeUpdated: row.timeRollingUpdated ?? new Date(),
+      }),
+      weeklyUsage: Black.analyzeWeeklyUsage({
+        usage: row.fixedUsage ?? 0,
+        timeUpdated: row.timeFixedUpdated ?? new Date(),
+      }),
+    }
+  }, workspaceID)
+}, "subscription.get")
+
+function formatResetTime(seconds: number) {
+  const days = Math.floor(seconds / 86400)
+  if (days >= 1) {
+    const hours = Math.floor((seconds % 86400) / 3600)
+    return `${days} ${days === 1 ? "day" : "days"} ${hours} ${hours === 1 ? "hour" : "hours"}`
+  }
+  const hours = Math.floor(seconds / 3600)
+  const minutes = Math.floor((seconds % 3600) / 60)
+  if (hours >= 1) return `${hours} ${hours === 1 ? "hour" : "hours"} ${minutes} ${minutes === 1 ? "minute" : "minutes"}`
+  if (minutes === 0) return "a few seconds"
+  return `${minutes} ${minutes === 1 ? "minute" : "minutes"}`
+}
+
 const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
 const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
   "use server"
   "use server"
   return json(
   return json(
@@ -26,6 +74,7 @@ export function BlackSection() {
   const params = useParams()
   const params = useParams()
   const sessionAction = useAction(createSessionUrl)
   const sessionAction = useAction(createSessionUrl)
   const sessionSubmission = useSubmission(createSessionUrl)
   const sessionSubmission = useSubmission(createSessionUrl)
+  const subscription = createAsync(() => querySubscription(params.id!))
   const [store, setStore] = createStore({
   const [store, setStore] = createStore({
     sessionRedirecting: false,
     sessionRedirecting: false,
   })
   })
@@ -53,6 +102,32 @@ export function BlackSection() {
           </button>
           </button>
         </div>
         </div>
       </div>
       </div>
+      <Show when={subscription()}>
+        {(sub) => (
+          <div data-slot="usage">
+            <div data-slot="usage-item">
+              <div data-slot="usage-header">
+                <span data-slot="usage-label">5-hour Usage</span>
+                <span data-slot="usage-value">{sub().rollingUsage.usagePercent}%</span>
+              </div>
+              <div data-slot="progress">
+                <div data-slot="progress-bar" style={{ width: `${sub().rollingUsage.usagePercent}%` }} />
+              </div>
+              <span data-slot="reset-time">Resets in {formatResetTime(sub().rollingUsage.resetInSec)}</span>
+            </div>
+            <div data-slot="usage-item">
+              <div data-slot="usage-header">
+                <span data-slot="usage-label">Weekly Usage</span>
+                <span data-slot="usage-value">{sub().weeklyUsage.usagePercent}%</span>
+              </div>
+              <div data-slot="progress">
+                <div data-slot="progress-bar" style={{ width: `${sub().weeklyUsage.usagePercent}%` }} />
+              </div>
+              <span data-slot="reset-time">Resets in {formatResetTime(sub().weeklyUsage.resetInSec)}</span>
+            </div>
+          </div>
+        )}
+      </Show>
     </section>
     </section>
   )
   )
 }
 }

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

@@ -3,7 +3,6 @@ import { Actor } from "@opencode-ai/console-core/actor.js"
 import { action, json, query } from "@solidjs/router"
 import { action, json, query } from "@solidjs/router"
 import { withActor } from "~/context/auth.withActor"
 import { withActor } from "~/context/auth.withActor"
 import { Billing } from "@opencode-ai/console-core/billing.js"
 import { Billing } from "@opencode-ai/console-core/billing.js"
-import { User } from "@opencode-ai/console-core/user.js"
 import { and, Database, desc, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js"
 import { and, Database, desc, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js"
 import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
 import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
 import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
 import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
@@ -96,11 +95,22 @@ export const queryBillingInfo = query(async (workspaceID: string) => {
   return withActor(async () => {
   return withActor(async () => {
     const billing = await Billing.get()
     const billing = await Billing.get()
     return {
     return {
-      ...billing,
+      customerID: billing.customerID,
+      paymentMethodID: billing.paymentMethodID,
+      paymentMethodType: billing.paymentMethodType,
+      paymentMethodLast4: billing.paymentMethodLast4,
+      balance: billing.balance,
+      reload: billing.reload,
       reloadAmount: billing.reloadAmount ?? Billing.RELOAD_AMOUNT,
       reloadAmount: billing.reloadAmount ?? Billing.RELOAD_AMOUNT,
       reloadAmountMin: Billing.RELOAD_AMOUNT_MIN,
       reloadAmountMin: Billing.RELOAD_AMOUNT_MIN,
       reloadTrigger: billing.reloadTrigger ?? Billing.RELOAD_TRIGGER,
       reloadTrigger: billing.reloadTrigger ?? Billing.RELOAD_TRIGGER,
       reloadTriggerMin: Billing.RELOAD_TRIGGER_MIN,
       reloadTriggerMin: Billing.RELOAD_TRIGGER_MIN,
+      monthlyLimit: billing.monthlyLimit,
+      monthlyUsage: billing.monthlyUsage,
+      timeMonthlyUsageUpdated: billing.timeMonthlyUsageUpdated,
+      reloadError: billing.reloadError,
+      timeReloadError: billing.timeReloadError,
+      subscriptionID: billing.subscriptionID,
     }
     }
   }, workspaceID)
   }, workspaceID)
 }, "billing.get")
 }, "billing.get")

+ 38 - 28
packages/console/app/src/routes/zen/util/handler.ts

@@ -9,7 +9,7 @@ import { Billing } from "@opencode-ai/console-core/billing.js"
 import { Actor } from "@opencode-ai/console-core/actor.js"
 import { Actor } from "@opencode-ai/console-core/actor.js"
 import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
 import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
 import { ZenData } from "@opencode-ai/console-core/model.js"
 import { ZenData } from "@opencode-ai/console-core/model.js"
-import { BlackData } from "@opencode-ai/console-core/black.js"
+import { Black, BlackData } from "@opencode-ai/console-core/black.js"
 import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
 import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
 import { ModelTable } from "@opencode-ai/console-core/schema/model.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 { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
@@ -81,12 +81,13 @@ export async function handler(
     const isTrial = await trialLimiter?.isTrial()
     const isTrial = await trialLimiter?.isTrial()
     const rateLimiter = createRateLimiter(modelInfo.rateLimit, ip)
     const rateLimiter = createRateLimiter(modelInfo.rateLimit, ip)
     await rateLimiter?.check()
     await rateLimiter?.check()
-    const stickyTracker = createStickyTracker(modelInfo.stickyProvider ?? false, sessionId)
+    const stickyTracker = createStickyTracker(modelInfo.stickyProvider, sessionId)
     const stickyProvider = await stickyTracker?.get()
     const stickyProvider = await stickyTracker?.get()
     const authInfo = await authenticate(modelInfo)
     const authInfo = await authenticate(modelInfo)
 
 
     const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
     const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
       const providerInfo = selectProvider(
       const providerInfo = selectProvider(
+        model,
         zenData,
         zenData,
         authInfo,
         authInfo,
         modelInfo,
         modelInfo,
@@ -101,7 +102,7 @@ export async function handler(
       logger.metric({ provider: providerInfo.id })
       logger.metric({ provider: providerInfo.id })
 
 
       const startTimestamp = Date.now()
       const startTimestamp = Date.now()
-      const reqUrl = providerInfo.modifyUrl(providerInfo.api, providerInfo.model, isStream)
+      const reqUrl = providerInfo.modifyUrl(providerInfo.api, isStream)
       const reqBody = JSON.stringify(
       const reqBody = JSON.stringify(
         providerInfo.modifyBody({
         providerInfo.modifyBody({
           ...createBodyConverter(opts.format, providerInfo.format)(body),
           ...createBodyConverter(opts.format, providerInfo.format)(body),
@@ -135,7 +136,7 @@ export async function handler(
         // ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found.
         // ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found.
         res.status !== 404 &&
         res.status !== 404 &&
         // ie. cannot change codex model providers mid-session
         // ie. cannot change codex model providers mid-session
-        !modelInfo.stickyProvider &&
+        modelInfo.stickyProvider !== "strict" &&
         modelInfo.fallbackProvider &&
         modelInfo.fallbackProvider &&
         providerInfo.id !== modelInfo.fallbackProvider
         providerInfo.id !== modelInfo.fallbackProvider
       ) {
       ) {
@@ -194,17 +195,19 @@ export async function handler(
     // Handle streaming response
     // Handle streaming response
     const streamConverter = createStreamPartConverter(providerInfo.format, opts.format)
     const streamConverter = createStreamPartConverter(providerInfo.format, opts.format)
     const usageParser = providerInfo.createUsageParser()
     const usageParser = providerInfo.createUsageParser()
+    const binaryDecoder = providerInfo.createBinaryStreamDecoder()
     const stream = new ReadableStream({
     const stream = new ReadableStream({
       start(c) {
       start(c) {
         const reader = res.body?.getReader()
         const reader = res.body?.getReader()
         const decoder = new TextDecoder()
         const decoder = new TextDecoder()
         const encoder = new TextEncoder()
         const encoder = new TextEncoder()
+
         let buffer = ""
         let buffer = ""
         let responseLength = 0
         let responseLength = 0
 
 
         function pump(): Promise<void> {
         function pump(): Promise<void> {
           return (
           return (
-            reader?.read().then(async ({ done, value }) => {
+            reader?.read().then(async ({ done, value: rawValue }) => {
               if (done) {
               if (done) {
                 logger.metric({
                 logger.metric({
                   response_length: responseLength,
                   response_length: responseLength,
@@ -230,6 +233,10 @@ export async function handler(
                   "timestamp.first_byte": now,
                   "timestamp.first_byte": now,
                 })
                 })
               }
               }
+
+              const value = binaryDecoder ? binaryDecoder(rawValue) : rawValue
+              if (!value) return
+
               responseLength += value.length
               responseLength += value.length
               buffer += decoder.decode(value, { stream: true })
               buffer += decoder.decode(value, { stream: true })
               dataDumper?.provideStream(buffer)
               dataDumper?.provideStream(buffer)
@@ -331,6 +338,7 @@ export async function handler(
   }
   }
 
 
   function selectProvider(
   function selectProvider(
+    reqModel: string,
     zenData: ZenData,
     zenData: ZenData,
     authInfo: AuthInfo,
     authInfo: AuthInfo,
     modelInfo: ModelInfo,
     modelInfo: ModelInfo,
@@ -339,7 +347,7 @@ export async function handler(
     retry: RetryOptions,
     retry: RetryOptions,
     stickyProvider: string | undefined,
     stickyProvider: string | undefined,
   ) {
   ) {
-    const provider = (() => {
+    const modelProvider = (() => {
       if (authInfo?.provider?.credentials) {
       if (authInfo?.provider?.credentials) {
         return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider)
         return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider)
       }
       }
@@ -372,18 +380,19 @@ export async function handler(
       return providers[index || 0]
       return providers[index || 0]
     })()
     })()
 
 
-    if (!provider) throw new ModelError("No provider available")
-    if (!(provider.id in zenData.providers)) throw new ModelError(`Provider ${provider.id} not supported`)
+    if (!modelProvider) throw new ModelError("No provider available")
+    if (!(modelProvider.id in zenData.providers)) throw new ModelError(`Provider ${modelProvider.id} not supported`)
 
 
     return {
     return {
-      ...provider,
-      ...zenData.providers[provider.id],
+      ...modelProvider,
+      ...zenData.providers[modelProvider.id],
       ...(() => {
       ...(() => {
-        const format = zenData.providers[provider.id].format
-        if (format === "anthropic") return anthropicHelper
-        if (format === "google") return googleHelper
-        if (format === "openai") return openaiHelper
-        return oaCompatHelper
+        const format = zenData.providers[modelProvider.id].format
+        const providerModel = modelProvider.model
+        if (format === "anthropic") return anthropicHelper({ reqModel, providerModel })
+        if (format === "google") return googleHelper({ reqModel, providerModel })
+        if (format === "openai") return openaiHelper({ reqModel, providerModel })
+        return oaCompatHelper({ reqModel, providerModel })
       })(),
       })(),
     }
     }
   }
   }
@@ -495,27 +504,28 @@ export async function handler(
 
 
       // Check weekly limit
       // Check weekly limit
       if (sub.fixedUsage && sub.timeFixedUpdated) {
       if (sub.fixedUsage && sub.timeFixedUpdated) {
-        const week = getWeekBounds(now)
-        if (sub.timeFixedUpdated >= week.start && sub.fixedUsage >= centsToMicroCents(black.fixedLimit * 100)) {
-          const retryAfter = Math.ceil((week.end.getTime() - now.getTime()) / 1000)
+        const result = Black.analyzeWeeklyUsage({
+          usage: sub.fixedUsage,
+          timeUpdated: sub.timeFixedUpdated,
+        })
+        if (result.status === "rate-limited")
           throw new SubscriptionError(
           throw new SubscriptionError(
-            `Subscription quota exceeded. Retry in ${formatRetryTime(retryAfter)}.`,
-            retryAfter,
+            `Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
+            result.resetInSec,
           )
           )
-        }
       }
       }
 
 
       // Check rolling limit
       // Check rolling limit
       if (sub.rollingUsage && sub.timeRollingUpdated) {
       if (sub.rollingUsage && sub.timeRollingUpdated) {
-        const rollingWindowMs = black.rollingWindow * 3600 * 1000
-        const windowStart = new Date(now.getTime() - rollingWindowMs)
-        if (sub.timeRollingUpdated >= windowStart && sub.rollingUsage >= centsToMicroCents(black.rollingLimit * 100)) {
-          const retryAfter = Math.ceil((sub.timeRollingUpdated.getTime() + rollingWindowMs - now.getTime()) / 1000)
+        const result = Black.analyzeRollingUsage({
+          usage: sub.rollingUsage,
+          timeUpdated: sub.timeRollingUpdated,
+        })
+        if (result.status === "rate-limited")
           throw new SubscriptionError(
           throw new SubscriptionError(
-            `Subscription quota exceeded. Retry in ${formatRetryTime(retryAfter)}.`,
-            retryAfter,
+            `Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
+            result.resetInSec,
           )
           )
-        }
       }
       }
 
 
       return
       return

+ 161 - 56
packages/console/app/src/routes/zen/util/provider/anthropic.ts

@@ -1,4 +1,6 @@
+import { EventStreamCodec } from "@smithy/eventstream-codec"
 import { ProviderHelper, CommonRequest, CommonResponse, CommonChunk } from "./provider"
 import { ProviderHelper, CommonRequest, CommonResponse, CommonChunk } from "./provider"
+import { fromUtf8, toUtf8 } from "@smithy/util-utf8"
 
 
 type Usage = {
 type Usage = {
   cache_creation?: {
   cache_creation?: {
@@ -14,65 +16,168 @@ type Usage = {
   }
   }
 }
 }
 
 
-export const anthropicHelper = {
-  format: "anthropic",
-  modifyUrl: (providerApi: string) => providerApi + "/messages",
-  modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
-    headers.set("x-api-key", apiKey)
-    headers.set("anthropic-version", headers.get("anthropic-version") ?? "2023-06-01")
-    if (body.model.startsWith("claude-sonnet-")) {
-      headers.set("anthropic-beta", "context-1m-2025-08-07")
-    }
-  },
-  modifyBody: (body: Record<string, any>) => {
-    return {
+export const anthropicHelper: ProviderHelper = ({ reqModel, providerModel }) => {
+  const isBedrockModelArn = providerModel.startsWith("arn:aws:bedrock:")
+  const isBedrockModelID = providerModel.startsWith("global.anthropic.")
+  const isBedrock = isBedrockModelArn || isBedrockModelID
+  const isSonnet = reqModel.includes("sonnet")
+  return {
+    format: "anthropic",
+    modifyUrl: (providerApi: string, isStream?: boolean) =>
+      isBedrock
+        ? `${providerApi}/model/${isBedrockModelArn ? encodeURIComponent(providerModel) : providerModel}/${isStream ? "invoke-with-response-stream" : "invoke"}`
+        : providerApi + "/messages",
+    modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
+      if (isBedrock) {
+        headers.set("Authorization", `Bearer ${apiKey}`)
+      } else {
+        headers.set("x-api-key", apiKey)
+        headers.set("anthropic-version", headers.get("anthropic-version") ?? "2023-06-01")
+        if (body.model.startsWith("claude-sonnet-")) {
+          headers.set("anthropic-beta", "context-1m-2025-08-07")
+        }
+      }
+    },
+    modifyBody: (body: Record<string, any>) => ({
       ...body,
       ...body,
-      service_tier: "standard_only",
-    }
-  },
-  streamSeparator: "\n\n",
-  createUsageParser: () => {
-    let usage: Usage
-
-    return {
-      parse: (chunk: string) => {
-        const data = chunk.split("\n")[1]
-        if (!data.startsWith("data: ")) return
-
-        let json
-        try {
-          json = JSON.parse(data.slice(6))
-        } catch (e) {
-          return
+      ...(isBedrock
+        ? {
+            anthropic_version: "bedrock-2023-05-31",
+            anthropic_beta: isSonnet ? "context-1m-2025-08-07" : undefined,
+            model: undefined,
+            stream: undefined,
+          }
+        : {
+            service_tier: "standard_only",
+          }),
+    }),
+    createBinaryStreamDecoder: () => {
+      if (!isBedrock) return undefined
+
+      const decoder = new TextDecoder()
+      const encoder = new TextEncoder()
+      const codec = new EventStreamCodec(toUtf8, fromUtf8)
+      let buffer = new Uint8Array(0)
+      return (value: Uint8Array) => {
+        const newBuffer = new Uint8Array(buffer.length + value.length)
+        newBuffer.set(buffer)
+        newBuffer.set(value, buffer.length)
+        buffer = newBuffer
+
+        const messages = []
+
+        while (buffer.length >= 4) {
+          // first 4 bytes are the total length (big-endian)
+          const totalLength = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength).getUint32(0, false)
+
+          // wait for more chunks
+          if (buffer.length < totalLength) break
+
+          try {
+            const subView = buffer.subarray(0, totalLength)
+            const decoded = codec.decode(subView)
+            buffer = buffer.slice(totalLength)
+
+            /* Example of Bedrock data
+      ```
+        {
+          bytes: 'eyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtb3B1cy00LTUtMjAyNTExMDEiLCJpZCI6Im1zZ19iZHJrXzAxMjVGdHRGb2lkNGlwWmZ4SzZMbktxeCIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwidXNhZ2UiOnsiaW5wdXRfdG9rZW5zIjo0LCJjYWNoZV9jcmVhdGlvbl9pbnB1dF90b2tlbnMiOjEsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjoxMTk2MywiY2FjaGVfY3JlYXRpb24iOnsiZXBoZW1lcmFsXzVtX2lucHV0X3Rva2VucyI6MSwiZXBoZW1lcmFsXzFoX2lucHV0X3Rva2VucyI6MH0sIm91dHB1dF90b2tlbnMiOjF9fX0=',
+          p: '...'
         }
         }
-
-        const usageUpdate = json.usage ?? json.message?.usage
-        if (!usageUpdate) return
-        usage = {
-          ...usage,
-          ...usageUpdate,
-          cache_creation: {
-            ...usage?.cache_creation,
-            ...usageUpdate.cache_creation,
-          },
-          server_tool_use: {
-            ...usage?.server_tool_use,
-            ...usageUpdate.server_tool_use,
-          },
+      ```
+
+      Decoded bytes
+      ```
+        {
+          type: 'message_start',
+          message: {
+            model: 'claude-opus-4-5-20251101',
+            id: 'msg_bdrk_0125FttFoid4ipZfxK6LnKqx',
+            type: 'message',
+            role: 'assistant',
+            content: [],
+            stop_reason: null,
+            stop_sequence: null,
+            usage: {
+              input_tokens: 4,
+              cache_creation_input_tokens: 1,
+              cache_read_input_tokens: 11963,
+              cache_creation: [Object],
+              output_tokens: 1
+            }
+          }
         }
         }
-      },
-      retrieve: () => usage,
-    }
-  },
-  normalizeUsage: (usage: Usage) => ({
-    inputTokens: usage.input_tokens ?? 0,
-    outputTokens: usage.output_tokens ?? 0,
-    reasoningTokens: undefined,
-    cacheReadTokens: usage.cache_read_input_tokens ?? undefined,
-    cacheWrite5mTokens: usage.cache_creation?.ephemeral_5m_input_tokens ?? undefined,
-    cacheWrite1hTokens: usage.cache_creation?.ephemeral_1h_input_tokens ?? undefined,
-  }),
-} satisfies ProviderHelper
+      ```
+      */
+
+            /* Example of Anthropic data
+      ```
+        event: message_delta
+        data: {"type":"message_start","message":{"model":"claude-opus-4-5-20251101","id":"msg_01ETvwVWSKULxzPdkQ1xAnk2","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":11543,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":11543,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}}
+      ```
+      */
+            if (decoded.headers[":message-type"]?.value === "event") {
+              const data = decoder.decode(decoded.body, { stream: true })
+
+              const parsedDataResult = JSON.parse(data)
+              delete parsedDataResult.p
+              const bytes = atob(parsedDataResult.bytes)
+              const eventName = JSON.parse(bytes).type
+              messages.push([`event: ${eventName}`, "\n", `data: ${bytes}`, "\n\n"].join(""))
+            }
+          } catch (e) {
+            console.log("@@@EE@@@")
+            console.log(e)
+            break
+          }
+        }
+        return encoder.encode(messages.join(""))
+      }
+    },
+    streamSeparator: "\n\n",
+    createUsageParser: () => {
+      let usage: Usage
+
+      return {
+        parse: (chunk: string) => {
+          const data = chunk.split("\n")[1]
+          if (!data.startsWith("data: ")) return
+
+          let json
+          try {
+            json = JSON.parse(data.slice(6))
+          } catch (e) {
+            return
+          }
+
+          const usageUpdate = json.usage ?? json.message?.usage
+          if (!usageUpdate) return
+          usage = {
+            ...usage,
+            ...usageUpdate,
+            cache_creation: {
+              ...usage?.cache_creation,
+              ...usageUpdate.cache_creation,
+            },
+            server_tool_use: {
+              ...usage?.server_tool_use,
+              ...usageUpdate.server_tool_use,
+            },
+          }
+        },
+        retrieve: () => usage,
+      }
+    },
+    normalizeUsage: (usage: Usage) => ({
+      inputTokens: usage.input_tokens ?? 0,
+      outputTokens: usage.output_tokens ?? 0,
+      reasoningTokens: undefined,
+      cacheReadTokens: usage.cache_read_input_tokens ?? undefined,
+      cacheWrite5mTokens: usage.cache_creation?.ephemeral_5m_input_tokens ?? undefined,
+      cacheWrite1hTokens: usage.cache_creation?.ephemeral_1h_input_tokens ?? undefined,
+    }),
+  }
+}
 
 
 export function fromAnthropicRequest(body: any): CommonRequest {
 export function fromAnthropicRequest(body: any): CommonRequest {
   if (!body || typeof body !== "object") return body
   if (!body || typeof body !== "object") return body

+ 5 - 4
packages/console/app/src/routes/zen/util/provider/google.ts

@@ -26,16 +26,17 @@ type Usage = {
   thoughtsTokenCount?: number
   thoughtsTokenCount?: number
 }
 }
 
 
-export const googleHelper = {
+export const googleHelper: ProviderHelper = ({ providerModel }) => ({
   format: "google",
   format: "google",
-  modifyUrl: (providerApi: string, model?: string, isStream?: boolean) =>
-    `${providerApi}/models/${model}:${isStream ? "streamGenerateContent?alt=sse" : "generateContent"}`,
+  modifyUrl: (providerApi: string, isStream?: boolean) =>
+    `${providerApi}/models/${providerModel}:${isStream ? "streamGenerateContent?alt=sse" : "generateContent"}`,
   modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
   modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
     headers.set("x-goog-api-key", apiKey)
     headers.set("x-goog-api-key", apiKey)
   },
   },
   modifyBody: (body: Record<string, any>) => {
   modifyBody: (body: Record<string, any>) => {
     return body
     return body
   },
   },
+  createBinaryStreamDecoder: () => undefined,
   streamSeparator: "\r\n\r\n",
   streamSeparator: "\r\n\r\n",
   createUsageParser: () => {
   createUsageParser: () => {
     let usage: Usage
     let usage: Usage
@@ -71,4 +72,4 @@ export const googleHelper = {
       cacheWrite1hTokens: undefined,
       cacheWrite1hTokens: undefined,
     }
     }
   },
   },
-} satisfies ProviderHelper
+})

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

@@ -21,7 +21,7 @@ type Usage = {
   }
   }
 }
 }
 
 
-export const oaCompatHelper = {
+export const oaCompatHelper: ProviderHelper = () => ({
   format: "oa-compat",
   format: "oa-compat",
   modifyUrl: (providerApi: string) => providerApi + "/chat/completions",
   modifyUrl: (providerApi: string) => providerApi + "/chat/completions",
   modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
   modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
@@ -33,6 +33,7 @@ export const oaCompatHelper = {
       ...(body.stream ? { stream_options: { include_usage: true } } : {}),
       ...(body.stream ? { stream_options: { include_usage: true } } : {}),
     }
     }
   },
   },
+  createBinaryStreamDecoder: () => undefined,
   streamSeparator: "\n\n",
   streamSeparator: "\n\n",
   createUsageParser: () => {
   createUsageParser: () => {
     let usage: Usage
     let usage: Usage
@@ -68,7 +69,7 @@ export const oaCompatHelper = {
       cacheWrite1hTokens: undefined,
       cacheWrite1hTokens: undefined,
     }
     }
   },
   },
-} satisfies ProviderHelper
+})
 
 
 export function fromOaCompatibleRequest(body: any): CommonRequest {
 export function fromOaCompatibleRequest(body: any): CommonRequest {
   if (!body || typeof body !== "object") return body
   if (!body || typeof body !== "object") return body

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

@@ -12,7 +12,7 @@ type Usage = {
   total_tokens?: number
   total_tokens?: number
 }
 }
 
 
-export const openaiHelper = {
+export const openaiHelper: ProviderHelper = () => ({
   format: "openai",
   format: "openai",
   modifyUrl: (providerApi: string) => providerApi + "/responses",
   modifyUrl: (providerApi: string) => providerApi + "/responses",
   modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
   modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
@@ -21,6 +21,7 @@ export const openaiHelper = {
   modifyBody: (body: Record<string, any>) => {
   modifyBody: (body: Record<string, any>) => {
     return body
     return body
   },
   },
+  createBinaryStreamDecoder: () => undefined,
   streamSeparator: "\n\n",
   streamSeparator: "\n\n",
   createUsageParser: () => {
   createUsageParser: () => {
     let usage: Usage
     let usage: Usage
@@ -58,7 +59,7 @@ export const openaiHelper = {
       cacheWrite1hTokens: undefined,
       cacheWrite1hTokens: undefined,
     }
     }
   },
   },
-} satisfies ProviderHelper
+})
 
 
 export function fromOpenaiRequest(body: any): CommonRequest {
 export function fromOpenaiRequest(body: any): CommonRequest {
   if (!body || typeof body !== "object") return body
   if (!body || typeof body !== "object") return body

+ 3 - 2
packages/console/app/src/routes/zen/util/provider/provider.ts

@@ -33,11 +33,12 @@ export type UsageInfo = {
   cacheWrite1hTokens?: number
   cacheWrite1hTokens?: number
 }
 }
 
 
-export type ProviderHelper = {
+export type ProviderHelper = (input: { reqModel: string; providerModel: string }) => {
   format: ZenData.Format
   format: ZenData.Format
-  modifyUrl: (providerApi: string, model?: string, isStream?: boolean) => string
+  modifyUrl: (providerApi: string, isStream?: boolean) => string
   modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => void
   modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => void
   modifyBody: (body: Record<string, any>) => Record<string, any>
   modifyBody: (body: Record<string, any>) => Record<string, any>
+  createBinaryStreamDecoder: () => ((chunk: Uint8Array) => Uint8Array | undefined) | undefined
   streamSeparator: string
   streamSeparator: string
   createUsageParser: () => {
   createUsageParser: () => {
     parse: (chunk: string) => void
     parse: (chunk: string) => void

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

@@ -1,6 +1,6 @@
 import { Resource } from "@opencode-ai/console-resource"
 import { Resource } from "@opencode-ai/console-resource"
 
 
-export function createStickyTracker(stickyProvider: boolean, session: string) {
+export function createStickyTracker(stickyProvider: "strict" | "prefer" | undefined, session: string) {
   if (!stickyProvider) return
   if (!stickyProvider) return
   if (!session) return
   if (!session) return
   const key = `sticky:${session}`
   const key = `sticky:${session}`

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

@@ -12,7 +12,7 @@
     "allowJs": true,
     "allowJs": true,
     "strict": true,
     "strict": true,
     "noEmit": true,
     "noEmit": true,
-    "types": ["vite/client"],
+    "types": ["vite/client", "@webgpu/types"],
     "isolatedModules": true,
     "isolatedModules": true,
     "paths": {
     "paths": {
       "~/*": ["./src/*"]
       "~/*": ["./src/*"]

+ 1 - 0
packages/console/core/migrations/0051_jazzy_green_goblin.sql

@@ -0,0 +1 @@
+ALTER TABLE `billing` ADD `time_subscription_booked` timestamp(3);

+ 1 - 0
packages/console/core/migrations/0052_aromatic_agent_zero.sql

@@ -0,0 +1 @@
+ALTER TABLE `billing` ADD `subscription_plan` enum('20','100','200');

+ 1228 - 0
packages/console/core/migrations/meta/0051_snapshot.json

@@ -0,0 +1,1228 @@
+{
+  "version": "5",
+  "dialect": "mysql",
+  "id": "14cbf4c8-55f1-4488-956f-56fb5ccb3a5a",
+  "prevId": "a0d18802-c390-47d4-98f1-d1f83869cf0c",
+  "tables": {
+    "account": {
+      "name": "account",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {},
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "account_id_pk": {
+          "name": "account_id_pk",
+          "columns": ["id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "auth": {
+      "name": "auth",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "provider": {
+          "name": "provider",
+          "type": "enum('email','github','google')",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "subject": {
+          "name": "subject",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "account_id": {
+          "name": "account_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "provider": {
+          "name": "provider",
+          "columns": ["provider", "subject"],
+          "isUnique": true
+        },
+        "account_id": {
+          "name": "account_id",
+          "columns": ["account_id"],
+          "isUnique": false
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "auth_id_pk": {
+          "name": "auth_id_pk",
+          "columns": ["id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "benchmark": {
+      "name": "benchmark",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "model": {
+          "name": "model",
+          "type": "varchar(64)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "agent": {
+          "name": "agent",
+          "type": "varchar(64)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "result": {
+          "name": "result",
+          "type": "mediumtext",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "time_created": {
+          "name": "time_created",
+          "columns": ["time_created"],
+          "isUnique": false
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "benchmark_id_pk": {
+          "name": "benchmark_id_pk",
+          "columns": ["id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "billing": {
+      "name": "billing",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "customer_id": {
+          "name": "customer_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "payment_method_id": {
+          "name": "payment_method_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "payment_method_type": {
+          "name": "payment_method_type",
+          "type": "varchar(32)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "payment_method_last4": {
+          "name": "payment_method_last4",
+          "type": "varchar(4)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "balance": {
+          "name": "balance",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "monthly_limit": {
+          "name": "monthly_limit",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "monthly_usage": {
+          "name": "monthly_usage",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_monthly_usage_updated": {
+          "name": "time_monthly_usage_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "reload": {
+          "name": "reload",
+          "type": "boolean",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "reload_trigger": {
+          "name": "reload_trigger",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "reload_amount": {
+          "name": "reload_amount",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "reload_error": {
+          "name": "reload_error",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_reload_error": {
+          "name": "time_reload_error",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_reload_locked_till": {
+          "name": "time_reload_locked_till",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "subscription_id": {
+          "name": "subscription_id",
+          "type": "varchar(28)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "subscription_coupon_id": {
+          "name": "subscription_coupon_id",
+          "type": "varchar(28)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_subscription_booked": {
+          "name": "time_subscription_booked",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "global_customer_id": {
+          "name": "global_customer_id",
+          "columns": ["customer_id"],
+          "isUnique": true
+        },
+        "global_subscription_id": {
+          "name": "global_subscription_id",
+          "columns": ["subscription_id"],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "billing_workspace_id_id_pk": {
+          "name": "billing_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "payment": {
+      "name": "payment",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "customer_id": {
+          "name": "customer_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "invoice_id": {
+          "name": "invoice_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "payment_id": {
+          "name": "payment_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "amount": {
+          "name": "amount",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_refunded": {
+          "name": "time_refunded",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "enrichment": {
+          "name": "enrichment",
+          "type": "json",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {},
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "payment_workspace_id_id_pk": {
+          "name": "payment_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "subscription": {
+      "name": "subscription",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "user_id": {
+          "name": "user_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "rolling_usage": {
+          "name": "rolling_usage",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "fixed_usage": {
+          "name": "fixed_usage",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_rolling_updated": {
+          "name": "time_rolling_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_fixed_updated": {
+          "name": "time_fixed_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "workspace_user_id": {
+          "name": "workspace_user_id",
+          "columns": ["workspace_id", "user_id"],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "subscription_workspace_id_id_pk": {
+          "name": "subscription_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "usage": {
+      "name": "usage",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "model": {
+          "name": "model",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "provider": {
+          "name": "provider",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "input_tokens": {
+          "name": "input_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "output_tokens": {
+          "name": "output_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "reasoning_tokens": {
+          "name": "reasoning_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "cache_read_tokens": {
+          "name": "cache_read_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "cache_write_5m_tokens": {
+          "name": "cache_write_5m_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "cache_write_1h_tokens": {
+          "name": "cache_write_1h_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "cost": {
+          "name": "cost",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "key_id": {
+          "name": "key_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "enrichment": {
+          "name": "enrichment",
+          "type": "json",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "usage_time_created": {
+          "name": "usage_time_created",
+          "columns": ["workspace_id", "time_created"],
+          "isUnique": false
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "usage_workspace_id_id_pk": {
+          "name": "usage_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "ip_rate_limit": {
+      "name": "ip_rate_limit",
+      "columns": {
+        "ip": {
+          "name": "ip",
+          "type": "varchar(45)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "interval": {
+          "name": "interval",
+          "type": "varchar(10)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "count": {
+          "name": "count",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {},
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "ip_rate_limit_ip_interval_pk": {
+          "name": "ip_rate_limit_ip_interval_pk",
+          "columns": ["ip", "interval"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "ip": {
+      "name": "ip",
+      "columns": {
+        "ip": {
+          "name": "ip",
+          "type": "varchar(45)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "usage": {
+          "name": "usage",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {},
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "ip_ip_pk": {
+          "name": "ip_ip_pk",
+          "columns": ["ip"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "key": {
+      "name": "key",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "name": {
+          "name": "name",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "key": {
+          "name": "key",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "user_id": {
+          "name": "user_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_used": {
+          "name": "time_used",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "global_key": {
+          "name": "global_key",
+          "columns": ["key"],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "key_workspace_id_id_pk": {
+          "name": "key_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "model": {
+      "name": "model",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "model": {
+          "name": "model",
+          "type": "varchar(64)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "model_workspace_model": {
+          "name": "model_workspace_model",
+          "columns": ["workspace_id", "model"],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "model_workspace_id_id_pk": {
+          "name": "model_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "provider": {
+      "name": "provider",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "provider": {
+          "name": "provider",
+          "type": "varchar(64)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "credentials": {
+          "name": "credentials",
+          "type": "text",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "workspace_provider": {
+          "name": "workspace_provider",
+          "columns": ["workspace_id", "provider"],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "provider_workspace_id_id_pk": {
+          "name": "provider_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "user": {
+      "name": "user",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "account_id": {
+          "name": "account_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "email": {
+          "name": "email",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "name": {
+          "name": "name",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_seen": {
+          "name": "time_seen",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "color": {
+          "name": "color",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "role": {
+          "name": "role",
+          "type": "enum('admin','member')",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "monthly_limit": {
+          "name": "monthly_limit",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "monthly_usage": {
+          "name": "monthly_usage",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_monthly_usage_updated": {
+          "name": "time_monthly_usage_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "user_account_id": {
+          "name": "user_account_id",
+          "columns": ["workspace_id", "account_id"],
+          "isUnique": true
+        },
+        "user_email": {
+          "name": "user_email",
+          "columns": ["workspace_id", "email"],
+          "isUnique": true
+        },
+        "global_account_id": {
+          "name": "global_account_id",
+          "columns": ["account_id"],
+          "isUnique": false
+        },
+        "global_email": {
+          "name": "global_email",
+          "columns": ["email"],
+          "isUnique": false
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "user_workspace_id_id_pk": {
+          "name": "user_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "workspace": {
+      "name": "workspace",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "slug": {
+          "name": "slug",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "name": {
+          "name": "name",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "slug": {
+          "name": "slug",
+          "columns": ["slug"],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "workspace_id": {
+          "name": "workspace_id",
+          "columns": ["id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    }
+  },
+  "views": {},
+  "_meta": {
+    "schemas": {},
+    "tables": {},
+    "columns": {}
+  },
+  "internal": {
+    "tables": {},
+    "indexes": {}
+  }
+}

+ 1235 - 0
packages/console/core/migrations/meta/0052_snapshot.json

@@ -0,0 +1,1235 @@
+{
+  "version": "5",
+  "dialect": "mysql",
+  "id": "00774acd-a1e5-49c0-b296-cacc9506a566",
+  "prevId": "14cbf4c8-55f1-4488-956f-56fb5ccb3a5a",
+  "tables": {
+    "account": {
+      "name": "account",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {},
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "account_id_pk": {
+          "name": "account_id_pk",
+          "columns": ["id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "auth": {
+      "name": "auth",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "provider": {
+          "name": "provider",
+          "type": "enum('email','github','google')",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "subject": {
+          "name": "subject",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "account_id": {
+          "name": "account_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "provider": {
+          "name": "provider",
+          "columns": ["provider", "subject"],
+          "isUnique": true
+        },
+        "account_id": {
+          "name": "account_id",
+          "columns": ["account_id"],
+          "isUnique": false
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "auth_id_pk": {
+          "name": "auth_id_pk",
+          "columns": ["id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "benchmark": {
+      "name": "benchmark",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "model": {
+          "name": "model",
+          "type": "varchar(64)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "agent": {
+          "name": "agent",
+          "type": "varchar(64)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "result": {
+          "name": "result",
+          "type": "mediumtext",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "time_created": {
+          "name": "time_created",
+          "columns": ["time_created"],
+          "isUnique": false
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "benchmark_id_pk": {
+          "name": "benchmark_id_pk",
+          "columns": ["id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "billing": {
+      "name": "billing",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "customer_id": {
+          "name": "customer_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "payment_method_id": {
+          "name": "payment_method_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "payment_method_type": {
+          "name": "payment_method_type",
+          "type": "varchar(32)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "payment_method_last4": {
+          "name": "payment_method_last4",
+          "type": "varchar(4)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "balance": {
+          "name": "balance",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "monthly_limit": {
+          "name": "monthly_limit",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "monthly_usage": {
+          "name": "monthly_usage",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_monthly_usage_updated": {
+          "name": "time_monthly_usage_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "reload": {
+          "name": "reload",
+          "type": "boolean",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "reload_trigger": {
+          "name": "reload_trigger",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "reload_amount": {
+          "name": "reload_amount",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "reload_error": {
+          "name": "reload_error",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_reload_error": {
+          "name": "time_reload_error",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_reload_locked_till": {
+          "name": "time_reload_locked_till",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "subscription_id": {
+          "name": "subscription_id",
+          "type": "varchar(28)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "subscription_coupon_id": {
+          "name": "subscription_coupon_id",
+          "type": "varchar(28)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "subscription_plan": {
+          "name": "subscription_plan",
+          "type": "enum('20','100','200')",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_subscription_booked": {
+          "name": "time_subscription_booked",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "global_customer_id": {
+          "name": "global_customer_id",
+          "columns": ["customer_id"],
+          "isUnique": true
+        },
+        "global_subscription_id": {
+          "name": "global_subscription_id",
+          "columns": ["subscription_id"],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "billing_workspace_id_id_pk": {
+          "name": "billing_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "payment": {
+      "name": "payment",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "customer_id": {
+          "name": "customer_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "invoice_id": {
+          "name": "invoice_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "payment_id": {
+          "name": "payment_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "amount": {
+          "name": "amount",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_refunded": {
+          "name": "time_refunded",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "enrichment": {
+          "name": "enrichment",
+          "type": "json",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {},
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "payment_workspace_id_id_pk": {
+          "name": "payment_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "subscription": {
+      "name": "subscription",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "user_id": {
+          "name": "user_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "rolling_usage": {
+          "name": "rolling_usage",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "fixed_usage": {
+          "name": "fixed_usage",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_rolling_updated": {
+          "name": "time_rolling_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_fixed_updated": {
+          "name": "time_fixed_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "workspace_user_id": {
+          "name": "workspace_user_id",
+          "columns": ["workspace_id", "user_id"],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "subscription_workspace_id_id_pk": {
+          "name": "subscription_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "usage": {
+      "name": "usage",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "model": {
+          "name": "model",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "provider": {
+          "name": "provider",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "input_tokens": {
+          "name": "input_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "output_tokens": {
+          "name": "output_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "reasoning_tokens": {
+          "name": "reasoning_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "cache_read_tokens": {
+          "name": "cache_read_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "cache_write_5m_tokens": {
+          "name": "cache_write_5m_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "cache_write_1h_tokens": {
+          "name": "cache_write_1h_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "cost": {
+          "name": "cost",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "key_id": {
+          "name": "key_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "enrichment": {
+          "name": "enrichment",
+          "type": "json",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "usage_time_created": {
+          "name": "usage_time_created",
+          "columns": ["workspace_id", "time_created"],
+          "isUnique": false
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "usage_workspace_id_id_pk": {
+          "name": "usage_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "ip_rate_limit": {
+      "name": "ip_rate_limit",
+      "columns": {
+        "ip": {
+          "name": "ip",
+          "type": "varchar(45)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "interval": {
+          "name": "interval",
+          "type": "varchar(10)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "count": {
+          "name": "count",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {},
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "ip_rate_limit_ip_interval_pk": {
+          "name": "ip_rate_limit_ip_interval_pk",
+          "columns": ["ip", "interval"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "ip": {
+      "name": "ip",
+      "columns": {
+        "ip": {
+          "name": "ip",
+          "type": "varchar(45)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "usage": {
+          "name": "usage",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {},
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "ip_ip_pk": {
+          "name": "ip_ip_pk",
+          "columns": ["ip"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "key": {
+      "name": "key",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "name": {
+          "name": "name",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "key": {
+          "name": "key",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "user_id": {
+          "name": "user_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_used": {
+          "name": "time_used",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "global_key": {
+          "name": "global_key",
+          "columns": ["key"],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "key_workspace_id_id_pk": {
+          "name": "key_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "model": {
+      "name": "model",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "model": {
+          "name": "model",
+          "type": "varchar(64)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "model_workspace_model": {
+          "name": "model_workspace_model",
+          "columns": ["workspace_id", "model"],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "model_workspace_id_id_pk": {
+          "name": "model_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "provider": {
+      "name": "provider",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "provider": {
+          "name": "provider",
+          "type": "varchar(64)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "credentials": {
+          "name": "credentials",
+          "type": "text",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "workspace_provider": {
+          "name": "workspace_provider",
+          "columns": ["workspace_id", "provider"],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "provider_workspace_id_id_pk": {
+          "name": "provider_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "user": {
+      "name": "user",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "account_id": {
+          "name": "account_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "email": {
+          "name": "email",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "name": {
+          "name": "name",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_seen": {
+          "name": "time_seen",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "color": {
+          "name": "color",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "role": {
+          "name": "role",
+          "type": "enum('admin','member')",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "monthly_limit": {
+          "name": "monthly_limit",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "monthly_usage": {
+          "name": "monthly_usage",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_monthly_usage_updated": {
+          "name": "time_monthly_usage_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "user_account_id": {
+          "name": "user_account_id",
+          "columns": ["workspace_id", "account_id"],
+          "isUnique": true
+        },
+        "user_email": {
+          "name": "user_email",
+          "columns": ["workspace_id", "email"],
+          "isUnique": true
+        },
+        "global_account_id": {
+          "name": "global_account_id",
+          "columns": ["account_id"],
+          "isUnique": false
+        },
+        "global_email": {
+          "name": "global_email",
+          "columns": ["email"],
+          "isUnique": false
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "user_workspace_id_id_pk": {
+          "name": "user_workspace_id_id_pk",
+          "columns": ["workspace_id", "id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "workspace": {
+      "name": "workspace",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "slug": {
+          "name": "slug",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "name": {
+          "name": "name",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "slug": {
+          "name": "slug",
+          "columns": ["slug"],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "workspace_id": {
+          "name": "workspace_id",
+          "columns": ["id"]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    }
+  },
+  "views": {},
+  "_meta": {
+    "schemas": {},
+    "tables": {},
+    "columns": {}
+  },
+  "internal": {
+    "tables": {},
+    "indexes": {}
+  }
+}

+ 14 - 0
packages/console/core/migrations/meta/_journal.json

@@ -358,6 +358,20 @@
       "when": 1767931290031,
       "when": 1767931290031,
       "tag": "0050_bumpy_mephistopheles",
       "tag": "0050_bumpy_mephistopheles",
       "breakpoints": true
       "breakpoints": true
+    },
+    {
+      "idx": 51,
+      "version": "5",
+      "when": 1768341152722,
+      "tag": "0051_jazzy_green_goblin",
+      "breakpoints": true
+    },
+    {
+      "idx": 52,
+      "version": "5",
+      "when": 1768343920467,
+      "tag": "0052_aromatic_agent_zero",
+      "breakpoints": true
     }
     }
   ]
   ]
 }
 }

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

@@ -1,7 +1,7 @@
 {
 {
   "$schema": "https://json.schemastore.org/package.json",
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/console-core",
   "name": "@opencode-ai/console-core",
-  "version": "1.1.8",
+  "version": "1.1.24",
   "private": true,
   "private": true,
   "type": "module",
   "type": "module",
   "license": "MIT",
   "license": "MIT",
@@ -32,6 +32,7 @@
     "promote-models-to-dev": "script/promote-models.ts dev",
     "promote-models-to-dev": "script/promote-models.ts dev",
     "promote-models-to-prod": "script/promote-models.ts production",
     "promote-models-to-prod": "script/promote-models.ts production",
     "pull-models-from-dev": "script/pull-models.ts dev",
     "pull-models-from-dev": "script/pull-models.ts dev",
+    "pull-models-from-prod": "script/pull-models.ts production",
     "update-black": "script/update-black.ts",
     "update-black": "script/update-black.ts",
     "promote-black-to-dev": "script/promote-black.ts dev",
     "promote-black-to-dev": "script/promote-black.ts dev",
     "promote-black-to-prod": "script/promote-black.ts production",
     "promote-black-to-prod": "script/promote-black.ts production",

+ 41 - 0
packages/console/core/script/black-cancel-waitlist.ts

@@ -0,0 +1,41 @@
+import { subscribe } from "diagnostics_channel"
+import { Billing } from "../src/billing.js"
+import { and, Database, eq } from "../src/drizzle/index.js"
+import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js"
+
+const workspaceID = process.argv[2]
+
+if (!workspaceID) {
+  console.error("Usage: bun script/foo.ts <workspaceID>")
+  process.exit(1)
+}
+
+console.log(`Removing from Black waitlist`)
+
+const billing = await Database.use((tx) =>
+  tx
+    .select({
+      subscriptionPlan: BillingTable.subscriptionPlan,
+      timeSubscriptionBooked: BillingTable.timeSubscriptionBooked,
+    })
+    .from(BillingTable)
+    .where(eq(BillingTable.workspaceID, workspaceID))
+    .then((rows) => rows[0]),
+)
+
+if (!billing?.timeSubscriptionBooked) {
+  console.error(`Error: Workspace is not on the waitlist`)
+  process.exit(1)
+}
+
+await Database.use((tx) =>
+  tx
+    .update(BillingTable)
+    .set({
+      subscriptionPlan: null,
+      timeSubscriptionBooked: null,
+    })
+    .where(eq(BillingTable.workspaceID, workspaceID)),
+)
+
+console.log(`Done`)

+ 163 - 0
packages/console/core/script/black-transfer.ts

@@ -0,0 +1,163 @@
+import { Billing } from "../src/billing.js"
+import { and, Database, desc, eq, isNotNull, lt, sql } from "../src/drizzle/index.js"
+import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js"
+
+const fromWrkID = process.argv[2]
+const toWrkID = process.argv[3]
+
+if (!fromWrkID || !toWrkID) {
+  console.error("Usage: bun foo.ts <fromWrkID> <toWrkID>")
+  process.exit(1)
+}
+
+console.log(`Transferring subscription from ${fromWrkID} to ${toWrkID}`)
+
+// Look up the FROM workspace billing
+const fromBilling = await Database.use((tx) =>
+  tx
+    .select({
+      customerID: BillingTable.customerID,
+      subscriptionID: BillingTable.subscriptionID,
+      subscriptionCouponID: BillingTable.subscriptionCouponID,
+      paymentMethodID: BillingTable.paymentMethodID,
+      paymentMethodType: BillingTable.paymentMethodType,
+      paymentMethodLast4: BillingTable.paymentMethodLast4,
+    })
+    .from(BillingTable)
+    .where(eq(BillingTable.workspaceID, fromWrkID))
+    .then((rows) => rows[0]),
+)
+if (!fromBilling) throw new Error(`Error: FROM workspace has no billing record`)
+if (!fromBilling.customerID) throw new Error(`Error: FROM workspace has no Stripe customer ID`)
+if (!fromBilling.subscriptionID) throw new Error(`Error: FROM workspace has no subscription`)
+
+const fromSubscription = await Database.use((tx) =>
+  tx
+    .select({ userID: SubscriptionTable.userID })
+    .from(SubscriptionTable)
+    .where(eq(SubscriptionTable.workspaceID, fromWrkID))
+    .then((rows) => rows[0]),
+)
+if (!fromSubscription) throw new Error(`Error: FROM workspace has no subscription`)
+
+// Look up the previous customer ID in FROM workspace
+const subscriptionPayment = await Database.use((tx) =>
+  tx
+    .select({
+      customerID: PaymentTable.customerID,
+      timeCreated: PaymentTable.timeCreated,
+    })
+    .from(PaymentTable)
+    .where(and(eq(PaymentTable.workspaceID, fromWrkID), sql`JSON_EXTRACT(enrichment, '$.type') = 'subscription'`))
+    .then((rows) => {
+      if (rows.length > 1) {
+        console.error(`Error: Multiple subscription payments found for workspace ${fromWrkID}`)
+        process.exit(1)
+      }
+      return rows[0]
+    }),
+)
+const fromPrevPayment = await Database.use((tx) =>
+  tx
+    .select({ customerID: PaymentTable.customerID })
+    .from(PaymentTable)
+    .where(
+      and(
+        eq(PaymentTable.workspaceID, fromWrkID),
+        isNotNull(PaymentTable.customerID),
+        lt(PaymentTable.timeCreated, subscriptionPayment.timeCreated),
+      ),
+    )
+    .orderBy(desc(PaymentTable.timeCreated))
+    .limit(1)
+    .then((rows) => rows[0]),
+)
+if (!fromPrevPayment?.customerID) throw new Error(`Error: FROM workspace has no previous Stripe customer to revert to`)
+if (fromPrevPayment.customerID === fromBilling.customerID)
+  throw new Error(`Error: FROM workspace has the same Stripe customer ID as the current one`)
+
+const fromPrevPaymentMethods = await Billing.stripe().customers.listPaymentMethods(fromPrevPayment.customerID, {})
+if (fromPrevPaymentMethods.data.length === 0)
+  throw new Error(`Error: FROM workspace has no previous Stripe payment methods`)
+
+// Look up the TO workspace billing
+const toBilling = await Database.use((tx) =>
+  tx
+    .select({
+      customerID: BillingTable.customerID,
+      subscriptionID: BillingTable.subscriptionID,
+    })
+    .from(BillingTable)
+    .where(eq(BillingTable.workspaceID, toWrkID))
+    .then((rows) => rows[0]),
+)
+if (!toBilling) throw new Error(`Error: TO workspace has no billing record`)
+if (toBilling.subscriptionID) throw new Error(`Error: TO workspace already has a subscription`)
+
+console.log(`FROM:`)
+console.log(`  Old Customer ID: ${fromBilling.customerID}`)
+console.log(`  New Customer ID: ${fromPrevPayment.customerID}`)
+console.log(`TO:`)
+console.log(`  Old Customer ID: ${toBilling.customerID}`)
+console.log(`  New Customer ID: ${fromBilling.customerID}`)
+
+// Clear workspaceID from Stripe customer metadata
+await Billing.stripe().customers.update(fromPrevPayment.customerID, {
+  metadata: {
+    workspaceID: fromWrkID,
+  },
+})
+await Billing.stripe().customers.update(fromBilling.customerID, {
+  metadata: {
+    workspaceID: toWrkID,
+  },
+})
+
+await Database.transaction(async (tx) => {
+  await tx
+    .update(BillingTable)
+    .set({
+      customerID: fromPrevPayment.customerID,
+      subscriptionID: null,
+      subscriptionCouponID: null,
+      paymentMethodID: fromPrevPaymentMethods.data[0].id,
+      paymentMethodLast4: fromPrevPaymentMethods.data[0].card?.last4 ?? null,
+      paymentMethodType: fromPrevPaymentMethods.data[0].type,
+    })
+    .where(eq(BillingTable.workspaceID, fromWrkID))
+
+  await tx
+    .update(BillingTable)
+    .set({
+      customerID: fromBilling.customerID,
+      subscriptionID: fromBilling.subscriptionID,
+      subscriptionCouponID: fromBilling.subscriptionCouponID,
+      paymentMethodID: fromBilling.paymentMethodID,
+      paymentMethodLast4: fromBilling.paymentMethodLast4,
+      paymentMethodType: fromBilling.paymentMethodType,
+    })
+    .where(eq(BillingTable.workspaceID, toWrkID))
+
+  await tx
+    .update(SubscriptionTable)
+    .set({
+      workspaceID: toWrkID,
+      userID: fromSubscription.userID,
+    })
+    .where(eq(SubscriptionTable.workspaceID, fromWrkID))
+
+  await tx
+    .update(PaymentTable)
+    .set({
+      workspaceID: toWrkID,
+    })
+    .where(
+      and(
+        eq(PaymentTable.workspaceID, fromWrkID),
+        sql`JSON_EXTRACT(enrichment, '$.type') = 'subscription'`,
+        eq(PaymentTable.amount, 20000000000),
+      ),
+    )
+})
+
+console.log(`done`)

+ 15 - 0
packages/console/core/script/credit-workspace.ts

@@ -1,4 +1,6 @@
 import { Billing } from "../src/billing.js"
 import { Billing } from "../src/billing.js"
+import { Database, eq } from "../src/drizzle/index.js"
+import { WorkspaceTable } from "../src/schema/workspace.sql.js"
 
 
 // get input from command line
 // get input from command line
 const workspaceID = process.argv[2]
 const workspaceID = process.argv[2]
@@ -9,6 +11,19 @@ if (!workspaceID || !dollarAmount) {
   process.exit(1)
   process.exit(1)
 }
 }
 
 
+// check workspace exists
+const workspace = await Database.use((tx) =>
+  tx
+    .select()
+    .from(WorkspaceTable)
+    .where(eq(WorkspaceTable.id, workspaceID))
+    .then((rows) => rows[0]),
+)
+if (!workspace) {
+  console.error("Error: Workspace not found")
+  process.exit(1)
+}
+
 const amountInDollars = parseFloat(dollarAmount)
 const amountInDollars = parseFloat(dollarAmount)
 if (isNaN(amountInDollars) || amountInDollars <= 0) {
 if (isNaN(amountInDollars) || amountInDollars <= 0) {
   console.error("Error: dollarAmount must be a positive number")
   console.error("Error: dollarAmount must be a positive number")

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

@@ -38,10 +38,21 @@ if (identifier.startsWith("wrk_")) {
         workspaceID: UserTable.workspaceID,
         workspaceID: UserTable.workspaceID,
         workspaceName: WorkspaceTable.name,
         workspaceName: WorkspaceTable.name,
         role: UserTable.role,
         role: UserTable.role,
+        subscribed: SubscriptionTable.timeCreated,
       })
       })
       .from(UserTable)
       .from(UserTable)
-      .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
-      .where(eq(UserTable.accountID, accountID)),
+      .rightJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
+      .leftJoin(SubscriptionTable, eq(SubscriptionTable.userID, UserTable.id))
+      .where(eq(UserTable.accountID, accountID))
+      .then((rows) =>
+        rows.map((row) => ({
+          userID: row.userID,
+          workspaceID: row.workspaceID,
+          workspaceName: row.workspaceName,
+          role: row.role,
+          subscribed: formatDate(row.subscribed),
+        })),
+      ),
   )
   )
 
 
   // Get all payments for these workspaces
   // Get all payments for these workspaces
@@ -102,6 +113,13 @@ async function printWorkspace(workspaceID: string) {
       .select({
       .select({
         balance: BillingTable.balance,
         balance: BillingTable.balance,
         customerID: BillingTable.customerID,
         customerID: BillingTable.customerID,
+        reload: BillingTable.reload,
+        subscription: {
+          id: BillingTable.subscriptionID,
+          couponID: BillingTable.subscriptionCouponID,
+          plan: BillingTable.subscriptionPlan,
+          booked: BillingTable.timeSubscriptionBooked,
+        },
       })
       })
       .from(BillingTable)
       .from(BillingTable)
       .where(eq(BillingTable.workspaceID, workspace.id))
       .where(eq(BillingTable.workspaceID, workspace.id))
@@ -110,6 +128,11 @@ async function printWorkspace(workspaceID: string) {
           rows.map((row) => ({
           rows.map((row) => ({
             ...row,
             ...row,
             balance: `$${(row.balance / 100000000).toFixed(2)}`,
             balance: `$${(row.balance / 100000000).toFixed(2)}`,
+            subscription: row.subscription.id
+              ? `Subscribed ${row.subscription.couponID ? `(coupon: ${row.subscription.couponID}) ` : ""}`
+              : row.subscription.booked
+                ? `Waitlist ${row.subscription.plan} plan`
+                : undefined,
           }))[0],
           }))[0],
       ),
       ),
   )
   )
@@ -120,6 +143,7 @@ async function printWorkspace(workspaceID: string) {
         amount: PaymentTable.amount,
         amount: PaymentTable.amount,
         paymentID: PaymentTable.paymentID,
         paymentID: PaymentTable.paymentID,
         invoiceID: PaymentTable.invoiceID,
         invoiceID: PaymentTable.invoiceID,
+        customerID: PaymentTable.customerID,
         timeCreated: PaymentTable.timeCreated,
         timeCreated: PaymentTable.timeCreated,
         timeRefunded: PaymentTable.timeRefunded,
         timeRefunded: PaymentTable.timeRefunded,
       })
       })
@@ -138,6 +162,7 @@ async function printWorkspace(workspaceID: string) {
       ),
       ),
   )
   )
 
 
+  /*
   await printTable("Usage", (tx) =>
   await printTable("Usage", (tx) =>
     tx
     tx
       .select({
       .select({
@@ -163,6 +188,7 @@ async function printWorkspace(workspaceID: string) {
         })),
         })),
       ),
       ),
   )
   )
+        */
 }
 }
 
 
 function formatMicroCents(value: number | null | undefined) {
 function formatMicroCents(value: number | null | undefined) {

+ 14 - 22
packages/console/core/script/promote-models.ts

@@ -8,33 +8,25 @@ const stage = process.argv[2]
 if (!stage) throw new Error("Stage is required")
 if (!stage) throw new Error("Stage is required")
 
 
 const root = path.resolve(process.cwd(), "..", "..", "..")
 const root = path.resolve(process.cwd(), "..", "..", "..")
+const PARTS = 8
 
 
 // read the secret
 // read the secret
 const ret = await $`bun sst secret list`.cwd(root).text()
 const ret = await $`bun sst secret list`.cwd(root).text()
 const lines = ret.split("\n")
 const lines = ret.split("\n")
-const value1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[1]
-const value2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1]
-const value3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1]
-const value4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1]
-const value5 = lines.find((line) => line.startsWith("ZEN_MODELS5"))?.split("=")[1]
-const value6 = lines.find((line) => line.startsWith("ZEN_MODELS6"))?.split("=")[1]
-const value7 = lines.find((line) => line.startsWith("ZEN_MODELS7"))?.split("=")[1]
-if (!value1) throw new Error("ZEN_MODELS1 not found")
-if (!value2) throw new Error("ZEN_MODELS2 not found")
-if (!value3) throw new Error("ZEN_MODELS3 not found")
-if (!value4) throw new Error("ZEN_MODELS4 not found")
-if (!value5) throw new Error("ZEN_MODELS5 not found")
-if (!value6) throw new Error("ZEN_MODELS6 not found")
-if (!value7) throw new Error("ZEN_MODELS7 not found")
+const values = Array.from({ length: PARTS }, (_, i) => {
+  const value = lines
+    .find((line) => line.startsWith(`ZEN_MODELS${i + 1}`))
+    ?.split("=")
+    .slice(1)
+    .join("=")
+  if (!value) throw new Error(`ZEN_MODELS${i + 1} not found`)
+  return value
+})
 
 
 // validate value
 // validate value
-ZenData.validate(JSON.parse(value1 + value2 + value3 + value4 + value5 + value6 + value7))
+ZenData.validate(JSON.parse(values.join("")))
 
 
 // update the secret
 // update the secret
-await $`bun sst secret set ZEN_MODELS1 ${value1} --stage ${stage}`
-await $`bun sst secret set ZEN_MODELS2 ${value2} --stage ${stage}`
-await $`bun sst secret set ZEN_MODELS3 ${value3} --stage ${stage}`
-await $`bun sst secret set ZEN_MODELS4 ${value4} --stage ${stage}`
-await $`bun sst secret set ZEN_MODELS5 ${value5} --stage ${stage}`
-await $`bun sst secret set ZEN_MODELS6 ${value6} --stage ${stage}`
-await $`bun sst secret set ZEN_MODELS7 ${value7} --stage ${stage}`
+for (let i = 0; i < PARTS; i++) {
+  await $`bun sst secret set ZEN_MODELS${i + 1} --stage ${stage} -- ${values[i]}`
+}

Some files were not shown because too many files changed in this diff