Просмотр исходного кода

Merge branch 'dev' into sqlite2

Dax Raad 2 месяцев назад
Родитель
Сommit
2619acc0ff
76 измененных файлов с 1148 добавлено и 678 удалено
  1. 33 0
      .github/workflows/contributors-label.yml
  2. 89 72
      .github/workflows/publish.yml
  3. 215 214
      STATS.md
  4. 18 15
      bun.lock
  5. 4 4
      nix/hashes.json
  6. 1 1
      packages/app/package.json
  7. 1 1
      packages/app/src/app.tsx
  8. 3 3
      packages/app/src/components/settings-general.tsx
  9. 1 1
      packages/app/src/components/settings-keybinds.tsx
  10. 2 2
      packages/app/src/components/settings-models.tsx
  11. 4 4
      packages/app/src/components/settings-permissions.tsx
  12. 3 3
      packages/app/src/components/settings-providers.tsx
  13. 40 0
      packages/app/src/pages/layout.tsx
  14. 56 1
      packages/app/src/pages/session.tsx
  15. 1 1
      packages/console/app/package.json
  16. 1 1
      packages/console/core/package.json
  17. 1 1
      packages/console/function/package.json
  18. 1 1
      packages/console/mail/package.json
  19. 2 1
      packages/desktop/package.json
  20. 102 4
      packages/desktop/src-tauri/Cargo.lock
  21. 2 1
      packages/desktop/src-tauri/Cargo.toml
  22. 1 0
      packages/desktop/src-tauri/capabilities/default.json
  23. 6 0
      packages/desktop/src-tauri/src/lib.rs
  24. 7 0
      packages/desktop/src-tauri/tauri.conf.json
  25. 18 0
      packages/desktop/src/index.tsx
  26. 1 1
      packages/enterprise/package.json
  27. 6 6
      packages/extensions/zed/extension.toml
  28. 1 1
      packages/function/package.json
  29. 1 1
      packages/opencode/package.json
  30. 11 0
      packages/opencode/script/build.ts
  31. 0 187
      packages/opencode/script/publish-registries.ts
  32. 194 25
      packages/opencode/script/publish.ts
  33. 28 4
      packages/opencode/src/cli/cmd/stats.ts
  34. 7 0
      packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
  35. 2 3
      packages/opencode/src/cli/cmd/tui/context/keybind.tsx
  36. 2 1
      packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
  37. 1 0
      packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx
  38. 1 0
      packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx
  39. 5 1
      packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
  40. 7 0
      packages/opencode/src/config/config.ts
  41. 17 12
      packages/opencode/src/config/markdown.ts
  42. 10 2
      packages/opencode/src/file/ripgrep.ts
  43. 0 1
      packages/opencode/src/flag/flag.ts
  44. 14 11
      packages/opencode/src/plugin/copilot.ts
  45. 1 1
      packages/opencode/src/provider/provider.ts
  46. 13 6
      packages/opencode/src/provider/transform.ts
  47. 1 1
      packages/opencode/src/server/server.ts
  48. 14 0
      packages/opencode/src/session/index.ts
  49. 15 15
      packages/opencode/src/session/llm.ts
  50. 21 0
      packages/opencode/src/skill/skill.ts
  51. 1 0
      packages/opencode/src/tool/glob.ts
  52. 1 0
      packages/opencode/src/tool/grep.ts
  53. 1 1
      packages/opencode/src/tool/ls.ts
  54. 2 3
      packages/opencode/src/tool/skill.ts
  55. 11 0
      packages/opencode/test/config/fixtures/markdown-header.md
  56. 13 0
      packages/opencode/test/config/fixtures/weird-model-id.md
  57. 41 5
      packages/opencode/test/config/markdown.test.ts
  58. 2 2
      packages/opencode/test/provider/transform.test.ts
  59. 1 1
      packages/plugin/package.json
  60. 4 0
      packages/script/src/index.ts
  61. 1 1
      packages/sdk/js/package.json
  62. 0 3
      packages/sdk/js/script/publish.ts
  63. 10 0
      packages/sdk/js/src/v2/gen/types.gen.ts
  64. 16 0
      packages/sdk/openapi.json
  65. 1 1
      packages/slack/package.json
  66. 1 1
      packages/ui/package.json
  67. 7 2
      packages/ui/src/components/tabs.css
  68. 1 1
      packages/util/package.json
  69. 1 1
      packages/web/package.json
  70. 5 1
      packages/web/src/content/docs/cli.mdx
  71. 8 0
      packages/web/src/content/docs/zen.mdx
  72. 1 0
      script/changelog.ts
  73. 0 14
      script/publish-complete.ts
  74. 16 30
      script/publish.ts
  75. 17 0
      script/version.ts
  76. 1 1
      sdks/vscode/package.json

+ 33 - 0
.github/workflows/contributors-label.yml

@@ -0,0 +1,33 @@
+name: Add Contributors Label
+
+on:
+  # issues:
+  #   types: [opened]
+
+  pull_request_target:
+    types: [opened]
+
+jobs:
+  add-contributor-label:
+    runs-on: ubuntu-latest
+    permissions:
+      pull-requests: write
+      issues: write
+
+    steps:
+      - name: Add Contributor Label
+        uses: actions/github-script@v8
+        with:
+          script: |
+            const isPR = !!context.payload.pull_request;
+            const issueNumber = isPR ? context.payload.pull_request.number : context.payload.issue.number;
+            const authorAssociation = isPR ? context.payload.pull_request.author_association : context.payload.issue.author_association;
+
+            if (authorAssociation === 'CONTRIBUTOR') {
+              await github.rest.issues.addLabels({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                issue_number: issueNumber,
+                labels: ['contributor']
+              });
+            }

+ 89 - 72
.github/workflows/publish.yml

@@ -4,6 +4,7 @@ run-name: "${{ format('release {0}', inputs.bump) }}"
 on:
   push:
     branches:
+      - ci
       - dev
       - snapshot-*
   workflow_dispatch:
@@ -29,56 +30,46 @@ permissions:
   packages: write
 
 jobs:
-  publish:
+  version:
     runs-on: blacksmith-4vcpu-ubuntu-2404
     if: github.repository == 'anomalyco/opencode'
     steps:
       - uses: actions/checkout@v3
         with:
-          fetch-depth: 0
-
-      - run: git fetch --force --tags
-
+          fetch-depth: 1
       - uses: ./.github/actions/setup-bun
+      - id: version
+        run: |
+          ./script/version.ts
+        env:
+          GH_TOKEN: ${{ github.token }}
+          OPENCODE_BUMP: ${{ inputs.bump }}
+          OPENCODE_VERSION: ${{ inputs.version }}
+    outputs:
+      version: ${{ steps.version.outputs.version }}
+      release: ${{ steps.version.outputs.release }}
+      tag: ${{ steps.version.outputs.tag }}
 
-      - name: Install OpenCode
-        if: inputs.bump || inputs.version
-        run: bun i -g [email protected]
-
-      - name: Login to GitHub Container Registry
-        uses: docker/login-action@v3
+  build-cli:
+    needs: version
+    runs-on: blacksmith-4vcpu-ubuntu-2404
+    if: github.repository == 'anomalyco/opencode'
+    steps:
+      - uses: actions/checkout@v3
         with:
-          registry: ghcr.io
-          username: ${{ github.repository_owner }}
-          password: ${{ secrets.GITHUB_TOKEN }}
+          fetch-depth: 1
+          fetch-tags: true
 
-      - name: Set up QEMU
-        uses: docker/setup-qemu-action@v3
-
-      - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
-
-      - uses: actions/setup-node@v4
-        with:
-          node-version: "24"
-          registry-url: "https://registry.npmjs.org"
+      - uses: ./.github/actions/setup-bun
 
-      - name: Setup Git Identity
+      - name: Build
+        id: build
         run: |
-          git config --global user.email "[email protected]"
-          git config --global user.name "opencode"
-          git remote set-url origin https://x-access-token:${{ secrets.SST_GITHUB_TOKEN }}@github.com/${{ github.repository }}
-
-      - name: Publish
-        id: publish
-        run: ./script/publish-start.ts
+          ./packages/opencode/script/build.ts
         env:
-          OPENCODE_BUMP: ${{ inputs.bump }}
-          OPENCODE_VERSION: ${{ inputs.version }}
-          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
-          AUR_KEY: ${{ secrets.AUR_KEY }}
-          GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
-          NPM_CONFIG_PROVENANCE: false
+          OPENCODE_VERSION: ${{ needs.version.outputs.version }}
+          OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
+          GH_TOKEN: ${{ github.token }}
 
       - uses: actions/upload-artifact@v4
         with:
@@ -86,12 +77,12 @@ jobs:
           path: packages/opencode/dist
 
     outputs:
-      release: ${{ steps.publish.outputs.release }}
-      tag: ${{ steps.publish.outputs.tag }}
-      version: ${{ steps.publish.outputs.version }}
+      version: ${{ needs.version.outputs.version }}
 
-  publish-tauri:
-    needs: publish
+  build-tauri:
+    needs:
+      - build-cli
+      - version
     continue-on-error: false
     strategy:
       fail-fast: false
@@ -111,8 +102,8 @@ jobs:
     steps:
       - uses: actions/checkout@v3
         with:
-          fetch-depth: 0
-          ref: ${{ needs.publish.outputs.tag }}
+          fetch-depth: 1
+          fetch-tags: true
 
       - uses: apple-actions/import-codesign-certs@v2
         if: ${{ runner.os == 'macOS' }}
@@ -134,8 +125,6 @@ jobs:
         run: |
           echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8
 
-      - run: git fetch --force --tags
-
       - uses: ./.github/actions/setup-bun
 
       - name: install dependencies (ubuntu only)
@@ -160,10 +149,7 @@ jobs:
           bun ./scripts/prepare.ts
         env:
           OPENCODE_VERSION: ${{ needs.publish.outputs.version }}
-          NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
           GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
-          AUR_KEY: ${{ secrets.AUR_KEY }}
-          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
           RUST_TARGET: ${{ matrix.settings.target }}
           GH_TOKEN: ${{ github.token }}
           GITHUB_RUN_ID: ${{ github.run_id }}
@@ -177,22 +163,18 @@ jobs:
           cargo tauri --version
 
       - name: Build and upload artifacts
-        uses: Wandalen/wretry.action@v3
+        uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
         timeout-minutes: 60
         with:
-          attempt_limit: 3
-          attempt_delay: 10000
-          action: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
-          with: |
-            projectPath: packages/desktop
-            uploadWorkflowArtifacts: true
-            tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
-            args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose
-            updaterJsonPreferNsis: true
-            releaseId: ${{ needs.publish.outputs.release }}
-            tagName: ${{ needs.publish.outputs.tag }}
-            releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
-            releaseDraft: true
+          projectPath: packages/desktop
+          uploadWorkflowArtifacts: true
+          tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
+          args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose
+          updaterJsonPreferNsis: true
+          releaseId: ${{ needs.version.outputs.release }}
+          tagName: ${{ needs.version.outputs.tag }}
+          releaseDraft: true
+          releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
@@ -205,20 +187,52 @@ jobs:
           APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
           APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
 
-  publish-release:
+  publish:
     needs:
-      - publish
-      - publish-tauri
-    if: needs.publish.outputs.tag
+      - version
+      - build-cli
+      - build-tauri
     runs-on: blacksmith-4vcpu-ubuntu-2404
     steps:
       - uses: actions/checkout@v3
         with:
