diff --git a/.github/workflows/contributors-label.yml b/.github/workflows/contributors-label.yml new file mode 100644 index 0000000000..e97c5c4704 --- /dev/null +++ b/.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'] + }); + } diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8d7a823b14..2966ceb66f 100644 --- a/.github/workflows/publish.yml +++ b/.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 + 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 }} - - run: git fetch --force --tags + build-cli: + needs: version + runs-on: blacksmith-4vcpu-ubuntu-2404 + if: github.repository == 'anomalyco/opencode' + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 1 + fetch-tags: true - uses: ./.github/actions/setup-bun - - name: Install OpenCode - if: inputs.bump || inputs.version - run: bun i -g opencode-ai@1.0.169 - - - 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 + - name: Build + id: build run: | - git config --global user.email "opencode@sst.dev" - 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 "opencode@sst.dev" + 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 diff --git a/STATS.md b/STATS.md index 01be7f3d20..44819a6eb8 100644 --- a/STATS.md +++ b/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) | diff --git a/bun.lock b/bun.lock index 2651cd4943..67d44b1043 100644 --- a/bun.lock +++ b/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/cli-win32-x64-msvc@2.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-EdYd4c9wGvtPB95kqtEyY+bUR+k4kRw3IA30mAQ1jPH6z57AftT8q84qwv0RDp6kkEqOBKxeInKfqi4BESYuqg=="], + "@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.6", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-UUOSt0U5juK20uhO2MoHZX/IPblkrhUh+VPtIeu3RwtzI0R9Em3Auzfg/PwcZ9Pv8mLne3cQ4p9CFXD6WxqCZA=="], + "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.4.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-lNIn5CZuw8WZOn8zHzmFmDSzg5zfohWoa3mdULP0YFh/VogVdMVWZPcWSHlydsiJhRQYaTNSYKN7RmZKE2lCYQ=="], "@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.4", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-/i4U/9za3mrytTgfRn5RHneKubZE/dwRmshYwyMvNRlkWjvu1m4Ma72kcbVJMZFGXpkbl+qLyWMGrihtWB76Zg=="], diff --git a/nix/hashes.json b/nix/hashes.json index 9d2bb999d6..8e95135aa2 100644 --- a/nix/hashes.json +++ b/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=" } } diff --git a/packages/app/package.json b/packages/app/package.json index 887b6498cc..61e7edcd16 100644 --- a/packages/app/package.json +++ b/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": { diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index ba0d1e7aa4..11fdb57432 100644 --- a/packages/app/src/app.tsx +++ b/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[] } } } diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 180a99c739..3b08652bbb 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -130,7 +130,7 @@ export const SettingsGeneral: Component = () => { const soundOptions = [...SOUND_OPTIONS] return ( -
+

{language.t("settings.tab.general")}

@@ -406,8 +406,8 @@ interface SettingsRowProps { const SettingsRow: Component = (props) => { return ( -
-
+
+
{props.title} {props.description}
diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx index a2cd3280c8..393da0c2ab 100644 --- a/packages/app/src/components/settings-keybinds.tsx +++ b/packages/app/src/components/settings-keybinds.tsx @@ -352,7 +352,7 @@ export const SettingsKeybinds: Component = () => { }) return ( -
+
diff --git a/packages/app/src/components/settings-models.tsx b/packages/app/src/components/settings-models.tsx index 0cb6deee1f..1807d561ea 100644 --- a/packages/app/src/components/settings-models.tsx +++ b/packages/app/src/components/settings-models.tsx @@ -39,7 +39,7 @@ export const SettingsModels: Component = () => { }) return ( -
+

{language.t("settings.models.title")}

@@ -99,7 +99,7 @@ export const SettingsModels: Component = () => { {(item) => { const key = { providerID: item.provider.id, modelID: item.id } return ( -
+
{item.name}
diff --git a/packages/app/src/components/settings-permissions.tsx b/packages/app/src/components/settings-permissions.tsx index bcbf7b68fa..7dd43a7075 100644 --- a/packages/app/src/components/settings-permissions.tsx +++ b/packages/app/src/components/settings-permissions.tsx @@ -176,13 +176,13 @@ export const SettingsPermissions: Component = () => { return (
-
+

{language.t("settings.permissions.title")}

{language.t("settings.permissions.description")}

-
+

{language.t("settings.permissions.section.tools")}

@@ -217,8 +217,8 @@ interface SettingsRowProps { const SettingsRow: Component = (props) => { return ( -
-
+
+
{props.title} {props.description}
diff --git a/packages/app/src/components/settings-providers.tsx b/packages/app/src/components/settings-providers.tsx index abc2bee77d..aa43cee4f2 100644 --- a/packages/app/src/components/settings-providers.tsx +++ b/packages/app/src/components/settings-providers.tsx @@ -115,7 +115,7 @@ export const SettingsProviders: Component = () => { } return ( -
+

{language.t("settings.providers.title")}

@@ -136,7 +136,7 @@ export const SettingsProviders: Component = () => { > {(item) => ( -
+
{item.name} @@ -166,7 +166,7 @@ export const SettingsProviders: Component = () => {
{(item) => ( -
+
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index afef14c84a..73480e8f20 100644 --- a/packages/app/src/pages/layout.tsx +++ b/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) { diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 87ebf6db72..b346fa6928 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -2183,7 +2183,62 @@ export default function Page() {
- + { + 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) + }) + }} + > { 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): Platform => ({ platform: "desktop", os: (() => { @@ -332,6 +349,7 @@ const createPlatform = (password: Accessor): Platform => ({ }) createMenu() +void listenForDeepLinks() render(() => { const [serverPassword, setServerPassword] = createSignal(null) diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 6eef4b810c..996e7ed943 100644 --- a/packages/enterprise/package.json +++ b/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", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 26d8045edb..4e0db0d712 100644 --- a/packages/extensions/zed/extension.toml +++ b/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"] diff --git a/packages/function/package.json b/packages/function/package.json index c48548cb76..a0d333a349 100644 --- a/packages/function/package.json +++ b/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", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 15d61f2a64..aa9a77fad8 100644 --- a/packages/opencode/package.json +++ b/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", diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index 06ed45e4d2..637fb983fa 100755 --- a/packages/opencode/script/build.ts +++ b/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 } diff --git a/packages/opencode/script/publish-registries.ts b/packages/opencode/script/publish-registries.ts deleted file mode 100644 index efcf4f4c6f..0000000000 --- a/packages/opencode/script/publish-registries.ts +++ /dev/null @@ -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://aur@aur.archlinux.org/${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` -} diff --git a/packages/opencode/script/publish.ts b/packages/opencode/script/publish.ts index 4e5846d27e..8cdeb35b40 100755 --- a/packages/opencode/script/publish.ts +++ b/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 = {} +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://aur@aur.archlinux.org/${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` } diff --git a/packages/opencode/src/cli/cmd/stats.ts b/packages/opencode/src/cli/cmd/stats.ts index 1f7263b325..04c1fe2ebc 100644 --- a/packages/opencode/src/cli/cmd/stats.ts +++ b/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("├────────────────────────────────────────────────────────┤") } diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index e19c8b7098..caa1303229 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/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) }} diff --git a/packages/opencode/src/cli/cmd/tui/context/keybind.tsx b/packages/opencode/src/cli/cmd/tui/context/keybind.tsx index 4c82e594c3..0dbbbc6f9e 100644 --- a/packages/opencode/src/cli/cmd/tui/context/keybind.tsx +++ b/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 } diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 496757d32c..693043450c 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/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) } diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx index 90699e1f0b..867ed68100 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx +++ b/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() diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx index 1b9acb5898..b296524124 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx +++ b/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() diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx index 8ff5a3b23b..bd1de7d4de 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx @@ -241,7 +241,11 @@ export function DialogSelect(props: DialogSelectProps) { 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"} /> diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 8c65726e23..adf733e322 100644 --- a/packages/opencode/src/config/config.ts +++ b/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 + export const Skills = z.object({ + paths: z.array(z.string()).optional().describe("Additional paths to skill folders"), + }) + export type Skills = z.infer + 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(), diff --git a/packages/opencode/src/config/markdown.ts b/packages/opencode/src/config/markdown.ts index d1eeeac382..4cd17746c5 100644 --- a/packages/opencode/src/config/markdown.ts +++ b/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 }, + ) + } } } diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 0d18173565..0f6889779a 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/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[] diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 9dea041e46..9084bf4443 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/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") diff --git a/packages/opencode/src/plugin/copilot.ts b/packages/opencode/src/plugin/copilot.ts index 51f29db5ed..ef41ee38d3 100644 --- a/packages/opencode/src/plugin/copilot.ts +++ b/packages/opencode/src/plugin/copilot.ts @@ -40,22 +40,25 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { }, } + // 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` - }) + // 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.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() diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index ee7ee75c9f..27ff5475db 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/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 diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 39eef6c916..6ca089c2f0 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/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), }, }, } diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 7d2023fc0e..b2cd7246a5 100644 --- a/packages/opencode/src/server/server.ts +++ b/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, diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 0f8a16f741..2d506e2e96 100644 --- a/packages/opencode/src/session/index.ts +++ b/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() diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index d651308032..4c4f4114a2 100644 --- a/packages/opencode/src/session/llm.ts +++ b/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, + }, + }, }) } diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index 12fc9ee90c..5b300a9287 100644 --- a/packages/opencode/src/skill/skill.ts +++ b/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 = {} @@ -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 }) diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts index dda57f6ee1..6943795f88 100644 --- a/packages/opencode/src/tool/glob.ts +++ b/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 diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts index 097dedf4aa..6cb70d022e 100644 --- a/packages/opencode/src/tool/grep.ts +++ b/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() diff --git a/packages/opencode/src/tool/ls.ts b/packages/opencode/src/tool/ls.ts index cc3d750078..b848e969b7 100644 --- a/packages/opencode/src/tool/ls.ts +++ b/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 } diff --git a/packages/opencode/src/tool/skill.ts b/packages/opencode/src/tool/skill.ts index 9536685ef9..76d9fd535e 100644 --- a/packages/opencode/src/tool/skill.ts +++ b/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}`, diff --git a/packages/opencode/test/config/fixtures/markdown-header.md b/packages/opencode/test/config/fixtures/markdown-header.md new file mode 100644 index 0000000000..d5af1f1c23 --- /dev/null +++ b/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 diff --git a/packages/opencode/test/config/fixtures/weird-model-id.md b/packages/opencode/test/config/fixtures/weird-model-id.md new file mode 100644 index 0000000000..bb02b0650f --- /dev/null +++ b/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 diff --git a/packages/opencode/test/config/markdown.test.ts b/packages/opencode/test/config/markdown.test.ts index b4263ee6b5..c6133317e2 100644 --- a/packages/opencode/test/config/markdown.test.ts +++ b/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") + }) +}) diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 037083d5e3..1d69a2a295 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/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: {}, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 574f17a7f4..36816fe1c9 100644 --- a/packages/plugin/package.json +++ b/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": { diff --git a/packages/script/src/index.ts b/packages/script/src/index.ts index e722ba5094..2d991ff0c3 100644 --- a/packages/script/src/index.ts +++ b/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)) diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 4b4faebab2..ccd0b6a945 100644 --- a/packages/sdk/js/package.json +++ b/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": { diff --git a/packages/sdk/js/script/publish.ts b/packages/sdk/js/script/publish.ts index 79701b50e7..46dd42b700 100755 --- a/packages/sdk/js/script/publish.ts +++ b/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", diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 12c7bf7dfd..8555e84384 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/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 | PermissionActionConfig | undefined } | PermissionActionConfig @@ -1633,6 +1634,15 @@ export type Config = { subtask?: boolean } } + /** + * Additional skill folder paths + */ + skills?: { + /** + * Additional paths to skill folders + */ + paths?: Array + } watcher?: { ignore?: Array } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 0c60bdd407..dc2e51e5fb 100644 --- a/packages/sdk/openapi.json +++ b/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": { diff --git a/packages/slack/package.json b/packages/slack/package.json index 0ac4e9b07b..dff46642af 100644 --- a/packages/slack/package.json +++ b/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": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 7cba4c66f9..be261a318a 100644 --- a/packages/ui/package.json +++ b/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": { diff --git a/packages/ui/src/components/tabs.css b/packages/ui/src/components/tabs.css index 6b57f0c04b..56c3e083f5 100644 --- a/packages/ui/src/components/tabs.css +++ b/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); diff --git a/packages/util/package.json b/packages/util/package.json index f8360df7ed..40a6080cbf 100644 --- a/packages/util/package.json +++ b/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", diff --git a/packages/web/package.json b/packages/web/package.json index c3b8d0b750..666654a2ef 100644 --- a/packages/web/package.json +++ b/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", diff --git a/packages/web/src/content/docs/cli.mdx b/packages/web/src/content/docs/cli.mdx index d1d11ed70d..7fb948f505 100644 --- a/packages/web/src/content/docs/cli.mdx +++ b/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 | diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 9fc732d057..ae7a982964 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/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. Contact us 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). diff --git a/script/changelog.ts b/script/changelog.ts index 388c097302..ace579ee4b 100755 --- a/script/changelog.ts +++ b/script/changelog.ts @@ -15,6 +15,7 @@ export const team = [ "adamdotdevin", "iamdavidhill", "opencode-agent[bot]", + "R44VC0RP", ] export async function getLatestRelease() { diff --git a/script/publish-complete.ts b/script/publish-complete.ts deleted file mode 100755 index a3bdceae07..0000000000 --- a/script/publish-complete.ts +++ /dev/null @@ -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`) diff --git a/script/publish-start.ts b/script/publish.ts similarity index 77% rename from script/publish-start.ts rename to script/publish.ts index 385a2384bc..23a6e5c9df 100755 --- a/script/publish-start.ts +++ b/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 = `