-          fetch-depth: 0
-          ref: ${{ needs.publish.outputs.tag }}
+          fetch-depth: 1
+
+      - name: Install OpenCode
+        if: inputs.bump || inputs.version
+        run: bun i -g opencode-ai
+
+      - name: Login to GitHub Container Registry
+        uses: docker/login-action@v3
+        with:
+          registry: ghcr.io
+          username: ${{ github.repository_owner }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v3
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - uses: actions/setup-node@v4
+        with:
+          node-version: "24"
+          registry-url: "https://registry.npmjs.org"
+
+      - name: Setup Git Identity
+        run: |
+          git config --global user.email "[email protected]"
+          git config --global user.name "opencode"
+          git remote set-url origin https://x-access-token:${{ secrets.SST_GITHUB_TOKEN }}@github.com/${{ github.repository }}
 
       - uses: ./.github/actions/setup-bun
 
+      - uses: actions/download-artifact@v4
+        with:
+          name: opencode-cli
+          path: packages/opencode/dist
+
       - name: Setup SSH for AUR
         run: |
           sudo apt-get update
@@ -230,8 +244,11 @@ jobs:
           git config --global user.name "opencode"
           ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
 
-      - run: ./script/publish-complete.ts
+      - run: ./script/publish.ts
         env:
-          OPENCODE_VERSION: ${{ needs.publish.outputs.version  }}
+          OPENCODE_VERSION: ${{ needs.version.outputs.version }}
+          OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
           AUR_KEY: ${{ secrets.AUR_KEY }}
           GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
+          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+          NPM_CONFIG_PROVENANCE: false

+ 215 - 214
STATS.md

@@ -1,216 +1,217 @@
 # 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) |
-| 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) |
-| 2026-01-17 | 4,389,558 (+268,008) | 1,805,315 (+50,897)  | 6,194,873 (+318,905) |
-| 2026-01-18 | 4,627,623 (+238,065) | 1,839,171 (+33,856)  | 6,466,794 (+271,921) |
-| 2026-01-19 | 4,861,108 (+233,485) | 1,863,112 (+23,941)  | 6,724,220 (+257,426) |
-| 2026-01-20 | 5,128,999 (+267,891) | 1,903,665 (+40,553)  | 7,032,664 (+308,444) |
-| 2026-01-21 | 5,444,842 (+315,843) | 1,962,531 (+58,866)  | 7,407,373 (+374,709) |
-| 2026-01-22 | 5,766,340 (+321,498) | 2,029,487 (+66,956)  | 7,795,827 (+388,454) |
-| 2026-01-23 | 6,096,236 (+329,896) | 2,096,235 (+66,748)  | 8,192,471 (+396,644) |
-| 2026-01-24 | 6,371,019 (+274,783) | 2,156,870 (+60,635)  | 8,527,889 (+335,418) |
-| 2026-01-25 | 6,639,082 (+268,063) | 2,187,853 (+30,983)  | 8,826,935 (+299,046) |
-| 2026-01-26 | 6,941,620 (+302,538) | 2,232,115 (+44,262)  | 9,173,735 (+346,800) |
-| 2026-01-27 | 7,208,093 (+266,473) | 2,280,762 (+48,647)  | 9,488,855 (+315,120) |
-| 2026-01-28 | 7,489,370 (+281,277) | 2,314,849 (+34,087)  | 9,804,219 (+315,364) |
+| 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)  |
+| 2026-01-17 | 4,389,558 (+268,008) | 1,805,315 (+50,897)  | 6,194,873 (+318,905)  |
+| 2026-01-18 | 4,627,623 (+238,065) | 1,839,171 (+33,856)  | 6,466,794 (+271,921)  |
+| 2026-01-19 | 4,861,108 (+233,485) | 1,863,112 (+23,941)  | 6,724,220 (+257,426)  |
+| 2026-01-20 | 5,128,999 (+267,891) | 1,903,665 (+40,553)  | 7,032,664 (+308,444)  |
+| 2026-01-21 | 5,444,842 (+315,843) | 1,962,531 (+58,866)  | 7,407,373 (+374,709)  |
+| 2026-01-22 | 5,766,340 (+321,498) | 2,029,487 (+66,956)  | 7,795,827 (+388,454)  |
+| 2026-01-23 | 6,096,236 (+329,896) | 2,096,235 (+66,748)  | 8,192,471 (+396,644)  |
+| 2026-01-24 | 6,371,019 (+274,783) | 2,156,870 (+60,635)  | 8,527,889 (+335,418)  |
+| 2026-01-25 | 6,639,082 (+268,063) | 2,187,853 (+30,983)  | 8,826,935 (+299,046)  |
+| 2026-01-26 | 6,941,620 (+302,538) | 2,232,115 (+44,262)  | 9,173,735 (+346,800)  |
+| 2026-01-27 | 7,208,093 (+266,473) | 2,280,762 (+48,647)  | 9,488,855 (+315,120)  |
+| 2026-01-28 | 7,489,370 (+281,277) | 2,314,849 (+34,087)  | 9,804,219 (+315,364)  |
+| 2026-01-29 | 7,815,471 (+326,101) | 2,374,982 (+60,133)  | 10,190,453 (+386,234) |

+ 18 - 15
bun.lock

@@ -23,7 +23,7 @@
     },
     "packages/app": {
       "name": "@opencode-ai/app",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
@@ -73,7 +73,7 @@
     },
     "packages/console/app": {
       "name": "@opencode-ai/console-app",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "@cloudflare/vite-plugin": "1.15.2",
         "@ibm/plex": "6.4.1",
@@ -107,7 +107,7 @@
     },
     "packages/console/core": {
       "name": "@opencode-ai/console-core",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "@aws-sdk/client-sts": "3.782.0",
         "@jsx-email/render": "1.1.1",
@@ -134,7 +134,7 @@
     },
     "packages/console/function": {
       "name": "@opencode-ai/console-function",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "@ai-sdk/anthropic": "2.0.0",
         "@ai-sdk/openai": "2.0.2",
@@ -158,7 +158,7 @@
     },
     "packages/console/mail": {
       "name": "@opencode-ai/console-mail",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "@jsx-email/all": "2.2.3",
         "@jsx-email/cli": "1.4.3",
@@ -182,13 +182,14 @@
     },
     "packages/desktop": {
       "name": "@opencode-ai/desktop",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "@opencode-ai/app": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
         "@solid-primitives/i18n": "2.2.1",
         "@solid-primitives/storage": "catalog:",
         "@tauri-apps/api": "^2",
+        "@tauri-apps/plugin-deep-link": "~2",
         "@tauri-apps/plugin-dialog": "~2",
         "@tauri-apps/plugin-http": "~2",
         "@tauri-apps/plugin-notification": "~2",
@@ -212,7 +213,7 @@
     },
     "packages/enterprise": {
       "name": "@opencode-ai/enterprise",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "@opencode-ai/ui": "workspace:*",
         "@opencode-ai/util": "workspace:*",
@@ -241,7 +242,7 @@
     },
     "packages/function": {
       "name": "@opencode-ai/function",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "@octokit/auth-app": "8.0.1",
         "@octokit/rest": "catalog:",
@@ -257,7 +258,7 @@
     },
     "packages/opencode": {
       "name": "opencode",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "bin": {
         "opencode": "./bin/opencode",
       },
@@ -364,7 +365,7 @@
     },
     "packages/plugin": {
       "name": "@opencode-ai/plugin",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "zod": "catalog:",
@@ -384,7 +385,7 @@
     },
     "packages/sdk/js": {
       "name": "@opencode-ai/sdk",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "devDependencies": {
         "@hey-api/openapi-ts": "0.90.10",
         "@tsconfig/node22": "catalog:",
@@ -395,7 +396,7 @@
     },
     "packages/slack": {
       "name": "@opencode-ai/slack",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@slack/bolt": "^3.17.1",
@@ -408,7 +409,7 @@
     },
     "packages/ui": {
       "name": "@opencode-ai/ui",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
@@ -450,7 +451,7 @@
     },
     "packages/util": {
       "name": "@opencode-ai/util",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "zod": "catalog:",
       },
@@ -461,7 +462,7 @@
     },
     "packages/web": {
       "name": "@opencode-ai/web",
-      "version": "1.1.40",
+      "version": "0.0.0-ci-202601291718",
       "dependencies": {
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/markdown-remark": "6.3.1",
@@ -1767,6 +1768,8 @@
 
     "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-EdYd4c9wGvtPB95kqtEyY+bUR+k4kRw3IA30mAQ1jPH6z57AftT8q84qwv0RDp6kkEqOBKxeInKfqi4BESYuqg=="],
 
+    "@tauri-apps/plugin-deep-link": ["@tauri-apps/[email protected]", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-UUOSt0U5juK20uhO2MoHZX/IPblkrhUh+VPtIeu3RwtzI0R9Em3Auzfg/PwcZ9Pv8mLne3cQ4p9CFXD6WxqCZA=="],
+
     "@tauri-apps/plugin-dialog": ["@tauri-apps/[email protected]", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-lNIn5CZuw8WZOn8zHzmFmDSzg5zfohWoa3mdULP0YFh/VogVdMVWZPcWSHlydsiJhRQYaTNSYKN7RmZKE2lCYQ=="],
 
     "@tauri-apps/plugin-http": ["@tauri-apps/[email protected]", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-/i4U/9za3mrytTgfRn5RHneKubZE/dwRmshYwyMvNRlkWjvu1m4Ma72kcbVJMZFGXpkbl+qLyWMGrihtWB76Zg=="],

+ 4 - 4
nix/hashes.json

@@ -1,8 +1,8 @@
 {
   "nodeModules": {
-    "x86_64-linux": "sha256-L3peTVUra1QZIv1We+yuXnia1Eq3Je206BLlqVOswL8=",
-    "aarch64-linux": "sha256-d0vqy64cUpqrkLTPEC+6JrGhD5msfivrXBwpLjs/qD4=",
-    "aarch64-darwin": "sha256-iqxztCONsuV77SGEJCCenwew9QWCx9NPzY6PlO+872I=",
-    "x86_64-darwin": "sha256-FwrCnbJ5kGxewoXK+lMk0I0UtYvGcOZjOb2Xhzmqwkk="
+    "x86_64-linux": "sha256-yAtZlh6YR78RwPt0LK/7Pk0qUm0/97+s6ghhZzuoE/0=",
+    "aarch64-linux": "sha256-6j81rdjQ7Wps9bvfw+mmdwW5p01qUOwX40UZltCTe3Y=",
+    "aarch64-darwin": "sha256-pDM8M/QMWR6Go5pz3XXsJqcJDHAlHrx2Faijjkzcngo=",
+    "x86_64-darwin": "sha256-eOAPtMd1n5xYupBOevCLhY1eFy3wzGqFk/EsZocl9Y8="
   }
 }

+ 1 - 1
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/app",
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "description": "",
   "type": "module",
   "exports": {

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

@@ -43,7 +43,7 @@ function UiI18nBridge(props: ParentProps) {
 
 declare global {
   interface Window {
-    __OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string }
+    __OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string; deepLinks?: string[] }
   }
 }
 

+ 3 - 3
packages/app/src/components/settings-general.tsx

@@ -130,7 +130,7 @@ export const SettingsGeneral: Component = () => {
   const soundOptions = [...SOUND_OPTIONS]
 
   return (
-    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-10 pb-10">
+    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
       <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
         <div class="flex flex-col gap-1 pt-6 pb-8">
           <h2 class="text-16-medium text-text-strong">{language.t("settings.tab.general")}</h2>
@@ -406,8 +406,8 @@ interface SettingsRowProps {
 
 const SettingsRow: Component<SettingsRowProps> = (props) => {
   return (
-    <div class="flex items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
-      <div class="flex flex-col gap-0.5">
+    <div class="flex flex-wrap items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
+      <div class="flex flex-col gap-0.5 min-w-0">
         <span class="text-14-medium text-text-strong">{props.title}</span>
         <span class="text-12-regular text-text-weak">{props.description}</span>
       </div>

+ 1 - 1
packages/app/src/components/settings-keybinds.tsx

@@ -352,7 +352,7 @@ export const SettingsKeybinds: Component = () => {
   })
 
   return (
-    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-10 pb-10">
+    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
       <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
         <div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
           <div class="flex items-center justify-between gap-4">

+ 2 - 2
packages/app/src/components/settings-models.tsx

@@ -39,7 +39,7 @@ export const SettingsModels: Component = () => {
   })
 
   return (
-    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-10 pb-10">
+    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
       <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
         <div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
           <h2 class="text-16-medium text-text-strong">{language.t("settings.models.title")}</h2>
@@ -99,7 +99,7 @@ export const SettingsModels: Component = () => {
                       {(item) => {
                         const key = { providerID: item.provider.id, modelID: item.id }
                         return (
-                          <div class="flex items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
+                          <div class="flex flex-wrap items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
                             <div class="min-w-0">
                               <span class="text-14-regular text-text-strong truncate block">{item.name}</span>
                             </div>

+ 4 - 4
packages/app/src/components/settings-permissions.tsx

@@ -176,13 +176,13 @@ export const SettingsPermissions: Component = () => {
   return (
     <div class="flex flex-col h-full overflow-y-auto no-scrollbar">
       <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
-        <div class="flex flex-col gap-1 p-8 max-w-[720px]">
+        <div class="flex flex-col gap-1 px-4 py-8 sm:p-8 max-w-[720px]">
           <h2 class="text-16-medium text-text-strong">{language.t("settings.permissions.title")}</h2>
           <p class="text-14-regular text-text-weak">{language.t("settings.permissions.description")}</p>
         </div>
       </div>
 
-      <div class="flex flex-col gap-6 p-8 pt-6 max-w-[720px]">
+      <div class="flex flex-col gap-6 px-4 py-6 sm:p-8 sm:pt-6 max-w-[720px]">
         <div class="flex flex-col gap-2">
           <h3 class="text-14-medium text-text-strong">{language.t("settings.permissions.section.tools")}</h3>
           <div class="border border-border-weak-base rounded-lg overflow-hidden">
@@ -217,8 +217,8 @@ interface SettingsRowProps {
 
 const SettingsRow: Component<SettingsRowProps> = (props) => {
   return (
-    <div class="flex items-center justify-between gap-4 px-4 py-3 border-b border-border-weak-base last:border-none">
-      <div class="flex flex-col gap-0.5">
+    <div class="flex flex-wrap items-center justify-between gap-4 px-4 py-3 border-b border-border-weak-base last:border-none">
+      <div class="flex flex-col gap-0.5 min-w-0">
         <span class="text-14-medium text-text-strong">{props.title}</span>
         <span class="text-12-regular text-text-weak">{props.description}</span>
       </div>

+ 3 - 3
packages/app/src/components/settings-providers.tsx

@@ -115,7 +115,7 @@ export const SettingsProviders: Component = () => {
   }
 
   return (
-    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-10 pb-10">
+    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
       <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
         <div class="flex flex-col gap-1 pt-6 pb-8 max-w-[720px]">
           <h2 class="text-16-medium text-text-strong">{language.t("settings.providers.title")}</h2>
@@ -136,7 +136,7 @@ export const SettingsProviders: Component = () => {
             >
               <For each={connected()}>
                 {(item) => (
-                  <div class="group flex items-center justify-between gap-4 h-16 border-b border-border-weak-base last:border-none">
+                  <div class="group flex flex-wrap items-center justify-between gap-4 min-h-16 py-3 border-b border-border-weak-base last:border-none">
                     <div class="flex items-center gap-3 min-w-0">
                       <ProviderIcon id={icon(item.id)} class="size-5 shrink-0 icon-strong-base" />
                       <span class="text-14-medium text-text-strong truncate">{item.name}</span>
@@ -166,7 +166,7 @@ export const SettingsProviders: Component = () => {
           <div class="bg-surface-raised-base px-4 rounded-lg">
             <For each={popular()}>
               {(item) => (
-                <div class="flex items-center justify-between gap-4 h-16 border-b border-border-weak-base last:border-none">
+                <div class="flex flex-wrap items-center justify-between gap-4 min-h-16 py-3 border-b border-border-weak-base last:border-none">
                   <div class="flex flex-col min-w-0">
                     <div class="flex items-center gap-x-3">
                       <ProviderIcon id={icon(item.id)} class="size-5 shrink-0 icon-strong-base" />

+ 40 - 0
packages/app/src/pages/layout.tsx

@@ -1136,6 +1136,46 @@ export default function Layout(props: ParentProps) {
     if (navigate) navigateToProject(directory)
   }
 
+  const deepLinkEvent = "opencode:deep-link"
+
+  const parseDeepLink = (input: string) => {
+    if (!input.startsWith("opencode://")) return
+    const url = new URL(input)
+    if (url.hostname !== "open-project") return
+    const directory = url.searchParams.get("directory")
+    if (!directory) return
+    return directory
+  }
+
+  const handleDeepLinks = (urls: string[]) => {
+    if (!server.isLocal()) return
+    for (const input of urls) {
+      const directory = parseDeepLink(input)
+      if (!directory) continue
+      openProject(directory)
+    }
+  }
+
+  const drainDeepLinks = () => {
+    const pending = window.__OPENCODE__?.deepLinks ?? []
+    if (pending.length === 0) return
+    if (window.__OPENCODE__) window.__OPENCODE__.deepLinks = []
+    handleDeepLinks(pending)
+  }
+
+  onMount(() => {
+    const handler = (event: Event) => {
+      const detail = (event as CustomEvent<{ urls: string[] }>).detail
+      const urls = detail?.urls ?? []
+      if (urls.length === 0) return
+      handleDeepLinks(urls)
+    }
+
+    drainDeepLinks()
+    window.addEventListener(deepLinkEvent, handler as EventListener)
+    onCleanup(() => window.removeEventListener(deepLinkEvent, handler as EventListener))
+  })
+
   const displayName = (project: LocalProject) => project.name || getFilename(project.worktree)
 
   async function renameProject(project: LocalProject, next: string) {

+ 56 - 1
packages/app/src/pages/session.tsx

@@ -2183,7 +2183,62 @@ export default function Page() {
                     <ConstrainDragYAxis />
                     <Tabs value={activeTab()} onChange={openTab}>
                       <div class="sticky top-0 shrink-0 flex">
-                        <Tabs.List>
+                        <Tabs.List
+                          ref={(el: HTMLDivElement) => {
+                            let scrollTimeout: number | undefined
+                            let prevScrollWidth = el.scrollWidth
+                            let prevContextOpen = contextOpen()
+
+                            const handler = () => {
+                              if (scrollTimeout !== undefined) clearTimeout(scrollTimeout)
+                              scrollTimeout = window.setTimeout(() => {
+                                const scrollWidth = el.scrollWidth
+                                const clientWidth = el.clientWidth
+                                const currentContextOpen = contextOpen()
+
+                                // Only scroll when a tab is added (width increased), not on removal
+                                if (scrollWidth > prevScrollWidth) {
+                                  if (!prevContextOpen && currentContextOpen) {
+                                    // Context tab was opened, scroll to first
+                                    el.scrollTo({
+                                      left: 0,
+                                      behavior: "smooth",
+                                    })
+                                  } else if (scrollWidth > clientWidth) {
+                                    // File tab was added, scroll to rightmost
+                                    el.scrollTo({
+                                      left: scrollWidth - clientWidth,
+                                      behavior: "smooth",
+                                    })
+                                  }
+                                }
+                                // When width decreases (tab removed), don't scroll - let browser handle it naturally
+
+                                prevScrollWidth = scrollWidth
+                                prevContextOpen = currentContextOpen
+                              }, 0)
+                            }
+
+                            const wheelHandler = (e: WheelEvent) => {
+                              // Enable horizontal scrolling with mouse wheel
+                              if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
+                                el.scrollLeft += e.deltaY > 0 ? 50 : -50
+                                e.preventDefault()
+                              }
+                            }
+
+                            el.addEventListener("wheel", wheelHandler, { passive: false })
+
+                            const observer = new MutationObserver(handler)
+                            observer.observe(el, { childList: true })
+
+                            onCleanup(() => {
+                              el.removeEventListener("wheel", wheelHandler)
+                              observer.disconnect()
+                              if (scrollTimeout !== undefined) clearTimeout(scrollTimeout)
+                            })
+                          }}
+                        >
                           <Show when={contextOpen()}>
                             <Tabs.Trigger
                               value="context"

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

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-app",
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "type": "module",
   "license": "MIT",
   "scripts": {

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

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

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

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

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

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-mail",
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "dependencies": {
     "@jsx-email/all": "2.2.3",
     "@jsx-email/cli": "1.4.3",

+ 2 - 1
packages/desktop/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@opencode-ai/desktop",
   "private": true,
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "type": "module",
   "license": "MIT",
   "scripts": {
@@ -18,6 +18,7 @@
     "@solid-primitives/i18n": "2.2.1",
     "@solid-primitives/storage": "catalog:",
     "@tauri-apps/api": "^2",
+    "@tauri-apps/plugin-deep-link": "~2",
     "@tauri-apps/plugin-dialog": "~2",
     "@tauri-apps/plugin-opener": "^2",
     "@tauri-apps/plugin-os": "~2",

+ 102 - 4
packages/desktop/src-tauri/Cargo.lock

@@ -609,6 +609,26 @@ dependencies = [
  "crossbeam-utils",
 ]
 
+[[package]]
+name = "const-random"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
+dependencies = [
+ "const-random-macro",
+]
+
+[[package]]
+name = "const-random-macro"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
+dependencies = [
+ "getrandom 0.2.16",
+ "once_cell",
+ "tiny-keccak",
+]
+
 [[package]]
 name = "convert_case"
 version = "0.4.0"
@@ -980,6 +1000,15 @@ dependencies = [
  "rand 0.8.5",
 ]
 
+[[package]]
+name = "dlv-list"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
+dependencies = [
+ "const-random",
+]
+
 [[package]]
 name = "document-features"
 version = "0.2.12"
@@ -1777,6 +1806,12 @@ version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
 
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
 [[package]]
 name = "hashbrown"
 version = "0.15.5"
@@ -1930,7 +1965,7 @@ dependencies = [
  "tokio",
  "tower-service",
  "tracing",
- "windows-registry",
+ "windows-registry 0.6.1",
 ]
 
 [[package]]
@@ -2345,7 +2380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a4f8240c33bb08c5d8b8cdea87b683b05e61037aa76ff26bef40672cc6ecbb80"
 dependencies = [
  "freedesktop_entry_parser",
- "rust-ini",
+ "rust-ini 0.17.0",
 ]
 
 [[package]]
@@ -3038,6 +3073,7 @@ dependencies = [
  "tauri-build",
  "tauri-plugin-clipboard-manager",
  "tauri-plugin-decorum",
+ "tauri-plugin-deep-link",
  "tauri-plugin-dialog",
  "tauri-plugin-http",
  "tauri-plugin-notification",
@@ -3067,10 +3103,20 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485"
 dependencies = [
- "dlv-list",
+ "dlv-list 0.2.3",
  "hashbrown 0.9.1",
 ]
 
+[[package]]
+name = "ordered-multimap"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
+dependencies = [
+ "dlv-list 0.5.2",
+ "hashbrown 0.14.5",
+]
+
 [[package]]
 name = "ordered-stream"
 version = "0.2.0"
@@ -3947,7 +3993,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "63471c4aa97a1cf8332a5f97709a79a4234698de6a1f5087faf66f2dae810e22"
 dependencies = [
  "cfg-if",
- "ordered-multimap",
+ "ordered-multimap 0.3.1",
+]
+
+[[package]]
+name = "rust-ini"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7"
+dependencies = [
+ "cfg-if",
+ "ordered-multimap 0.7.3",
 ]
 
 [[package]]
@@ -4817,6 +4873,27 @@ dependencies = [
  "tauri-plugin",
 ]
 
+[[package]]
+name = "tauri-plugin-deep-link"
+version = "2.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "444b091f24f2f6bdb4a305b54d3961f629c11861c685aceeea9a1972f89e43d5"
+dependencies = [
+ "dunce",
+ "plist",
+ "rust-ini 0.21.3",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-plugin",
+ "tauri-utils",
+ "thiserror 2.0.17",
+ "tracing",
+ "url",
+ "windows-registry 0.5.3",
+ "windows-result 0.3.4",
+]
+
 [[package]]
 name = "tauri-plugin-dialog"
 version = "2.4.2"
@@ -4980,6 +5057,7 @@ dependencies = [
  "serde",
  "serde_json",
  "tauri",
+ "tauri-plugin-deep-link",
  "thiserror 2.0.17",
  "tracing",
  "windows-sys 0.60.2",
@@ -5271,6 +5349,15 @@ dependencies = [
  "time-core",
 ]
 
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
 [[package]]
 name = "tinystr"
 version = "0.8.2"
@@ -6208,6 +6295,17 @@ dependencies = [
  "windows-link 0.1.3",
 ]
 
+[[package]]
+name = "windows-registry"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
+dependencies = [
+ "windows-link 0.1.3",
+ "windows-result 0.3.4",
+ "windows-strings 0.4.2",
+]
+
 [[package]]
 name = "windows-registry"
 version = "0.6.1"

+ 2 - 1
packages/desktop/src-tauri/Cargo.toml

@@ -20,6 +20,7 @@ tauri-build = { version = "2", features = [] }
 [dependencies]
 tauri = { version = "2", features = ["macos-private-api", "devtools"] }
 tauri-plugin-opener = "2"
+tauri-plugin-deep-link = "2.4.6"
 tauri-plugin-shell = "2"
 tauri-plugin-dialog = "2"
 tauri-plugin-updater = "2"
@@ -29,7 +30,7 @@ tauri-plugin-window-state = "2"
 tauri-plugin-clipboard-manager = "2"
 tauri-plugin-http = "2"
 tauri-plugin-notification = "2"
-tauri-plugin-single-instance = "2"
+tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
 
 serde = { version = "1", features = ["derive"] }
 serde_json = "1"

+ 1 - 0
packages/desktop/src-tauri/capabilities/default.json

@@ -6,6 +6,7 @@
   "permissions": [
     "core:default",
     "opener:default",
+    "deep-link:default",
     "core:window:allow-start-dragging",
     "core:window:allow-set-theme",
     "core:webview:allow-set-webview-zoom",

+ 6 - 0
packages/desktop/src-tauri/src/lib.rs

@@ -16,6 +16,8 @@ use std::{
     time::{Duration, Instant},
 };
 use tauri::{AppHandle, LogicalSize, Manager, RunEvent, State, WebviewWindowBuilder};
+#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
+use tauri_plugin_deep_link::DeepLinkExt;
 #[cfg(windows)]
 use tauri_plugin_decorum::WebviewWindowExt;
 use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
@@ -263,6 +265,7 @@ pub fn run() {
                 let _ = window.unminimize();
             }
         }))
+        .plugin(tauri_plugin_deep_link::init())
         .plugin(tauri_plugin_os::init())
         .plugin(
             tauri_plugin_window_state::Builder::new()
@@ -291,6 +294,9 @@ pub fn run() {
             markdown::parse_markdown_command
         ])
         .setup(move |app| {
+            #[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
+            app.deep_link().register_all().ok();
+
             let app = app.handle().clone();
 
             // Initialize log state

+ 7 - 0
packages/desktop/src-tauri/tauri.conf.json

@@ -52,5 +52,12 @@
         "sidebarImage": "assets/nsis-sidebar.bmp"
       }
     }
+  },
+  "plugins": {
+    "deep-link": {
+      "desktop": {
+        "schemes": ["opencode"]
+      }
+    }
   }
 }

+ 18 - 0
packages/desktop/src/index.tsx

@@ -3,6 +3,7 @@ import "./webview-zoom"
 import { render } from "solid-js/web"
 import { AppBaseProviders, AppInterface, PlatformProvider, Platform } from "@opencode-ai/app"
 import { open, save } from "@tauri-apps/plugin-dialog"
+import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link"
 import { open as shellOpen } from "@tauri-apps/plugin-shell"
 import { type as ostype } from "@tauri-apps/plugin-os"
 import { check, Update } from "@tauri-apps/plugin-updater"
@@ -42,6 +43,22 @@ window.getComputedStyle = ((elt: Element, pseudoElt?: string | null) => {
 
 let update: Update | null = null
 
+const deepLinkEvent = "opencode:deep-link"
+
+const emitDeepLinks = (urls: string[]) => {
+  if (urls.length === 0) return
+  window.__OPENCODE__ ??= {}
+  const pending = window.__OPENCODE__.deepLinks ?? []
+  window.__OPENCODE__.deepLinks = [...pending, ...urls]
+  window.dispatchEvent(new CustomEvent(deepLinkEvent, { detail: { urls } }))
+}
+
+const listenForDeepLinks = async () => {
+  const startUrls = await getCurrent().catch(() => null)
+  if (startUrls?.length) emitDeepLinks(startUrls)
+  await onOpenUrl((urls) => emitDeepLinks(urls)).catch(() => undefined)
+}
+
 const createPlatform = (password: Accessor<string | null>): Platform => ({
   platform: "desktop",
   os: (() => {
@@ -332,6 +349,7 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
 })
 
 createMenu()
+void listenForDeepLinks()
 
 render(() => {
   const [serverPassword, setServerPassword] = createSignal<string | null>(null)

+ 1 - 1
packages/enterprise/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/enterprise",
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "private": true,
   "type": "module",
   "license": "MIT",

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

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

+ 1 - 1
packages/function/package.json

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

+ 1 - 1
packages/opencode/package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "name": "opencode",
   "type": "module",
   "license": "MIT",

+ 11 - 0
packages/opencode/script/build.ts

@@ -192,4 +192,15 @@ for (const item of targets) {
   binaries[name] = Script.version
 }
 
+if (Script.release) {
+  for (const key of Object.keys(binaries)) {
+    if (key.includes("linux")) {
+      await $`tar -czf ../../${key}.tar.gz *`.cwd(`dist/${key}/bin`)
+    } else {
+      await $`zip -r ../../${key}.zip *`.cwd(`dist/${key}/bin`)
+    }
+  }
+  await $`gh release upload v${Script.version} ./dist/*.zip ./dist/*.tar.gz --clobber`
+}
+
 export { binaries }

+ 0 - 187
packages/opencode/script/publish-registries.ts

@@ -1,187 +0,0 @@
-#!/usr/bin/env bun
-import { $ } from "bun"
-import { Script } from "@opencode-ai/script"
-
-if (!Script.preview) {
-  // Calculate SHA values
-  const arm64Sha = await $`sha256sum ./dist/opencode-linux-arm64.tar.gz | cut -d' ' -f1`.text().then((x) => x.trim())
-  const x64Sha = await $`sha256sum ./dist/opencode-linux-x64.tar.gz | cut -d' ' -f1`.text().then((x) => x.trim())
-  const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
-  const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
-
-  const [pkgver, _subver = ""] = Script.version.split(/(-.*)/, 2)
-
-  // arch
-  const binaryPkgbuild = [
-    "# Maintainer: dax",
-    "# Maintainer: adam",
-    "",
-    "pkgname='opencode-bin'",
-    `pkgver=${pkgver}`,
-    `_subver=${_subver}`,
-    "options=('!debug' '!strip')",
-    "pkgrel=1",
-    "pkgdesc='The AI coding agent built for the terminal.'",
-    "url='https://github.com/anomalyco/opencode'",
-    "arch=('aarch64' 'x86_64')",
-    "license=('MIT')",
-    "provides=('opencode')",
-    "conflicts=('opencode')",
-    "depends=('ripgrep')",
-    "",
-    `source_aarch64=("\${pkgname}_\${pkgver}_aarch64.tar.gz::https://github.com/anomalyco/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-arm64.tar.gz")`,
-    `sha256sums_aarch64=('${arm64Sha}')`,
-
-    `source_x86_64=("\${pkgname}_\${pkgver}_x86_64.tar.gz::https://github.com/anomalyco/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-x64.tar.gz")`,
-    `sha256sums_x86_64=('${x64Sha}')`,
-    "",
-    "package() {",
-    '  install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode"',
-    "}",
-    "",
-  ].join("\n")
-
-  // Source-based PKGBUILD for opencode
-  const sourcePkgbuild = [
-    "# Maintainer: dax",
-    "# Maintainer: adam",
-    "",
-    "pkgname='opencode'",
-    `pkgver=${pkgver}`,
-    `_subver=${_subver}`,
-    "options=('!debug' '!strip')",
-    "pkgrel=1",
-    "pkgdesc='The AI coding agent built for the terminal.'",
-    "url='https://github.com/anomalyco/opencode'",
-    "arch=('aarch64' 'x86_64')",
-    "license=('MIT')",
-    "provides=('opencode')",
-    "conflicts=('opencode-bin')",
-    "depends=('ripgrep')",
-    "makedepends=('git' 'bun' 'go')",
-    "",
-    `source=("opencode-\${pkgver}.tar.gz::https://github.com/anomalyco/opencode/archive/v\${pkgver}\${_subver}.tar.gz")`,
-    `sha256sums=('SKIP')`,
-    "",
-    "build() {",
-    `  cd "opencode-\${pkgver}"`,
-    `  bun install`,
-    "  cd ./packages/opencode",
-    `  OPENCODE_CHANNEL=latest OPENCODE_VERSION=${pkgver} bun run ./script/build.ts --single`,
-    "}",
-    "",
-    "package() {",
-    `  cd "opencode-\${pkgver}/packages/opencode"`,
-    '  mkdir -p "${pkgdir}/usr/bin"',
-    '  target_arch="x64"',
-    '  case "$CARCH" in',
-    '    x86_64) target_arch="x64" ;;',
-    '    aarch64) target_arch="arm64" ;;',
-    '    *) printf "unsupported architecture: %s\\n" "$CARCH" >&2 ; return 1 ;;',
-    "  esac",
-    '  libc=""',
-    "  if command -v ldd >/dev/null 2>&1; then",
-    "    if ldd --version 2>&1 | grep -qi musl; then",
-    '      libc="-musl"',
-    "    fi",
-    "  fi",
-    '  if [ -z "$libc" ] && ls /lib/ld-musl-* >/dev/null 2>&1; then',
-    '    libc="-musl"',
-    "  fi",
-    '  base=""',
-    '  if [ "$target_arch" = "x64" ]; then',
-    "    if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then",
-    '      base="-baseline"',
-    "    fi",
-    "  fi",
-    '  bin="dist/opencode-linux-${target_arch}${base}${libc}/bin/opencode"',
-    '  if [ ! -f "$bin" ]; then',
-    '    printf "unable to find binary for %s%s%s\\n" "$target_arch" "$base" "$libc" >&2',
-    "    return 1",
-    "  fi",
-    '  install -Dm755 "$bin" "${pkgdir}/usr/bin/opencode"',
-    "}",
-    "",
-  ].join("\n")
-
-  for (const [pkg, pkgbuild] of [
-    ["opencode-bin", binaryPkgbuild],
-    ["opencode", sourcePkgbuild],
-  ]) {
-    for (let i = 0; i < 30; i++) {
-      try {
-        await $`rm -rf ./dist/aur-${pkg}`
-        await $`git clone ssh://[email protected]/${pkg}.git ./dist/aur-${pkg}`
-        await $`cd ./dist/aur-${pkg} && git checkout master`
-        await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild)
-        await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
-        await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
-        await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${Script.version}"`
-        await $`cd ./dist/aur-${pkg} && git push`
-        break
-      } catch (e) {
-        continue
-      }
-    }
-  }
-
-  // Homebrew formula
-  const homebrewFormula = [
-    "# typed: false",
-    "# frozen_string_literal: true",
-    "",
-    "# This file was generated by GoReleaser. DO NOT EDIT.",
-    "class Opencode < Formula",
-    `  desc "The AI coding agent built for the terminal."`,
-    `  homepage "https://github.com/anomalyco/opencode"`,
-    `  version "${Script.version.split("-")[0]}"`,
-    "",
-    `  depends_on "ripgrep"`,
-    "",
-    "  on_macos do",
-    "    if Hardware::CPU.intel?",
-    `      url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-darwin-x64.zip"`,
-    `      sha256 "${macX64Sha}"`,
-    "",
-    "      def install",
-    '        bin.install "opencode"',
-    "      end",
-    "    end",
-    "    if Hardware::CPU.arm?",
-    `      url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-darwin-arm64.zip"`,
-    `      sha256 "${macArm64Sha}"`,
-    "",
-    "      def install",
-    '        bin.install "opencode"',
-    "      end",
-    "    end",
-    "  end",
-    "",
-    "  on_linux do",
-    "    if Hardware::CPU.intel? and Hardware::CPU.is_64_bit?",
-    `      url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-linux-x64.tar.gz"`,
-    `      sha256 "${x64Sha}"`,
-    "      def install",
-    '        bin.install "opencode"',
-    "      end",
-    "    end",
-    "    if Hardware::CPU.arm? and Hardware::CPU.is_64_bit?",
-    `      url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-linux-arm64.tar.gz"`,
-    `      sha256 "${arm64Sha}"`,
-    "      def install",
-    '        bin.install "opencode"',
-    "      end",
-    "    end",
-    "  end",
-    "end",
-    "",
-    "",
-  ].join("\n")
-
-  await $`rm -rf ./dist/homebrew-tap`
-  await $`git clone https://${process.env["GITHUB_TOKEN"]}@github.com/sst/homebrew-tap.git ./dist/homebrew-tap`
-  await Bun.file("./dist/homebrew-tap/opencode.rb").write(homebrewFormula)
-  await $`cd ./dist/homebrew-tap && git add opencode.rb`
-  await $`cd ./dist/homebrew-tap && git commit -m "Update to v${Script.version}"`
-  await $`cd ./dist/homebrew-tap && git push`
-}

+ 194 - 25
packages/opencode/script/publish.ts

@@ -7,12 +7,13 @@ import { fileURLToPath } from "url"
 const dir = fileURLToPath(new URL("..", import.meta.url))
 process.chdir(dir)
 
-const { binaries } = await import("./build.ts")
-{
-  const name = `${pkg.name}-${process.platform}-${process.arch}`
-  console.log(`smoke test: running dist/${name}/bin/opencode --version`)
-  await $`./dist/${name}/bin/opencode --version`
+const binaries: Record<string, string> = {}
+for (const filepath of new Bun.Glob("*/package.json").scanSync({ cwd: "./dist" })) {
+  const pkg = await Bun.file(`./dist/${filepath}`).json()
+  binaries[pkg.name] = pkg.version
 }
+console.log("binaries", binaries)
+const version = Object.values(binaries)[0]
 
 await $`mkdir -p ./dist/${pkg.name}`
 await $`cp -r ./bin ./dist/${pkg.name}/bin`
@@ -28,7 +29,7 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
       scripts: {
         postinstall: "bun ./postinstall.mjs || node ./postinstall.mjs",
       },
-      version: Script.version,
+      version: version,
       optionalDependencies: binaries,
     },
     null,
@@ -36,35 +37,203 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
   ),
 )
 
-const tags = [Script.channel]
-
 const tasks = Object.entries(binaries).map(async ([name]) => {
   if (process.platform !== "win32") {
     await $`chmod -R 755 .`.cwd(`./dist/${name}`)
   }
   await $`bun pm pack`.cwd(`./dist/${name}`)
-  for (const tag of tags) {
-    await $`npm publish *.tgz --access public --tag ${tag}`.cwd(`./dist/${name}`)
-  }
+  await $`npm publish *.tgz --access public --tag ${Script.channel}`.cwd(`./dist/${name}`)
 })
 await Promise.all(tasks)
-for (const tag of tags) {
-  await $`cd ./dist/${pkg.name} && bun pm pack && npm publish *.tgz --access public --tag ${tag}`
-}
+await $`cd ./dist/${pkg.name} && bun pm pack && npm publish *.tgz --access public --tag ${Script.channel}`
 
+const image = "ghcr.io/anomalyco/opencode"
+const platforms = "linux/amd64,linux/arm64"
+const tags = [`${image}:${version}`, `${image}:${Script.channel}`]
+const tagFlags = tags.flatMap((t) => ["-t", t])
+await $`docker buildx build --platform ${platforms} ${tagFlags} --push .`
+
+// registries
 if (!Script.preview) {
-  // Create archives for GitHub release
-  for (const key of Object.keys(binaries)) {
-    if (key.includes("linux")) {
-      await $`tar -czf ../../${key}.tar.gz *`.cwd(`dist/${key}/bin`)
-    } else {
-      await $`zip -r ../../${key}.zip *`.cwd(`dist/${key}/bin`)
+  // Calculate SHA values
+  const arm64Sha = await $`sha256sum ./dist/opencode-linux-arm64.tar.gz | cut -d' ' -f1`.text().then((x) => x.trim())
+  const x64Sha = await $`sha256sum ./dist/opencode-linux-x64.tar.gz | cut -d' ' -f1`.text().then((x) => x.trim())
+  const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
+  const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
+
+  const [pkgver, _subver = ""] = Script.version.split(/(-.*)/, 2)
+
+  // arch
+  const binaryPkgbuild = [
+    "# Maintainer: dax",
+    "# Maintainer: adam",
+    "",
+    "pkgname='opencode-bin'",
+    `pkgver=${pkgver}`,
+    `_subver=${_subver}`,
+    "options=('!debug' '!strip')",
+    "pkgrel=1",
+    "pkgdesc='The AI coding agent built for the terminal.'",
+    "url='https://github.com/anomalyco/opencode'",
+    "arch=('aarch64' 'x86_64')",
+    "license=('MIT')",
+    "provides=('opencode')",
+    "conflicts=('opencode')",
+    "depends=('ripgrep')",
+    "",
+    `source_aarch64=("\${pkgname}_\${pkgver}_aarch64.tar.gz::https://github.com/anomalyco/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-arm64.tar.gz")`,
+    `sha256sums_aarch64=('${arm64Sha}')`,
+
+    `source_x86_64=("\${pkgname}_\${pkgver}_x86_64.tar.gz::https://github.com/anomalyco/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-x64.tar.gz")`,
+    `sha256sums_x86_64=('${x64Sha}')`,
+    "",
+    "package() {",
+    '  install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode"',
+    "}",
+    "",
+  ].join("\n")
+
+  // Source-based PKGBUILD for opencode
+  const sourcePkgbuild = [
+    "# Maintainer: dax",
+    "# Maintainer: adam",
+    "",
+    "pkgname='opencode'",
+    `pkgver=${pkgver}`,
+    `_subver=${_subver}`,
+    "options=('!debug' '!strip')",
+    "pkgrel=1",
+    "pkgdesc='The AI coding agent built for the terminal.'",
+    "url='https://github.com/anomalyco/opencode'",
+    "arch=('aarch64' 'x86_64')",
+    "license=('MIT')",
+    "provides=('opencode')",
+    "conflicts=('opencode-bin')",
+    "depends=('ripgrep')",
+    "makedepends=('git' 'bun' 'go')",
+    "",
+    `source=("opencode-\${pkgver}.tar.gz::https://github.com/anomalyco/opencode/archive/v\${pkgver}\${_subver}.tar.gz")`,
+    `sha256sums=('SKIP')`,
+    "",
+    "build() {",
+    `  cd "opencode-\${pkgver}"`,
+    `  bun install`,
+    "  cd ./packages/opencode",
+    `  OPENCODE_CHANNEL=latest OPENCODE_VERSION=${pkgver} bun run ./script/build.ts --single`,
+    "}",
+    "",
+    "package() {",
+    `  cd "opencode-\${pkgver}/packages/opencode"`,
+    '  mkdir -p "${pkgdir}/usr/bin"',
+    '  target_arch="x64"',
+    '  case "$CARCH" in',
+    '    x86_64) target_arch="x64" ;;',
+    '    aarch64) target_arch="arm64" ;;',
+    '    *) printf "unsupported architecture: %s\\n" "$CARCH" >&2 ; return 1 ;;',
+    "  esac",
+    '  libc=""',
+    "  if command -v ldd >/dev/null 2>&1; then",
+    "    if ldd --version 2>&1 | grep -qi musl; then",
+    '      libc="-musl"',
+    "    fi",
+    "  fi",
+    '  if [ -z "$libc" ] && ls /lib/ld-musl-* >/dev/null 2>&1; then',
+    '    libc="-musl"',
+    "  fi",
+    '  base=""',
+    '  if [ "$target_arch" = "x64" ]; then',
+    "    if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then",
+    '      base="-baseline"',
+    "    fi",
+    "  fi",
+    '  bin="dist/opencode-linux-${target_arch}${base}${libc}/bin/opencode"',
+    '  if [ ! -f "$bin" ]; then',
+    '    printf "unable to find binary for %s%s%s\\n" "$target_arch" "$base" "$libc" >&2',
+    "    return 1",
+    "  fi",
+    '  install -Dm755 "$bin" "${pkgdir}/usr/bin/opencode"',
+    "}",
+    "",
+  ].join("\n")
+
+  for (const [pkg, pkgbuild] of [
+    ["opencode-bin", binaryPkgbuild],
+    ["opencode", sourcePkgbuild],
+  ]) {
+    for (let i = 0; i < 30; i++) {
+      try {
+        await $`rm -rf ./dist/aur-${pkg}`
+        await $`git clone ssh://[email protected]/${pkg}.git ./dist/aur-${pkg}`
+        await $`cd ./dist/aur-${pkg} && git checkout master`
+        await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild)
+        await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
+        await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
+        await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${Script.version}"`
+        await $`cd ./dist/aur-${pkg} && git push`
+        break
+      } catch (e) {
+        continue
+      }
     }
   }
 
-  const image = "ghcr.io/anomalyco/opencode"
-  const platforms = "linux/amd64,linux/arm64"
-  const tags = [`${image}:${Script.version}`, `${image}:latest`]
-  const tagFlags = tags.flatMap((t) => ["-t", t])
-  await $`docker buildx build --platform ${platforms} ${tagFlags} --push .`
+  // Homebrew formula
+  const homebrewFormula = [
+    "# typed: false",
+    "# frozen_string_literal: true",
+    "",
+    "# This file was generated by GoReleaser. DO NOT EDIT.",
+    "class Opencode < Formula",
+    `  desc "The AI coding agent built for the terminal."`,
+    `  homepage "https://github.com/anomalyco/opencode"`,
+    `  version "${Script.version.split("-")[0]}"`,
+    "",
+    `  depends_on "ripgrep"`,
+    "",
+    "  on_macos do",
+    "    if Hardware::CPU.intel?",
+    `      url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-darwin-x64.zip"`,
+    `      sha256 "${macX64Sha}"`,
+    "",
+    "      def install",
+    '        bin.install "opencode"',
+    "      end",
+    "    end",
+    "    if Hardware::CPU.arm?",
+    `      url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-darwin-arm64.zip"`,
+    `      sha256 "${macArm64Sha}"`,
+    "",
+    "      def install",
+    '        bin.install "opencode"',
+    "      end",
+    "    end",
+    "  end",
+    "",
+    "  on_linux do",
+    "    if Hardware::CPU.intel? and Hardware::CPU.is_64_bit?",
+    `      url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-linux-x64.tar.gz"`,
+    `      sha256 "${x64Sha}"`,
+    "      def install",
+    '        bin.install "opencode"',
+    "      end",
+    "    end",
+    "    if Hardware::CPU.arm? and Hardware::CPU.is_64_bit?",
+    `      url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-linux-arm64.tar.gz"`,
+    `      sha256 "${arm64Sha}"`,
+    "      def install",
+    '        bin.install "opencode"',
+    "      end",
+    "    end",
+    "  end",
+    "end",
+    "",
+    "",
+  ].join("\n")
+
+  await $`rm -rf ./dist/homebrew-tap`
+  await $`git clone https://${process.env["GITHUB_TOKEN"]}@github.com/sst/homebrew-tap.git ./dist/homebrew-tap`
+  await Bun.file("./dist/homebrew-tap/opencode.rb").write(homebrewFormula)
+  await $`cd ./dist/homebrew-tap && git add opencode.rb`
+  await $`cd ./dist/homebrew-tap && git commit -m "Update to v${Script.version}"`
+  await $`cd ./dist/homebrew-tap && git push`
 }

+ 28 - 4
packages/opencode/src/cli/cmd/stats.ts

@@ -28,6 +28,10 @@ interface SessionStats {
       tokens: {
         input: number
         output: number
+        cache: {
+          read: number
+          write: number
+        }
       }
       cost: number
     }
@@ -175,6 +179,10 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
           tokens: {
             input: number
             output: number
+            cache: {
+              read: number
+              write: number
+            }
           }
           cost: number
         }
@@ -188,7 +196,7 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
           if (!sessionModelUsage[modelKey]) {
             sessionModelUsage[modelKey] = {
               messages: 0,
-              tokens: { input: 0, output: 0 },
+              tokens: { input: 0, output: 0, cache: { read: 0, write: 0 } },
               cost: 0,
             }
           }
@@ -205,6 +213,8 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
             sessionModelUsage[modelKey].tokens.input += message.info.tokens.input || 0
             sessionModelUsage[modelKey].tokens.output +=
               (message.info.tokens.output || 0) + (message.info.tokens.reasoning || 0)
+            sessionModelUsage[modelKey].tokens.cache.read += message.info.tokens.cache?.read || 0
+            sessionModelUsage[modelKey].tokens.cache.write += message.info.tokens.cache?.write || 0
           }
         }
 
@@ -219,7 +229,12 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
         messageCount: messages.length,
         sessionCost,
         sessionTokens,
-        sessionTotalTokens: sessionTokens.input + sessionTokens.output + sessionTokens.reasoning,
+        sessionTotalTokens:
+          sessionTokens.input +
+          sessionTokens.output +
+          sessionTokens.reasoning +
+          sessionTokens.cache.read +
+          sessionTokens.cache.write,
         sessionToolUsage,
         sessionModelUsage,
         earliestTime: cutoffTime > 0 ? session.time.updated : session.time.created,
@@ -250,13 +265,15 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
         if (!stats.modelUsage[model]) {
           stats.modelUsage[model] = {
             messages: 0,
-            tokens: { input: 0, output: 0 },
+            tokens: { input: 0, output: 0, cache: { read: 0, write: 0 } },
             cost: 0,
           }
         }
         stats.modelUsage[model].messages += usage.messages
         stats.modelUsage[model].tokens.input += usage.tokens.input
         stats.modelUsage[model].tokens.output += usage.tokens.output
+        stats.modelUsage[model].tokens.cache.read += usage.tokens.cache.read
+        stats.modelUsage[model].tokens.cache.write += usage.tokens.cache.write
         stats.modelUsage[model].cost += usage.cost
       }
     }
@@ -270,7 +287,12 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
   }
   stats.days = effectiveDays
   stats.costPerDay = stats.totalCost / effectiveDays
-  const totalTokens = stats.totalTokens.input + stats.totalTokens.output + stats.totalTokens.reasoning
+  const totalTokens =
+    stats.totalTokens.input +
+    stats.totalTokens.output +
+    stats.totalTokens.reasoning +
+    stats.totalTokens.cache.read +
+    stats.totalTokens.cache.write
   stats.tokensPerSession = filteredSessions.length > 0 ? totalTokens / filteredSessions.length : 0
   sessionTotalTokens.sort((a, b) => a - b)
   const mid = Math.floor(sessionTotalTokens.length / 2)
@@ -337,6 +359,8 @@ export function displayStats(stats: SessionStats, toolLimit?: number, modelLimit
       console.log(renderRow("  Messages", usage.messages.toLocaleString()))
       console.log(renderRow("  Input Tokens", formatNumber(usage.tokens.input)))
       console.log(renderRow("  Output Tokens", formatNumber(usage.tokens.output)))
+      console.log(renderRow("  Cache Read", formatNumber(usage.tokens.cache.read)))
+      console.log(renderRow("  Cache Write", formatNumber(usage.tokens.cache.write)))
       console.log(renderRow("  Cost", `$${usage.cost.toFixed(4)}`))
       console.log("├────────────────────────────────────────────────────────┤")
     }

+ 7 - 0
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

@@ -93,8 +93,11 @@ export function Prompt(props: PromptProps) {
   let promptPartTypeId = 0
 
   sdk.event.on(TuiEvent.PromptAppend.type, (evt) => {
+    if (!input || input.isDestroyed) return
     input.insertText(evt.properties.text)
     setTimeout(() => {
+      // setTimeout is a workaround and needs to be addressed properly
+      if (!input || input.isDestroyed) return
       input.getLayoutNode().markDirty()
       input.gotoBufferEnd()
       renderer.requestRender()
@@ -924,6 +927,8 @@ export function Prompt(props: PromptProps) {
 
                 // Force layout update and render for the pasted content
                 setTimeout(() => {
+                  // setTimeout is a workaround and needs to be addressed properly
+                  if (!input || input.isDestroyed) return
                   input.getLayoutNode().markDirty()
                   renderer.requestRender()
                 }, 0)
@@ -935,6 +940,8 @@ export function Prompt(props: PromptProps) {
                 }
                 props.ref?.(ref)
                 setTimeout(() => {
+                  // setTimeout is a workaround and needs to be addressed properly
+                  if (!input || input.isDestroyed) return
                   input.cursorColor = theme.text
                 }, 0)
               }}

+ 2 - 3
packages/opencode/src/cli/cmd/tui/context/keybind.tsx

@@ -34,9 +34,8 @@ export const { use: useKeybind, provider: KeybindProvider } = createSimpleContex
         timeout = setTimeout(() => {
           if (!store.leader) return
           leader(false)
-          if (focus) {
-            focus.focus()
-          }
+          if (!focus || focus.isDestroyed) return
+          focus.focus()
         }, 2000)
         return
       }

+ 2 - 1
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

@@ -275,7 +275,8 @@ export function Session() {
 
   function toBottom() {
     setTimeout(() => {
-      if (scroll) scroll.scrollTo(scroll.scrollHeight)
+      if (!scroll || scroll.isDestroyed) return
+      scroll.scrollTo(scroll.scrollHeight)
     }, 50)
   }
 

+ 1 - 0
packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx

@@ -68,6 +68,7 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
   onMount(() => {
     dialog.setSize("medium")
     setTimeout(() => {
+      if (!textarea || textarea.isDestroyed) return
       textarea.focus()
     }, 1)
     textarea.gotoLineEnd()

+ 1 - 0
packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx

@@ -27,6 +27,7 @@ export function DialogPrompt(props: DialogPromptProps) {
   onMount(() => {
     dialog.setSize("medium")
     setTimeout(() => {
+      if (!textarea || textarea.isDestroyed) return
       textarea.focus()
     }, 1)
     textarea.gotoLineEnd()

+ 5 - 1
packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx

@@ -241,7 +241,11 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
             focusedTextColor={theme.textMuted}
             ref={(r) => {
               input = r
-              setTimeout(() => input.focus(), 1)
+              setTimeout(() => {
+                if (!input) return
+                if (input.isDestroyed) return
+                input.focus()
+              }, 1)
             }}
             placeholder={props.placeholder ?? "Search"}
           />

+ 7 - 0
packages/opencode/src/config/config.ts

@@ -540,6 +540,7 @@ export namespace Config {
           codesearch: PermissionAction.optional(),
           lsp: PermissionRule.optional(),
           doom_loop: PermissionAction.optional(),
+          skill: PermissionRule.optional(),
         })
         .catchall(PermissionRule)
         .or(PermissionAction),
@@ -559,6 +560,11 @@ export namespace Config {
   })
   export type Command = z.infer<typeof Command>
 
+  export const Skills = z.object({
+    paths: z.array(z.string()).optional().describe("Additional paths to skill folders"),
+  })
+  export type Skills = z.infer<typeof Skills>
+
   export const Agent = z
     .object({
       model: z.string().optional(),
@@ -894,6 +900,7 @@ export namespace Config {
         .record(z.string(), Command)
         .optional()
         .describe("Command configuration, see https://opencode.ai/docs/commands"),
+      skills: Skills.optional().describe("Additional skill folder paths"),
       watcher: z
         .object({
           ignore: z.array(z.string()).optional(),

+ 17 - 12
packages/opencode/src/config/markdown.ts

@@ -14,7 +14,9 @@ export namespace ConfigMarkdown {
     return Array.from(template.matchAll(SHELL_REGEX))
   }
 
-  export function preprocessFrontmatter(content: string): string {
+  // other coding agents like claude code allow invalid yaml in their
+  // frontmatter, we need to fallback to a more permissive parser for those cases
+  export function fallbackSanitization(content: string): string {
     const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
     if (!match) return content
 
@@ -53,7 +55,7 @@ export namespace ConfigMarkdown {
 
       // if value contains a colon, convert to block scalar
       if (value.includes(":")) {
-        result.push(`${key}: |`)
+        result.push(`${key}: |-`)
         result.push(`  ${value}`)
         continue
       }
@@ -66,20 +68,23 @@ export namespace ConfigMarkdown {
   }
 
   export async function parse(filePath: string) {
-    const raw = await Bun.file(filePath).text()
-    const template = preprocessFrontmatter(raw)
+    const template = await Bun.file(filePath).text()
 
     try {
       const md = matter(template)
       return md
-    } catch (err) {
-      throw new FrontmatterError(
-        {
-          path: filePath,
-          message: `${filePath}: Failed to parse YAML frontmatter: ${err instanceof Error ? err.message : String(err)}`,
-        },
-        { cause: err },
-      )
+    } catch {
+      try {
+        return matter(fallbackSanitization(template))
+      } catch (err) {
+        throw new FrontmatterError(
+          {
+            path: filePath,
+            message: `${filePath}: Failed to parse YAML frontmatter: ${err instanceof Error ? err.message : String(err)}`,
+          },
+          { cause: err },
+        )
+      }
     }
   }
 

+ 10 - 2
packages/opencode/src/file/ripgrep.ts

@@ -209,7 +209,10 @@ export namespace Ripgrep {
     hidden?: boolean
     follow?: boolean
     maxDepth?: number
+    signal?: AbortSignal
   }) {
+    input.signal?.throwIfAborted()
+
     const args = [await filepath(), "--files", "--glob=!.git/*"]
     if (input.follow !== false) args.push("--follow")
     if (input.hidden !== false) args.push("--hidden")
@@ -235,6 +238,7 @@ export namespace Ripgrep {
       stdout: "pipe",
       stderr: "ignore",
       maxBuffer: 1024 * 1024 * 20,
+      signal: input.signal,
     })
 
     const reader = proc.stdout.getReader()
@@ -243,6 +247,8 @@ export namespace Ripgrep {
 
     try {
       while (true) {
+        input.signal?.throwIfAborted()
+
         const { done, value } = await reader.read()
         if (done) break
 
@@ -261,11 +267,13 @@ export namespace Ripgrep {
       reader.releaseLock()
       await proc.exited
     }
+
+    input.signal?.throwIfAborted()
   }
 
-  export async function tree(input: { cwd: string; limit?: number }) {
+  export async function tree(input: { cwd: string; limit?: number; signal?: AbortSignal }) {
     log.info("tree", input)
-    const files = await Array.fromAsync(Ripgrep.files({ cwd: input.cwd }))
+    const files = await Array.fromAsync(Ripgrep.files({ cwd: input.cwd, signal: input.signal }))
     interface Node {
       path: string[]
       children: Node[]

+ 0 - 1
packages/opencode/src/flag/flag.ts

@@ -38,7 +38,6 @@ export namespace Flag {
   export const OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT = truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT")
   export const OPENCODE_ENABLE_EXA =
     truthy("OPENCODE_ENABLE_EXA") || OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA")
-  export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH = number("OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH")
   export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS = number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS")
   export const OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX = number("OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX")
   export const OPENCODE_EXPERIMENTAL_OXFMT = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_OXFMT")

+ 14 - 11
packages/opencode/src/plugin/copilot.ts

@@ -40,22 +40,25 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
               },
             }
 
+            // TODO: re-enable once messages api has higher rate limits
             // TODO: move some of this hacky-ness to models.dev presets once we have better grasp of things here...
-            const base = baseURL ?? model.api.url
-            const claude = model.id.includes("claude")
-            const url = iife(() => {
-              if (!claude) return base
-              if (base.endsWith("/v1")) return base
-              if (base.endsWith("/")) return `${base}v1`
-              return `${base}/v1`
-            })
-
-            model.api.url = url
-            model.api.npm = claude ? "@ai-sdk/anthropic" : "@ai-sdk/github-copilot"
+            // const base = baseURL ?? model.api.url
+            // const claude = model.id.includes("claude")
+            // const url = iife(() => {
+            //   if (!claude) return base
+            //   if (base.endsWith("/v1")) return base
+            //   if (base.endsWith("/")) return `${base}v1`
+            //   return `${base}/v1`
+            // })
+
+            // model.api.url = url
+            // model.api.npm = claude ? "@ai-sdk/anthropic" : "@ai-sdk/github-copilot"
+            model.api.npm = "@ai-sdk/github-copilot"
           }
         }
 
         return {
+          baseURL,
           apiKey: "",
           async fetch(request: RequestInfo | URL, init?: RequestInit) {
             const info = await getAuth()

+ 1 - 1
packages/opencode/src/provider/provider.ts

@@ -977,7 +977,7 @@ export namespace Provider {
           ...model.headers,
         }
 
-      const key = Bun.hash.xxHash32(JSON.stringify({ npm: model.api.npm, options }))
+      const key = Bun.hash.xxHash32(JSON.stringify({ providerID: model.providerID, npm: model.api.npm, options }))
       const existing = s.sdk.get(key)
       if (existing) return existing
 

+ 13 - 6
packages/opencode/src/provider/transform.ts

@@ -284,8 +284,8 @@ export namespace ProviderTransform {
     if (id.includes("glm-4.7")) return 1.0
     if (id.includes("minimax-m2")) return 1.0
     if (id.includes("kimi-k2")) {
-      // kimi-k2-thinking & kimi-k2.5
-      if (id.includes("thinking") || id.includes("k2.")) {
+      // kimi-k2-thinking & kimi-k2.5 && kimi-k2p5
+      if (id.includes("thinking") || id.includes("k2.") || id.includes("k2p")) {
         return 1.0
       }
       return 0.6
@@ -296,7 +296,7 @@ export namespace ProviderTransform {
   export function topP(model: Provider.Model) {
     const id = model.id.toLowerCase()
     if (id.includes("qwen")) return 1
-    if (id.includes("minimax-m2") || id.includes("kimi-k2.5") || id.includes("gemini")) {
+    if (id.includes("minimax-m2") || id.includes("kimi-k2.5") || id.includes("kimi-k2p5") || id.includes("gemini")) {
       return 0.95
     }
     return undefined
@@ -319,7 +319,14 @@ export namespace ProviderTransform {
     if (!model.capabilities.reasoning) return {}
 
     const id = model.id.toLowerCase()
-    if (id.includes("deepseek") || id.includes("minimax") || id.includes("glm") || id.includes("mistral")) return {}
+    if (
+      id.includes("deepseek") ||
+      id.includes("minimax") ||
+      id.includes("glm") ||
+      id.includes("mistral") ||
+      id.includes("kimi")
+    )
+      return {}
 
     // see: https://docs.x.ai/docs/guides/reasoning#control-how-hard-the-model-thinks
     if (id.includes("grok") && id.includes("grok-3-mini")) {
@@ -428,13 +435,13 @@ export namespace ProviderTransform {
           high: {
             thinking: {
               type: "enabled",
-              budgetTokens: 16000,
+              budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
             },
           },
           max: {
             thinking: {
               type: "enabled",
-              budgetTokens: 31999,
+              budgetTokens: Math.min(31_999, model.limit.output - 1),
             },
           },
         }

+ 1 - 1
packages/opencode/src/server/server.ts

@@ -539,7 +539,7 @@ export namespace Server {
           })
           response.headers.set(
             "Content-Security-Policy",
-            "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' data:",
+            "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:",
           )
           return response
         }) as unknown as Hono,

+ 14 - 0
packages/opencode/src/session/index.ts

@@ -99,6 +99,16 @@ export namespace Session {
     }
   }
 
+  function getForkedTitle(title: string): string {
+    const match = title.match(/^(.+) \(fork #(\d+)\)$/)
+    if (match) {
+      const base = match[1]
+      const num = parseInt(match[2], 10)
+      return `${base} (fork #${num + 1})`
+    }
+    return `${title} (fork #1)`
+  }
+
   export const Info = z
     .object({
       id: Identifier.schema("session"),
@@ -201,8 +211,12 @@ export namespace Session {
       messageID: Identifier.schema("message").optional(),
     }),
     async (input) => {
+      const original = await get(input.sessionID)
+      if (!original) throw new Error("session not found")
+      const title = getForkedTitle(original.title)
       const session = await createNext({
         directory: Instance.directory,
+        title,
       })
       const msgs = await messages({ sessionID: input.sessionID })
       const idMap = new Map<string, string>()

+ 15 - 15
packages/opencode/src/session/llm.ts

@@ -150,20 +150,14 @@ export namespace LLM {
       },
     )
 
-    const maxOutputTokens = isCodex ? undefined : undefined
-    log.info("max_output_tokens", {
-      tokens: ProviderTransform.maxOutputTokens(
-        input.model.api.npm,
-        params.options,
-        input.model.limit.output,
-        OUTPUT_TOKEN_MAX,
-      ),
-      modelOptions: params.options,
-      outputLimit: input.model.limit.output,
-    })
-    // tokens = 32000
-    // outputLimit = 64000
-    // modelOptions={"reasoningEffort":"minimal"}
+    const maxOutputTokens = isCodex
+      ? undefined
+      : ProviderTransform.maxOutputTokens(
+          input.model.api.npm,
+          params.options,
+          input.model.limit.output,
+          OUTPUT_TOKEN_MAX,
+        )
 
     const tools = await resolveTools(input)
 
@@ -270,7 +264,13 @@ export namespace LLM {
           extractReasoningMiddleware({ tagName: "think", startWithReasoning: false }),
         ],
       }),
-      experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
+      experimental_telemetry: {
+        isEnabled: cfg.experimental?.openTelemetry,
+        metadata: {
+          userId: cfg.username ?? "unknown",
+          sessionId: input.sessionID,
+        },
+      },
     })
   }
 

+ 21 - 0
packages/opencode/src/skill/skill.ts

@@ -1,5 +1,6 @@
 import z from "zod"
 import path from "path"
+import os from "os"
 import { Config } from "../config/config"
 import { Instance } from "../project/instance"
 import { NamedError } from "@opencode-ai/util/error"
@@ -40,6 +41,7 @@ export namespace Skill {
 
   const OPENCODE_SKILL_GLOB = new Bun.Glob("{skill,skills}/**/SKILL.md")
   const CLAUDE_SKILL_GLOB = new Bun.Glob("skills/**/SKILL.md")
+  const SKILL_GLOB = new Bun.Glob("**/SKILL.md")
 
   export const state = Instance.state(async () => {
     const skills: Record<string, Info> = {}
@@ -122,6 +124,25 @@ export namespace Skill {
       }
     }
 
+    // Scan additional skill paths from config
+    const config = await Config.get()
+    for (const skillPath of config.skills?.paths ?? []) {
+      const expanded = skillPath.startsWith("~/") ? path.join(os.homedir(), skillPath.slice(2)) : skillPath
+      const resolved = path.isAbsolute(expanded) ? expanded : path.join(Instance.directory, expanded)
+      if (!(await Filesystem.isDir(resolved))) {
+        log.warn("skill path not found", { path: resolved })
+        continue
+      }
+      for await (const match of SKILL_GLOB.scan({
+        cwd: resolved,
+        absolute: true,
+        onlyFiles: true,
+        followSymlinks: true,
+      })) {
+        await addSkill(match)
+      }
+    }
+
     return skills
   })
 

+ 1 - 0
packages/opencode/src/tool/glob.ts

@@ -38,6 +38,7 @@ export const GlobTool = Tool.define("glob", {
     for await (const file of Ripgrep.files({
       cwd: search,
       glob: [params.pattern],
+      signal: ctx.abort,
     })) {
       if (files.length >= limit) {
         truncated = true

+ 1 - 0
packages/opencode/src/tool/grep.ts

@@ -54,6 +54,7 @@ export const GrepTool = Tool.define("grep", {
     const proc = Bun.spawn([rgPath, ...args], {
       stdout: "pipe",
       stderr: "pipe",
+      signal: ctx.abort,
     })
 
     const output = await new Response(proc.stdout).text()

+ 1 - 1
packages/opencode/src/tool/ls.ts

@@ -56,7 +56,7 @@ export const ListTool = Tool.define("list", {
 
     const ignoreGlobs = IGNORE_PATTERNS.map((p) => `!${p}*`).concat(params.ignore?.map((p) => `!${p}`) || [])
     const files = []
-    for await (const file of Ripgrep.files({ cwd: searchPath, glob: ignoreGlobs })) {
+    for await (const file of Ripgrep.files({ cwd: searchPath, glob: ignoreGlobs, signal: ctx.abort })) {
       files.push(file)
       if (files.length >= LIMIT) break
     }

+ 2 - 3
packages/opencode/src/tool/skill.ts

@@ -62,12 +62,11 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
         always: [params.name],
         metadata: {},
       })
-      // Load and parse skill content
-      const parsed = await ConfigMarkdown.parse(skill.location)
+      const content = (await ConfigMarkdown.parse(skill.location)).content
       const dir = path.dirname(skill.location)
 
       // Format output similar to plugin pattern
-      const output = [`## Skill: ${skill.name}`, "", `**Base directory**: ${dir}`, "", parsed.content.trim()].join("\n")
+      const output = [`## Skill: ${skill.name}`, "", `**Base directory**: ${dir}`, "", content.trim()].join("\n")
 
       return {
         title: `Loaded skill: ${skill.name}`,

+ 11 - 0
packages/opencode/test/config/fixtures/markdown-header.md

@@ -0,0 +1,11 @@
+# Response Formatting Requirements
+
+Always structure your responses using clear markdown formatting:
+
+- By default don't put information into tables for questions (but do put information into tables when creating or updating files)
+- Use headings (##, ###) to organise sections, always
+- Use bullet points or numbered lists for multiple items
+- Use code blocks with language tags for any code
+- Use **bold** for key terms and emphasis
+- Use tables when comparing options or listing structured data
+- Break long responses into logical sections with headings

+ 13 - 0
packages/opencode/test/config/fixtures/weird-model-id.md

@@ -0,0 +1,13 @@
+---
+description: General coding and planning agent
+mode: subagent
+model: synthetic/hf:zai-org/GLM-4.7
+tools:
+  write: true
+  read: true
+  edit: true
+stuff: >
+  This is some stuff
+---
+
+Strictly follow da rules

+ 41 - 5
packages/opencode/test/config/markdown.test.ts

@@ -104,7 +104,7 @@ describe("ConfigMarkdown: frontmatter parsing", async () => {
   })
 
   test("should extract occupation field with colon in value", () => {
-    expect(parsed.data.occupation).toBe("This man has the following occupation: Software Engineer\n")
+    expect(parsed.data.occupation).toBe("This man has the following occupation: Software Engineer")
   })
 
   test("should extract title field with single quotes", () => {
@@ -128,15 +128,15 @@ describe("ConfigMarkdown: frontmatter parsing", async () => {
   })
 
   test("should extract URL with port", () => {
-    expect(parsed.data.url).toBe("https://example.com:8080/path?query=value\n")
+    expect(parsed.data.url).toBe("https://example.com:8080/path?query=value")
   })
 
   test("should extract time with colons", () => {
-    expect(parsed.data.time).toBe("The time is 12:30:00 PM\n")
+    expect(parsed.data.time).toBe("The time is 12:30:00 PM")
   })
 
   test("should extract value with multiple colons", () => {
-    expect(parsed.data.nested).toBe("First: Second: Third: Fourth\n")
+    expect(parsed.data.nested).toBe("First: Second: Third: Fourth")
   })
 
   test("should preserve already double-quoted values with colons", () => {
@@ -148,7 +148,7 @@ describe("ConfigMarkdown: frontmatter parsing", async () => {
   })
 
   test("should extract value with quotes and colons mixed", () => {
-    expect(parsed.data.mixed).toBe('He said "hello: world" and then left\n')
+    expect(parsed.data.mixed).toBe('He said "hello: world" and then left')
   })
 
   test("should handle empty values", () => {
@@ -190,3 +190,39 @@ describe("ConfigMarkdown: frontmatter parsing w/ no frontmatter", async () => {
     expect(result.content.trim()).toBe("Content")
   })
 })
+
+describe("ConfigMarkdown: frontmatter parsing w/ Markdown header", async () => {
+  const result = await ConfigMarkdown.parse(import.meta.dir + "/fixtures/markdown-header.md")
+
+  test("should parse and match", () => {
+    expect(result).toBeDefined()
+    expect(result.data).toEqual({})
+    expect(result.content.trim()).toBe(`# Response Formatting Requirements
+
+Always structure your responses using clear markdown formatting:
+
+- By default don't put information into tables for questions (but do put information into tables when creating or updating files)
+- Use headings (##, ###) to organise sections, always
+- Use bullet points or numbered lists for multiple items
+- Use code blocks with language tags for any code
+- Use **bold** for key terms and emphasis
+- Use tables when comparing options or listing structured data
+- Break long responses into logical sections with headings`)
+  })
+})
+
+describe("ConfigMarkdown: frontmatter has weird model id", async () => {
+  const result = await ConfigMarkdown.parse(import.meta.dir + "/fixtures/weird-model-id.md")
+
+  test("should parse and match", () => {
+    expect(result).toBeDefined()
+    expect(result.data["description"]).toEqual("General coding and planning agent")
+    expect(result.data["mode"]).toEqual("subagent")
+    expect(result.data["model"]).toEqual("synthetic/hf:zai-org/GLM-4.7")
+    expect(result.data["tools"]["write"]).toBeTrue()
+    expect(result.data["tools"]["read"]).toBeTrue()
+    expect(result.data["stuff"]).toBe("This is some stuff\n")
+
+    expect(result.content.trim()).toBe("Strictly follow da rules")
+  })
+})

+ 2 - 2
packages/opencode/test/provider/transform.test.ts

@@ -1056,8 +1056,8 @@ describe("ProviderTransform.variants", () => {
       cache: { read: 0.0001, write: 0.0002 },
     },
     limit: {
-      context: 128000,
-      output: 8192,
+      context: 200_000,
+      output: 64_000,
     },
     status: "active",
     options: {},

+ 1 - 1
packages/plugin/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/plugin",
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 4 - 0
packages/script/src/index.ts

@@ -20,6 +20,7 @@ const env = {
   OPENCODE_CHANNEL: process.env["OPENCODE_CHANNEL"],
   OPENCODE_BUMP: process.env["OPENCODE_BUMP"],
   OPENCODE_VERSION: process.env["OPENCODE_VERSION"],
+  OPENCODE_RELEASE: process.env["OPENCODE_RELEASE"],
 }
 const CHANNEL = await (async () => {
   if (env.OPENCODE_CHANNEL) return env.OPENCODE_CHANNEL
@@ -55,5 +56,8 @@ export const Script = {
   get preview() {
     return IS_PREVIEW
   },
+  get release() {
+    return env.OPENCODE_RELEASE
+  },
 }
 console.log(`opencode script`, JSON.stringify(Script, null, 2))

+ 1 - 1
packages/sdk/js/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/sdk",
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 0 - 3
packages/sdk/js/script/publish.ts

@@ -6,13 +6,10 @@ import { $ } from "bun"
 const dir = new URL("..", import.meta.url).pathname
 process.chdir(dir)
 
-await import("./build")
-
 const pkg = await import("../package.json").then((m) => m.default)
 const original = JSON.parse(JSON.stringify(pkg))
 for (const [key, value] of Object.entries(pkg.exports)) {
   const file = value.replace("./src/", "./dist/").replace(".ts", "")
-  /// @ts-expect-error
   pkg.exports[key] = {
     import: file + ".js",
     types: file + ".d.ts",

+ 10 - 0
packages/sdk/js/src/v2/gen/types.gen.ts

@@ -1364,6 +1364,7 @@ export type PermissionConfig =
       codesearch?: PermissionActionConfig
       lsp?: PermissionRuleConfig
       doom_loop?: PermissionActionConfig
+      skill?: PermissionRuleConfig
       [key: string]: PermissionRuleConfig | Array<string> | PermissionActionConfig | undefined
     }
   | PermissionActionConfig
@@ -1633,6 +1634,15 @@ export type Config = {
       subtask?: boolean
     }
   }
+  /**
+   * Additional skill folder paths
+   */
+  skills?: {
+    /**
+     * Additional paths to skill folders
+     */
+    paths?: Array<string>
+  }
   watcher?: {
     ignore?: Array<string>
   }

+ 16 - 0
packages/sdk/openapi.json

@@ -8994,6 +8994,9 @@
               },
               "doom_loop": {
                 "$ref": "#/components/schemas/PermissionActionConfig"
+              },
+              "skill": {
+                "$ref": "#/components/schemas/PermissionRuleConfig"
               }
             },
             "additionalProperties": {
@@ -9506,6 +9509,19 @@
               "required": ["template"]
             }
           },
+          "skills": {
+            "description": "Additional skill folder paths",
+            "type": "object",
+            "properties": {
+              "paths": {
+                "description": "Additional paths to skill folders",
+                "type": "array",
+                "items": {
+                  "type": "string"
+                }
+              }
+            }
+          },
           "watcher": {
             "type": "object",
             "properties": {

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/slack",
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/ui",
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "type": "module",
   "license": "MIT",
   "exports": {

+ 7 - 2
packages/ui/src/components/tabs.css

@@ -375,8 +375,13 @@
 
     &[data-variant="settings"] {
       [data-slot="tabs-list"] {
-        width: 200px;
-        min-width: 200px;
+        width: 150px;
+        min-width: 150px;
+
+        @media (min-width: 640px) {
+          width: 200px;
+          min-width: 200px;
+        }
         padding: 12px;
         gap: 0;
         background-color: var(--background-base);

+ 1 - 1
packages/util/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/util",
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "private": true,
   "type": "module",
   "license": "MIT",

+ 1 - 1
packages/web/package.json

@@ -2,7 +2,7 @@
   "name": "@opencode-ai/web",
   "type": "module",
   "license": "MIT",
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "scripts": {
     "dev": "astro dev",
     "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",

+ 5 - 1
packages/web/src/content/docs/cli.mdx

@@ -585,9 +585,13 @@ These environment variables enable experimental features that may change or be r
 | `OPENCODE_EXPERIMENTAL`                         | boolean | Enable all experimental features        |
 | `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY`          | boolean | Enable icon discovery                   |
 | `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT`  | boolean | Disable copy on select in TUI           |
-| `OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH`  | number  | Max output length for bash commands     |
 | `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number  | Default timeout for bash commands in ms |
 | `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX`        | number  | Max output tokens for LLM responses     |
 | `OPENCODE_EXPERIMENTAL_FILEWATCHER`             | boolean | Enable file watcher for entire dir      |
 | `OPENCODE_EXPERIMENTAL_OXFMT`                   | boolean | Enable oxfmt formatter                  |
 | `OPENCODE_EXPERIMENTAL_LSP_TOOL`                | boolean | Enable experimental LSP tool            |
+| `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | Disable file watcher                    |
+| `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | Enable experimental Exa features        |
+| `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | Enable experimental LSP type checking   |
+| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolean | Enable experimental markdown features   |
+| `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | Enable plan mode                        |

+ 8 - 0
packages/web/src/content/docs/zen.mdx

@@ -82,7 +82,9 @@ You can also access our models through the following API endpoints.
 | Gemini 3 Pro       | gemini-3-pro       | `https://opencode.ai/zen/v1/models/gemini-3-pro`   | `@ai-sdk/google`            |
 | Gemini 3 Flash     | gemini-3-flash     | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google`            |
 | MiniMax M2.1       | minimax-m2.1       | `https://opencode.ai/zen/v1/chat/completions`      | `@ai-sdk/openai-compatible` |
+| MiniMax M2.1 Free  | minimax-m2.1-free  | `https://opencode.ai/zen/v1/messages`              | `@ai-sdk/anthropic`         |
 | GLM 4.7            | glm-4.7            | `https://opencode.ai/zen/v1/chat/completions`      | `@ai-sdk/openai-compatible` |
+| GLM 4.7 Free       | glm-4.7-free       | `https://opencode.ai/zen/v1/chat/completions`      | `@ai-sdk/openai-compatible` |
 | GLM 4.6            | glm-4.6            | `https://opencode.ai/zen/v1/chat/completions`      | `@ai-sdk/openai-compatible` |
 | Kimi K2.5          | kimi-k2.5          | `https://opencode.ai/zen/v1/chat/completions`      | `@ai-sdk/openai-compatible` |
 | Kimi K2 Thinking   | kimi-k2-thinking   | `https://opencode.ai/zen/v1/chat/completions`      | `@ai-sdk/openai-compatible` |
@@ -113,7 +115,9 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
 | Model                             | Input  | Output | Cached Read | Cached Write |
 | --------------------------------- | ------ | ------ | ----------- | ------------ |
 | Big Pickle                        | Free   | Free   | Free        | -            |
+| MiniMax M2.1 Free                 | Free   | Free   | Free        | -            |
 | MiniMax M2.1                      | $0.30  | $1.20  | $0.10       | -            |
+| GLM 4.7 Free                      | Free   | Free   | Free        | -            |
 | GLM 4.7                           | $0.60  | $2.20  | $0.10       | -            |
 | GLM 4.6                           | $0.60  | $2.20  | $0.10       | -            |
 | Kimi K2.5                         | $0.60  | $3.00  | $0.10       | -            |
@@ -149,6 +153,8 @@ Credit card fees are passed along at cost (4.4% + $0.30 per transaction); we don
 
 The free models:
 
+- GLM 4.7 is currently free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
+- MiniMax M2.1 is currently free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
 - Big Pickle is a stealth model that's free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
 
 <a href={email}>Contact us</a> if you have any questions.
@@ -178,6 +184,8 @@ charging you more than $20 if your balance goes below $5.
 
 All our models are hosted in the US. Our providers follow a zero-retention policy and do not use your data for model training, with the following exceptions:
 
+- GLM 4.7: During its free period, collected data may be used to improve the model.
+- MiniMax M2.1: During its free period, collected data may be used to improve the model.
 - Big Pickle: During its free period, collected data may be used to improve the model.
 - OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
 - Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).

+ 1 - 0
script/changelog.ts

@@ -15,6 +15,7 @@ export const team = [
   "adamdotdevin",
   "iamdavidhill",
   "opencode-agent[bot]",
+  "R44VC0RP",
 ]
 
 export async function getLatestRelease() {

+ 0 - 14
script/publish-complete.ts

@@ -1,14 +0,0 @@
-#!/usr/bin/env bun
-
-import { Script } from "@opencode-ai/script"
-import { $ } from "bun"
-
-if (!Script.preview) {
-  await $`gh release edit v${Script.version} --draft=false`
-}
-
-await $`bun install`
-
-await $`gh release download --pattern "opencode-linux-*64.tar.gz" --pattern "opencode-darwin-*64.zip" -D dist`
-
-await import(`../packages/opencode/script/publish-registries.ts`)

+ 16 - 30
script/publish-start.ts → script/publish.ts

@@ -4,8 +4,7 @@ import { $ } from "bun"
 import { Script } from "@opencode-ai/script"
 import { buildNotes, getLatestRelease } from "./changelog"
 
-const highlightsTemplate = `## Highlights
-
+const highlightsTemplate = `
 <!--
 Add highlights before publishing. Delete this section if no highlights.
 
@@ -33,16 +32,8 @@ Add highlights before publishing. Delete this section if no highlights.
 
 `
 
-let notes: string[] = []
-
 console.log("=== publishing ===\n")
 
-if (!Script.preview) {
-  const previous = await getLatestRelease()
-  notes = await buildNotes(previous, "HEAD")
-  // notes.unshift(highlightsTemplate)
-}
-
 const pkgjsons = await Array.fromAsync(
   new Bun.Glob("**/package.json").scan({
     absolute: true,
@@ -64,8 +55,22 @@ console.log("updated:", extensionToml)
 await Bun.file(extensionToml).write(toml)
 
 await $`bun install`
+await import(`../packages/sdk/js/script/build.ts`)
 
-console.log("\n=== opencode ===\n")
+if (Script.release) {
+  const previous = await getLatestRelease()
+  const notes = await buildNotes(previous, "HEAD")
+  // notes.unshift(highlightsTemplate)
+  await $`git commit -am "release: v${Script.version}"`
+  await $`git tag v${Script.version}`
+  await $`git fetch origin`
+  await $`git cherry-pick HEAD..origin/dev`.nothrow()
+  await $`git push origin HEAD --tags --no-verify --force-with-lease`
+  await new Promise((resolve) => setTimeout(resolve, 5_000))
+  await $`gh release edit v${Script.version} --draft=false --title "v${Script.version}" --notes ${notes.join("\n") || "No notable changes"}`
+}
+
+console.log("\n=== cli ===\n")
 await import(`../packages/opencode/script/publish.ts`)
 
 console.log("\n=== sdk ===\n")
@@ -76,22 +81,3 @@ await import(`../packages/plugin/script/publish.ts`)
 
 const dir = new URL("..", import.meta.url).pathname
 process.chdir(dir)
-
-let output = `version=${Script.version}\n`
-
-if (!Script.preview) {
-  await $`git commit -am "release: v${Script.version}"`
-  await $`git tag v${Script.version}`
-  await $`git fetch origin`
-  await $`git cherry-pick HEAD..origin/dev`.nothrow()
-  await $`git push origin HEAD --tags --no-verify --force-with-lease`
-  await new Promise((resolve) => setTimeout(resolve, 5_000))
-  await $`gh release create v${Script.version} -d --title "v${Script.version}" --notes ${notes.join("\n") || "No notable changes"} ./packages/opencode/dist/*.zip ./packages/opencode/dist/*.tar.gz`
-  const release = await $`gh release view v${Script.version} --json id,tagName`.json()
-  output += `release=${release.id}\n`
-  output += `tag=${release.tagName}\n`
-}
-
-if (process.env.GITHUB_OUTPUT) {
-  await Bun.write(process.env.GITHUB_OUTPUT, output)
-}

+ 17 - 0
script/version.ts

@@ -0,0 +1,17 @@
+#!/usr/bin/env bun
+
+import { Script } from "@opencode-ai/script"
+import { $ } from "bun"
+
+let output = [`version=${Script.version}`]
+
+if (!Script.preview) {
+  await $`gh release create v${Script.version} -d --title "v${Script.version}" ${Script.preview ? "--prerelease" : ""}`
+  const release = await $`gh release view v${Script.version} --json id,tagName`.json()
+  output.push(`release=${release.id}`)
+  output.push(`tag=${release.tagName}`)
+}
+
+if (process.env.GITHUB_OUTPUT) {
+  await Bun.write(process.env.GITHUB_OUTPUT, output.join("\n"))
+}

+ 1 - 1
sdks/vscode/package.json

@@ -2,7 +2,7 @@
   "name": "opencode",
   "displayName": "opencode",
   "description": "opencode for VS Code",
-  "version": "1.1.40",
+  "version": "0.0.0-ci-202601291718",
   "publisher": "sst-dev",
   "repository": {
     "type": "git",