diff --git a/.github/workflows/auto-label-tui.yml b/.github/workflows/auto-label-tui.yml deleted file mode 100644 index c2f81a38011..00000000000 --- a/.github/workflows/auto-label-tui.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Auto-label TUI Issues - -on: - issues: - types: [opened] - -jobs: - auto-label: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - issues: write - steps: - - name: Auto-label and assign issues - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const issue = context.payload.issue; - const title = issue.title; - const description = issue.body || ''; - - // Check for "opencode web" keyword - const webPattern = /(opencode web)/i; - const isWebRelated = webPattern.test(title) || webPattern.test(description); - - // Check for version patterns like v1.0.x or 1.0.x - const versionPattern = /[v]?1\.0\./i; - const isVersionRelated = versionPattern.test(title) || versionPattern.test(description); - - // Check for "nix" keyword - const nixPattern = /\bnix\b/i; - const isNixRelated = nixPattern.test(title) || nixPattern.test(description); - - const labels = []; - - if (isWebRelated) { - labels.push('web'); - - // Assign to adamdotdevin - await github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - assignees: ['adamdotdevin'] - }); - } else if (isVersionRelated) { - // Only add opentui if NOT web-related - labels.push('opentui'); - } - - if (isNixRelated) { - labels.push('nix'); - } - - if (labels.length > 0) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - labels: labels - }); - } diff --git a/.github/workflows/docs-update.yml b/.github/workflows/docs-update.yml new file mode 100644 index 00000000000..a8dd2ae4f2b --- /dev/null +++ b/.github/workflows/docs-update.yml @@ -0,0 +1,72 @@ +name: Docs Update + +on: + schedule: + - cron: "0 */12 * * *" + workflow_dispatch: + +env: + LOOKBACK_HOURS: 4 + +jobs: + update-docs: + if: github.repository == 'sst/opencode' + runs-on: blacksmith-4vcpu-ubuntu-2404 + permissions: + id-token: write + contents: write + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch full history to access commits + + - name: Setup Bun + uses: ./.github/actions/setup-bun + + - name: Get recent commits + id: commits + run: | + COMMITS=$(git log --since="${{ env.LOOKBACK_HOURS }} hours ago" --pretty=format:"- %h %s" 2>/dev/null || echo "") + if [ -z "$COMMITS" ]; then + echo "No commits in the last ${{ env.LOOKBACK_HOURS }} hours" + echo "has_commits=false" >> $GITHUB_OUTPUT + else + echo "has_commits=true" >> $GITHUB_OUTPUT + { + echo "list<> $GITHUB_OUTPUT + fi + + - name: Run opencode + if: steps.commits.outputs.has_commits == 'true' + uses: sst/opencode/github@latest + env: + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + with: + model: opencode/gpt-5.2 + agent: docs + prompt: | + Review the following commits from the last ${{ env.LOOKBACK_HOURS }} hours and identify any new features that may need documentation. + + + ${{ steps.commits.outputs.list }} + + + Steps: + 1. For each commit that looks like a new feature or significant change: + - Read the changed files to understand what was added + - Check if the feature is already documented in packages/web/src/content/docs/* + 2. If you find undocumented features: + - Update the relevant documentation files in packages/web/src/content/docs/* + - Follow the existing documentation style and structure + - Make sure to document the feature clearly with examples where appropriate + 3. If all new features are already documented, report that no updates are needed + 4. If you are creating a new documentation file be sure to update packages/web/astro.config.mjs too. + + Focus on user-facing features and API changes. Skip internal refactors, bug fixes, and test updates unless they affect user-facing behavior. + Don't feel the need to document every little thing. It is perfectly okay to make 0 changes at all. + Try to keep documentation only for large features or changes that already have a good spot to be documented. diff --git a/.github/workflows/duplicate-issues.yml b/.github/workflows/duplicate-issues.yml index 5969d9d41e4..dc82d297bd1 100644 --- a/.github/workflows/duplicate-issues.yml +++ b/.github/workflows/duplicate-issues.yml @@ -16,6 +16,8 @@ jobs: with: fetch-depth: 1 + - uses: ./.github/actions/setup-bun + - name: Install opencode run: curl -fsSL https://opencode.ai/install | bash diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml deleted file mode 100644 index f353d4c8f03..00000000000 --- a/.github/workflows/format.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: format - -on: - push: - branches-ignore: - - production - pull_request: - branches-ignore: - - production - workflow_dispatch: -jobs: - format: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - ref: ${{ github.event.pull_request.head.ref || github.ref_name }} - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: run - run: | - ./script/format.ts - env: - CI: true - PUSH_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }} diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml new file mode 100644 index 00000000000..29cc9895393 --- /dev/null +++ b/.github/workflows/generate.yml @@ -0,0 +1,51 @@ +name: generate + +on: + push: + branches: + - dev + workflow_dispatch: + +jobs: + generate: + runs-on: blacksmith-4vcpu-ubuntu-2404 + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + ref: ${{ github.event.pull_request.head.ref || github.ref_name }} + + - name: Setup Bun + uses: ./.github/actions/setup-bun + + - name: Generate + run: ./script/generate.ts + + - name: Commit and push + run: | + if [ -z "$(git status --porcelain)" ]; then + echo "No changes to commit" + exit 0 + fi + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add -A + git commit -m "chore: generate" + git push origin HEAD:${{ github.ref_name }} --no-verify + # if ! git push origin HEAD:${{ github.event.pull_request.head.ref || github.ref_name }} --no-verify; then + # echo "" + # echo "============================================" + # echo "Failed to push generated code." + # echo "Please run locally and push:" + # echo "" + # echo " ./script/generate.ts" + # echo " git add -A && git commit -m \"chore: generate\" && git push" + # echo "" + # echo "============================================" + # exit 1 + # fi diff --git a/.github/workflows/notify-discord.yml b/.github/workflows/notify-discord.yml index d12cc7d733d..62577ecf00e 100644 --- a/.github/workflows/notify-discord.yml +++ b/.github/workflows/notify-discord.yml @@ -2,7 +2,7 @@ name: discord on: release: - types: [published] # fires only when a release is published + types: [released] # fires when a draft release is published jobs: notify: diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml index 44c8d4a5837..37210191e39 100644 --- a/.github/workflows/opencode.yml +++ b/.github/workflows/opencode.yml @@ -29,5 +29,6 @@ jobs: uses: sst/opencode/github@latest env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + OPENCODE_PERMISSION: '{"bash": "deny"}' with: - model: opencode/claude-haiku-4-5 + model: opencode/claude-opus-4-5 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 36a36ffe221..ec98d7061b3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,11 +2,15 @@ name: publish run-name: "${{ format('release {0}', inputs.bump) }}" on: + push: + branches: + - dev + - snapshot-* workflow_dispatch: inputs: bump: description: "Bump major, minor, or patch" - required: true + required: false type: choice options: - major @@ -17,16 +21,17 @@ on: required: false type: string -concurrency: ${{ github.workflow }}-${{ github.ref }} +concurrency: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.version || inputs.bump }} permissions: + id-token: write contents: write packages: write jobs: publish: runs-on: blacksmith-4vcpu-ubuntu-2404 - if: github.repository == 'sst/opencode' && github.ref == 'refs/heads/dev' + if: github.repository == 'sst/opencode' steps: - uses: actions/checkout@v3 with: @@ -34,33 +39,11 @@ jobs: - run: git fetch --force --tags - - uses: actions/setup-go@v5 - with: - go-version: ">=1.24.0" - cache: true - cache-dependency-path: go.sum - - uses: ./.github/actions/setup-bun - - name: Install makepkg - run: | - sudo apt-get update - sudo apt-get install -y pacman-package-manager - - name: Setup SSH for AUR - run: | - mkdir -p ~/.ssh - echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - git config --global user.email "opencode@sst.dev" - git config --global user.name "opencode" - ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true - - name: Install OpenCode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Setup npm auth - run: | - echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc + 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 @@ -69,35 +52,67 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Publish + - 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: | - ./script/publish.ts + 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 env: OPENCODE_BUMP: ${{ inputs.bump }} OPENCODE_VERSION: ${{ inputs.version }} - OPENCODE_CHANNEL: latest - NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} - GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }} - AUR_KEY: ${{ secrets.AUR_KEY }} OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + AUR_KEY: ${{ secrets.AUR_KEY }} + GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }} + NPM_CONFIG_PROVENANCE: false + + - uses: actions/upload-artifact@v4 + with: + name: opencode-cli + path: packages/opencode/dist + + outputs: + release: ${{ steps.publish.outputs.release }} + tag: ${{ steps.publish.outputs.tag }} + version: ${{ steps.publish.outputs.version }} publish-tauri: + needs: publish + continue-on-error: true strategy: + fail-fast: false matrix: settings: - host: macos-latest target: x86_64-apple-darwin - host: macos-latest target: aarch64-apple-darwin - - host: windows-latest + - host: blacksmith-4vcpu-windows-2025 target: x86_64-pc-windows-msvc - - host: ubuntu-24.04 + - host: blacksmith-4vcpu-ubuntu-2404 target: x86_64-unknown-linux-gnu + - host: blacksmith-4vcpu-ubuntu-2404-arm + target: aarch64-unknown-linux-gnu runs-on: ${{ matrix.settings.host }} steps: - uses: actions/checkout@v3 with: fetch-depth: 0 + ref: ${{ needs.publish.outputs.tag }} - uses: apple-actions/import-codesign-certs@v2 if: ${{ runner.os == 'macOS' }} @@ -124,7 +139,7 @@ jobs: - uses: ./.github/actions/setup-bun - name: install dependencies (ubuntu only) - if: startsWith(matrix.settings.host, 'ubuntu') + if: contains(matrix.settings.host, 'ubuntu') run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf @@ -136,29 +151,33 @@ jobs: - uses: Swatinem/rust-cache@v2 with: - workspaces: packages/tauri/src-tauri + workspaces: packages/desktop/src-tauri shared-key: ${{ matrix.settings.target }} - name: Prepare run: | - cd packages/tauri + cd packages/desktop bun ./scripts/prepare.ts env: - OPENCODE_BUMP: ${{ inputs.bump }} - OPENCODE_VERSION: ${{ inputs.version }} - OPENCODE_CHANNEL: latest + 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 }} # Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released - - run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch feat/truly-portable-appimage - if: startsWith(matrix.settings.host, 'ubuntu') + - name: Install tauri-cli from portable appimage branch + if: contains(matrix.settings.host, 'ubuntu') + run: | + cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch feat/truly-portable-appimage --force + echo "Installed tauri-cli version:" + cargo tauri --version - name: Build and upload artifacts + timeout-minutes: 20 uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -172,9 +191,43 @@ jobs: APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8 with: - projectPath: packages/tauri + projectPath: packages/desktop uploadWorkflowArtifacts: true - tauriScript: ${{ (startsWith(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }} - args: --target ${{ matrix.settings.target }} + tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }} + args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose updaterJsonPreferNsis: true - # releaseId: TODO + releaseId: ${{ needs.publish.outputs.release }} + tagName: ${{ needs.publish.outputs.tag }} + releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext] + releaseDraft: true + + publish-release: + needs: + - publish + - publish-tauri + if: needs.publish.outputs.tag + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{ needs.publish.outputs.tag }} + + - uses: ./.github/actions/setup-bun + + - name: Setup SSH for AUR + run: | + sudo apt-get update + sudo apt-get install -y pacman-package-manager + mkdir -p ~/.ssh + echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + git config --global user.email "opencode@sst.dev" + git config --global user.name "opencode" + ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true + + - run: ./script/publish-complete.ts + env: + OPENCODE_VERSION: ${{ needs.publish.outputs.version }} + AUR_KEY: ${{ secrets.AUR_KEY }} + GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }} diff --git a/.github/workflows/release-github-action.yml b/.github/workflows/release-github-action.yml new file mode 100644 index 00000000000..3f5caa55c8d --- /dev/null +++ b/.github/workflows/release-github-action.yml @@ -0,0 +1,29 @@ +name: release-github-action + +on: + push: + branches: + - dev + paths: + - "github/**" + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +permissions: + contents: write + +jobs: + release: + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - run: git fetch --force --tags + + - name: Release + run: | + git config --global user.email "opencode@sst.dev" + git config --global user.name "opencode" + ./github/script/release diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index d974e2a76d7..44bfeb33661 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -29,6 +29,8 @@ jobs: with: fetch-depth: 1 + - uses: ./.github/actions/setup-bun + - name: Install opencode run: curl -fsSL https://opencode.ai/install | bash @@ -62,9 +64,11 @@ jobs: Please check all the code changes in this pull request against the style guide, also look for any bugs if they exist. Diffs are important but make sure you read the entire file to get proper context. Make it clear the suggestions are merely suggestions and the human can decide what to do When critiquing code against the style guide, be sure that the code is ACTUALLY in violation, don't complain about else statements if they already use early returns there. You may complain about excessive nesting though, regardless of else statement usage. - When critiquing code style don't be a zealot, we don't like "let" statements but sometimes they are the simpliest option, if someone does a bunch of nesting with let, they should consider using iife (see packages/opencode/src/util.iife.ts) + When critiquing code style don't be a zealot, we don't like "let" statements but sometimes they are the simplest option, if someone does a bunch of nesting with let, they should consider using iife (see packages/opencode/src/util.iife.ts) Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block. + If you are writing suggested fixes, BE SURE THAT the change you are recommending is actually valid typescript, often I have seen missing closing "}" or other syntax errors. + Generally, write a comment instead of writing suggested change if you can help it. Command MUST be like this. \`\`\` diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml deleted file mode 100644 index 2d2553b6dbe..00000000000 --- a/.github/workflows/sdk.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: sdk - -on: - push: - branches-ignore: - - production - pull_request: - branches-ignore: - - production - workflow_dispatch: -jobs: - format: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: run - run: | - bun ./packages/sdk/js/script/build.ts - (cd packages/opencode && bun dev generate > ../sdk/openapi.json) - if [ -z "$(git status --porcelain)" ]; then - echo "No changes to commit" - exit 0 - fi - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add -A - git commit -m "chore: regen sdk" - git push --no-verify - env: - CI: true diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml deleted file mode 100644 index cffd1e946bd..00000000000 --- a/.github/workflows/snapshot.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: snapshot - -on: - workflow_dispatch: - push: - branches: - - dev - - test-bedrock - - v0 - - otui-diffs - - snapshot-* - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - publish: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - run: git fetch --force --tags - - - uses: actions/setup-go@v5 - with: - go-version: ">=1.24.0" - cache: true - cache-dependency-path: go.sum - - - uses: ./.github/actions/setup-bun - - - name: Publish - run: | - ./script/publish.ts - env: - GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }} - NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml new file mode 100644 index 00000000000..b5378d7d527 --- /dev/null +++ b/.github/workflows/stale-issues.yml @@ -0,0 +1,33 @@ +name: "Auto-close stale issues" + +on: + schedule: + - cron: "30 1 * * *" # Daily at 1:30 AM + workflow_dispatch: + +env: + DAYS_BEFORE_STALE: 90 + DAYS_BEFORE_CLOSE: 7 + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/stale@v10 + with: + days-before-stale: ${{ env.DAYS_BEFORE_STALE }} + days-before-close: ${{ env.DAYS_BEFORE_CLOSE }} + stale-issue-label: "stale" + close-issue-message: | + [automated] Closing due to ${{ env.DAYS_BEFORE_STALE }}+ days of inactivity. + + Feel free to reopen if you still need this! + stale-issue-message: | + [automated] This issue has had no activity for ${{ env.DAYS_BEFORE_STALE }} days. + + It will be closed in ${{ env.DAYS_BEFORE_CLOSE }} days if there's no new activity. + remove-stale-when-updated: true + exempt-issue-labels: "pinned,security,feature-request,on-hold" + start-date: "2025-12-27" diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml index 97e9245179b..57e93642b27 100644 --- a/.github/workflows/stats.yml +++ b/.github/workflows/stats.yml @@ -5,8 +5,11 @@ on: - cron: "0 12 * * *" # Run daily at 12:00 UTC workflow_dispatch: # Allow manual trigger +concurrency: ${{ github.workflow }}-${{ github.ref }} + jobs: stats: + if: github.repository == 'sst/opencode' runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: write diff --git a/.github/workflows/sync-zed-extension.yml b/.github/workflows/sync-zed-extension.yml index 445adbc530b..f14487cde97 100644 --- a/.github/workflows/sync-zed-extension.yml +++ b/.github/workflows/sync-zed-extension.yml @@ -31,4 +31,5 @@ jobs: run: | ./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }} env: - GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }} + ZED_EXTENSIONS_PAT: ${{ secrets.ZED_EXTENSIONS_PAT }} + ZED_PR_PAT: ${{ secrets.ZED_PR_PAT }} diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml new file mode 100644 index 00000000000..6e150957291 --- /dev/null +++ b/.github/workflows/triage.yml @@ -0,0 +1,37 @@ +name: Issue Triage + +on: + issues: + types: [opened] + +jobs: + triage: + runs-on: blacksmith-4vcpu-ubuntu-2404 + permissions: + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup Bun + uses: ./.github/actions/setup-bun + + - name: Install opencode + run: curl -fsSL https://opencode.ai/install | bash + + - name: Triage issue + env: + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + ISSUE_TITLE: ${{ github.event.issue.title }} + ISSUE_BODY: ${{ github.event.issue.body }} + run: | + opencode run --agent triage "The following issue was just opened, triage it: + + Title: $ISSUE_TITLE + + $ISSUE_BODY" diff --git a/.gitignore b/.gitignore index 7f9a8523561..fc175568df6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ node_modules playground tmp dist +ts-dist .turbo **/.serena .serena/ @@ -18,3 +19,8 @@ Session.vim opencode.json a.out target +.scripts + +# Local dev files +opencode-dev +logs/ diff --git a/.opencode/agent/docs.md b/.opencode/agent/docs.md index be7c203668e..21cfc6a16e0 100644 --- a/.opencode/agent/docs.md +++ b/.opencode/agent/docs.md @@ -1,11 +1,14 @@ --- description: ALWAYS use this when writing docs +color: "#38A3EE" --- You are an expert technical documentation writer You are not verbose +Use a relaxed and friendly tone + The title of the page should be a word or a 2-3 word phrase The description should be one short line, should not start with "The", should diff --git a/.opencode/agent/triage.md b/.opencode/agent/triage.md new file mode 100644 index 00000000000..539be154917 --- /dev/null +++ b/.opencode/agent/triage.md @@ -0,0 +1,78 @@ +--- +mode: primary +hidden: true +model: opencode/claude-haiku-4-5 +color: "#44BA81" +tools: + "*": false + "github-triage": true +--- + +You are a triage agent responsible for triaging github issues. + +Use your github-triage tool to triage issues. + +## Labels + +### windows + +Use for any issue that mentions Windows (the OS). Be sure they are saying that they are on Windows. + +- Use if they mention WSL too + +#### perf + +Performance-related issues: + +- Slow performance +- High RAM usage +- High CPU usage + +**Only** add if it's likely a RAM or CPU issue. **Do not** add for LLM slowness. + +#### desktop + +Desktop app issues: + +- `opencode web` command +- The desktop app itself + +**Only** add if it's specifically about the Desktop application or `opencode web` view. **Do not** add for terminal, TUI, or general opencode issues. + +#### nix + +**Only** add if the issue explicitly mentions nix. + +#### zen + +**Only** add if the issue mentions "zen" or "opencode zen". Zen is our gateway for coding models. **Do not** add for other gateways or inference providers. + +If the issue doesn't have "zen" in it then don't add zen label + +#### docs + +Add if the issue requests better documentation or docs updates. + +#### opentui + +TUI issues potentially caused by our underlying TUI library: + +- Keybindings not working +- Scroll speed issues (too fast/slow/laggy) +- Screen flickering +- Crashes with opentui in the log + +**Do not** add for general TUI bugs. + +When assigning to people here are the following rules: + +adamdotdev: +ONLY assign adam if the issue will have the "desktop" label. + +fwang: +ONLY assign fwang if the issue will have the "zen" label. + +jayair: +ONLY assign jayair if the issue will have the "docs" label. + +In all other cases use best judgment. Avoid assigning to kommander needlessly, when in doubt assign to rekram1-node. diff --git a/.opencode/command/commit.md b/.opencode/command/commit.md index 46673d95a13..8e9346ebc88 100644 --- a/.opencode/command/commit.md +++ b/.opencode/command/commit.md @@ -1,5 +1,7 @@ --- description: git commit and push +model: opencode/glm-4.6 +subtask: true --- commit and push diff --git a/.opencode/env.d.ts b/.opencode/env.d.ts new file mode 100644 index 00000000000..f2b13a934c4 --- /dev/null +++ b/.opencode/env.d.ts @@ -0,0 +1,4 @@ +declare module "*.txt" { + const content: string + export default content +} diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc index c3416388961..cbcbb0c6518 100644 --- a/.opencode/opencode.jsonc +++ b/.opencode/opencode.jsonc @@ -1,28 +1,17 @@ { "$schema": "https://opencode.ai/config.json", - "plugin": ["opencode-openai-codex-auth"], + // "plugin": ["opencode-openai-codex-auth"], // "enterprise": { // "url": "https://enterprise.dev.opencode.ai", // }, "instructions": ["STYLE_GUIDE.md"], "provider": { "opencode": { - "options": { - // "baseURL": "http://localhost:8080", - }, + "options": {}, }, }, - "mcp": { - "exa": { - "type": "remote", - "url": "https://mcp.exa.ai/mcp", - }, - "morph": { - "type": "local", - "command": ["bunx", "@morphllm/morphmcp"], - "environment": { - "ENABLED_TOOLS": "warp_grep", - }, - }, + "mcp": {}, + "tools": { + "github-triage": false, }, } diff --git a/.opencode/skill/test-skill/SKILL.md b/.opencode/skill/test-skill/SKILL.md new file mode 100644 index 00000000000..3fef059f2e9 --- /dev/null +++ b/.opencode/skill/test-skill/SKILL.md @@ -0,0 +1,6 @@ +--- +name: test-skill +description: use this when asked to test skill +--- + +woah this is a test skill diff --git a/.opencode/tool/github-triage.ts b/.opencode/tool/github-triage.ts new file mode 100644 index 00000000000..a5e6c811d83 --- /dev/null +++ b/.opencode/tool/github-triage.ts @@ -0,0 +1,90 @@ +/// +// import { Octokit } from "@octokit/rest" +import { tool } from "@opencode-ai/plugin" +import DESCRIPTION from "./github-triage.txt" + +function getIssueNumber(): number { + const issue = parseInt(process.env.ISSUE_NUMBER ?? "", 10) + if (!issue) throw new Error("ISSUE_NUMBER env var not set") + return issue +} + +async function githubFetch(endpoint: string, options: RequestInit = {}) { + const response = await fetch(`https://api.github.com${endpoint}`, { + ...options, + headers: { + Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, + Accept: "application/vnd.github+json", + "Content-Type": "application/json", + ...options.headers, + }, + }) + if (!response.ok) { + throw new Error(`GitHub API error: ${response.status} ${response.statusText}`) + } + return response.json() +} + +export default tool({ + description: DESCRIPTION, + args: { + assignee: tool.schema + .enum(["thdxr", "adamdotdevin", "rekram1-node", "fwang", "jayair", "kommander"]) + .describe("The username of the assignee") + .default("rekram1-node"), + labels: tool.schema + .array(tool.schema.enum(["nix", "opentui", "perf", "desktop", "zen", "docs", "windows"])) + .describe("The labels(s) to add to the issue") + .default([]), + }, + async execute(args) { + const issue = getIssueNumber() + // const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }) + const owner = "sst" + const repo = "opencode" + + const results: string[] = [] + + if (args.assignee === "adamdotdevin" && !args.labels.includes("desktop")) { + throw new Error("Only desktop issues should be assigned to adamdotdevin") + } + + if (args.assignee === "fwang" && !args.labels.includes("zen")) { + throw new Error("Only zen issues should be assigned to fwang") + } + + if (args.assignee === "kommander" && !args.labels.includes("opentui")) { + throw new Error("Only opentui issues should be assigned to kommander") + } + + // await octokit.rest.issues.addAssignees({ + // owner, + // repo, + // issue_number: issue, + // assignees: [args.assignee], + // }) + await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/assignees`, { + method: "POST", + body: JSON.stringify({ assignees: [args.assignee] }), + }) + results.push(`Assigned @${args.assignee} to issue #${issue}`) + + const labels: string[] = args.labels.map((label) => (label === "desktop" ? "web" : label)) + + if (labels.length > 0) { + // await octokit.rest.issues.addLabels({ + // owner, + // repo, + // issue_number: issue, + // labels, + // }) + await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/labels`, { + method: "POST", + body: JSON.stringify({ labels }), + }) + results.push(`Added labels: ${args.labels.join(", ")}`) + } + + return results.join("\n") + }, +}) diff --git a/.opencode/tool/github-triage.txt b/.opencode/tool/github-triage.txt new file mode 100644 index 00000000000..4c46a72c162 --- /dev/null +++ b/.opencode/tool/github-triage.txt @@ -0,0 +1,88 @@ +Use this tool to assign and/or label a Github issue. + +You can assign the following users: +- thdxr +- adamdotdevin +- fwang +- jayair +- kommander +- rekram1-node + + +You can use the following labels: +- nix +- opentui +- perf +- web +- zen +- docs + +Always try to assign an issue, if in doubt, assign rekram1-node to it. + +## Breakdown of responsibilities: + +### thdxr + +Dax is responsible for managing core parts of the application, for large feature requests, api changes, or things that require significant changes to the codebase assign him. + +This relates to OpenCode server primarily but has overlap with just about anything + +### adamdotdevin + +Adam is responsible for managing the Desktop/Web app. If there is an issue relating to the desktop app or `opencode web` command. Assign him. + + +### fwang + +Frank is responsible for managing Zen, if you see complaints about OpenCode Zen, maybe it's the dashboard, the model quality, billing issues, etc. Assign him to the issue. + +### jayair + +Jay is responsible for documentation. If there is an issue relating to documentation assign him. + +### kommander + +Sebastian is responsible for managing an OpenTUI (a library for building terminal user interfaces). OpenCode's TUI is built with OpenTUI. If there are issues about: +- random characters on screen +- keybinds not working on different terminals +- general terminal stuff +Then assign the issue to Him. + +### rekram1-node + +ALL BUGS SHOULD BE assigned to rekram1-node unless they have the `opentui` label. + +Assign Aiden to an issue as a catch all, if you can't assign anyone else. Most of the time this will be bugs/polish things. +If no one else makes sense to assign, assign rekram1-node to it. + +Always assign to aiden if the issue mentions "acp", "zed", or model performance issues + +## Breakdown of Labels: + +### nix + +Any issue that mentions nix, or nixos should have a nix label + +### opentui + +Anything relating to the TUI itself should have an opentui label + +### perf + +Anything related to slow performance, high ram, high cpu usage, or any other performance related issue should have a perf label + +### desktop + +Anything related to `opencode web` command or the desktop app should have a desktop label. Never add this label for anything terminal/tui related + +### zen + +Anything related to OpenCode Zen, billing, or model quality from Zen should have a zen label + +### docs + +Anything related to the documentation should have a docs label + +### windows + +Use for any issue that involves the windows OS diff --git a/AGENTS.md b/AGENTS.md index 5a95fc509fe..bbb2a96f2bf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,31 +4,4 @@ ## Tool Calling -- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE. Here is an example illustrating how to execute 3 parallel file reads in this chat environment: - -json -{ -"recipient_name": "multi_tool_use.parallel", -"parameters": { -"tool_uses": [ -{ -"recipient_name": "functions.read", -"parameters": { -"filePath": "path/to/file.tsx" -} -}, -{ -"recipient_name": "functions.read", -"parameters": { -"filePath": "path/to/file.ts" -} -}, -{ -"recipient_name": "functions.read", -"parameters": { -"filePath": "path/to/file.md" -} -} -] -} -} +- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6a24995e81a..290e67d40db 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,13 +34,43 @@ Want to take on an issue? Leave a comment and a maintainer may assign it to you bun dev ``` +### Running against a different directory + +By default, `bun dev` runs OpenCode in the `packages/opencode` directory. To run it against a different directory or repository: + +```bash +bun dev +``` + +To run OpenCode in the root of the opencode repo itself: + +```bash +bun dev . +``` + +### Building a "localcode" + +To compile a standalone executable: + +```bash +./packages/opencode/script/build.ts --single +``` + +Then run it with: + +```bash +./packages/opencode/dist/opencode-/bin/opencode +``` + +Replace `` with your platform (e.g., `darwin-arm64`, `linux-x64`). + - Core pieces: - `packages/opencode`: OpenCode core business logic & server. - `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui) - `packages/plugin`: Source for `@opencode-ai/plugin` > [!NOTE] -> After touching `packages/opencode/src/server/server.ts`, run "./packages/sdk/js/script/build.ts" to regenerate the JS sdk. +> If you make changes to the API or SDK (e.g. `packages/opencode/src/server/server.ts`), run `./script/generate.ts` to regenerate the SDK and related files. Please try to follow the [style guide](./STYLE_GUIDE.md) @@ -53,12 +83,12 @@ your debugger via that URL. Other methods can result in breakpoints being mapped Caveats: -- `*.tsx` files won't have their breakpoints correctly mapped. This seems due to Bun currently not supporting source maps on code transformed - via `BunPlugin`s (currently necessary due to our dependency on `@opentui/solid`). Currently, the best you can do in terms of debugging `*.tsx` - files is writing a `debugger;` statement. Debugging facilities like stepping won't work, but at least you will be informed if a specific code - is triggered. - If you want to run the OpenCode TUI and have breakpoints triggered in the server code, you might need to run `bun dev spawn` instead of the usual `bun dev`. This is because `bun dev` runs the server in a worker thread and breakpoints might not work there. +- If `spawn` does not work for you, you can debug the server separately: + - Debug server: `bun run --inspect=ws://localhost:6499/ ./src/index.ts serve --port 4096`, + then attach TUI with `opencode attach http://localhost:4096` + - Debug TUI: `bun run --inspect=ws://localhost:6499/ --conditions=browser ./src/index.ts` Other tips and tricks: diff --git a/README.md b/README.md index 799cf00a2a8..b68195abdbe 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

-

The AI coding agent built for the terminal.

+

The open source AI coding agent.

Discord npm @@ -30,13 +30,29 @@ scoop bucket add extras; scoop install extras/opencode # Windows choco install opencode # Windows brew install opencode # macOS and Linux paru -S opencode-bin # Arch Linux -mise use --pin -g ubi:sst/opencode # Any OS +mise use -g github:sst/opencode # Any OS nix run nixpkgs#opencode # or github:sst/opencode for latest dev branch ``` > [!TIP] > Remove versions older than 0.1.x before installing. +### Desktop App (BETA) + +OpenCode is also available as a desktop application. Download directly from the [releases page](https://github.com/sst/opencode/releases) or [opencode.ai/download](https://opencode.ai/download). + +| Platform | Download | +| --------------------- | ------------------------------------- | +| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` | +| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` | +| Windows | `opencode-desktop-windows-x64.exe` | +| Linux | `.deb`, `.rpm`, or AppImage | + +```bash +# macOS (Homebrew) +brew install --cask opencode-desktop +``` + #### Installation Directory The install script respects the following priority order for the installation path: @@ -63,7 +79,7 @@ you can switch between these using the `Tab` key. - Asks permission before running bash commands - Ideal for exploring unfamiliar codebases or planning changes -Also, included is a **general** subagent for complex searches and multi-step tasks. +Also, included is a **general** subagent for complex searches and multistep tasks. This is used internally and can be invoked using `@general` in messages. Learn more about [agents](https://opencode.ai/docs/agents). @@ -78,11 +94,11 @@ If you're interested in contributing to OpenCode, please read our [contributing ### Building on OpenCode -If you are working on a project that's related to OpenCode and is using "opencode" as a part of its name; for example, "opencode-dashboard" or "opencode-mobile", please add a note to your README to clarify that it is not built by the OpenCode team and is not affiliated with us in anyway. +If you are working on a project that's related to OpenCode and is using "opencode" as a part of its name; for example, "opencode-dashboard" or "opencode-mobile", please add a note to your README to clarify that it is not built by the OpenCode team and is not affiliated with us in any way. ### FAQ -#### How is this different than Claude Code? +#### How is this different from Claude Code? It's very similar to Claude Code in terms of capability. Here are the key differences: diff --git a/README.zh-TW.md b/README.zh-TW.md new file mode 100644 index 00000000000..d3cbb263c89 --- /dev/null +++ b/README.zh-TW.md @@ -0,0 +1,115 @@ +

+ + + + + OpenCode logo + + +

+

開源的 AI Coding Agent。

+

+ Discord + npm + Build status +

+ +[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai) + +--- + +### 安裝 + +```bash +# 直接安裝 (YOLO) +curl -fsSL https://opencode.ai/install | bash + +# 套件管理員 +npm i -g opencode-ai@latest # 也可使用 bun/pnpm/yarn +scoop bucket add extras; scoop install extras/opencode # Windows +choco install opencode # Windows +brew install opencode # macOS 與 Linux +paru -S opencode-bin # Arch Linux +mise use -g github:sst/opencode # 任何作業系統 +nix run nixpkgs#opencode # 或使用 github:sst/opencode 以取得最新開發分支 +``` + +> [!TIP] +> 安裝前請先移除 0.1.x 以前的舊版本。 + +### 桌面應用程式 (BETA) + +OpenCode 也提供桌面版應用程式。您可以直接從 [發佈頁面 (releases page)](https://github.com/sst/opencode/releases) 或 [opencode.ai/download](https://opencode.ai/download) 下載。 + +| 平台 | 下載連結 | +| --------------------- | ------------------------------------- | +| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` | +| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` | +| Windows | `opencode-desktop-windows-x64.exe` | +| Linux | `.deb`, `.rpm`, 或 AppImage | + +```bash +# macOS (Homebrew Cask) +brew install --cask opencode-desktop +``` + +#### 安裝目錄 + +安裝腳本會依據以下優先順序決定安裝路徑: + +1. `$OPENCODE_INSTALL_DIR` - 自定義安裝目錄 +2. `$XDG_BIN_DIR` - 符合 XDG 基礎目錄規範的路徑 +3. `$HOME/bin` - 標準使用者執行檔目錄 (若存在或可建立) +4. `$HOME/.opencode/bin` - 預設備用路徑 + +```bash +# 範例 +OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash +``` + +### Agents + +OpenCode 內建了兩種 Agent,您可以使用 `Tab` 鍵快速切換。 + +- **build** - 預設模式,具備完整權限的 Agent,適用於開發工作。 +- **plan** - 唯讀模式,適用於程式碼分析與探索。 + - 預設禁止修改檔案。 + - 執行 bash 指令前會詢問權限。 + - 非常適合用來探索陌生的程式碼庫或規劃變更。 + +此外,OpenCode 還包含一個 **general** 子 Agent,用於處理複雜搜尋與多步驟任務。此 Agent 供系統內部使用,亦可透過在訊息中輸入 `@general` 來呼叫。 + +了解更多關於 [Agents](https://opencode.ai/docs/agents) 的資訊。 + +### 線上文件 + +關於如何設定 OpenCode 的詳細資訊,請參閱我們的 [**官方文件**](https://opencode.ai/docs)。 + +### 參與貢獻 + +如果您有興趣參與 OpenCode 的開發,請在提交 Pull Request 前先閱讀我們的 [貢獻指南 (Contributing Docs)](./CONTRIBUTING.md)。 + +### 基於 OpenCode 進行開發 + +如果您正在開發與 OpenCode 相關的專案,並在名稱中使用了 "opencode"(例如 "opencode-dashboard" 或 "opencode-mobile"),請在您的 README 中加入聲明,說明該專案並非由 OpenCode 團隊開發,且與我們沒有任何隸屬關係。 + +### 常見問題 (FAQ) + +#### 這跟 Claude Code 有什麼不同? + +在功能面上與 Claude Code 非常相似。以下是關鍵差異: + +- 100% 開源。 +- 不綁定特定的服務提供商。雖然我們推薦使用透過 [OpenCode Zen](https://opencode.ai/zen) 提供的模型,但 OpenCode 也可搭配 Claude, OpenAI, Google 甚至本地模型使用。隨著模型不斷演進,彼此間的差距會縮小且價格會下降,因此具備「不限廠商 (provider-agnostic)」的特性至關重要。 +- 內建 LSP (語言伺服器協定) 支援。 +- 專注於終端機介面 (TUI)。OpenCode 由 Neovim 愛好者與 [terminal.shop](https://terminal.shop) 的創作者打造;我們將不斷挑戰終端機介面的極限。 +- 客戶端/伺服器架構 (Client/Server Architecture)。這讓 OpenCode 能夠在您的電腦上運行的同時,由行動裝置進行遠端操控。這意味著 TUI 前端只是眾多可能的客戶端之一。 + +#### 另一個同名的 Repo 是什麼? + +另一個名稱相近的儲存庫與本專案無關。您可以點此[閱讀背後的故事](https://x.com/thdxr/status/1933561254481666466)。 + +--- + +**加入我們的社群** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode) diff --git a/STATS.md b/STATS.md index 205ab0c0181..a5174e72e3b 100644 --- a/STATS.md +++ b/STATS.md @@ -1,165 +1,188 @@ # 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) | +| 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) | diff --git a/bun.lock b/bun.lock index 8feff3ae8c4..711db071ec0 100644 --- a/bun.lock +++ b/bun.lock @@ -6,11 +6,13 @@ "name": "opencode", "dependencies": { "@aws-sdk/client-s3": "3.933.0", + "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", "typescript": "catalog:", }, "devDependencies": { + "@actions/artifact": "5.0.1", "@tsconfig/bun": "catalog:", "husky": "9.1.7", "prettier": "3.6.2", @@ -18,9 +20,57 @@ "turbo": "2.5.6", }, }, + "packages/app": { + "name": "@opencode-ai/app", + "version": "1.0.217", + "dependencies": { + "@kobalte/core": "catalog:", + "@opencode-ai/sdk": "workspace:*", + "@opencode-ai/ui": "workspace:*", + "@opencode-ai/util": "workspace:*", + "@shikijs/transformers": "3.9.2", + "@solid-primitives/active-element": "2.1.3", + "@solid-primitives/audio": "1.4.2", + "@solid-primitives/event-bus": "1.1.2", + "@solid-primitives/media": "2.3.3", + "@solid-primitives/resize-observer": "2.1.3", + "@solid-primitives/scroll": "2.1.3", + "@solid-primitives/storage": "catalog:", + "@solid-primitives/websocket": "1.3.1", + "@solidjs/meta": "catalog:", + "@solidjs/router": "catalog:", + "@thisbeyond/solid-dnd": "0.7.5", + "diff": "catalog:", + "fuzzysort": "catalog:", + "ghostty-web": "0.3.0", + "luxon": "catalog:", + "marked": "catalog:", + "marked-shiki": "catalog:", + "remeda": "catalog:", + "shiki": "catalog:", + "solid-js": "catalog:", + "solid-list": "catalog:", + "tailwindcss": "catalog:", + "virtua": "catalog:", + "zod": "catalog:", + }, + "devDependencies": { + "@happy-dom/global-registrator": "20.0.11", + "@tailwindcss/vite": "catalog:", + "@tsconfig/bun": "1.0.9", + "@types/bun": "catalog:", + "@types/luxon": "catalog:", + "@types/node": "catalog:", + "@typescript/native-preview": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plugin-icons-spritesheet": "3.0.1", + "vite-plugin-solid": "catalog:", + }, + }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -48,7 +98,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -75,7 +125,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -99,7 +149,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -123,62 +173,46 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { - "@kobalte/core": "catalog:", - "@opencode-ai/sdk": "workspace:*", - "@opencode-ai/ui": "workspace:*", - "@opencode-ai/util": "workspace:*", - "@shikijs/transformers": "3.9.2", - "@solid-primitives/active-element": "2.1.3", - "@solid-primitives/event-bus": "1.1.2", - "@solid-primitives/resize-observer": "2.1.3", - "@solid-primitives/scroll": "2.1.3", - "@solid-primitives/storage": "4.3.3", - "@solid-primitives/websocket": "1.3.1", - "@solidjs/meta": "catalog:", - "@solidjs/router": "catalog:", - "@thisbeyond/solid-dnd": "0.7.5", - "diff": "catalog:", - "fuzzysort": "catalog:", - "ghostty-web": "0.3.0", - "luxon": "catalog:", - "marked": "16.2.0", - "marked-shiki": "1.2.1", - "remeda": "catalog:", - "shiki": "3.9.2", + "@opencode-ai/app": "workspace:*", + "@solid-primitives/storage": "catalog:", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "~2", + "@tauri-apps/plugin-http": "~2", + "@tauri-apps/plugin-notification": "~2", + "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-os": "~2", + "@tauri-apps/plugin-process": "~2", + "@tauri-apps/plugin-shell": "~2", + "@tauri-apps/plugin-store": "~2", + "@tauri-apps/plugin-updater": "~2", + "@tauri-apps/plugin-window-state": "~2", "solid-js": "catalog:", - "solid-list": "catalog:", - "tailwindcss": "catalog:", - "virtua": "catalog:", }, "devDependencies": { - "@happy-dom/global-registrator": "20.0.11", - "@tailwindcss/vite": "catalog:", - "@tsconfig/bun": "1.0.9", + "@actions/artifact": "4.0.0", + "@tauri-apps/cli": "^2", "@types/bun": "catalog:", - "@types/luxon": "catalog:", - "@types/node": "catalog:", "@typescript/native-preview": "catalog:", - "typescript": "catalog:", + "typescript": "~5.6.2", "vite": "catalog:", - "vite-plugin-icons-spritesheet": "3.0.1", - "vite-plugin-solid": "catalog:", }, }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", - "@pierre/precision-diffs": "catalog:", + "@pierre/diffs": "catalog:", "@solidjs/meta": "catalog:", "@solidjs/router": "catalog:", "@solidjs/start": "catalog:", "aws4fetch": "^1.0.20", "hono": "catalog:", "hono-openapi": "catalog:", + "js-base64": "3.7.7", "luxon": "catalog:", "nitro": "3.0.1-alpha.1", "solid-js": "catalog:", @@ -196,10 +230,10 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { "@octokit/auth-app": "8.0.1", - "@octokit/rest": "22.0.0", + "@octokit/rest": "catalog:", "hono": "catalog:", "jose": "6.0.11", }, @@ -212,7 +246,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.0.134", + "version": "1.0.217", "bin": { "opencode": "./bin/opencode", }, @@ -221,35 +255,44 @@ "@actions/github": "6.0.1", "@agentclientprotocol/sdk": "0.5.1", "@ai-sdk/amazon-bedrock": "3.0.57", - "@ai-sdk/anthropic": "2.0.50", - "@ai-sdk/azure": "2.0.73", - "@ai-sdk/google": "2.0.44", + "@ai-sdk/anthropic": "2.0.56", + "@ai-sdk/azure": "2.0.82", + "@ai-sdk/cerebras": "1.0.33", + "@ai-sdk/cohere": "2.0.21", + "@ai-sdk/deepinfra": "1.0.30", + "@ai-sdk/gateway": "2.0.23", + "@ai-sdk/google": "2.0.49", "@ai-sdk/google-vertex": "3.0.81", - "@ai-sdk/mcp": "0.0.8", + "@ai-sdk/groq": "2.0.33", + "@ai-sdk/mistral": "2.0.26", "@ai-sdk/openai": "2.0.71", - "@ai-sdk/openai-compatible": "1.0.27", + "@ai-sdk/openai-compatible": "1.0.29", + "@ai-sdk/perplexity": "2.0.22", "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.18", + "@ai-sdk/provider-utils": "3.0.19", + "@ai-sdk/togetherai": "1.0.30", + "@ai-sdk/xai": "2.0.42", "@clack/prompts": "1.0.0-alpha.1", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", "@modelcontextprotocol/sdk": "1.15.1", "@octokit/graphql": "9.0.2", - "@octokit/rest": "22.0.0", + "@octokit/rest": "catalog:", "@openauthjs/openauth": "catalog:", "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", - "@openrouter/ai-sdk-provider": "1.2.8", - "@opentui/core": "0.1.56", - "@opentui/solid": "0.1.56", + "@openrouter/ai-sdk-provider": "1.5.2", + "@opentui/core": "0.1.67", + "@opentui/solid": "0.1.67", "@parcel/watcher": "2.5.1", - "@pierre/precision-diffs": "catalog:", + "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", + "bonjour-service": "1.3.0", "bun-pty": "0.4.2", "chokidar": "4.0.3", "clipboardy": "4.0.0", @@ -285,7 +328,9 @@ "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1", "@standard-schema/spec": "1.0.0", "@tsconfig/bun": "catalog:", @@ -302,7 +347,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -322,7 +367,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.0.134", + "version": "1.0.217", "devDependencies": { "@hey-api/openapi-ts": "0.88.1", "@tsconfig/node22": "catalog:", @@ -333,7 +378,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -344,43 +389,27 @@ "typescript": "catalog:", }, }, - "packages/tauri": { - "name": "@opencode-ai/tauri", - "version": "1.0.134", - "dependencies": { - "@opencode-ai/desktop": "workspace:*", - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-dialog": "~2", - "@tauri-apps/plugin-opener": "^2", - "@tauri-apps/plugin-shell": "~2", - "@tauri-apps/plugin-updater": "~2", - "solid-js": "catalog:", - }, - "devDependencies": { - "@actions/artifact": "4.0.0", - "@tauri-apps/cli": "^2", - "@types/bun": "catalog:", - "typescript": "~5.6.2", - "vite": "catalog:", - }, - }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", - "@pierre/precision-diffs": "catalog:", + "@pierre/diffs": "catalog:", "@shikijs/transformers": "3.9.2", + "@solid-primitives/bounds": "0.1.3", + "@solid-primitives/resize-observer": "2.1.3", "@solidjs/meta": "catalog:", "@typescript/native-preview": "catalog:", "fuzzysort": "catalog:", + "katex": "0.16.27", "luxon": "catalog:", - "marked": "16.2.0", - "marked-shiki": "1.2.1", + "marked": "catalog:", + "marked-katex-extension": "5.1.6", + "marked-shiki": "catalog:", "remeda": "catalog:", - "shiki": "3.9.2", + "shiki": "catalog:", "solid-js": "catalog:", "solid-list": "catalog:", "virtua": "catalog:", @@ -389,6 +418,8 @@ "@tailwindcss/vite": "catalog:", "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", + "@types/katex": "0.16.7", + "@types/luxon": "catalog:", "tailwindcss": "catalog:", "typescript": "catalog:", "vite": "catalog:", @@ -398,7 +429,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { "zod": "catalog:", }, @@ -409,7 +440,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -424,12 +455,11 @@ "js-base64": "3.7.7", "lang-map": "0.4.0", "luxon": "catalog:", - "marked": "15.0.12", - "marked-shiki": "1.2.1", + "marked": "catalog:", + "marked-shiki": "catalog:", "rehype-autolink-headings": "7.1.0", "remeda": "catalog:", - "sharp": "0.32.5", - "shiki": "3.4.2", + "shiki": "catalog:", "solid-js": "catalog:", "toolbeam-docs-theme": "0.4.8", }, @@ -441,7 +471,6 @@ }, }, "trustedDependencies": [ - "sharp", "esbuild", "web-tree-sitter", "tree-sitter-bash", @@ -457,15 +486,17 @@ "@cloudflare/workers-types": "4.20251008.0", "@hono/zod-validator": "0.4.2", "@kobalte/core": "0.13.11", + "@octokit/rest": "22.0.0", "@openauthjs/openauth": "0.0.0-20250322224806", - "@pierre/precision-diffs": "0.6.0-beta.3", + "@pierre/diffs": "1.0.2", + "@solid-primitives/storage": "4.3.3", "@solidjs/meta": "0.29.4", "@solidjs/router": "0.15.4", "@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020", "@tailwindcss/vite": "4.1.11", "@tsconfig/bun": "1.0.9", "@tsconfig/node22": "22.0.2", - "@types/bun": "1.3.3", + "@types/bun": "1.3.4", "@types/luxon": "3.7.1", "@types/node": "22.13.9", "@typescript/native-preview": "7.0.0-dev.20251207.1", @@ -475,7 +506,10 @@ "hono": "4.10.7", "hono-openapi": "1.1.2", "luxon": "3.6.1", + "marked": "17.0.1", + "marked-shiki": "1.2.1", "remeda": "2.26.0", + "shiki": "3.20.0", "solid-js": "1.9.10", "solid-list": "0.3.0", "tailwindcss": "4.1.11", @@ -487,7 +521,7 @@ "zod": "4.1.8", }, "packages": { - "@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="], + "@actions/artifact": ["@actions/artifact@5.0.1", "", { "dependencies": { "@actions/core": "^2.0.0", "@actions/github": "^6.0.1", "@actions/http-client": "^3.0.0", "@azure/storage-blob": "^12.29.1", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-dHJ5rHduhCKUikKTT9eXeWoUvfKia3IjR1sO/VTAV3DVAL4yMTRnl2iO5mcfiBjySHLwPNezwENAVskKYU5ymw=="], "@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="], @@ -495,7 +529,7 @@ "@actions/github": ["@actions/github@6.0.1", "", { "dependencies": { "@actions/http-client": "^2.2.0", "@octokit/core": "^5.0.1", "@octokit/plugin-paginate-rest": "^9.2.2", "@octokit/plugin-rest-endpoint-methods": "^10.4.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "undici": "^5.28.5" } }, "sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw=="], - "@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], + "@actions/http-client": ["@actions/http-client@3.0.0", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.28.5" } }, "sha512-1s3tXAfVMSz9a4ZEBkXXRQD4QhY3+GAsWSbaYpeknPOKEeyRiU3lH+bHiLMZdo2x/fIeQ/hscL1wCkDLVM2DZQ=="], "@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="], @@ -507,23 +541,37 @@ "@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="], - "@ai-sdk/azure": ["@ai-sdk/azure@2.0.73", "", { "dependencies": { "@ai-sdk/openai": "2.0.71", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LpAg3Ak/V3WOemBu35Qbx9jfQfApsHNXX9p3bXVsnRu3XXi1QQUt5gMOCIb4znPonz+XnHenIDZMBwdsb1TfRQ=="], + "@ai-sdk/azure": ["@ai-sdk/azure@2.0.82", "", { "dependencies": { "@ai-sdk/openai": "2.0.80", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Bpab51ETBB4adZC1xGMYsryL/CB8j1sA+t5aDqhRv3t3WRLTxhaBDcFKtQTIuxiEQTFosz9Q2xQqdfBvQm5jHw=="], + + "@ai-sdk/cerebras": ["@ai-sdk/cerebras@1.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2gSSS/7kunIwMdC4td5oWsUAzoLw84ccGpz6wQbxVnrb1iWnrEnKa5tRBduaP6IXpzLWsu8wME3+dQhZy+gT7w=="], + + "@ai-sdk/cohere": ["@ai-sdk/cohere@2.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZjaZFvJlc5XOPi3QwTLEFZbHIgTJc6YGvxz+8zIMGVZi/hdynR8/f/C1A9x6mhzmBtAqi/dZ2h11oouAQH5z4g=="], + + "@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.30", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XK8oRZFApzo6xnS5C+FhWUUkB2itA5Nfon3pU9dJVM0goViq8GwdleZTBRqhu4DE4KJURo5DGWpJr2hfV54cEg=="], - "@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.12", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W+cB1sOWvPcz9qiIsNtD+HxUrBUva2vWv2K1EFukuImX+HA0uZx3EyyOjhYQ9gtf/teqEG80M6OvJ7xx/VLV2A=="], + "@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-qmX7afPRszUqG5hryHF3UN8ITPIRSGmDW6VYCmByzjoUkgm3MekzSx2hMV1wr0P+llDeuXb378SjqUfpvWJulg=="], - "@ai-sdk/google": ["@ai-sdk/google@2.0.44", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-c5dck36FjqiVoeeMJQLTEmUheoURcGTU/nBT6iJu8/nZiKFT/y8pD85KMDRB7RerRYaaQOtslR2d6/5PditiRw=="], + "@ai-sdk/google": ["@ai-sdk/google@2.0.49", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-efwKk4mOV0SpumUaQskeYABk37FJPmEYwoDJQEjyLRmGSjtHRe9P5Cwof5ffLvaFav2IaJpBGEz98pyTs7oNWA=="], "@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.81", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.50", "@ai-sdk/google": "2.0.44", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18", "google-auth-library": "^9.15.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yrl5Ug0Mqwo9ya45oxczgy2RWgpEA/XQQCSFYP+3NZMQ4yA3Iim1vkOjVCsGaZZ8rjVk395abi1ZMZV0/6rqVA=="], - "@ai-sdk/mcp": ["@ai-sdk/mcp@0.0.8", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "pkce-challenge": "^5.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9y9GuGcZ9/+pMIHfpOCJgZVp+AZMv6TkjX2NVT17SQZvTF2N8LXuCXyoUPyi1PxIxzxl0n463LxxaB2O6olC+Q=="], + "@ai-sdk/groq": ["@ai-sdk/groq@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FWGl7xNr88NBveao3y9EcVWYUt9ABPrwLFY7pIutSNgaTf32vgvyhREobaMrLU4Scr5G/2tlNqOPZ5wkYMaZig=="], + + "@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.26", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jxDB++4WI1wEx5ONNBI+VbkmYJOYIuS8UQY13/83UGRaiW7oB/WHiH4ETe6KzbKpQPB3XruwTJQjUMsMfKyTXA=="], "@ai-sdk/openai": ["@ai-sdk/openai@2.0.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="], "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-luHVcU+yKzwv3ekKgbP3v+elUVxb2Rt+8c6w9qi7g2NYG2/pEL21oIrnaEnc6UtTZLLZX9EFBcpq2N1FQKDIMw=="], + "@ai-sdk/perplexity": ["@ai-sdk/perplexity@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zwzcnk08R2J3mZcQPn4Ifl4wYGrvANR7jsBB0hCTUSbb+Rx3ybpikSWiGuXQXxdiRc1I5MWXgj70m+bZaLPvHw=="], + "@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="], - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="], + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], + + "@ai-sdk/togetherai": ["@ai-sdk/togetherai@1.0.30", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9bxQbIXnWSN4bNismrza3NvIo+ui/Y3pj3UN6e9vCszCWFCN45RgISi4oDe10RqmzaJ/X8cfO/Tem+K8MT3wGQ=="], + + "@ai-sdk/xai": ["@ai-sdk/xai@2.0.42", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wlwO4yRoZ/d+ca29vN8SDzxus7POdnL7GBTyRdSrt6icUF0hooLesauC8qRUC4aLxtqvMEc1YHtJOU7ZnLWbTQ=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -631,7 +679,7 @@ "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.1", "", {}, "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww=="], - "@azure/abort-controller": ["@azure/abort-controller@1.1.0", "", { "dependencies": { "tslib": "^2.2.0" } }, "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw=="], + "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], "@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="], @@ -647,7 +695,7 @@ "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.22.2", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg=="], - "@azure/core-tracing": ["@azure/core-tracing@1.0.0-preview.13", "", { "dependencies": { "@opentelemetry/api": "^1.0.1", "tslib": "^2.2.0" } }, "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ=="], + "@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], "@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="], @@ -1035,6 +1083,8 @@ "@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="], + "@leichtgewicht/ip-codec": ["@leichtgewicht/ip-codec@2.0.5", "", {}, "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="], + "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="], @@ -1069,11 +1119,11 @@ "@octokit/auth-oauth-user": ["@octokit/auth-oauth-user@6.0.2", "", { "dependencies": { "@octokit/auth-oauth-device": "^8.0.3", "@octokit/oauth-methods": "^6.0.2", "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A=="], - "@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="], + "@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="], - "@octokit/core": ["@octokit/core@7.0.6", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q=="], + "@octokit/core": ["@octokit/core@5.2.2", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="], - "@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + "@octokit/endpoint": ["@octokit/endpoint@9.0.6", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="], "@octokit/graphql": ["@octokit/graphql@9.0.2", "", { "dependencies": { "@octokit/request": "^10.0.4", "@octokit/types": "^15.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-iz6KzZ7u95Fzy9Nt2L8cG88lGRMr/qy1Q36ih/XVzMIlPDMYwaNLE/ENhqmIzgPrlNWiYJkwmveEetvxAgFBJw=="], @@ -1085,15 +1135,15 @@ "@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@13.2.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw=="], - "@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="], + "@octokit/plugin-request-log": ["@octokit/plugin-request-log@1.0.4", "", { "peerDependencies": { "@octokit/core": ">=3" } }, "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA=="], "@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.1.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-VztDkhM0ketQYSh5Im3IcKWFZl7VIrrsCaHbDINkdYeiiAsJzjhS2xRFCSJgfN6VOcsoW4laMtsmf3HcNqIimg=="], "@octokit/plugin-retry": ["@octokit/plugin-retry@3.0.9", "", { "dependencies": { "@octokit/types": "^6.0.3", "bottleneck": "^2.15.3" } }, "sha512-r+fArdP5+TG6l1Rv/C9hVoty6tldw6cE2pRHNGmFPdyfrc696R6JjrQ3d7HdVqGwuzfyrcaLAKD7K8TX8aehUQ=="], - "@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/request": ["@octokit/request@8.4.1", "", { "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw=="], - "@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], + "@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="], "@octokit/rest": ["@octokit/rest@22.0.0", "", { "dependencies": { "@octokit/core": "^7.0.2", "@octokit/plugin-paginate-rest": "^13.0.1", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^16.0.0" } }, "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA=="], @@ -1105,6 +1155,8 @@ "@openauthjs/openauth": ["@openauthjs/openauth@0.0.0-20250322224806", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-p5IWSRXvABcwocH2dNI0w8c1QJelIOFulwhKk+aLLFfUbs8u1pr7kQbYe8yCSM2+bcLHiwbogpUQc2ovrGwCuw=="], + "@opencode-ai/app": ["@opencode-ai/app@workspace:packages/app"], + "@opencode-ai/console-app": ["@opencode-ai/console-app@workspace:packages/console/app"], "@opencode-ai/console-core": ["@opencode-ai/console-core@workspace:packages/console/core"], @@ -1129,35 +1181,33 @@ "@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"], - "@opencode-ai/tauri": ["@opencode-ai/tauri@workspace:packages/tauri"], - "@opencode-ai/ui": ["@opencode-ai/ui@workspace:packages/ui"], "@opencode-ai/util": ["@opencode-ai/util@workspace:packages/util"], "@opencode-ai/web": ["@opencode-ai/web@workspace:packages/web"], - "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.2.8", "", { "dependencies": { "@openrouter/sdk": "^0.1.8" }, "peerDependencies": { "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" } }, "sha512-pQT8AzZBKg9f4bkt4doF486ZlhK0XjKkevrLkiqYgfh1Jplovieu28nK4Y+xy3sF18/mxjqh9/2y6jh01qzLrA=="], + "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.5.2", "", { "dependencies": { "@openrouter/sdk": "^0.1.27" }, "peerDependencies": { "@toon-format/toon": "^2.0.0", "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" }, "optionalPeers": ["@toon-format/toon"] }, "sha512-3Th0vmJ9pjnwcPc2H1f59Mb0LFvwaREZAScfOQIpUxAHjZ7ZawVKDP27qgsteZPmMYqccNMy4r4Y3kgUnNcKAg=="], "@openrouter/sdk": ["@openrouter/sdk@0.1.27", "", { "dependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-RH//L10bSmc81q25zAZudiI4kNkLgxF2E+WU42vghp3N6TEvZ6F0jK7uT3tOxkEn91gzmMw9YVmDENy7SJsajQ=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@opentui/core": ["@opentui/core@0.1.56", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.56", "@opentui/core-darwin-x64": "0.1.56", "@opentui/core-linux-arm64": "0.1.56", "@opentui/core-linux-x64": "0.1.56", "@opentui/core-win32-arm64": "0.1.56", "@opentui/core-win32-x64": "0.1.56", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-TI5cSCPYythHIQYpAEdXyZhewGACn2TfnfC1qZmrSyEq33zFo4W7zpQ4EZNpy9xZJFCI+elAUVJFARwhudp9EQ=="], + "@opentui/core": ["@opentui/core@0.1.67", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.67", "@opentui/core-darwin-x64": "0.1.67", "@opentui/core-linux-arm64": "0.1.67", "@opentui/core-linux-x64": "0.1.67", "@opentui/core-win32-arm64": "0.1.67", "@opentui/core-win32-x64": "0.1.67", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-zmfyA10QUbzT6ohacPoHmGiYzuJrDSCfQWRWrKtao0BrHj9bii73qWy3V/eR4ibVueoRREwxJs5GlBOSvK6IoA=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.56", "", { "os": "darwin", "cpu": "arm64" }, "sha512-x5U9J2k1Fmbb9Mdh1nOd/yZVpg4ARCrV5pFngpaeKrIWDhs8RLpQW3ap+r7uyFLGFkSn4h5wBR0jj6Dg+Tyw+A=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.67", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LtOcTlFD+kO7neItmkiF77H8cnjTYzBOZe8JQGwRSt9aaCke3UzMvLxmQnj4BP/kPC3hi9V6NRnFdptz0sJZIQ=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.56", "", { "os": "darwin", "cpu": "x64" }, "sha512-7swq9rV/SaNVBWoUbC7mlP1VNyKBl7SSwmyVMkcaBP71lkm95zWuh4pgGj82fLgZ9gITRBD95TJVDmTovOyW0A=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.67", "", { "os": "darwin", "cpu": "x64" }, "sha512-9i+awVWgpEVqZhFLaLq8usNGyCiyT5QxMLy6eH7JmRic79S34u23HfxiniGRtdYh3aqpm9SbLzo60v0nRIUkCA=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.56", "", { "os": "linux", "cpu": "arm64" }, "sha512-v8b+kiTlynAJzR0hFeVpGFzVi5PGqXAe3Zql9iTiQqTExkm/sR34sfC/P6rBOUhuAnos8ovPDKWtDb6eCTSm9g=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.67", "", { "os": "linux", "cpu": "arm64" }, "sha512-WLjnTM3Ig//SRo0FUZYZJ5TITVbR6dKDVg6axU2D+sMoUzJMBP/Xo04q/TvZ3wP764Yca9l7oVMKWDxHlygyjQ=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.56", "", { "os": "linux", "cpu": "x64" }, "sha512-lbxgvAi5SBswK/2hoMPtLhPvJxASgquPUwvGTRHqzDkCvrOChP/loTjBQpL09/nAFc3jbM3SAbZtnEgA2SGYVw=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.67", "", { "os": "linux", "cpu": "x64" }, "sha512-5UbZ/TqWi/DAmHIZL4NvhdpgTwglszRiddkRiQ8cT0IbnE4lutd4XxWUWcLKwsNT1YJv32TtcGWkuthluLiriQ=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.56", "", { "os": "win32", "cpu": "arm64" }, "sha512-RoCAbvDo+59OevX+6GrEGbaueERiBVnTaWJkrS41hRAD2fFS3CZpW7UuS5jIg7zn5clHmOGyfvCiBkTRXmgkhw=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.67", "", { "os": "win32", "cpu": "arm64" }, "sha512-KNam5rObhN8/U9+GVVuvtAlGXp3MfdMHnw4W2P6YH7xp8HTsLvABUT91SJEyJ/ktVe9e1itLDG2fDHSoA5NbUg=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.56", "", { "os": "win32", "cpu": "x64" }, "sha512-i6N5TjZU5gRkJsKmH8e/qY9vwSk0rh6A5t37mHDGlzN4E5yO/MbBrYH4ppLp5stps9Zfi1Re51ofJX1s2hZY/Q=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.67", "", { "os": "win32", "cpu": "x64" }, "sha512-740lkOw42zLNh9YfahXjCwV2DS/amH2uMDh3tCADDCLckrMhemIhqArXDiMlalDxDqYspoaZCpBsFVsG9dMS6A=="], - "@opentui/solid": ["@opentui/solid@0.1.56", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.56", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-3R7AfxsYHUyehwJK98rt5dI9u2WCT/uH/CYvddZIgXPHyfFm1SHJekMdy3DUoiQTCUllt68eFGKMv9zRi6Laww=="], + "@opentui/solid": ["@opentui/solid@0.1.67", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.67", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-dVNq0+PJIdNb63D0T7vcbyVF/ZvLCihGvivTU50zDOzd0Sk5prbrIfpG8+DjMErFubXfdZQvdy/PqFdtw0rjtQ=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], @@ -1273,7 +1323,7 @@ "@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="], - "@pierre/precision-diffs": ["@pierre/precision-diffs@0.6.0-beta.3", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-1FBm9jhLWZvs7BqN3yG2Wh9SpGuO1us2QsKZlQqSwyCctMr9DRGzYQJ9lF6yR03LHzXs3fuIzO++d9sCObYzrQ=="], + "@pierre/diffs": ["@pierre/diffs@1.0.2", "", { "dependencies": { "@shikijs/core": "^3.0.0", "@shikijs/engine-javascript": "3.19.0", "@shikijs/transformers": "3.19.0", "diff": "8.0.2", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "3.19.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-RkFSDD5X/U+8QjyilPViYGJfmJNWXR17zTL8zw48+DcVC1Ujbh6I1edyuRnFfgRzpft05x2DSCkz2cjoIAxPvQ=="], "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], @@ -1409,13 +1459,13 @@ "@shikijs/core": ["@shikijs/core@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3q/mzmw09B2B6PgFNeiaN8pkNOixWS726IHmJEpjDAcneDPMQmUg2cweT9cWXY4XcyQS3i6mOOUgQz9RRUP6HA=="], - "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-kUTRVKPsB/28H5Ko6qEsyudBiWEDLst+Sfi+hwr59E0GLHV0h8RfgbQU7fdN5Lt9A8R1ulRiZyTvAizkROjwDA=="], + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg=="], - "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-Vn/w5oyQ6TUgTVDIC/BrpXwIlfK6V6kGWDVVz2eRkF2v13YoENUvaNwxMsQU/t6oCuZKzqp9vqtEtEzKl9VegA=="], + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ=="], - "@shikijs/langs": ["@shikijs/langs@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2" } }, "sha512-X1Q6wRRQXY7HqAuX3I8WjMscjeGjqXCg/Sve7J2GWFORXkSrXud23UECqTBIdCSNKJioFtmUGJQNKtlMMZMn0w=="], + "@shikijs/langs": ["@shikijs/langs@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0" } }, "sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA=="], - "@shikijs/themes": ["@shikijs/themes@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2" } }, "sha512-6z5lBPBMRfLyyEsgf6uJDHPa6NAGVzFJqH4EAZ+03+7sedYir2yJBRu2uPZOKmj43GyhVHWHvyduLDAwJQfDjA=="], + "@shikijs/themes": ["@shikijs/themes@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0" } }, "sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ=="], "@shikijs/transformers": ["@shikijs/transformers@3.9.2", "", { "dependencies": { "@shikijs/core": "3.9.2", "@shikijs/types": "3.9.2" } }, "sha512-MW5hT4TyUp6bNAgTExRYLk1NNasVQMTCw1kgbxHcEC0O5cbepPWaB+1k+JzW9r3SP2/R8kiens8/3E6hGKfgsA=="], @@ -1541,6 +1591,10 @@ "@solid-primitives/active-element": ["@solid-primitives/active-element@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9t5K4aR2naVDj950XU8OjnLgOg94a8k5wr6JNOPK+N5ESLsJDq42c1ZP8UKpewi1R+wplMMxiM6OPKRzbxJY7A=="], + "@solid-primitives/audio": ["@solid-primitives/audio@1.4.2", "", { "dependencies": { "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-UMD3ORQfI5Ky8yuKPxidDiEazsjv/dsoiKK5yZxLnsgaeNR1Aym3/77h/qT1jBYeXUgj4DX6t7NMpFUSVr14OQ=="], + + "@solid-primitives/bounds": ["@solid-primitives/bounds@0.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/resize-observer": "^2.1.3", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-UbiyKMdSPmtijcEDnYLQL3zzaejpwWDAJJ4Gt5P0hgVs6A72piov0GyNw7V2SroH7NZFwxlYS22YmOr8A5xc1Q=="], + "@solid-primitives/event-bus": ["@solid-primitives/event-bus@1.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-l+n10/51neGcMaP3ypYt21bXfoeWh8IaC8k7fYuY3ww2a8S1Zv2N2a7FF5Qn+waTu86l0V8/nRHjkyqVIZBYwA=="], "@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg=="], @@ -1651,12 +1705,24 @@ "@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=="], + + "@tauri-apps/plugin-notification": ["@tauri-apps/plugin-notification@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg=="], + "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew=="], + "@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A=="], + + "@tauri-apps/plugin-process": ["@tauri-apps/plugin-process@2.3.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA=="], + "@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Xod+pRcFxmOWFWEnqH5yZcA7qwAMuaaDkMR1Sply+F8VfBj++CGnj2xf5UoialmjZ2Cvd8qrvSCbU+7GgNVsKQ=="], + "@tauri-apps/plugin-store": ["@tauri-apps/plugin-store@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ckGSEzZ5Ii4Hf2D5x25Oqnm2Zf9MfDWAzR+volY0z/OOBz6aucPKEY0F649JvQ0Vupku6UJo7ugpGRDOFOunkA=="], + "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.9.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg=="], + "@tauri-apps/plugin-window-state": ["@tauri-apps/plugin-window-state@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw=="], + "@thisbeyond/solid-dnd": ["@thisbeyond/solid-dnd@0.7.5", "", { "peerDependencies": { "solid-js": "^1.5" } }, "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], @@ -1679,7 +1745,7 @@ "@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="], - "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], + "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="], "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], @@ -1711,6 +1777,8 @@ "@types/jsonwebtoken": ["@types/jsonwebtoken@8.5.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg=="], + "@types/katex": ["@types/katex@0.16.7", "", {}, "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="], + "@types/luxon": ["@types/luxon@3.7.1", "", {}, "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg=="], "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], @@ -1791,19 +1859,19 @@ "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], - "@vitest/expect": ["@vitest/expect@4.0.13", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.13", "@vitest/utils": "4.0.13", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-zYtcnNIBm6yS7Gpr7nFTmq8ncowlMdOJkWLqYvhr/zweY6tFbDkDi8BPPOeHxEtK1rSI69H7Fd4+1sqvEGli6w=="], + "@vitest/expect": ["@vitest/expect@4.0.16", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.16", "@vitest/utils": "4.0.16", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA=="], - "@vitest/mocker": ["@vitest/mocker@4.0.13", "", { "dependencies": { "@vitest/spy": "4.0.13", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-eNCwzrI5djoauklwP1fuslHBjrbR8rqIVbvNlAnkq1OTa6XT+lX68mrtPirNM9TnR69XUPt4puBCx2Wexseylg=="], + "@vitest/mocker": ["@vitest/mocker@4.0.16", "", { "dependencies": { "@vitest/spy": "4.0.16", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg=="], - "@vitest/pretty-format": ["@vitest/pretty-format@4.0.13", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-ooqfze8URWbI2ozOeLDMh8YZxWDpGXoeY3VOgcDnsUxN0jPyPWSUvjPQWqDGCBks+opWlN1E4oP1UYl3C/2EQA=="], + "@vitest/pretty-format": ["@vitest/pretty-format@4.0.16", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA=="], - "@vitest/runner": ["@vitest/runner@4.0.13", "", { "dependencies": { "@vitest/utils": "4.0.13", "pathe": "^2.0.3" } }, "sha512-9IKlAru58wcVaWy7hz6qWPb2QzJTKt+IOVKjAx5vb5rzEFPTL6H4/R9BMvjZ2ppkxKgTrFONEJFtzvnyEpiT+A=="], + "@vitest/runner": ["@vitest/runner@4.0.16", "", { "dependencies": { "@vitest/utils": "4.0.16", "pathe": "^2.0.3" } }, "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q=="], - "@vitest/snapshot": ["@vitest/snapshot@4.0.13", "", { "dependencies": { "@vitest/pretty-format": "4.0.13", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-hb7Usvyika1huG6G6l191qu1urNPsq1iFc2hmdzQY3F5/rTgqQnwwplyf8zoYHkpt7H6rw5UfIw6i/3qf9oSxQ=="], + "@vitest/snapshot": ["@vitest/snapshot@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA=="], - "@vitest/spy": ["@vitest/spy@4.0.13", "", {}, "sha512-hSu+m4se0lDV5yVIcNWqjuncrmBgwaXa2utFLIrBkQCQkt+pSwyZTPFQAZiiF/63j8jYa8uAeUZ3RSfcdWaYWw=="], + "@vitest/spy": ["@vitest/spy@4.0.16", "", {}, "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw=="], - "@vitest/utils": ["@vitest/utils@4.0.13", "", { "dependencies": { "@vitest/pretty-format": "4.0.13", "tinyrainbow": "^3.0.3" } }, "sha512-ydozWyQ4LZuu8rLp47xFUWis5VOKMdHjXCWhs1LuJsTNKww+pTHQNK4e0assIB9K80TxFyskENL6vCu3j34EYA=="], + "@vitest/utils": ["@vitest/utils@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="], "@webgpu/types": ["@webgpu/types@0.1.66", "", {}, "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA=="], @@ -1917,16 +1985,6 @@ "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="], - "bare-fs": ["bare-fs@4.5.1", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-zGUCsm3yv/ePt2PHNbVxjjn0nNB1MkIaR4wOCxJ2ig5pCf5cCVAYJXVhQg/3OhhJV6DB1ts7Hv0oUaElc2TPQg=="], - - "bare-os": ["bare-os@3.6.2", "", {}, "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A=="], - - "bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="], - - "bare-stream": ["bare-stream@2.7.0", "", { "dependencies": { "streamx": "^2.21.0" }, "peerDependencies": { "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-buffer", "bare-events"] }, "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A=="], - - "bare-url": ["bare-url@2.3.2", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw=="], - "base-64": ["base-64@1.0.0", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="], "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], @@ -1937,7 +1995,7 @@ "bcp-47-match": ["bcp-47-match@2.0.3", "", {}, "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ=="], - "before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], + "before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="], "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], @@ -1945,8 +2003,6 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], - "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], "blob-to-buffer": ["blob-to-buffer@1.2.9", "", {}, "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA=="], @@ -1955,6 +2011,8 @@ "body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="], + "bonjour-service": ["bonjour-service@1.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA=="], + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], "bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="], @@ -1985,7 +2043,7 @@ "bun-pty": ["bun-pty@0.4.2", "", {}, "sha512-sHImDz6pJDsHAroYpC9ouKVgOyqZ7FP3N+stX5IdMddHve3rf9LIZBDomQcXrACQ7sQDNuwZQHG8BKR7w8krkQ=="], - "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], + "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="], "bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="], @@ -2023,7 +2081,7 @@ "chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="], - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], @@ -2149,10 +2207,6 @@ "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], - "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], - - "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], - "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], "default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="], @@ -2181,7 +2235,7 @@ "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], @@ -2203,6 +2257,8 @@ "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], + "dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="], + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], @@ -2241,8 +2297,6 @@ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], - "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -2309,7 +2363,7 @@ "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], - "events": ["events@1.1.1", "", {}, "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw=="], + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], @@ -2323,9 +2377,7 @@ "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], - "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], - - "expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], "express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="], @@ -2393,8 +2445,6 @@ "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], - "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], - "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], @@ -2445,8 +2495,6 @@ "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], - "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], - "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], @@ -2743,6 +2791,8 @@ "jwt-decode": ["jwt-decode@3.1.2", "", {}, "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="], + "katex": ["katex@0.16.27", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw=="], + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], @@ -2819,6 +2869,8 @@ "lru.min": ["lru.min@1.1.3", "", {}, "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q=="], + "lru_map": ["lru_map@0.4.1", "", {}, "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg=="], + "luxon": ["luxon@3.6.1", "", {}, "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -2829,7 +2881,9 @@ "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], - "marked": ["marked@16.2.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-LbbTuye+0dWRz2TS9KJ7wsnD4KAtpj0MVkWc90XvBa6AslXsT0hTBVH5k32pcSyHH1fst9XEFJunXHktVy0zlg=="], + "marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="], + + "marked-katex-extension": ["marked-katex-extension@5.1.6", "", { "peerDependencies": { "katex": ">=0.16 <0.17", "marked": ">=4 <18" } }, "sha512-vYpLXwmlIDKILIhJtiRTgdyZRn5sEYdFBuTmbpjD7lbCIzg0/DWyK3HXIntN3Tp8zV6hvOUgpZNLWRCgWVc24A=="], "marked-shiki": ["marked-shiki@1.2.1", "", { "peerDependencies": { "marked": ">=7.0.0", "shiki": ">=1.0.0" } }, "sha512-yHxYQhPY5oYaIRnROn98foKhuClark7M373/VpLxiy5TrDu9Jd/LsMwo8w+U91Up4oDb9IXFrP0N1MFRz8W/DQ=="], @@ -2969,8 +3023,6 @@ "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], - "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], - "miniflare": ["miniflare@4.20251118.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251118.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-uLSAE/DvOm392fiaig4LOaatxLjM7xzIniFRG5Y3yF9IduOYLLK/pkCPQNCgKQH3ou0YJRHnTN+09LPfqYNTQQ=="], "minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], @@ -2983,12 +3035,12 @@ "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], - "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], - "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "multicast-dns": ["multicast-dns@7.2.5", "", { "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" }, "bin": { "multicast-dns": "cli.js" } }, "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg=="], + "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], "mysql2": ["mysql2@3.14.4", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-Cs/jx3WZPNrYHVz+Iunp9ziahaG5uFMvD2R8Zlmc194AqXNxt9HBNu7ZsPYrUtmJsF0egETCWIdMIYAwOGjL1w=="], @@ -2999,8 +3051,6 @@ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], - "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], @@ -3013,9 +3063,7 @@ "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], - "node-abi": ["node-abi@3.85.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg=="], - - "node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="], + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], @@ -3053,6 +3101,8 @@ "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + "ofetch": ["ofetch@2.0.0-alpha.3", "", {}, "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA=="], "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], @@ -3205,8 +3255,6 @@ "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], - "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], - "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], "pretty": ["pretty@2.0.0", "", { "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", "js-beautify": "^1.6.12" } }, "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w=="], @@ -3229,8 +3277,6 @@ "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], - "punycode": ["punycode@1.3.2", "", {}, "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="], "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], @@ -3247,8 +3293,6 @@ "raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="], - "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], - "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], "react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], @@ -3407,7 +3451,7 @@ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], - "sharp": ["sharp@0.32.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.2", "node-addon-api": "^6.1.0", "prebuild-install": "^7.1.1", "semver": "^7.5.4", "simple-get": "^4.0.1", "tar-fs": "^3.0.4", "tunnel-agent": "^0.6.0" } }, "sha512-0dap3iysgDkNaPOaOL4X/0akdu0ma62GcdC2NBQ+93eqpePdDdr2/LM0sFdDSMmN7yS+odyZtPsb7tx/cYBKnQ=="], + "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -3415,7 +3459,7 @@ "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], - "shiki": ["shiki@3.9.2", "", { "dependencies": { "@shikijs/core": "3.9.2", "@shikijs/engine-javascript": "3.9.2", "@shikijs/engine-oniguruma": "3.9.2", "@shikijs/langs": "3.9.2", "@shikijs/themes": "3.9.2", "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-t6NKl5e/zGTvw/IyftLcumolgOczhuroqwXngDeMqJ3h3EQiTY/7wmfgPlsmloD8oYfqkEDqxiaH37Pjm1zUhQ=="], + "shiki": ["shiki@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/engine-javascript": "3.20.0", "@shikijs/engine-oniguruma": "3.20.0", "@shikijs/langs": "3.20.0", "@shikijs/themes": "3.20.0", "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg=="], "shikiji": ["shikiji@0.6.13", "", { "dependencies": { "hast-util-to-html": "^9.0.0" } }, "sha512-4T7X39csvhT0p7GDnq9vysWddf2b6BeioiN3Ymhnt3xcy9tXmDcnsEFVxX18Z4YcQgEE/w48dLJ4pPPUcG9KkA=="], @@ -3431,10 +3475,6 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], - - "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], - "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], "simple-xml-to-json": ["simple-xml-to-json@1.2.3", "", {}, "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA=="], @@ -3533,8 +3573,6 @@ "strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="], - "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], - "stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="], "strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="], @@ -3561,8 +3599,6 @@ "tar": ["tar@7.5.2", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg=="], - "tar-fs": ["tar-fs@3.1.1", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg=="], - "tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], "terracotta": ["terracotta@1.0.6", "", { "dependencies": { "solid-use": "^0.9.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-yVrmT/Lg6a3tEbeYEJH8ksb1PYkR5FA9k5gr1TchaSNIiA2ZWs5a+koEbePXwlBP0poaV7xViZ/v50bQFcMgqw=="], @@ -3577,6 +3613,8 @@ "three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="], + "thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="], + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], @@ -3623,8 +3661,6 @@ "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], - "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], - "turbo": ["turbo@2.5.6", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.6", "turbo-darwin-arm64": "2.5.6", "turbo-linux-64": "2.5.6", "turbo-linux-arm64": "2.5.6", "turbo-windows-64": "2.5.6", "turbo-windows-arm64": "2.5.6" }, "bin": { "turbo": "bin/turbo" } }, "sha512-gxToHmi9oTBNB05UjUsrWf0OyN5ZXtD0apOarC1KIx232Vp3WimRNy3810QzeNSgyD5rsaIDXlxlbnOzlouo+w=="], "turbo-darwin-64": ["turbo-darwin-64@2.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-3C1xEdo4aFwMJAPvtlPqz1Sw/+cddWIOmsalHFMrsqqydcptwBfu26WW2cDm3u93bUzMbBJ8k3zNKFqxJ9ei2A=="], @@ -3753,7 +3789,7 @@ "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], - "vitest": ["vitest@4.0.13", "", { "dependencies": { "@vitest/expect": "4.0.13", "@vitest/mocker": "4.0.13", "@vitest/pretty-format": "4.0.13", "@vitest/runner": "4.0.13", "@vitest/snapshot": "4.0.13", "@vitest/spy": "4.0.13", "@vitest/utils": "4.0.13", "debug": "^4.4.3", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/debug": "^4.1.12", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.13", "@vitest/browser-preview": "4.0.13", "@vitest/browser-webdriverio": "4.0.13", "@vitest/ui": "4.0.13", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/debug", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-QSD4I0fN6uZQfftryIXuqvqgBxTvJ3ZNkF6RWECd82YGAYAfhcppBLFXzXJHQAAhVFyYEuFTrq6h0hQqjB7jIQ=="], + "vitest": ["vitest@4.0.16", "", { "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", "@vitest/pretty-format": "4.0.16", "@vitest/runner": "4.0.16", "@vitest/snapshot": "4.0.16", "@vitest/spy": "4.0.16", "@vitest/utils": "4.0.16", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.16", "@vitest/browser-preview": "4.0.16", "@vitest/browser-webdriverio": "4.0.16", "@vitest/ui": "4.0.16", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q=="], "vscode-jsonrpc": ["vscode-jsonrpc@8.2.1", "", {}, "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ=="], @@ -3843,24 +3879,16 @@ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - "@actions/artifact/@octokit/core": ["@octokit/core@5.2.2", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="], - - "@actions/artifact/@octokit/plugin-request-log": ["@octokit/plugin-request-log@1.0.4", "", { "peerDependencies": { "@octokit/core": ">=3" } }, "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA=="], + "@actions/artifact/@actions/core": ["@actions/core@2.0.1", "", { "dependencies": { "@actions/exec": "^2.0.0", "@actions/http-client": "^3.0.0" } }, "sha512-oBfqT3GwkvLlo1fjvhQLQxuwZCGTarTE5OuZ2Wg10hvhBj7LRIlF611WT4aZS6fDhO5ZKlY7lCAZTlpmyaHaeg=="], - "@actions/artifact/@octokit/request": ["@octokit/request@8.4.1", "", { "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw=="], + "@actions/core/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], - "@actions/artifact/@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="], - - "@actions/github/@octokit/core": ["@octokit/core@5.2.2", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="], + "@actions/github/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], "@actions/github/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.2", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="], "@actions/github/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@10.4.1", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg=="], - "@actions/github/@octokit/request": ["@octokit/request@8.4.1", "", { "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw=="], - - "@actions/github/@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="], - "@actions/github/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], "@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], @@ -3873,20 +3901,24 @@ "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="], - "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.71", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tg+gj+R0z/On9P4V7hy7/7o04cQPjKGayMCL3gzWD/aNGjAKkhEnaocuNDidSnghizt8g2zJn16cAuAolnW+qQ=="], + "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.80", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tNHuraF11db+8xJEDBoU9E3vMcpnHFKRhnLQ3DQX2LnEzfPB9DksZ8rE+yVuDN1WRW9cm2OWAhgHFgVKs7ICuw=="], - "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + "@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="], - "@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + "@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="], "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="], - "@ai-sdk/mcp/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + "@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="], "@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="], "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="], + "@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="], + + "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="], + "@astrojs/cloudflare/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], "@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="], @@ -3931,40 +3963,16 @@ "@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], - "@azure/core-auth/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], - - "@azure/core-client/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], + "@azure/core-http/@azure/abort-controller": ["@azure/abort-controller@1.1.0", "", { "dependencies": { "tslib": "^2.2.0" } }, "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw=="], - "@azure/core-client/@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], + "@azure/core-http/@azure/core-tracing": ["@azure/core-tracing@1.0.0-preview.13", "", { "dependencies": { "@opentelemetry/api": "^1.0.1", "tslib": "^2.2.0" } }, "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ=="], "@azure/core-http/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], "@azure/core-http/xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], - "@azure/core-http-compat/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], - - "@azure/core-lro/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], - - "@azure/core-rest-pipeline/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], - - "@azure/core-rest-pipeline/@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], - - "@azure/core-util/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], - "@azure/core-xml/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], - "@azure/storage-blob/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], - - "@azure/storage-blob/@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], - - "@azure/storage-blob/events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - - "@azure/storage-common/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], - - "@azure/storage-common/@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], - - "@azure/storage-common/events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], @@ -3977,6 +3985,8 @@ "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], + "@dot/log/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@expressive-code/plugin-shiki/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="], @@ -4023,6 +4033,8 @@ "@jimp/types/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@jsx-email/cli/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "@jsx-email/cli/esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], "@jsx-email/cli/tailwindcss": ["tailwindcss@3.3.3", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.18.2", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w=="], @@ -4037,43 +4049,71 @@ "@modelcontextprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + + "@octokit/auth-app/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], + + "@octokit/auth-oauth-app/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/auth-oauth-app/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + "@octokit/auth-oauth-device/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/auth-oauth-device/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + "@octokit/auth-oauth-user/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/auth-oauth-user/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], - "@octokit/core/@octokit/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="], + "@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="], + + "@octokit/core/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + + "@octokit/core/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], - "@octokit/core/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + "@octokit/endpoint/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], - "@octokit/endpoint/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + "@octokit/endpoint/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], + + "@octokit/graphql/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], "@octokit/graphql/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="], + "@octokit/oauth-methods/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + + "@octokit/oauth-methods/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], + "@octokit/oauth-methods/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + "@octokit/plugin-paginate-rest/@octokit/core": ["@octokit/core@7.0.6", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q=="], + "@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="], + "@octokit/plugin-rest-endpoint-methods/@octokit/core": ["@octokit/core@7.0.6", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q=="], + "@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="], "@octokit/plugin-retry/@octokit/types": ["@octokit/types@6.41.0", "", { "dependencies": { "@octokit/openapi-types": "^12.11.0" } }, "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg=="], - "@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + "@octokit/request/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + + "@octokit/request/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], - "@octokit/request-error/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + "@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + + "@octokit/rest/@octokit/core": ["@octokit/core@7.0.6", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q=="], + + "@octokit/rest/@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="], "@openauthjs/openauth/@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.3", "", {}, "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw=="], "@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], - "@opencode-ai/tauri/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], - - "@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.4.2", "", { "dependencies": { "@shikijs/core": "3.4.2", "@shikijs/types": "3.4.2" } }, "sha512-I5baLVi/ynLEOZoWSAMlACHNnG+yw5HDmse0oe+GW6U1u+ULdEB3UHiVWaHoJSSONV7tlcVxuaMy74sREDkSvg=="], + "@opencode-ai/desktop/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="], - "@opencode-ai/web/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], + "@opencode-ai/desktop/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], - "@opencode-ai/web/shiki": ["shiki@3.4.2", "", { "dependencies": { "@shikijs/core": "3.4.2", "@shikijs/engine-javascript": "3.4.2", "@shikijs/engine-oniguruma": "3.4.2", "@shikijs/langs": "3.4.2", "@shikijs/themes": "3.4.2", "@shikijs/types": "3.4.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-wuxzZzQG8kvZndD7nustrNFIKYJ1jJoWIPaBpVe2+KHSvtzMi4SBjOxrigs8qeqce/l3U0cwiC+VAkLKSunHQQ=="], + "@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.4.2", "", { "dependencies": { "@shikijs/core": "3.4.2", "@shikijs/types": "3.4.2" } }, "sha512-I5baLVi/ynLEOZoWSAMlACHNnG+yw5HDmse0oe+GW6U1u+ULdEB3UHiVWaHoJSSONV7tlcVxuaMy74sREDkSvg=="], "@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], @@ -4081,15 +4121,13 @@ "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="], - "@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], - - "@parcel/watcher/node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + "@pierre/diffs/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], - "@pierre/precision-diffs/@shikijs/core": ["@shikijs/core@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg=="], + "@pierre/diffs/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-ZfWJNm2VMhKkQIKT9qXbs76RRcT0SF/CAvEz0+RkpUDAoDaCx0uFdCGzSRiD9gSlhm6AHkjdieOBJMaO2eC1rQ=="], - "@pierre/precision-diffs/@shikijs/transformers": ["@shikijs/transformers@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/types": "3.15.0" } }, "sha512-Hmwip5ovvSkg+Kc41JTvSHHVfCYF+C8Cp1omb5AJj4Xvd+y9IXz2rKJwmFRGsuN0vpHxywcXJ1+Y4B9S7EG1/A=="], + "@pierre/diffs/@shikijs/transformers": ["@shikijs/transformers@3.19.0", "", { "dependencies": { "@shikijs/core": "3.19.0", "@shikijs/types": "3.19.0" } }, "sha512-e6vwrsyw+wx4OkcrDbL+FVCxwx8jgKiCoXzakVur++mIWVcgpzIi8vxf4/b4dVTYrV/nUx5RjinMf4tq8YV8Fw=="], - "@pierre/precision-diffs/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="], + "@pierre/diffs/shiki": ["shiki@3.19.0", "", { "dependencies": { "@shikijs/core": "3.19.0", "@shikijs/engine-javascript": "3.19.0", "@shikijs/engine-oniguruma": "3.19.0", "@shikijs/langs": "3.19.0", "@shikijs/themes": "3.19.0", "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-77VJr3OR/VUZzPiStyRhADmO2jApMM0V2b1qf0RpfWya8Zr1PeZev5AEpPGAAKWdiYUtcZGBE4F5QvJml1PvWA=="], "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], @@ -4097,6 +4135,14 @@ "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@shikijs/engine-javascript/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + + "@shikijs/engine-oniguruma/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + + "@shikijs/langs/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + + "@shikijs/themes/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + "@slack/bolt/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], "@slack/oauth/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="], @@ -4121,6 +4167,8 @@ "@solidjs/start/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="], + "@tailwindcss/oxide/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], @@ -4135,6 +4183,8 @@ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "ai/@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.12", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W+cB1sOWvPcz9qiIsNtD+HxUrBUva2vWv2K1EFukuImX+HA0uZx3EyyOjhYQ9gtf/teqEG80M6OvJ7xx/VLV2A=="], + "ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -4149,8 +4199,6 @@ "astro/diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="], - "astro/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], - "astro/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="], "astro/unstorage": ["unstorage@1.17.3", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q=="], @@ -4159,22 +4207,18 @@ "astro/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "aws-sdk/events": ["events@1.1.1", "", {}, "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw=="], + "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], "babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], - "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - - "bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], "body-parser/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="], - "boxen/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "clean-css/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], @@ -4193,6 +4237,8 @@ "es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "esbuild-plugin-copy/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "esbuild-plugin-copy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], @@ -4227,8 +4273,12 @@ "jsonwebtoken/jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="], + "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="], "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], @@ -4237,8 +4287,6 @@ "miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], - "miniflare/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], - "miniflare/undici": ["undici@7.14.0", "", {}, "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ=="], "miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], @@ -4251,11 +4299,11 @@ "nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], - "opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="], + "opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ=="], "opencode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.71", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tg+gj+R0z/On9P4V7hy7/7o04cQPjKGayMCL3gzWD/aNGjAKkhEnaocuNDidSnghizt8g2zJn16cAuAolnW+qQ=="], - "opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.27", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bpYruxVLhrTbVH6CCq48zMJNeHu6FmHtEedl9FXckEgcIEAi036idFhJlcRwC1jNCwlacbzb8dPD7OAH1EKJaQ=="], + "opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="], "opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="], @@ -4285,16 +4333,12 @@ "postcss-load-config/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - "prebuild-install/tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], - "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], "raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], "readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - "readable-stream/events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - "readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], @@ -4309,6 +4353,12 @@ "send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + "sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "shiki/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], + + "shiki/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + "sitemap/sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="], "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], @@ -4345,7 +4395,7 @@ "utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], - "vite-plugin-icons-spritesheet/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "vitest/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], "vitest/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="], @@ -4367,46 +4417,14 @@ "zod-to-ts/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@actions/artifact/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="], - - "@actions/artifact/@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="], - - "@actions/artifact/@octokit/core/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], - - "@actions/artifact/@octokit/core/before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="], - - "@actions/artifact/@octokit/core/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], - - "@actions/artifact/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@9.0.6", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="], - - "@actions/artifact/@octokit/request/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], - - "@actions/artifact/@octokit/request/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], - - "@actions/artifact/@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + "@actions/artifact/@actions/core/@actions/exec": ["@actions/exec@2.0.0", "", { "dependencies": { "@actions/io": "^2.0.0" } }, "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw=="], - "@actions/github/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="], - - "@actions/github/@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="], - - "@actions/github/@octokit/core/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], - - "@actions/github/@octokit/core/before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="], - - "@actions/github/@octokit/core/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], + "@actions/core/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], "@actions/github/@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], "@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], - "@actions/github/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@9.0.6", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="], - - "@actions/github/@octokit/request/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], - - "@actions/github/@octokit/request/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], - - "@actions/github/@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], - "@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/core@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg=="], "@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg=="], @@ -4597,61 +4615,117 @@ "@modelcontextprotocol/sdk/raw-body/http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + "@octokit/auth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + + "@octokit/auth-app/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + + "@octokit/auth-app/@octokit/request-error/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + + "@octokit/auth-oauth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + + "@octokit/auth-oauth-app/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], + "@octokit/auth-oauth-app/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@octokit/auth-oauth-device/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + + "@octokit/auth-oauth-device/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], + "@octokit/auth-oauth-device/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@octokit/auth-oauth-user/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + + "@octokit/auth-oauth-user/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], + "@octokit/auth-oauth-user/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], - "@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], - "@octokit/endpoint/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@octokit/endpoint/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], + + "@octokit/graphql/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + + "@octokit/graphql/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], + + "@octokit/graphql/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], "@octokit/graphql/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="], + "@octokit/oauth-methods/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + "@octokit/oauth-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + + "@octokit/plugin-paginate-rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], + "@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="], + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], + "@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="], "@octokit/plugin-retry/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@12.11.0", "", {}, "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="], - "@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], - "@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], - "@opencode-ai/web/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.4.2", "", { "dependencies": { "@shikijs/types": "3.4.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AG8vnSi1W2pbgR2B911EfGqtLE9c4hQBYkv/x7Z+Kt0VxhgQKcW7UNDVYsu9YxwV6u+OJrvdJrMq6DNWoBjihQ=="], + "@octokit/rest/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="], - "@opencode-ai/web/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.4.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zHC1l7L+eQlDXLnxvM9R91Efh2V4+rN3oMVS2swCBssbj2U/FBwybD1eeLaq8yl/iwT+zih8iUbTBCgGZOYlVg=="], + "@octokit/rest/@octokit/core/@octokit/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="], + + "@octokit/rest/@octokit/core/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], - "@opencode-ai/web/shiki/@shikijs/core": ["@shikijs/core@3.4.2", "", { "dependencies": { "@shikijs/types": "3.4.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AG8vnSi1W2pbgR2B911EfGqtLE9c4hQBYkv/x7Z+Kt0VxhgQKcW7UNDVYsu9YxwV6u+OJrvdJrMq6DNWoBjihQ=="], + "@octokit/rest/@octokit/core/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], - "@opencode-ai/web/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.4.2", "", { "dependencies": { "@shikijs/types": "3.4.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-1/adJbSMBOkpScCE/SB6XkjJU17ANln3Wky7lOmrnpl+zBdQ1qXUJg2GXTYVHRq+2j3hd1DesmElTXYDgtfSOQ=="], + "@octokit/rest/@octokit/core/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], - "@opencode-ai/web/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.4.2", "", { "dependencies": { "@shikijs/types": "3.4.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-zcZKMnNndgRa3ORja6Iemsr3DrLtkX3cAF7lTJkdMB6v9alhlBsX9uNiCpqofNrXOvpA3h6lHcLJxgCIhVOU5Q=="], + "@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], - "@opencode-ai/web/shiki/@shikijs/langs": ["@shikijs/langs@3.4.2", "", { "dependencies": { "@shikijs/types": "3.4.2" } }, "sha512-H6azIAM+OXD98yztIfs/KH5H4PU39t+SREhmM8LaNXyUrqj2mx+zVkr8MWYqjceSjDw9I1jawm1WdFqU806rMA=="], + "@opencode-ai/desktop/@actions/artifact/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], - "@opencode-ai/web/shiki/@shikijs/themes": ["@shikijs/themes@3.4.2", "", { "dependencies": { "@shikijs/types": "3.4.2" } }, "sha512-qAEuAQh+brd8Jyej2UDDf+b4V2g1Rm8aBIdvt32XhDPrHvDkEnpb7Kzc9hSuHUxz0Iuflmq7elaDuQAP9bHIhg=="], + "@opencode-ai/web/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.4.2", "", { "dependencies": { "@shikijs/types": "3.4.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AG8vnSi1W2pbgR2B911EfGqtLE9c4hQBYkv/x7Z+Kt0VxhgQKcW7UNDVYsu9YxwV6u+OJrvdJrMq6DNWoBjihQ=="], - "@opencode-ai/web/shiki/@shikijs/types": ["@shikijs/types@3.4.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zHC1l7L+eQlDXLnxvM9R91Efh2V4+rN3oMVS2swCBssbj2U/FBwybD1eeLaq8yl/iwT+zih8iUbTBCgGZOYlVg=="], + "@opencode-ai/web/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.4.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zHC1l7L+eQlDXLnxvM9R91Efh2V4+rN3oMVS2swCBssbj2U/FBwybD1eeLaq8yl/iwT+zih8iUbTBCgGZOYlVg=="], "@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@pierre/precision-diffs/@shikijs/core/@shikijs/types": ["@shikijs/types@3.15.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw=="], + "@pierre/diffs/@shikijs/core/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], - "@pierre/precision-diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.15.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw=="], + "@pierre/diffs/@shikijs/engine-javascript/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="], - "@pierre/precision-diffs/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg=="], + "@pierre/diffs/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA=="], - "@pierre/precision-diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA=="], + "@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="], - "@pierre/precision-diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0" } }, "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A=="], + "@pierre/diffs/shiki/@shikijs/core": ["@shikijs/core@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA=="], - "@pierre/precision-diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0" } }, "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ=="], + "@pierre/diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg=="], - "@pierre/precision-diffs/shiki/@shikijs/types": ["@shikijs/types@3.15.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw=="], + "@pierre/diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0" } }, "sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg=="], + + "@pierre/diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0" } }, "sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A=="], + + "@pierre/diffs/shiki/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="], "@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], @@ -4833,8 +4907,6 @@ "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], - "opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], - "opencontrol/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="], "opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="], @@ -4849,10 +4921,6 @@ "pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="], - "prebuild-install/tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], - - "prebuild-install/tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], - "readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -4877,22 +4945,12 @@ "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@actions/artifact/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], - - "@actions/artifact/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], - - "@actions/artifact/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], - - "@actions/github/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], + "@actions/artifact/@actions/core/@actions/exec/@actions/io": ["@actions/io@2.0.0", "", {}, "sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg=="], "@actions/github/@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], "@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], - "@actions/github/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], - - "@actions/github/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], - "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/core@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg=="], "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg=="], @@ -4975,6 +5033,26 @@ "@modelcontextprotocol/sdk/raw-body/http-errors/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + "@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + + "@octokit/auth-app/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + + "@octokit/graphql/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + + "@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + + "@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + + "@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + "@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], @@ -5027,8 +5105,6 @@ "pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], - "prebuild-install/tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], diff --git a/flake.lock b/flake.lock index 6aaa22e94ea..5dae5a88cfa 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1764947035, - "narHash": "sha256-EYHSjVM4Ox4lvCXUMiKKs2vETUSL5mx+J2FfutM7T9w=", + "lastModified": 1767026758, + "narHash": "sha256-7fsac/f7nh/VaKJ/qm3I338+wAJa/3J57cOGpXi0Sbg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a672be65651c80d3f592a89b3945466584a22069", + "rev": "346dd96ad74dc4457a9db9de4f4f57dab2e5731d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index a6614a5dc9c..a578da9c1f7 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,7 @@ "aarch64-darwin" "x86_64-darwin" ]; - lib = nixpkgs.lib; + inherit (nixpkgs) lib; forEachSystem = lib.genAttrs systems; pkgsFor = system: nixpkgs.legacyPackages.${system}; packageJson = builtins.fromJSON (builtins.readFile ./packages/opencode/package.json); @@ -70,12 +70,12 @@ in { default = mkPackage { - version = packageJson.version; + inherit (packageJson) version; src = ./.; scripts = ./nix/scripts; target = bunTarget.${system}; modelsDev = "${modelsDev.${system}}/dist/_api.json"; - mkNodeModules = mkNodeModules; + inherit mkNodeModules; }; } ); diff --git a/github/README.md b/github/README.md index 36342b40995..e35860340c4 100644 --- a/github/README.md +++ b/github/README.md @@ -6,7 +6,7 @@ Mention `/opencode` in your comment, and opencode will execute tasks within your ## Features -#### Explain an issues +#### Explain an issue Leave the following comment on a GitHub issue. `opencode` will read the entire thread, including all comments, and reply with a clear explanation. @@ -14,7 +14,7 @@ Leave the following comment on a GitHub issue. `opencode` will read the entire t /opencode explain this issue ``` -#### Fix an issues +#### Fix an issue Leave the following comment on a GitHub issue. opencode will create a new branch, implement the changes, and open a PR with the changes. diff --git a/github/action.yml b/github/action.yml index d22d19990ae..57e26d8566c 100644 --- a/github/action.yml +++ b/github/action.yml @@ -9,6 +9,10 @@ inputs: description: "Model to use" required: true + agent: + description: "Agent to use. Must be a primary agent. Falls back to default_agent from config or 'build' if not found." + required: false + share: description: "Share the opencode session (defaults to true for public repos)" required: false @@ -17,18 +21,54 @@ inputs: description: "Custom prompt to override the default prompt" required: false + use_github_token: + description: "Use GITHUB_TOKEN directly instead of OpenCode App token exchange. When true, skips OIDC and uses the GITHUB_TOKEN env var." + required: false + default: "false" + + mentions: + description: "Comma-separated list of trigger phrases (case-insensitive). Defaults to '/opencode,/oc'" + required: false + + oidc_base_url: + description: "Base URL for OIDC token exchange API. Only required when running a custom GitHub App install. Defaults to https://api.opencode.ai" + required: false + runs: using: "composite" steps: + - name: Get opencode version + id: version + shell: bash + run: | + VERSION=$(curl -sf https://api.github.com/repos/sst/opencode/releases/latest | grep -o '"tag_name": *"[^"]*"' | cut -d'"' -f4) + echo "version=${VERSION:-latest}" >> $GITHUB_OUTPUT + + - name: Cache opencode + id: cache + uses: actions/cache@v4 + with: + path: ~/.opencode/bin + key: opencode-${{ runner.os }}-${{ runner.arch }}-${{ steps.version.outputs.version }} + - name: Install opencode + if: steps.cache.outputs.cache-hit != 'true' shell: bash run: curl -fsSL https://opencode.ai/install | bash + - name: Add opencode to PATH + shell: bash + run: echo "$HOME/.opencode/bin" >> $GITHUB_PATH + - name: Run opencode shell: bash id: run_opencode run: opencode github run env: MODEL: ${{ inputs.model }} + AGENT: ${{ inputs.agent }} SHARE: ${{ inputs.share }} PROMPT: ${{ inputs.prompt }} + USE_GITHUB_TOKEN: ${{ inputs.use_github_token }} + MENTIONS: ${{ inputs.mentions }} + OIDC_BASE_URL: ${{ inputs.oidc_base_url }} diff --git a/github/index.ts b/github/index.ts index 6d826326ed7..2dcf6e75480 100644 --- a/github/index.ts +++ b/github/index.ts @@ -318,6 +318,10 @@ function useEnvRunUrl() { return `/${repo.owner}/${repo.repo}/actions/runs/${runId}` } +function useEnvAgent() { + return process.env["AGENT"] || undefined +} + function useEnvShare() { const value = process.env["SHARE"] if (!value) return undefined @@ -570,24 +574,49 @@ async function subscribeSessionEvents() { } async function summarize(response: string) { - const payload = useContext().payload as IssueCommentEvent try { return await chat(`Summarize the following in less than 40 characters:\n\n${response}`) } catch (e) { + if (isScheduleEvent()) { + return "Scheduled task changes" + } + const payload = useContext().payload as IssueCommentEvent return `Fix issue: ${payload.issue.title}` } } +async function resolveAgent(): Promise { + const envAgent = useEnvAgent() + if (!envAgent) return undefined + + // Validate the agent exists and is a primary agent + const agents = await client.agent.list() + const agent = agents.data?.find((a) => a.name === envAgent) + + if (!agent) { + console.warn(`agent "${envAgent}" not found. Falling back to default agent`) + return undefined + } + + if (agent.mode === "subagent") { + console.warn(`agent "${envAgent}" is a subagent, not a primary agent. Falling back to default agent`) + return undefined + } + + return envAgent +} + async function chat(text: string, files: PromptFiles = []) { console.log("Sending message to opencode...") const { providerID, modelID } = useEnvModel() + const agent = await resolveAgent() const chat = await client.session.chat({ path: session, body: { providerID, modelID, - agent: "build", + agent, parts: [ { type: "text", diff --git a/github/package.json b/github/package.json index 1a6598d6ba1..4d447716fc4 100644 --- a/github/package.json +++ b/github/package.json @@ -13,7 +13,7 @@ "@actions/core": "1.11.1", "@actions/github": "6.0.1", "@octokit/graphql": "9.0.1", - "@octokit/rest": "22.0.0", + "@octokit/rest": "catalog:", "@opencode-ai/sdk": "workspace:*" } } diff --git a/infra/app.ts b/infra/app.ts index 7215995badd..1b2351ec8cd 100644 --- a/infra/app.ts +++ b/infra/app.ts @@ -44,3 +44,12 @@ new sst.cloudflare.x.Astro("Web", { VITE_API_URL: api.url.apply((url) => url!), }, }) + +new sst.cloudflare.StaticSite("WebApp", { + domain: "app." + domain, + path: "packages/app", + build: { + command: "bun turbo build", + output: "./dist", + }, +}) diff --git a/infra/console.ts b/infra/console.ts index 0a98ab07235..c69a706838e 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -102,6 +102,8 @@ const ZEN_MODELS = [ new sst.Secret("ZEN_MODELS2"), new sst.Secret("ZEN_MODELS3"), new sst.Secret("ZEN_MODELS4"), + new sst.Secret("ZEN_MODELS5"), + new sst.Secret("ZEN_MODELS6"), ] const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY") const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", { @@ -117,6 +119,7 @@ const gatewayKv = new sst.cloudflare.Kv("GatewayKv") //////////////// const bucket = new sst.cloudflare.Bucket("ZenData") +const bucketNew = new sst.cloudflare.Bucket("ZenDataNew") const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID") const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY") @@ -135,6 +138,7 @@ new sst.cloudflare.x.SolidStart("Console", { path: "packages/console/app", link: [ bucket, + bucketNew, database, AUTH_API_URL, STRIPE_WEBHOOK_SECRET, diff --git a/infra/desktop.ts b/infra/desktop.ts deleted file mode 100644 index d4e32c65d72..00000000000 --- a/infra/desktop.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { domain } from "./stage" - -new sst.cloudflare.StaticSite("Desktop", { - domain: "desktop." + domain, - path: "packages/desktop", - build: { - command: "bun turbo build", - output: "./dist", - }, -}) diff --git a/infra/enterprise.ts b/infra/enterprise.ts index 70693846a11..22b4c6f44ee 100644 --- a/infra/enterprise.ts +++ b/infra/enterprise.ts @@ -1,10 +1,10 @@ import { SECRET } from "./secret" -import { domain } from "./stage" +import { domain, shortDomain } from "./stage" const storage = new sst.cloudflare.Bucket("EnterpriseStorage") -const enterprise = new sst.cloudflare.x.SolidStart("Enterprise", { - domain: "enterprise." + domain, +const teams = new sst.cloudflare.x.SolidStart("Teams", { + domain: shortDomain, path: "packages/enterprise", buildCommand: "bun run build:cloudflare", environment: { diff --git a/infra/stage.ts b/infra/stage.ts index 729422905d6..f9a6fd75529 100644 --- a/infra/stage.ts +++ b/infra/stage.ts @@ -11,3 +11,9 @@ new cloudflare.RegionalHostname("RegionalHostname", { regionKey: "us", zoneId: zoneID, }) + +export const shortDomain = (() => { + if ($app.stage === "production") return "opncd.ai" + if ($app.stage === "dev") return "dev.opncd.ai" + return `${$app.stage}.dev.opncd.ai` +})() diff --git a/install b/install index c6f20973458..702fb4a534c 100755 --- a/install +++ b/install @@ -7,7 +7,51 @@ RED='\033[0;31m' ORANGE='\033[38;5;214m' NC='\033[0m' # No Color +usage() { + cat < Install a specific version (e.g., 1.0.180) + --no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.) + +Examples: + curl -fsSL https://opencode.ai/install | bash + curl -fsSL https://opencode.ai/install | bash -s -- --version 1.0.180 +EOF +} + requested_version=${VERSION:-} +no_modify_path=false + +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + -v|--version) + if [[ -n "${2:-}" ]]; then + requested_version="$2" + shift 2 + else + echo -e "${RED}Error: --version requires a version argument${NC}" + exit 1 + fi + ;; + --no-modify-path) + no_modify_path=true + shift + ;; + *) + echo -e "${ORANGE}Warning: Unknown option '$1'${NC}" >&2 + shift + ;; + esac +done raw_os=$(uname -s) os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]') @@ -111,8 +155,18 @@ if [ -z "$requested_version" ]; then exit 1 fi else + # Strip leading 'v' if present + requested_version="${requested_version#v}" url="https://github.com/sst/opencode/releases/download/v${requested_version}/$filename" specific_version=$requested_version + + # Verify the release exists before downloading + http_status=$(curl -sI -o /dev/null -w "%{http_code}" "https://github.com/sst/opencode/releases/tag/v${requested_version}") + if [ "$http_status" = "404" ]; then + echo -e "${RED}Error: Release v${requested_version} not found${NC}" + echo -e "${MUTED}Available releases: https://github.com/sst/opencode/releases${NC}" + exit 1 + fi fi print_message() { @@ -240,22 +294,23 @@ download_with_progress() { download_and_install() { print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version" - mkdir -p opencodetmp && cd opencodetmp + local tmp_dir="${TMPDIR:-/tmp}/opencode_install_$$" + mkdir -p "$tmp_dir" - if [[ "$os" == "windows" ]] || ! download_with_progress "$url" "$filename"; then - # Fallback to standard curl on Windows or if custom progress fails - curl -# -L -o "$filename" "$url" + if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then + # Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails + curl -# -L -o "$tmp_dir/$filename" "$url" fi if [ "$os" = "linux" ]; then - tar -xzf "$filename" + tar -xzf "$tmp_dir/$filename" -C "$tmp_dir" else - unzip -q "$filename" + unzip -q "$tmp_dir/$filename" -d "$tmp_dir" fi - mv opencode "$INSTALL_DIR" + mv "$tmp_dir/opencode" "$INSTALL_DIR" chmod 755 "${INSTALL_DIR}/opencode" - cd .. && rm -rf opencodetmp + rm -rf "$tmp_dir" } check_version @@ -303,42 +358,42 @@ case $current_shell in ;; esac -config_file="" -for file in $config_files; do - if [[ -f $file ]]; then - config_file=$file - break +if [[ "$no_modify_path" != "true" ]]; then + config_file="" + for file in $config_files; do + if [[ -f $file ]]; then + config_file=$file + break + fi + done + + if [[ -z $config_file ]]; then + print_message warning "No config file found for $current_shell. You may need to manually add to PATH:" + print_message info " export PATH=$INSTALL_DIR:\$PATH" + elif [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then + case $current_shell in + fish) + add_to_path "$config_file" "fish_add_path $INSTALL_DIR" + ;; + zsh) + add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" + ;; + bash) + add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" + ;; + ash) + add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" + ;; + sh) + add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" + ;; + *) + export PATH=$INSTALL_DIR:$PATH + print_message warning "Manually add the directory to $config_file (or similar):" + print_message info " export PATH=$INSTALL_DIR:\$PATH" + ;; + esac fi -done - -if [[ -z $config_file ]]; then - print_message error "No config file found for $current_shell. Checked files: ${config_files[@]}" - exit 1 -fi - -if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then - case $current_shell in - fish) - add_to_path "$config_file" "fish_add_path $INSTALL_DIR" - ;; - zsh) - add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" - ;; - bash) - add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" - ;; - ash) - add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" - ;; - sh) - add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" - ;; - *) - export PATH=$INSTALL_DIR:$PATH - print_message warning "Manually add the directory to $config_file (or similar):" - print_message info " export PATH=$INSTALL_DIR:\$PATH" - ;; - esac fi if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then diff --git a/nix/hashes.json b/nix/hashes.json index bd185357c63..2f0a3df12bf 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,3 +1,3 @@ { - "nodeModules": "sha256-7ItLfqYrXzC6LO2iXZ8m+ZfQH1D7NWtcAcgRMO5NXZI=" + "nodeModules": "sha256-2Wbnxy9SPcZkO03Sis3uiypPXa87jc5TzKbo6PvMlxY=" } diff --git a/nix/node-modules.nix b/nix/node-modules.nix index 7b22ef8e7da..be7edd9c7e7 100644 --- a/nix/node-modules.nix +++ b/nix/node-modules.nix @@ -1,18 +1,26 @@ -{ hash, lib, stdenvNoCC, bun, cacert, curl }: +{ + hash, + lib, + stdenvNoCC, + bun, + cacert, + curl, +}: args: stdenvNoCC.mkDerivation { pname = "opencode-node_modules"; - version = args.version; - src = args.src; + inherit (args) version src; - impureEnvVars = - lib.fetchers.proxyImpureEnvVars - ++ [ - "GIT_PROXY_COMMAND" - "SOCKS_SERVER" - ]; + impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ [ + "GIT_PROXY_COMMAND" + "SOCKS_SERVER" + ]; - nativeBuildInputs = [ bun cacert curl ]; + nativeBuildInputs = [ + bun + cacert + curl + ]; dontConfigure = true; diff --git a/nix/opencode.nix b/nix/opencode.nix index 8c4e9fb5713..4f10e749822 100644 --- a/nix/opencode.nix +++ b/nix/opencode.nix @@ -1,7 +1,13 @@ -{ lib, stdenvNoCC, bun, fzf, ripgrep, makeBinaryWrapper }: +{ + lib, + stdenvNoCC, + bun, + ripgrep, + makeBinaryWrapper, +}: args: let - scripts = args.scripts; + inherit (args) scripts; mkModules = attrs: args.mkNodeModules ( @@ -14,13 +20,10 @@ let in stdenvNoCC.mkDerivation (finalAttrs: { pname = "opencode"; - version = args.version; - - src = args.src; + inherit (args) version src; node_modules = mkModules { - version = finalAttrs.version; - src = finalAttrs.src; + inherit (finalAttrs) version src; }; nativeBuildInputs = [ @@ -97,7 +100,7 @@ stdenvNoCC.mkDerivation (finalAttrs: { makeWrapper ${bun}/bin/bun $out/bin/opencode \ --add-flags "run" \ --add-flags "$out/lib/opencode/dist/src/index.js" \ - --prefix PATH : ${lib.makeBinPath [ fzf ripgrep ]} \ + --prefix PATH : ${lib.makeBinPath [ ripgrep ]} \ --argv0 opencode runHook postInstall diff --git a/nix/scripts/patch-wasm.ts b/nix/scripts/patch-wasm.ts index 99f8a40e9f3..88a06c2bd2b 100644 --- a/nix/scripts/patch-wasm.ts +++ b/nix/scripts/patch-wasm.ts @@ -31,9 +31,13 @@ for (const [name, wasmPath] of byName) { next = next.replaceAll("tree-sitter.wasm", mainWasm).replaceAll("web-tree-sitter/tree-sitter.wasm", mainWasm) // Collapse any relative prefixes before absolute store paths (e.g., "../../../..//nix/store/...") +const nixStorePrefix = process.env.NIX_STORE || "/nix/store" next = next.replace(/(\.\/)+/g, "./") -next = next.replace(/(\.\.\/)+\/?(\/nix\/store[^"']+)/g, "/$2") -next = next.replace(/(["'])\/{2,}(\/nix\/store[^"']+)(["'])/g, "$1/$2$3") -next = next.replace(/(["'])\/\/(nix\/store[^"']+)(["'])/g, "$1/$2$3") +next = next.replace( + new RegExp(`(\\.\\.\\/)+\\/{1,2}(${nixStorePrefix.replace(/^\//, "").replace(/\//g, "\\/")}[^"']+)`, "g"), + "/$2", +) +next = next.replace(new RegExp(`(["'])\\/{2,}(\\/${nixStorePrefix.replace(/\//g, "\\/")}[^"']+)(["'])`, "g"), "$1$2$3") +next = next.replace(new RegExp(`(["'])\\/\\/(${nixStorePrefix.replace(/\//g, "\\/")}[^"']+)(["'])`, "g"), "$1$2$3") if (next !== content) fs.writeFileSync(file, next) diff --git a/package.json b/package.json index c7ea47072db..aa7031bec72 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "AI-powered development tool", "private": true, "type": "module", - "packageManager": "bun@1.3.3", + "packageManager": "bun@1.3.5", "scripts": { "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", "typecheck": "bun turbo typecheck", @@ -20,7 +20,8 @@ "packages/slack" ], "catalog": { - "@types/bun": "1.3.3", + "@types/bun": "1.3.4", + "@octokit/rest": "22.0.0", "@hono/zod-validator": "0.4.2", "ulid": "3.0.1", "@kobalte/core": "0.13.11", @@ -30,7 +31,8 @@ "@tsconfig/bun": "1.0.9", "@cloudflare/workers-types": "4.20251008.0", "@openauthjs/openauth": "0.0.0-20250322224806", - "@pierre/precision-diffs": "0.6.0-beta.3", + "@pierre/diffs": "1.0.2", + "@solid-primitives/storage": "4.3.3", "@tailwindcss/vite": "4.1.11", "diff": "8.0.2", "ai": "5.0.97", @@ -38,10 +40,13 @@ "hono-openapi": "1.1.2", "fuzzysort": "3.1.0", "luxon": "3.6.1", + "marked": "17.0.1", + "marked-shiki": "1.2.1", "typescript": "5.8.2", "@typescript/native-preview": "7.0.0-dev.20251207.1", "zod": "4.1.8", "remeda": "2.26.0", + "shiki": "3.20.0", "solid-list": "0.3.0", "tailwindcss": "4.1.11", "virtua": "0.42.3", @@ -54,6 +59,7 @@ } }, "devDependencies": { + "@actions/artifact": "5.0.1", "@tsconfig/bun": "catalog:", "husky": "9.1.7", "prettier": "3.6.2", @@ -62,6 +68,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "3.933.0", + "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", "typescript": "catalog:" @@ -78,7 +85,6 @@ "trustedDependencies": [ "esbuild", "protobufjs", - "sharp", "tree-sitter", "tree-sitter-bash", "web-tree-sitter" diff --git a/packages/app/.gitignore b/packages/app/.gitignore new file mode 100644 index 00000000000..4a20d55a70d --- /dev/null +++ b/packages/app/.gitignore @@ -0,0 +1 @@ +src/assets/theme.css diff --git a/packages/desktop/AGENTS.md b/packages/app/AGENTS.md similarity index 100% rename from packages/desktop/AGENTS.md rename to packages/app/AGENTS.md diff --git a/packages/app/README.md b/packages/app/README.md new file mode 100644 index 00000000000..6a176453668 --- /dev/null +++ b/packages/app/README.md @@ -0,0 +1,34 @@ +## Usage + +Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`. + +This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template. + +```bash +$ npm install # or pnpm install or yarn install +``` + +### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) + +## Available Scripts + +In the project directory, you can run: + +### `npm run dev` or `npm start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+ +### `npm run build` + +Builds the app for production to the `dist` folder.
+It correctly bundles Solid in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +## Deployment + +You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.) diff --git a/packages/desktop/bunfig.toml b/packages/app/bunfig.toml similarity index 100% rename from packages/desktop/bunfig.toml rename to packages/app/bunfig.toml diff --git a/packages/desktop/happydom.ts b/packages/app/happydom.ts similarity index 100% rename from packages/desktop/happydom.ts rename to packages/app/happydom.ts diff --git a/packages/app/index.html b/packages/app/index.html new file mode 100644 index 00000000000..ea423780448 --- /dev/null +++ b/packages/app/index.html @@ -0,0 +1,53 @@ + + + + + + OpenCode + + + + + + + + + + + + + + +
+ + + diff --git a/packages/app/package.json b/packages/app/package.json new file mode 100644 index 00000000000..c5ad5f61f60 --- /dev/null +++ b/packages/app/package.json @@ -0,0 +1,62 @@ +{ + "name": "@opencode-ai/app", + "version": "1.0.217", + "description": "", + "type": "module", + "exports": { + ".": "./src/index.ts", + "./vite": "./vite.js" + }, + "scripts": { + "typecheck": "tsgo -b", + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "license": "MIT", + "devDependencies": { + "@happy-dom/global-registrator": "20.0.11", + "@tailwindcss/vite": "catalog:", + "@tsconfig/bun": "1.0.9", + "@types/bun": "catalog:", + "@types/luxon": "catalog:", + "@types/node": "catalog:", + "@typescript/native-preview": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plugin-icons-spritesheet": "3.0.1", + "vite-plugin-solid": "catalog:" + }, + "dependencies": { + "@kobalte/core": "catalog:", + "@opencode-ai/sdk": "workspace:*", + "@opencode-ai/ui": "workspace:*", + "@opencode-ai/util": "workspace:*", + "@shikijs/transformers": "3.9.2", + "@solid-primitives/active-element": "2.1.3", + "@solid-primitives/audio": "1.4.2", + "@solid-primitives/event-bus": "1.1.2", + "@solid-primitives/media": "2.3.3", + "@solid-primitives/resize-observer": "2.1.3", + "@solid-primitives/scroll": "2.1.3", + "@solid-primitives/storage": "catalog:", + "@solid-primitives/websocket": "1.3.1", + "@solidjs/meta": "catalog:", + "@solidjs/router": "catalog:", + "@thisbeyond/solid-dnd": "0.7.5", + "diff": "catalog:", + "fuzzysort": "catalog:", + "ghostty-web": "0.3.0", + "luxon": "catalog:", + "marked": "catalog:", + "marked-shiki": "catalog:", + "remeda": "catalog:", + "shiki": "catalog:", + "solid-js": "catalog:", + "solid-list": "catalog:", + "tailwindcss": "catalog:", + "virtua": "catalog:", + "zod": "catalog:" + } +} diff --git a/packages/app/public/_headers b/packages/app/public/_headers new file mode 100644 index 00000000000..f5157b1debc --- /dev/null +++ b/packages/app/public/_headers @@ -0,0 +1,17 @@ +/assets/*.js + Content-Type: application/javascript + +/assets/*.mjs + Content-Type: application/javascript + +/assets/*.css + Content-Type: text/css + +/*.js + Content-Type: application/javascript + +/*.mjs + Content-Type: application/javascript + +/*.css + Content-Type: text/css diff --git a/packages/desktop/public/apple-touch-icon.png b/packages/app/public/apple-touch-icon.png similarity index 100% rename from packages/desktop/public/apple-touch-icon.png rename to packages/app/public/apple-touch-icon.png diff --git a/packages/desktop/public/favicon-96x96.png b/packages/app/public/favicon-96x96.png similarity index 100% rename from packages/desktop/public/favicon-96x96.png rename to packages/app/public/favicon-96x96.png diff --git a/packages/desktop/public/favicon.ico b/packages/app/public/favicon.ico similarity index 100% rename from packages/desktop/public/favicon.ico rename to packages/app/public/favicon.ico diff --git a/packages/desktop/public/favicon.svg b/packages/app/public/favicon.svg similarity index 100% rename from packages/desktop/public/favicon.svg rename to packages/app/public/favicon.svg diff --git a/packages/desktop/public/site.webmanifest b/packages/app/public/site.webmanifest similarity index 100% rename from packages/desktop/public/site.webmanifest rename to packages/app/public/site.webmanifest diff --git a/packages/desktop/public/social-share-zen.png b/packages/app/public/social-share-zen.png similarity index 100% rename from packages/desktop/public/social-share-zen.png rename to packages/app/public/social-share-zen.png diff --git a/packages/desktop/public/social-share.png b/packages/app/public/social-share.png similarity index 100% rename from packages/desktop/public/social-share.png rename to packages/app/public/social-share.png diff --git a/packages/desktop/public/web-app-manifest-192x192.png b/packages/app/public/web-app-manifest-192x192.png similarity index 100% rename from packages/desktop/public/web-app-manifest-192x192.png rename to packages/app/public/web-app-manifest-192x192.png diff --git a/packages/desktop/public/web-app-manifest-512x512.png b/packages/app/public/web-app-manifest-512x512.png similarity index 100% rename from packages/desktop/public/web-app-manifest-512x512.png rename to packages/app/public/web-app-manifest-512x512.png diff --git a/packages/desktop/src/addons/serialize.test.ts b/packages/app/src/addons/serialize.test.ts similarity index 100% rename from packages/desktop/src/addons/serialize.test.ts rename to packages/app/src/addons/serialize.test.ts diff --git a/packages/desktop/src/addons/serialize.ts b/packages/app/src/addons/serialize.ts similarity index 100% rename from packages/desktop/src/addons/serialize.ts rename to packages/app/src/addons/serialize.ts diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx new file mode 100644 index 00000000000..ff9b8b4db11 --- /dev/null +++ b/packages/app/src/app.tsx @@ -0,0 +1,109 @@ +import "@/index.css" +import { ErrorBoundary, Show, type ParentProps } from "solid-js" +import { Router, Route, Navigate } from "@solidjs/router" +import { MetaProvider } from "@solidjs/meta" +import { Font } from "@opencode-ai/ui/font" +import { MarkedProvider } from "@opencode-ai/ui/context/marked" +import { DiffComponentProvider } from "@opencode-ai/ui/context/diff" +import { CodeComponentProvider } from "@opencode-ai/ui/context/code" +import { Diff } from "@opencode-ai/ui/diff" +import { Code } from "@opencode-ai/ui/code" +import { ThemeProvider } from "@opencode-ai/ui/theme" +import { GlobalSyncProvider } from "@/context/global-sync" +import { LayoutProvider } from "@/context/layout" +import { GlobalSDKProvider } from "@/context/global-sdk" +import { ServerProvider, useServer } from "@/context/server" +import { TerminalProvider } from "@/context/terminal" +import { PromptProvider } from "@/context/prompt" +import { NotificationProvider } from "@/context/notification" +import { DialogProvider } from "@opencode-ai/ui/context/dialog" +import { CommandProvider } from "@/context/command" +import Layout from "@/pages/layout" +import Home from "@/pages/home" +import DirectoryLayout from "@/pages/directory-layout" +import Session from "@/pages/session" +import { ErrorPage } from "./pages/error" +import { iife } from "@opencode-ai/util/iife" + +declare global { + interface Window { + __OPENCODE__?: { updaterEnabled?: boolean; port?: number } + } +} + +const defaultServerUrl = iife(() => { + const param = new URLSearchParams(document.location.search).get("url") + if (param) return param + + if (location.hostname.includes("opencode.ai")) return "http://localhost:4096" + if (window.__OPENCODE__) return `http://127.0.0.1:${window.__OPENCODE__.port}` + if (import.meta.env.DEV) + return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}` + + return window.location.origin +}) + +function ServerKey(props: ParentProps) { + const server = useServer() + return ( + + {props.children} + + ) +} + +export function App() { + return ( + + + + }> + + + + + + + + + + + ( + + {props.children} + + )} + > + + + } /> + ( + + + + + + + + )} + /> + + + + + + + + + + + + + + + + ) +} diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx new file mode 100644 index 00000000000..789a5d3b748 --- /dev/null +++ b/packages/app/src/components/dialog-connect-provider.tsx @@ -0,0 +1,383 @@ +import type { ProviderAuthAuthorization } from "@opencode-ai/sdk/v2/client" +import { Button } from "@opencode-ai/ui/button" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { Dialog } from "@opencode-ai/ui/dialog" +import { Icon } from "@opencode-ai/ui/icon" +import { IconButton } from "@opencode-ai/ui/icon-button" +import type { IconName } from "@opencode-ai/ui/icons/provider" +import { List, type ListRef } from "@opencode-ai/ui/list" +import { ProviderIcon } from "@opencode-ai/ui/provider-icon" +import { Spinner } from "@opencode-ai/ui/spinner" +import { TextField } from "@opencode-ai/ui/text-field" +import { showToast } from "@opencode-ai/ui/toast" +import { iife } from "@opencode-ai/util/iife" +import { createMemo, Match, onCleanup, onMount, Switch } from "solid-js" +import { createStore, produce } from "solid-js/store" +import { Link } from "@/components/link" +import { useGlobalSDK } from "@/context/global-sdk" +import { useGlobalSync } from "@/context/global-sync" +import { usePlatform } from "@/context/platform" +import { DialogSelectModel } from "./dialog-select-model" +import { DialogSelectProvider } from "./dialog-select-provider" + +export function DialogConnectProvider(props: { provider: string }) { + const dialog = useDialog() + const globalSync = useGlobalSync() + const globalSDK = useGlobalSDK() + const platform = usePlatform() + const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === props.provider)!) + const methods = createMemo( + () => + globalSync.data.provider_auth[props.provider] ?? [ + { + type: "api", + label: "API key", + }, + ], + ) + const [store, setStore] = createStore({ + methodIndex: undefined as undefined | number, + authorization: undefined as undefined | ProviderAuthAuthorization, + state: "pending" as undefined | "pending" | "complete" | "error", + error: undefined as string | undefined, + }) + + const method = createMemo(() => (store.methodIndex !== undefined ? methods().at(store.methodIndex!) : undefined)) + + async function selectMethod(index: number) { + const method = methods()[index] + setStore( + produce((draft) => { + draft.methodIndex = index + draft.authorization = undefined + draft.state = undefined + draft.error = undefined + }), + ) + + if (method.type === "oauth") { + setStore("state", "pending") + const start = Date.now() + await globalSDK.client.provider.oauth + .authorize( + { + providerID: props.provider, + method: index, + }, + { throwOnError: true }, + ) + .then((x) => { + const elapsed = Date.now() - start + const delay = 1000 - elapsed + + if (delay > 0) { + setTimeout(() => { + setStore("state", "complete") + setStore("authorization", x.data!) + }, delay) + return + } + setStore("state", "complete") + setStore("authorization", x.data!) + }) + .catch((e) => { + setStore("state", "error") + setStore("error", String(e)) + }) + } + } + + let listRef: ListRef | undefined + function handleKey(e: KeyboardEvent) { + if (e.key === "Enter" && e.target instanceof HTMLInputElement) { + return + } + if (e.key === "Escape") return + listRef?.onKeyDown(e) + } + + onMount(() => { + if (methods().length === 1) { + selectMethod(0) + } + document.addEventListener("keydown", handleKey) + onCleanup(() => { + document.removeEventListener("keydown", handleKey) + }) + }) + + async function complete() { + await globalSDK.client.global.dispose() + dialog.close() + showToast({ + variant: "success", + icon: "circle-check", + title: `${provider().name} connected`, + description: `${provider().name} models are now available to use.`, + }) + } + + function goBack() { + if (methods().length === 1) { + dialog.show(() => ) + return + } + if (store.authorization) { + setStore("authorization", undefined) + setStore("methodIndex", undefined) + return + } + if (store.methodIndex) { + setStore("methodIndex", undefined) + return + } + dialog.show(() => ) + } + + return ( + }> +
+
+ +
+ + + Login with Claude Pro/Max + + Connect {provider().name} + +
+
+
+ + +
Select login method for {provider().name}.
+
+ { + listRef = ref + }} + items={methods} + key={(m) => m?.label} + onSelect={async (method, index) => { + if (!method) return + selectMethod(index) + }} + > + {(i) => ( +
+
+ + {i.label} +
+ )} + +
+ + +
+
+ + Authorization in progress... +
+
+
+ +
+
+ + Authorization failed: {store.error} +
+
+
+ + {iife(() => { + const [formStore, setFormStore] = createStore({ + value: "", + error: undefined as string | undefined, + }) + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + + const form = e.currentTarget as HTMLFormElement + const formData = new FormData(form) + const apiKey = formData.get("apiKey") as string + + if (!apiKey?.trim()) { + setFormStore("error", "API key is required") + return + } + + setFormStore("error", undefined) + await globalSDK.client.auth.set({ + providerID: props.provider, + auth: { + type: "api", + key: apiKey, + }, + }) + await complete() + } + + return ( +
+ + +
+
+ OpenCode Zen gives you access to a curated set of reliable optimized models for coding + agents. +
+
+ With a single API key you'll get access to models such as Claude, GPT, Gemini, GLM and more. +
+
+ Visit{" "} + + opencode.ai/zen + {" "} + to collect your API key. +
+
+
+ +
+ Enter your {provider().name} API key to connect your account and use {provider().name} models + in OpenCode. +
+
+
+
+ + + +
+ ) + })} +
+ + + + {iife(() => { + const [formStore, setFormStore] = createStore({ + value: "", + error: undefined as string | undefined, + }) + + onMount(() => { + if (store.authorization?.method === "code" && store.authorization?.url) { + platform.openLink(store.authorization.url) + } + }) + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + + const form = e.currentTarget as HTMLFormElement + const formData = new FormData(form) + const code = formData.get("code") as string + + if (!code?.trim()) { + setFormStore("error", "Authorization code is required") + return + } + + setFormStore("error", undefined) + const { error } = await globalSDK.client.provider.oauth.callback({ + providerID: props.provider, + method: store.methodIndex, + code, + }) + if (!error) { + await complete() + return + } + setFormStore("error", "Invalid authorization code") + } + + return ( +
+
+ Visit this link to collect your authorization + code to connect your account and use {provider().name} models in OpenCode. +
+
+ + + +
+ ) + })} +
+ + {iife(() => { + const code = createMemo(() => { + const instructions = store.authorization?.instructions + if (instructions?.includes(":")) { + return instructions?.split(":")[1]?.trim() + } + return instructions + }) + + onMount(async () => { + const result = await globalSDK.client.provider.oauth.callback({ + providerID: props.provider, + method: store.methodIndex, + }) + if (result.error) { + // TODO: show error + dialog.close() + return + } + await complete() + }) + + return ( +
+
+ Visit this link and enter the code below to + connect your account and use {provider().name} models in OpenCode. +
+ +
+ + Waiting for authorization... +
+
+ ) + })} +
+
+
+ +
+
+
+ ) +} diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx new file mode 100644 index 00000000000..27ce3903cdc --- /dev/null +++ b/packages/app/src/components/dialog-edit-project.tsx @@ -0,0 +1,180 @@ +import { Button } from "@opencode-ai/ui/button" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { Dialog } from "@opencode-ai/ui/dialog" +import { TextField } from "@opencode-ai/ui/text-field" +import { Icon } from "@opencode-ai/ui/icon" +import { createMemo, createSignal, For, Show } from "solid-js" +import { createStore } from "solid-js/store" +import { useGlobalSDK } from "@/context/global-sdk" +import { type LocalProject, getAvatarColors } from "@/context/layout" +import { Avatar } from "@opencode-ai/ui/avatar" + +const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const + +function getFilename(input: string) { + const parts = input.split("/") + return parts[parts.length - 1] || input +} + +export function DialogEditProject(props: { project: LocalProject }) { + const dialog = useDialog() + const globalSDK = useGlobalSDK() + + const folderName = createMemo(() => getFilename(props.project.worktree)) + const defaultName = createMemo(() => props.project.name || folderName()) + + const [store, setStore] = createStore({ + name: defaultName(), + color: props.project.icon?.color || "pink", + iconUrl: props.project.icon?.url || "", + saving: false, + }) + + const [dragOver, setDragOver] = createSignal(false) + + function handleFileSelect(file: File) { + if (!file.type.startsWith("image/")) return + const reader = new FileReader() + reader.onload = (e) => setStore("iconUrl", e.target?.result as string) + reader.readAsDataURL(file) + } + + function handleDrop(e: DragEvent) { + e.preventDefault() + setDragOver(false) + const file = e.dataTransfer?.files[0] + if (file) handleFileSelect(file) + } + + function handleDragOver(e: DragEvent) { + e.preventDefault() + setDragOver(true) + } + + function handleDragLeave() { + setDragOver(false) + } + + function handleInputChange(e: Event) { + const input = e.target as HTMLInputElement + const file = input.files?.[0] + if (file) handleFileSelect(file) + } + + function clearIcon() { + setStore("iconUrl", "") + } + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + if (!props.project.id) return + + setStore("saving", true) + const name = store.name.trim() === folderName() ? "" : store.name.trim() + await globalSDK.client.project.update({ + projectID: props.project.id, + name, + icon: { color: store.color, url: store.iconUrl }, + }) + setStore("saving", false) + dialog.close() + } + + return ( + +
+
+ setStore("name", v)} + /> + +
+ +
+
+
document.getElementById("icon-upload")?.click()} + > + + +
+ } + > + Project icon + +
+ + + +
+ +
+ Click or drag an image + Recommended: 128x128px +
+
+
+ + +
+ +
+ + {(color) => ( + + )} + +
+
+
+ + +
+ + +
+
+
+ ) +} diff --git a/packages/app/src/components/dialog-manage-models.tsx b/packages/app/src/components/dialog-manage-models.tsx new file mode 100644 index 00000000000..66d12528891 --- /dev/null +++ b/packages/app/src/components/dialog-manage-models.tsx @@ -0,0 +1,57 @@ +import { Dialog } from "@opencode-ai/ui/dialog" +import { List } from "@opencode-ai/ui/list" +import { Switch } from "@opencode-ai/ui/switch" +import type { Component } from "solid-js" +import { useLocal } from "@/context/local" +import { popularProviders } from "@/hooks/use-providers" + +export const DialogManageModels: Component = () => { + const local = useLocal() + return ( + + `${x?.provider?.id}:${x?.id}`} + items={local.model.list()} + filterKeys={["provider.name", "name", "id"]} + sortBy={(a, b) => a.name.localeCompare(b.name)} + groupBy={(x) => x.provider.name} + sortGroupsBy={(a, b) => { + const aProvider = a.items[0].provider.id + const bProvider = b.items[0].provider.id + if (popularProviders.includes(aProvider) && !popularProviders.includes(bProvider)) return -1 + if (!popularProviders.includes(aProvider) && popularProviders.includes(bProvider)) return 1 + return popularProviders.indexOf(aProvider) - popularProviders.indexOf(bProvider) + }} + onSelect={(x) => { + if (!x) return + const visible = local.model.visible({ + modelID: x.id, + providerID: x.provider.id, + }) + local.model.setVisibility({ modelID: x.id, providerID: x.provider.id }, !visible) + }} + > + {(i) => ( +
+ {i.name} +
e.stopPropagation()}> + { + local.model.setVisibility({ modelID: i.id, providerID: i.provider.id }, checked) + }} + /> +
+
+ )} +
+
+ ) +} diff --git a/packages/app/src/components/dialog-select-directory.tsx b/packages/app/src/components/dialog-select-directory.tsx new file mode 100644 index 00000000000..bf4a1f9edd4 --- /dev/null +++ b/packages/app/src/components/dialog-select-directory.tsx @@ -0,0 +1,114 @@ +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { Dialog } from "@opencode-ai/ui/dialog" +import { FileIcon } from "@opencode-ai/ui/file-icon" +import { List } from "@opencode-ai/ui/list" +import { getDirectory, getFilename } from "@opencode-ai/util/path" +import { createMemo } from "solid-js" +import { useGlobalSDK } from "@/context/global-sdk" +import { useGlobalSync } from "@/context/global-sync" + +interface DialogSelectDirectoryProps { + title?: string + multiple?: boolean + onSelect: (result: string | string[] | null) => void +} + +export function DialogSelectDirectory(props: DialogSelectDirectoryProps) { + const sync = useGlobalSync() + const sdk = useGlobalSDK() + const dialog = useDialog() + + const home = createMemo(() => sync.data.path.home) + const root = createMemo(() => sync.data.path.home || sync.data.path.directory) + + function join(base: string | undefined, rel: string) { + const b = (base ?? "").replace(/[\\/]+$/, "") + const r = rel.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "") + if (!b) return r + if (!r) return b + return b + "/" + r + } + + function display(rel: string) { + const full = join(root(), rel) + const h = home() + if (!h) return full + if (full === h) return "~" + if (full.startsWith(h + "/") || full.startsWith(h + "\\")) { + return "~" + full.slice(h.length) + } + return full + } + + function normalizeQuery(query: string) { + const h = home() + + if (!query) return query + if (query.startsWith("~/")) return query.slice(2) + + if (h) { + const lc = query.toLowerCase() + const hc = h.toLowerCase() + if (lc === hc || lc.startsWith(hc + "/") || lc.startsWith(hc + "\\")) { + return query.slice(h.length).replace(/^[\\/]+/, "") + } + } + + return query + } + + async function fetchDirs(query: string) { + const directory = root() + if (!directory) return [] as string[] + + const results = await sdk.client.find + .files({ directory, query, type: "directory", limit: 50 }) + .then((x) => x.data ?? []) + .catch(() => []) + + return results.map((x) => x.replace(/[\\/]+$/, "")) + } + + const directories = async (filter: string) => { + const query = normalizeQuery(filter.trim()) + return fetchDirs(query) + } + + function resolve(rel: string) { + const absolute = join(root(), rel) + props.onSelect(props.multiple ? [absolute] : absolute) + dialog.close() + } + + return ( + + x} + onSelect={(path) => { + if (!path) return + resolve(path) + }} + > + {(rel) => { + const path = display(rel) + return ( +
+
+ +
+ + {getDirectory(path)} + + {getFilename(path)} +
+
+
+ ) + }} +
+
+ ) +} diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx new file mode 100644 index 00000000000..b27afdc8bc5 --- /dev/null +++ b/packages/app/src/components/dialog-select-file.tsx @@ -0,0 +1,48 @@ +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { Dialog } from "@opencode-ai/ui/dialog" +import { FileIcon } from "@opencode-ai/ui/file-icon" +import { List } from "@opencode-ai/ui/list" +import { getDirectory, getFilename } from "@opencode-ai/util/path" +import { useParams } from "@solidjs/router" +import { createMemo } from "solid-js" +import { useLayout } from "@/context/layout" +import { useLocal } from "@/context/local" + +export function DialogSelectFile() { + const layout = useLayout() + const local = useLocal() + const dialog = useDialog() + const params = useParams() + const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) + const tabs = createMemo(() => layout.tabs(sessionKey())) + return ( + + x} + onSelect={(path) => { + if (path) { + tabs().open("file://" + path) + } + dialog.close() + }} + > + {(i) => ( +
+
+ +
+ + {getDirectory(i)} + + {getFilename(i)} +
+
+
+ )} +
+
+ ) +} diff --git a/packages/app/src/components/dialog-select-mcp.tsx b/packages/app/src/components/dialog-select-mcp.tsx new file mode 100644 index 00000000000..c29cd827e3b --- /dev/null +++ b/packages/app/src/components/dialog-select-mcp.tsx @@ -0,0 +1,91 @@ +import { Component, createMemo, createSignal, Show } from "solid-js" +import { useSync } from "@/context/sync" +import { useSDK } from "@/context/sdk" +import { Dialog } from "@opencode-ai/ui/dialog" +import { List } from "@opencode-ai/ui/list" +import { Switch } from "@opencode-ai/ui/switch" + +export const DialogSelectMcp: Component = () => { + const sync = useSync() + const sdk = useSDK() + const [loading, setLoading] = createSignal(null) + + const items = createMemo(() => + Object.entries(sync.data.mcp ?? {}) + .map(([name, status]) => ({ name, status: status.status })) + .sort((a, b) => a.name.localeCompare(b.name)), + ) + + const toggle = async (name: string) => { + if (loading()) return + setLoading(name) + const status = sync.data.mcp[name] + if (status?.status === "connected") { + await sdk.client.mcp.disconnect({ name }) + } else { + await sdk.client.mcp.connect({ name }) + } + const result = await sdk.client.mcp.status() + if (result.data) sync.set("mcp", result.data) + setLoading(null) + } + + const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length) + const totalCount = createMemo(() => items().length) + + return ( + + x?.name ?? ""} + items={items} + filterKeys={["name", "status"]} + sortBy={(a, b) => a.name.localeCompare(b.name)} + onSelect={(x) => { + if (x) toggle(x.name) + }} + > + {(i) => { + const mcpStatus = () => sync.data.mcp[i.name] + const status = () => mcpStatus()?.status + const error = () => { + const s = mcpStatus() + return s?.status === "failed" ? s.error : undefined + } + const enabled = () => status() === "connected" + return ( +
+
+
+ {i.name} + + connected + + + failed + + + needs auth + + + disabled + + + ... + +
+ + {error()} + +
+
e.stopPropagation()}> + toggle(i.name)} /> +
+
+ ) + }} +
+
+ ) +} diff --git a/packages/app/src/components/dialog-select-model-unpaid.tsx b/packages/app/src/components/dialog-select-model-unpaid.tsx new file mode 100644 index 00000000000..24ec8092deb --- /dev/null +++ b/packages/app/src/components/dialog-select-model-unpaid.tsx @@ -0,0 +1,110 @@ +import { Button } from "@opencode-ai/ui/button" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { Dialog } from "@opencode-ai/ui/dialog" +import type { IconName } from "@opencode-ai/ui/icons/provider" +import { List, type ListRef } from "@opencode-ai/ui/list" +import { ProviderIcon } from "@opencode-ai/ui/provider-icon" +import { Tag } from "@opencode-ai/ui/tag" +import { type Component, onCleanup, onMount, Show } from "solid-js" +import { useLocal } from "@/context/local" +import { popularProviders, useProviders } from "@/hooks/use-providers" +import { DialogConnectProvider } from "./dialog-connect-provider" +import { DialogSelectProvider } from "./dialog-select-provider" + +export const DialogSelectModelUnpaid: Component = () => { + const local = useLocal() + const dialog = useDialog() + const providers = useProviders() + + let listRef: ListRef | undefined + const handleKey = (e: KeyboardEvent) => { + if (e.key === "Escape") return + listRef?.onKeyDown(e) + } + + onMount(() => { + document.addEventListener("keydown", handleKey) + onCleanup(() => { + document.removeEventListener("keydown", handleKey) + }) + }) + + return ( + +
+
Free models provided by OpenCode
+ (listRef = ref)} + items={local.model.list} + current={local.model.current()} + key={(x) => `${x.provider.id}:${x.id}`} + onSelect={(x) => { + local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, { + recent: true, + }) + dialog.close() + }} + > + {(i) => ( +
+ {i.name} + Free + + Latest + +
+ )} +
+
+
+
+
+
+
+
Add more models from popular providers
+
+ x?.id} + items={providers.popular} + activeIcon="plus-small" + sortBy={(a, b) => { + if (popularProviders.includes(a.id) && popularProviders.includes(b.id)) + return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id) + return a.name.localeCompare(b.name) + }} + onSelect={(x) => { + if (!x) return + dialog.show(() => ) + }} + > + {(i) => ( +
+ + {i.name} + + Recommended + + +
Connect with Claude Pro/Max or API key
+
+
+ )} +
+ +
+
+
+
+
+ ) +} diff --git a/packages/app/src/components/dialog-select-model.tsx b/packages/app/src/components/dialog-select-model.tsx new file mode 100644 index 00000000000..54783386a67 --- /dev/null +++ b/packages/app/src/components/dialog-select-model.tsx @@ -0,0 +1,83 @@ +import { Component, createMemo, Show } from "solid-js" +import { useLocal } from "@/context/local" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { popularProviders } from "@/hooks/use-providers" +import { Button } from "@opencode-ai/ui/button" +import { Tag } from "@opencode-ai/ui/tag" +import { Dialog } from "@opencode-ai/ui/dialog" +import { List } from "@opencode-ai/ui/list" +import { DialogSelectProvider } from "./dialog-select-provider" +import { DialogManageModels } from "./dialog-manage-models" + +export const DialogSelectModel: Component<{ provider?: string }> = (props) => { + const local = useLocal() + const dialog = useDialog() + + const models = createMemo(() => + local.model + .list() + .filter((m) => local.model.visible({ modelID: m.id, providerID: m.provider.id })) + .filter((m) => (props.provider ? m.provider.id === props.provider : true)), + ) + + return ( + dialog.show(() => )} + > + Connect provider + + } + > + `${x.provider.id}:${x.id}`} + items={models} + current={local.model.current()} + filterKeys={["provider.name", "name", "id"]} + sortBy={(a, b) => a.name.localeCompare(b.name)} + groupBy={(x) => x.provider.name} + sortGroupsBy={(a, b) => { + if (a.category === "Recent" && b.category !== "Recent") return -1 + if (b.category === "Recent" && a.category !== "Recent") return 1 + const aProvider = a.items[0].provider.id + const bProvider = b.items[0].provider.id + if (popularProviders.includes(aProvider) && !popularProviders.includes(bProvider)) return -1 + if (!popularProviders.includes(aProvider) && popularProviders.includes(bProvider)) return 1 + return popularProviders.indexOf(aProvider) - popularProviders.indexOf(bProvider) + }} + onSelect={(x) => { + local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, { + recent: true, + }) + dialog.close() + }} + > + {(i) => ( +
+ {i.name} + + Free + + + Latest + +
+ )} +
+ +
+ ) +} diff --git a/packages/app/src/components/dialog-select-provider.tsx b/packages/app/src/components/dialog-select-provider.tsx new file mode 100644 index 00000000000..5bbde5d41a2 --- /dev/null +++ b/packages/app/src/components/dialog-select-provider.tsx @@ -0,0 +1,54 @@ +import { Component, Show } from "solid-js" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { popularProviders, useProviders } from "@/hooks/use-providers" +import { Dialog } from "@opencode-ai/ui/dialog" +import { List } from "@opencode-ai/ui/list" +import { Tag } from "@opencode-ai/ui/tag" +import { ProviderIcon } from "@opencode-ai/ui/provider-icon" +import { IconName } from "@opencode-ai/ui/icons/provider" +import { DialogConnectProvider } from "./dialog-connect-provider" + +export const DialogSelectProvider: Component = () => { + const dialog = useDialog() + const providers = useProviders() + + return ( + + x?.id} + items={providers.all} + filterKeys={["id", "name"]} + groupBy={(x) => (popularProviders.includes(x.id) ? "Popular" : "Other")} + sortBy={(a, b) => { + if (popularProviders.includes(a.id) && popularProviders.includes(b.id)) + return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id) + return a.name.localeCompare(b.name) + }} + sortGroupsBy={(a, b) => { + if (a.category === "Popular" && b.category !== "Popular") return -1 + if (b.category === "Popular" && a.category !== "Popular") return 1 + return 0 + }} + onSelect={(x) => { + if (!x) return + dialog.show(() => ) + }} + > + {(i) => ( +
+ + {i.name} + + Recommended + + +
Connect with Claude Pro/Max or API key
+
+
+ )} +
+
+ ) +} diff --git a/packages/app/src/components/dialog-select-server.tsx b/packages/app/src/components/dialog-select-server.tsx new file mode 100644 index 00000000000..6d224c6c3f3 --- /dev/null +++ b/packages/app/src/components/dialog-select-server.tsx @@ -0,0 +1,179 @@ +import { createEffect, createMemo, onCleanup } from "solid-js" +import { createStore, reconcile } from "solid-js/store" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { Dialog } from "@opencode-ai/ui/dialog" +import { List } from "@opencode-ai/ui/list" +import { TextField } from "@opencode-ai/ui/text-field" +import { Button } from "@opencode-ai/ui/button" +import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server" +import { usePlatform } from "@/context/platform" +import { createOpencodeClient } from "@opencode-ai/sdk/v2/client" +import { useNavigate } from "@solidjs/router" + +type ServerStatus = { healthy: boolean; version?: string } + +async function checkHealth(url: string, fetch?: typeof globalThis.fetch): Promise { + const sdk = createOpencodeClient({ + baseUrl: url, + fetch, + signal: AbortSignal.timeout(3000), + }) + return sdk.global + .health() + .then((x) => ({ healthy: x.data?.healthy === true, version: x.data?.version })) + .catch(() => ({ healthy: false })) +} + +export function DialogSelectServer() { + const navigate = useNavigate() + const dialog = useDialog() + const server = useServer() + const platform = usePlatform() + const [store, setStore] = createStore({ + url: "", + adding: false, + error: "", + status: {} as Record, + }) + + const items = createMemo(() => { + const current = server.url + const list = server.list + if (!current) return list + if (!list.includes(current)) return [current, ...list] + return [current, ...list.filter((x) => x !== current)] + }) + + const current = createMemo(() => items().find((x) => x === server.url) ?? items()[0]) + + const sortedItems = createMemo(() => { + const list = items() + if (!list.length) return list + const active = current() + const order = new Map(list.map((url, index) => [url, index] as const)) + const rank = (value?: ServerStatus) => { + if (value?.healthy === true) return 0 + if (value?.healthy === false) return 2 + return 1 + } + return list.slice().sort((a, b) => { + if (a === active) return -1 + if (b === active) return 1 + const diff = rank(store.status[a]) - rank(store.status[b]) + if (diff !== 0) return diff + return (order.get(a) ?? 0) - (order.get(b) ?? 0) + }) + }) + + async function refreshHealth() { + const results: Record = {} + await Promise.all( + items().map(async (url) => { + results[url] = await checkHealth(url, platform.fetch) + }), + ) + setStore("status", reconcile(results)) + } + + createEffect(() => { + items() + refreshHealth() + const interval = setInterval(refreshHealth, 10_000) + onCleanup(() => clearInterval(interval)) + }) + + function select(value: string, persist?: boolean) { + if (!persist && store.status[value]?.healthy === false) return + dialog.close() + if (persist) { + server.add(value) + navigate("/") + return + } + server.setActive(value) + navigate("/") + } + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + const value = normalizeServerUrl(store.url) + if (!value) return + + setStore("adding", true) + setStore("error", "") + + const result = await checkHealth(value, platform.fetch) + setStore("adding", false) + + if (!result.healthy) { + setStore("error", "Could not connect to server") + return + } + + setStore("url", "") + select(value, true) + } + + return ( + +
+ x} + current={current()} + onSelect={(x) => { + if (x) select(x) + }} + > + {(i) => ( +
+
+ {serverDisplayName(i)} + {store.status[i]?.version} +
+ )} + + +
+
+

Add a server

+
+
+
+
+ { + setStore("url", v) + setStore("error", "") + }} + validationState={store.error ? "invalid" : "valid"} + error={store.error} + /> +
+ +
+
+
+
+
+ ) +} diff --git a/packages/desktop/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx similarity index 92% rename from packages/desktop/src/components/file-tree.tsx rename to packages/app/src/components/file-tree.tsx index 0841c71d1d9..3439d366cee 100644 --- a/packages/desktop/src/components/file-tree.tsx +++ b/packages/app/src/components/file-tree.tsx @@ -2,7 +2,7 @@ import { useLocal, type LocalFile } from "@/context/local" import { Collapsible } from "@opencode-ai/ui/collapsible" import { FileIcon } from "@opencode-ai/ui/file-icon" import { Tooltip } from "@opencode-ai/ui/tooltip" -import { For, Match, Switch, Show, type ComponentProps, type ParentProps } from "solid-js" +import { For, Match, Switch, type ComponentProps, type ParentProps } from "solid-js" import { Dynamic } from "solid-js/web" export default function FileTree(props: { @@ -57,14 +57,14 @@ export default function FileTree(props: { "text-text-muted/40": p.node.ignored, "text-text-muted/80": !p.node.ignored, // "!text-text": local.file.active()?.path === p.node.path, - "!text-primary": local.file.changed(p.node.path), + // "!text-primary": local.file.changed(p.node.path), }} > {p.node.name} - - - + {/* */} + {/* */} + {/* */} ) diff --git a/packages/app/src/components/header.tsx b/packages/app/src/components/header.tsx new file mode 100644 index 00000000000..74c49f07ac6 --- /dev/null +++ b/packages/app/src/components/header.tsx @@ -0,0 +1,215 @@ +import { useGlobalSync } from "@/context/global-sync" +import { useGlobalSDK } from "@/context/global-sdk" +import { useLayout } from "@/context/layout" +import { Session } from "@opencode-ai/sdk/v2/client" +import { Button } from "@opencode-ai/ui/button" +import { Icon } from "@opencode-ai/ui/icon" +import { Mark } from "@opencode-ai/ui/logo" +import { Popover } from "@opencode-ai/ui/popover" +import { Select } from "@opencode-ai/ui/select" +import { TextField } from "@opencode-ai/ui/text-field" +import { Tooltip } from "@opencode-ai/ui/tooltip" +import { base64Decode } from "@opencode-ai/util/encode" +import { useCommand } from "@/context/command" +import { getFilename } from "@opencode-ai/util/path" +import { A, useParams } from "@solidjs/router" +import { createMemo, createResource, Show } from "solid-js" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { iife } from "@opencode-ai/util/iife" + +export function Header(props: { + navigateToProject: (directory: string) => void + navigateToSession: (session: Session | undefined) => void + onMobileMenuToggle?: () => void +}) { + const globalSync = useGlobalSync() + const globalSDK = useGlobalSDK() + const layout = useLayout() + const params = useParams() + const command = useCommand() + + return ( +
+ + + + +
+ 0 && params.dir}> + {(directory) => { + const currentDirectory = createMemo(() => base64Decode(directory())) + const store = createMemo(() => globalSync.child(currentDirectory())[0]) + const sessions = createMemo(() => (store().session ?? []).filter((s) => !s.parentID)) + const currentSession = createMemo(() => sessions().find((s) => s.id === params.id)) + const shareEnabled = createMemo(() => store().config.share !== "disabled") + return ( + <> +
+
+ + agent.name)} + current={local.agent.current()?.name ?? ""} + onSelect={local.agent.set} + class="capitalize" + variant="ghost" + /> + + + Choose model + {command.keybind("model.choose")} +
+ } + > + + + + +
+
+ { + const file = e.currentTarget.files?.[0] + if (file) addImageAttachment(file) + e.currentTarget.value = "" + }} + /> + + + fileInputRef.click()} + /> + + + + +
+ Stop + ESC +
+
+ +
+ Send + +
+
+ + } + > + +
+
+
+ + + ) +} + +function createTextFragment(content: string): DocumentFragment { + const fragment = document.createDocumentFragment() + const segments = content.split("\n") + segments.forEach((segment, index) => { + if (segment) { + fragment.appendChild(document.createTextNode(segment)) + } else if (segments.length > 1) { + fragment.appendChild(document.createTextNode("\u200B")) + } + if (index < segments.length - 1) { + fragment.appendChild(document.createElement("br")) + } + }) + return fragment +} + +function getNodeLength(node: Node): number { + if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).tagName === "BR") return 1 + return (node.textContent ?? "").replace(/\u200B/g, "").length +} + +function getTextLength(node: Node): number { + if (node.nodeType === Node.TEXT_NODE) return (node.textContent ?? "").replace(/\u200B/g, "").length + if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).tagName === "BR") return 1 + let length = 0 + for (const child of Array.from(node.childNodes)) { + length += getTextLength(child) + } + return length +} + +function getCursorPosition(parent: HTMLElement): number { + const selection = window.getSelection() + if (!selection || selection.rangeCount === 0) return 0 + const range = selection.getRangeAt(0) + if (!parent.contains(range.startContainer)) return 0 + const preCaretRange = range.cloneRange() + preCaretRange.selectNodeContents(parent) + preCaretRange.setEnd(range.startContainer, range.startOffset) + return getTextLength(preCaretRange.cloneContents()) +} + +function setCursorPosition(parent: HTMLElement, position: number) { + let remaining = position + let node = parent.firstChild + while (node) { + const length = getNodeLength(node) + const isText = node.nodeType === Node.TEXT_NODE + const isFile = node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).dataset.type === "file" + const isBreak = node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).tagName === "BR" + + if (isText && remaining <= length) { + const range = document.createRange() + const selection = window.getSelection() + range.setStart(node, remaining) + range.collapse(true) + selection?.removeAllRanges() + selection?.addRange(range) + return + } + + if ((isFile || isBreak) && remaining <= length) { + const range = document.createRange() + const selection = window.getSelection() + if (remaining === 0) { + range.setStartBefore(node) + } + if (remaining > 0 && isFile) { + range.setStartAfter(node) + } + if (remaining > 0 && isBreak) { + const next = node.nextSibling + if (next && next.nodeType === Node.TEXT_NODE) { + range.setStart(next, 0) + } + if (!next || next.nodeType !== Node.TEXT_NODE) { + range.setStartAfter(node) + } + } + range.collapse(true) + selection?.removeAllRanges() + selection?.addRange(range) + return + } + + remaining -= length + node = node.nextSibling + } + + const fallbackRange = document.createRange() + const fallbackSelection = window.getSelection() + const last = parent.lastChild + if (last && last.nodeType === Node.TEXT_NODE) { + const len = last.textContent ? last.textContent.length : 0 + fallbackRange.setStart(last, len) + } + if (!last || last.nodeType !== Node.TEXT_NODE) { + fallbackRange.selectNodeContents(parent) + } + fallbackRange.collapse(false) + fallbackSelection?.removeAllRanges() + fallbackSelection?.addRange(fallbackRange) +} diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx new file mode 100644 index 00000000000..0fa6bc99c98 --- /dev/null +++ b/packages/app/src/components/session-context-usage.tsx @@ -0,0 +1,57 @@ +import { createMemo, Show } from "solid-js" +import { Tooltip } from "@opencode-ai/ui/tooltip" +import { ProgressCircle } from "@opencode-ai/ui/progress-circle" +import { useSync } from "@/context/sync" +import { useParams } from "@solidjs/router" +import { AssistantMessage } from "@opencode-ai/sdk/v2/client" + +export function SessionContextUsage() { + const sync = useSync() + const params = useParams() + const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : [])) + + const cost = createMemo(() => { + const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0) + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format(total) + }) + + const context = createMemo(() => { + const last = messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage + if (!last) return + const total = + last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write + const model = sync.data.provider.all.find((x) => x.id === last.providerID)?.models[last.modelID] + return { + tokens: total.toLocaleString(), + percentage: model?.limit.context ? Math.round((total / model.limit.context) * 100) : null, + } + }) + + return ( + + {(ctx) => ( + + Tokens + {ctx().tokens} + Usage + {ctx().percentage ?? 0}% + Cost + {cost()} + + } + placement="top" + > +
+ + {/* {`${ctx().percentage ?? 0}%`} */} +
+
+ )} +
+ ) +} diff --git a/packages/app/src/components/session-lsp-indicator.tsx b/packages/app/src/components/session-lsp-indicator.tsx new file mode 100644 index 00000000000..ac3a3999798 --- /dev/null +++ b/packages/app/src/components/session-lsp-indicator.tsx @@ -0,0 +1,38 @@ +import { createMemo, Show } from "solid-js" +import { useSync } from "@/context/sync" +import { Tooltip } from "@opencode-ai/ui/tooltip" + +export function SessionLspIndicator() { + const sync = useSync() + + const lspStats = createMemo(() => { + const lsp = sync.data.lsp ?? [] + const connected = lsp.filter((s) => s.status === "connected").length + const hasError = lsp.some((s) => s.status === "error") + const total = lsp.length + return { connected, hasError, total } + }) + + const tooltipContent = createMemo(() => { + const lsp = sync.data.lsp ?? [] + if (lsp.length === 0) return "No LSP servers" + return lsp.map((s) => s.name).join(", ") + }) + + return ( + 0}> + +
+
0, + }} + /> + {lspStats().connected} LSP +
+ + + ) +} diff --git a/packages/app/src/components/session-mcp-indicator.tsx b/packages/app/src/components/session-mcp-indicator.tsx new file mode 100644 index 00000000000..489223b9bf5 --- /dev/null +++ b/packages/app/src/components/session-mcp-indicator.tsx @@ -0,0 +1,34 @@ +import { createMemo, Show } from "solid-js" +import { Button } from "@opencode-ai/ui/button" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { useSync } from "@/context/sync" +import { DialogSelectMcp } from "@/components/dialog-select-mcp" + +export function SessionMcpIndicator() { + const sync = useSync() + const dialog = useDialog() + + const mcpStats = createMemo(() => { + const mcp = sync.data.mcp ?? {} + const entries = Object.entries(mcp) + const enabled = entries.filter(([, status]) => status.status === "connected").length + const failed = entries.some(([, status]) => status.status === "failed") + const total = entries.length + return { enabled, failed, total } + }) + + return ( + 0}> + + + ) +} diff --git a/packages/app/src/components/status-bar.tsx b/packages/app/src/components/status-bar.tsx new file mode 100644 index 00000000000..0ca403d7254 --- /dev/null +++ b/packages/app/src/components/status-bar.tsx @@ -0,0 +1,53 @@ +import { createMemo, Show, type ParentProps } from "solid-js" +import { useSync } from "@/context/sync" +import { useGlobalSync } from "@/context/global-sync" +import { useServer } from "@/context/server" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { Button } from "@opencode-ai/ui/button" +import { DialogSelectServer } from "@/components/dialog-select-server" + +export function StatusBar(props: ParentProps) { + const dialog = useDialog() + const server = useServer() + const sync = useSync() + const globalSync = useGlobalSync() + + const directoryDisplay = createMemo(() => { + const directory = sync.data.path.directory || "" + const home = globalSync.data.path.home || "" + const short = home && directory.startsWith(home) ? directory.replace(home, "~") : directory + const branch = sync.data.vcs?.branch + return branch ? `${short}:${branch}` : short + }) + + return ( +
+
+
+ +
+ + {directoryDisplay()} + +
+
{props.children}
+
+ ) +} diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx new file mode 100644 index 00000000000..b1bb36470ad --- /dev/null +++ b/packages/app/src/components/terminal.tsx @@ -0,0 +1,243 @@ +import { Ghostty, Terminal as Term, FitAddon } from "ghostty-web" +import { ComponentProps, createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js" +import { useSDK } from "@/context/sdk" +import { SerializeAddon } from "@/addons/serialize" +import { LocalPTY } from "@/context/terminal" +import { resolveThemeVariant, useTheme } from "@opencode-ai/ui/theme" + +export interface TerminalProps extends ComponentProps<"div"> { + pty: LocalPTY + onSubmit?: () => void + onCleanup?: (pty: LocalPTY) => void + onConnectError?: (error: unknown) => void +} + +type TerminalColors = { + background: string + foreground: string + cursor: string +} + +const DEFAULT_TERMINAL_COLORS: Record<"light" | "dark", TerminalColors> = { + light: { + background: "#fcfcfc", + foreground: "#211e1e", + cursor: "#211e1e", + }, + dark: { + background: "#191515", + foreground: "#d4d4d4", + cursor: "#d4d4d4", + }, +} + +export const Terminal = (props: TerminalProps) => { + const sdk = useSDK() + const theme = useTheme() + let container!: HTMLDivElement + const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnectError"]) + let ws: WebSocket + let term: Term + let ghostty: Ghostty + let serializeAddon: SerializeAddon + let fitAddon: FitAddon + let handleResize: () => void + + const getTerminalColors = (): TerminalColors => { + const mode = theme.mode() + const fallback = DEFAULT_TERMINAL_COLORS[mode] + const currentTheme = theme.themes()[theme.themeId()] + if (!currentTheme) return fallback + const variant = mode === "dark" ? currentTheme.dark : currentTheme.light + if (!variant?.seeds) return fallback + const resolved = resolveThemeVariant(variant, mode === "dark") + const text = resolved["text-base"] ?? fallback.foreground + const background = resolved["background-stronger"] ?? fallback.background + return { + background, + foreground: text, + cursor: text, + } + } + + const [terminalColors, setTerminalColors] = createSignal(getTerminalColors()) + + createEffect(() => { + const colors = getTerminalColors() + setTerminalColors(colors) + if (!term) return + const setOption = (term as unknown as { setOption?: (key: string, value: TerminalColors) => void }).setOption + if (!setOption) return + setOption("theme", colors) + }) + + const focusTerminal = () => term?.focus() + const copySelection = () => { + if (!term || !term.hasSelection()) return false + const selection = term.getSelection() + if (!selection) return false + const clipboard = navigator.clipboard + if (clipboard?.writeText) { + clipboard.writeText(selection).catch(() => {}) + return true + } + if (!document.body) return false + const textarea = document.createElement("textarea") + textarea.value = selection + textarea.setAttribute("readonly", "") + textarea.style.position = "fixed" + textarea.style.opacity = "0" + document.body.appendChild(textarea) + textarea.select() + const copied = document.execCommand("copy") + document.body.removeChild(textarea) + return copied + } + const handlePointerDown = () => { + const activeElement = document.activeElement + if (activeElement instanceof HTMLElement && activeElement !== container) { + activeElement.blur() + } + focusTerminal() + } + + onMount(async () => { + ghostty = await Ghostty.load() + + ws = new WebSocket(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`) + term = new Term({ + cursorBlink: true, + fontSize: 14, + fontFamily: "IBM Plex Mono, monospace", + allowTransparency: true, + theme: terminalColors(), + scrollback: 10_000, + ghostty, + }) + term.attachCustomKeyEventHandler((event) => { + const key = event.key.toLowerCase() + if (key === "c") { + const macCopy = event.metaKey && !event.ctrlKey && !event.altKey + const linuxCopy = event.ctrlKey && event.shiftKey && !event.metaKey + if ((macCopy || linuxCopy) && copySelection()) { + event.preventDefault() + return true + } + } + // allow for ctrl-` to toggle terminal in parent + if (event.ctrlKey && key === "`") { + event.preventDefault() + return true + } + return false + }) + + fitAddon = new FitAddon() + serializeAddon = new SerializeAddon() + term.loadAddon(serializeAddon) + term.loadAddon(fitAddon) + + term.open(container) + container.addEventListener("pointerdown", handlePointerDown) + focusTerminal() + + if (local.pty.buffer) { + if (local.pty.rows && local.pty.cols) { + term.resize(local.pty.cols, local.pty.rows) + } + term.reset() + term.write(local.pty.buffer) + if (local.pty.scrollY) { + term.scrollToLine(local.pty.scrollY) + } + fitAddon.fit() + } + + fitAddon.observeResize() + handleResize = () => fitAddon.fit() + window.addEventListener("resize", handleResize) + term.onResize(async (size) => { + if (ws && ws.readyState === WebSocket.OPEN) { + await sdk.client.pty + .update({ + ptyID: local.pty.id, + size: { + cols: size.cols, + rows: size.rows, + }, + }) + .catch(() => {}) + } + }) + term.onData((data) => { + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(data) + } + }) + term.onKey((key) => { + if (key.key == "Enter") { + props.onSubmit?.() + } + }) + // term.onScroll((ydisp) => { + // console.log("Scroll position:", ydisp) + // }) + ws.addEventListener("open", () => { + console.log("WebSocket connected") + sdk.client.pty + .update({ + ptyID: local.pty.id, + size: { + cols: term.cols, + rows: term.rows, + }, + }) + .catch(() => {}) + }) + ws.addEventListener("message", (event) => { + term.write(event.data) + }) + ws.addEventListener("error", (error) => { + console.error("WebSocket error:", error) + props.onConnectError?.(error) + }) + ws.addEventListener("close", () => { + console.log("WebSocket disconnected") + }) + }) + + onCleanup(() => { + if (handleResize) { + window.removeEventListener("resize", handleResize) + } + container.removeEventListener("pointerdown", handlePointerDown) + if (serializeAddon && props.onCleanup) { + const buffer = serializeAddon.serialize() + props.onCleanup({ + ...local.pty, + buffer, + rows: term.rows, + cols: term.cols, + scrollY: term.getViewportY(), + }) + } + ws?.close() + term?.dispose() + }) + + return ( +
+ ) +} diff --git a/packages/app/src/context/command.tsx b/packages/app/src/context/command.tsx new file mode 100644 index 00000000000..efd83bec861 --- /dev/null +++ b/packages/app/src/context/command.tsx @@ -0,0 +1,262 @@ +import { createMemo, createSignal, onCleanup, onMount, Show, type Accessor } from "solid-js" +import { createSimpleContext } from "@opencode-ai/ui/context" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { Dialog } from "@opencode-ai/ui/dialog" +import { List } from "@opencode-ai/ui/list" + +const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) + +export type KeybindConfig = string + +export interface Keybind { + key: string + ctrl: boolean + meta: boolean + shift: boolean + alt: boolean +} + +export interface CommandOption { + id: string + title: string + description?: string + category?: string + keybind?: KeybindConfig + slash?: string + suggested?: boolean + disabled?: boolean + onSelect?: (source?: "palette" | "keybind" | "slash") => void + onHighlight?: () => (() => void) | void +} + +export function parseKeybind(config: string): Keybind[] { + if (!config || config === "none") return [] + + return config.split(",").map((combo) => { + const parts = combo.trim().toLowerCase().split("+") + const keybind: Keybind = { + key: "", + ctrl: false, + meta: false, + shift: false, + alt: false, + } + + for (const part of parts) { + switch (part) { + case "ctrl": + case "control": + keybind.ctrl = true + break + case "meta": + case "cmd": + case "command": + keybind.meta = true + break + case "mod": + if (IS_MAC) keybind.meta = true + else keybind.ctrl = true + break + case "alt": + case "option": + keybind.alt = true + break + case "shift": + keybind.shift = true + break + default: + keybind.key = part + break + } + } + + return keybind + }) +} + +export function matchKeybind(keybinds: Keybind[], event: KeyboardEvent): boolean { + const eventKey = event.key.toLowerCase() + + for (const kb of keybinds) { + const keyMatch = kb.key === eventKey + const ctrlMatch = kb.ctrl === (event.ctrlKey || false) + const metaMatch = kb.meta === (event.metaKey || false) + const shiftMatch = kb.shift === (event.shiftKey || false) + const altMatch = kb.alt === (event.altKey || false) + + if (keyMatch && ctrlMatch && metaMatch && shiftMatch && altMatch) { + return true + } + } + + return false +} + +export function formatKeybind(config: string): string { + if (!config || config === "none") return "" + + const keybinds = parseKeybind(config) + if (keybinds.length === 0) return "" + + const kb = keybinds[0] + const parts: string[] = [] + + if (kb.ctrl) parts.push(IS_MAC ? "⌃" : "Ctrl") + if (kb.alt) parts.push(IS_MAC ? "⌥" : "Alt") + if (kb.shift) parts.push(IS_MAC ? "⇧" : "Shift") + if (kb.meta) parts.push(IS_MAC ? "⌘" : "Meta") + + if (kb.key) { + const displayKey = kb.key.length === 1 ? kb.key.toUpperCase() : kb.key.charAt(0).toUpperCase() + kb.key.slice(1) + parts.push(displayKey) + } + + return IS_MAC ? parts.join("") : parts.join("+") +} + +function DialogCommand(props: { options: CommandOption[] }) { + const dialog = useDialog() + let cleanup: (() => void) | void + let committed = false + + const handleMove = (option: CommandOption | undefined) => { + cleanup?.() + cleanup = option?.onHighlight?.() + } + + const handleSelect = (option: CommandOption | undefined) => { + if (option) { + committed = true + cleanup = undefined + dialog.close() + option.onSelect?.("palette") + } + } + + onCleanup(() => { + if (!committed) { + cleanup?.() + } + }) + + return ( + + props.options.filter((x) => !x.id.startsWith("suggested.") || !x.disabled)} + key={(x) => x?.id} + filterKeys={["title", "description", "category"]} + groupBy={(x) => x.category ?? ""} + onMove={handleMove} + onSelect={handleSelect} + > + {(option) => ( +
+
+ {option.title} + + {option.description} + +
+ + {formatKeybind(option.keybind!)} + +
+ )} +
+
+ ) +} + +export const { use: useCommand, provider: CommandProvider } = createSimpleContext({ + name: "Command", + init: () => { + const [registrations, setRegistrations] = createSignal[]>([]) + const [suspendCount, setSuspendCount] = createSignal(0) + const dialog = useDialog() + + const options = createMemo(() => { + const all = registrations().flatMap((x) => x()) + const suggested = all.filter((x) => x.suggested && !x.disabled) + return [ + ...suggested.map((x) => ({ + ...x, + id: "suggested." + x.id, + category: "Suggested", + })), + ...all, + ] + }) + + const suspended = () => suspendCount() > 0 + + const showPalette = () => { + if (!dialog.active) { + dialog.show(() => !x.disabled)} />) + } + } + + const handleKeyDown = (event: KeyboardEvent) => { + if (suspended()) return + + const paletteKeybinds = parseKeybind("mod+shift+p") + if (matchKeybind(paletteKeybinds, event)) { + event.preventDefault() + showPalette() + return + } + + for (const option of options()) { + if (option.disabled) continue + if (!option.keybind) continue + + const keybinds = parseKeybind(option.keybind) + if (matchKeybind(keybinds, event)) { + event.preventDefault() + option.onSelect?.("keybind") + return + } + } + } + + onMount(() => { + document.addEventListener("keydown", handleKeyDown) + }) + + onCleanup(() => { + document.removeEventListener("keydown", handleKeyDown) + }) + + return { + register(cb: () => CommandOption[]) { + const results = createMemo(cb) + setRegistrations((arr) => [results, ...arr]) + onCleanup(() => { + setRegistrations((arr) => arr.filter((x) => x !== results)) + }) + }, + trigger(id: string, source?: "palette" | "keybind" | "slash") { + for (const option of options()) { + if (option.id === id || option.id === "suggested." + id) { + option.onSelect?.(source) + return + } + } + }, + keybind(id: string) { + const option = options().find((x) => x.id === id || x.id === "suggested." + id) + if (!option?.keybind) return "" + return formatKeybind(option.keybind) + }, + show: showPalette, + keybinds(enabled: boolean) { + setSuspendCount((count) => count + (enabled ? -1 : 1)) + }, + suspended, + get options() { + return options() + }, + } + }, +}) diff --git a/packages/app/src/context/global-sdk.tsx b/packages/app/src/context/global-sdk.tsx new file mode 100644 index 00000000000..515be1d7abf --- /dev/null +++ b/packages/app/src/context/global-sdk.tsx @@ -0,0 +1,41 @@ +import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2/client" +import { createSimpleContext } from "@opencode-ai/ui/context" +import { createGlobalEmitter } from "@solid-primitives/event-bus" +import { onCleanup } from "solid-js" +import { usePlatform } from "./platform" +import { useServer } from "./server" + +export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleContext({ + name: "GlobalSDK", + init: () => { + const server = useServer() + const abort = new AbortController() + + const eventSdk = createOpencodeClient({ + baseUrl: server.url, + signal: abort.signal, + }) + const emitter = createGlobalEmitter<{ + [key: string]: Event + }>() + + void (async () => { + const events = await eventSdk.global.event() + for await (const event of events.stream) { + emitter.emit(event.directory ?? "global", event.payload) + } + })().catch(() => undefined) + + onCleanup(() => abort.abort()) + + const platform = usePlatform() + const sdk = createOpencodeClient({ + baseUrl: server.url, + signal: AbortSignal.timeout(1000 * 60 * 10), + fetch: platform.fetch, + throwOnError: true, + }) + + return { url: server.url, client: sdk, event: emitter } + }, +}) diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx new file mode 100644 index 00000000000..a6851aecbf1 --- /dev/null +++ b/packages/app/src/context/global-sync.tsx @@ -0,0 +1,487 @@ +import { + type Message, + type Agent, + type Session, + type Part, + type Config, + type Path, + type Project, + type FileDiff, + type Todo, + type SessionStatus, + type ProviderListResponse, + type ProviderAuthResponse, + type Command, + type McpStatus, + type LspStatus, + type VcsInfo, + type Permission, + createOpencodeClient, +} from "@opencode-ai/sdk/v2/client" +import { createStore, produce, reconcile } from "solid-js/store" +import { Binary } from "@opencode-ai/util/binary" +import { retry } from "@opencode-ai/util/retry" +import { useGlobalSDK } from "./global-sdk" +import { ErrorPage, type InitError } from "../pages/error" +import { batch, createContext, useContext, onMount, type ParentProps, Switch, Match } from "solid-js" +import { showToast } from "@opencode-ai/ui/toast" +import { getFilename } from "@opencode-ai/util/path" + +type State = { + ready: boolean + agent: Agent[] + command: Command[] + project: string + provider: ProviderListResponse + config: Config + path: Path + session: Session[] + session_status: { + [sessionID: string]: SessionStatus + } + session_diff: { + [sessionID: string]: FileDiff[] + } + todo: { + [sessionID: string]: Todo[] + } + permission: { + [sessionID: string]: Permission[] + } + mcp: { + [name: string]: McpStatus + } + lsp: LspStatus[] + vcs: VcsInfo | undefined + limit: number + message: { + [sessionID: string]: Message[] + } + part: { + [messageID: string]: Part[] + } +} + +function createGlobalSync() { + const globalSDK = useGlobalSDK() + const [globalStore, setGlobalStore] = createStore<{ + ready: boolean + error?: InitError + path: Path + project: Project[] + provider: ProviderListResponse + provider_auth: ProviderAuthResponse + }>({ + ready: false, + path: { state: "", config: "", worktree: "", directory: "", home: "" }, + project: [], + provider: { all: [], connected: [], default: {} }, + provider_auth: {}, + }) + + const children: Record>> = {} + function child(directory: string) { + if (!directory) console.error("No directory provided") + if (!children[directory]) { + children[directory] = createStore({ + project: "", + provider: { all: [], connected: [], default: {} }, + config: {}, + path: { state: "", config: "", worktree: "", directory: "", home: "" }, + ready: false, + agent: [], + command: [], + session: [], + session_status: {}, + session_diff: {}, + todo: {}, + permission: {}, + mcp: {}, + lsp: [], + vcs: undefined, + limit: 5, + message: {}, + part: {}, + }) + bootstrapInstance(directory) + } + return children[directory] + } + + async function loadSessions(directory: string) { + const [store, setStore] = child(directory) + globalSDK.client.session + .list({ directory }) + .then((x) => { + const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000 + const nonArchived = (x.data ?? []) + .slice() + .filter((s) => !s.time.archived) + .sort((a, b) => a.id.localeCompare(b.id)) + // Include up to the limit, plus any updated in the last 4 hours + const sessions = nonArchived.filter((s, i) => { + if (i < store.limit) return true + const updated = new Date(s.time.updated).getTime() + return updated > fourHoursAgo + }) + setStore("session", reconcile(sessions, { key: "id" })) + }) + .catch((err) => { + console.error("Failed to load sessions", err) + const project = getFilename(directory) + showToast({ title: `Failed to load sessions for ${project}`, description: err.message }) + }) + } + + async function bootstrapInstance(directory: string) { + if (!directory) return + const [store, setStore] = child(directory) + const sdk = createOpencodeClient({ + baseUrl: globalSDK.url, + directory, + throwOnError: true, + }) + const load = { + project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)), + provider: () => + sdk.provider.list().then((x) => { + const data = x.data! + setStore("provider", { + ...data, + all: data.all.map((provider) => ({ + ...provider, + models: Object.fromEntries( + Object.entries(provider.models).filter(([, info]) => info.status !== "deprecated"), + ), + })), + }) + }), + path: () => sdk.path.get().then((x) => setStore("path", x.data!)), + agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])), + command: () => sdk.command.list().then((x) => setStore("command", x.data ?? [])), + session: () => loadSessions(directory), + status: () => sdk.session.status().then((x) => setStore("session_status", x.data!)), + config: () => sdk.config.get().then((x) => setStore("config", x.data!)), + mcp: () => sdk.mcp.status().then((x) => setStore("mcp", x.data ?? {})), + lsp: () => sdk.lsp.status().then((x) => setStore("lsp", x.data ?? [])), + vcs: () => sdk.vcs.get().then((x) => setStore("vcs", x.data)), + permission: () => + sdk.permission.list().then((x) => { + const grouped: Record = {} + for (const perm of x.data ?? []) { + const existing = grouped[perm.sessionID] + if (existing) { + existing.push(perm) + continue + } + grouped[perm.sessionID] = [perm] + } + + batch(() => { + for (const sessionID of Object.keys(store.permission)) { + if (grouped[sessionID]) continue + setStore("permission", sessionID, []) + } + for (const [sessionID, permissions] of Object.entries(grouped)) { + setStore( + "permission", + sessionID, + reconcile( + permissions.slice().sort((a, b) => a.id.localeCompare(b.id)), + { key: "id" }, + ), + ) + } + }) + }), + } + await Promise.all(Object.values(load).map((p) => retry(p).catch((e) => setGlobalStore("error", e)))) + .then(() => setStore("ready", true)) + .catch((e) => setGlobalStore("error", e)) + } + + globalSDK.event.listen((e) => { + const directory = e.name + const event = e.details + + if (directory === "global") { + switch (event?.type) { + case "global.disposed": { + bootstrap() + break + } + case "project.updated": { + const result = Binary.search(globalStore.project, event.properties.id, (s) => s.id) + if (result.found) { + setGlobalStore("project", result.index, reconcile(event.properties)) + return + } + setGlobalStore( + "project", + produce((draft) => { + draft.splice(result.index, 0, event.properties) + }), + ) + break + } + } + return + } + + const [store, setStore] = child(directory) + switch (event.type) { + case "server.instance.disposed": { + bootstrapInstance(directory) + break + } + case "session.updated": { + const result = Binary.search(store.session, event.properties.info.id, (s) => s.id) + if (event.properties.info.time.archived) { + if (result.found) { + setStore( + "session", + produce((draft) => { + draft.splice(result.index, 1) + }), + ) + } + break + } + if (result.found) { + setStore("session", result.index, reconcile(event.properties.info)) + break + } + setStore( + "session", + produce((draft) => { + draft.splice(result.index, 0, event.properties.info) + }), + ) + break + } + case "session.diff": + setStore("session_diff", event.properties.sessionID, reconcile(event.properties.diff, { key: "file" })) + break + case "todo.updated": + setStore("todo", event.properties.sessionID, reconcile(event.properties.todos, { key: "id" })) + break + case "session.status": { + setStore("session_status", event.properties.sessionID, reconcile(event.properties.status)) + break + } + case "message.updated": { + const messages = store.message[event.properties.info.sessionID] + if (!messages) { + setStore("message", event.properties.info.sessionID, [event.properties.info]) + break + } + const result = Binary.search(messages, event.properties.info.id, (m) => m.id) + if (result.found) { + setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info)) + break + } + setStore( + "message", + event.properties.info.sessionID, + produce((draft) => { + draft.splice(result.index, 0, event.properties.info) + }), + ) + break + } + case "message.removed": { + const messages = store.message[event.properties.sessionID] + if (!messages) break + const result = Binary.search(messages, event.properties.messageID, (m) => m.id) + if (result.found) { + setStore( + "message", + event.properties.sessionID, + produce((draft) => { + draft.splice(result.index, 1) + }), + ) + } + break + } + case "message.part.updated": { + const part = event.properties.part + const parts = store.part[part.messageID] + if (!parts) { + setStore("part", part.messageID, [part]) + break + } + const result = Binary.search(parts, part.id, (p) => p.id) + if (result.found) { + setStore("part", part.messageID, result.index, reconcile(part)) + break + } + setStore( + "part", + part.messageID, + produce((draft) => { + draft.splice(result.index, 0, part) + }), + ) + break + } + case "message.part.removed": { + const parts = store.part[event.properties.messageID] + if (!parts) break + const result = Binary.search(parts, event.properties.partID, (p) => p.id) + if (result.found) { + setStore( + "part", + event.properties.messageID, + produce((draft) => { + draft.splice(result.index, 1) + }), + ) + } + break + } + case "vcs.branch.updated": { + setStore("vcs", { branch: event.properties.branch }) + break + } + case "permission.updated": { + const sessionID = event.properties.sessionID + const permissions = store.permission[sessionID] + if (!permissions) { + setStore("permission", sessionID, [event.properties]) + break + } + + const result = Binary.search(permissions, event.properties.id, (p) => p.id) + if (result.found) { + setStore("permission", sessionID, result.index, reconcile(event.properties)) + break + } + + setStore( + "permission", + sessionID, + produce((draft) => { + draft.splice(result.index, 0, event.properties) + }), + ) + break + } + case "permission.replied": { + const permissions = store.permission[event.properties.sessionID] + if (!permissions) break + const result = Binary.search(permissions, event.properties.permissionID, (p) => p.id) + if (!result.found) break + setStore( + "permission", + event.properties.sessionID, + produce((draft) => { + draft.splice(result.index, 1) + }), + ) + break + } + case "lsp.updated": { + const sdk = createOpencodeClient({ + baseUrl: globalSDK.url, + directory, + throwOnError: true, + }) + sdk.lsp.status().then((x) => setStore("lsp", x.data ?? [])) + break + } + } + }) + + async function bootstrap() { + const health = await globalSDK.client.global + .health() + .then((x) => x.data) + .catch(() => undefined) + if (!health?.healthy) { + setGlobalStore( + "error", + new Error(`Could not connect to server. Is there a server running at \`${globalSDK.url}\`?`), + ) + return + } + + return Promise.all([ + retry(() => + globalSDK.client.path.get().then((x) => { + setGlobalStore("path", x.data!) + }), + ), + retry(() => + globalSDK.client.project.list().then(async (x) => { + setGlobalStore( + "project", + x.data!.filter((p) => !p.worktree.includes("opencode-test")).sort((a, b) => a.id.localeCompare(b.id)), + ) + }), + ), + retry(() => + globalSDK.client.provider.list().then((x) => { + const data = x.data! + setGlobalStore("provider", { + ...data, + all: data.all.map((provider) => ({ + ...provider, + models: Object.fromEntries( + Object.entries(provider.models).filter(([, info]) => info.status !== "deprecated"), + ), + })), + }) + }), + ), + retry(() => + globalSDK.client.provider.auth().then((x) => { + setGlobalStore("provider_auth", x.data ?? {}) + }), + ), + ]) + .then(() => setGlobalStore("ready", true)) + .catch((e) => setGlobalStore("error", e)) + } + + onMount(() => { + bootstrap() + }) + + return { + data: globalStore, + get ready() { + return globalStore.ready + }, + get error() { + return globalStore.error + }, + child, + bootstrap, + project: { + loadSessions, + }, + } +} + +const GlobalSyncContext = createContext>() + +export function GlobalSyncProvider(props: ParentProps) { + const value = createGlobalSync() + return ( + + + + + + {props.children} + + + ) +} + +export function useGlobalSync() { + const context = useContext(GlobalSyncContext) + if (!context) throw new Error("useGlobalSync must be used within GlobalSyncProvider") + return context +} diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx new file mode 100644 index 00000000000..e57f69f8fbb --- /dev/null +++ b/packages/app/src/context/layout.tsx @@ -0,0 +1,253 @@ +import { createStore, produce } from "solid-js/store" +import { batch, createMemo, onMount } from "solid-js" +import { createSimpleContext } from "@opencode-ai/ui/context" +import { useGlobalSync } from "./global-sync" +import { useGlobalSDK } from "./global-sdk" +import { useServer } from "./server" +import { Project } from "@opencode-ai/sdk/v2" +import { persisted } from "@/utils/persist" + +const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const +export type AvatarColorKey = (typeof AVATAR_COLOR_KEYS)[number] + +export function getAvatarColors(key?: string) { + if (key && AVATAR_COLOR_KEYS.includes(key as AvatarColorKey)) { + return { + background: `var(--avatar-background-${key})`, + foreground: `var(--avatar-text-${key})`, + } + } + return { + background: "var(--surface-info-base)", + foreground: "var(--text-base)", + } +} + +type SessionTabs = { + active?: string + all: string[] +} + +export type LocalProject = Partial & { worktree: string; expanded: boolean } + +export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({ + name: "Layout", + init: () => { + const globalSdk = useGlobalSDK() + const globalSync = useGlobalSync() + const server = useServer() + const [store, setStore, _, ready] = persisted( + "layout.v4", + createStore({ + sidebar: { + opened: false, + width: 280, + }, + terminal: { + opened: false, + height: 280, + }, + review: { + opened: true, + }, + session: { + width: 600, + }, + sessionTabs: {} as Record, + }), + ) + + const usedColors = new Set() + + function pickAvailableColor(): AvatarColorKey { + const available = AVATAR_COLOR_KEYS.filter((c) => !usedColors.has(c)) + if (available.length === 0) return AVATAR_COLOR_KEYS[Math.floor(Math.random() * AVATAR_COLOR_KEYS.length)] + return available[Math.floor(Math.random() * available.length)] + } + + function enrich(project: { worktree: string; expanded: boolean }) { + const metadata = globalSync.data.project.find((x) => x.worktree === project.worktree) + return [ + { + ...project, + ...(metadata ?? {}), + icon: { url: metadata?.icon?.url, color: metadata?.icon?.color }, + }, + ] + } + + function colorize(project: LocalProject) { + if (project.icon?.color) return project + const color = pickAvailableColor() + usedColors.add(color) + project.icon = { ...project.icon, color } + if (project.id) { + globalSdk.client.project.update({ projectID: project.id, icon: { color } }) + } + return project + } + + const enriched = createMemo(() => server.projects.list().flatMap(enrich)) + const list = createMemo(() => enriched().flatMap(colorize)) + + onMount(() => { + Promise.all( + server.projects.list().map((project) => { + return globalSync.project.loadSessions(project.worktree) + }), + ) + }) + + return { + ready, + projects: { + list, + open(directory: string) { + if (server.projects.list().find((x) => x.worktree === directory)) { + return + } + globalSync.project.loadSessions(directory) + server.projects.open(directory) + }, + close(directory: string) { + server.projects.close(directory) + }, + expand(directory: string) { + server.projects.expand(directory) + }, + collapse(directory: string) { + server.projects.collapse(directory) + }, + move(directory: string, toIndex: number) { + server.projects.move(directory, toIndex) + }, + }, + sidebar: { + opened: createMemo(() => store.sidebar.opened), + open() { + setStore("sidebar", "opened", true) + }, + close() { + setStore("sidebar", "opened", false) + }, + toggle() { + setStore("sidebar", "opened", (x) => !x) + }, + width: createMemo(() => store.sidebar.width), + resize(width: number) { + setStore("sidebar", "width", width) + }, + }, + terminal: { + opened: createMemo(() => store.terminal.opened), + open() { + setStore("terminal", "opened", true) + }, + close() { + setStore("terminal", "opened", false) + }, + toggle() { + setStore("terminal", "opened", (x) => !x) + }, + height: createMemo(() => store.terminal.height), + resize(height: number) { + setStore("terminal", "height", height) + }, + }, + review: { + opened: createMemo(() => store.review?.opened ?? true), + open() { + setStore("review", "opened", true) + }, + close() { + setStore("review", "opened", false) + }, + toggle() { + setStore("review", "opened", (x) => !x) + }, + }, + session: { + width: createMemo(() => store.session?.width ?? 600), + resize(width: number) { + if (!store.session) { + setStore("session", { width }) + } else { + setStore("session", "width", width) + } + }, + }, + tabs(sessionKey: string) { + const tabs = createMemo(() => store.sessionTabs[sessionKey] ?? { all: [] }) + return { + tabs, + active: createMemo(() => tabs().active), + all: createMemo(() => tabs().all), + setActive(tab: string | undefined) { + if (!store.sessionTabs[sessionKey]) { + setStore("sessionTabs", sessionKey, { all: [], active: tab }) + } else { + setStore("sessionTabs", sessionKey, "active", tab) + } + }, + setAll(all: string[]) { + if (!store.sessionTabs[sessionKey]) { + setStore("sessionTabs", sessionKey, { all, active: undefined }) + } else { + setStore("sessionTabs", sessionKey, "all", all) + } + }, + async open(tab: string) { + const current = store.sessionTabs[sessionKey] ?? { all: [] } + if (tab !== "review") { + if (!current.all.includes(tab)) { + if (!store.sessionTabs[sessionKey]) { + setStore("sessionTabs", sessionKey, { all: [tab], active: tab }) + } else { + setStore("sessionTabs", sessionKey, "all", [...current.all, tab]) + setStore("sessionTabs", sessionKey, "active", tab) + } + return + } + } + if (!store.sessionTabs[sessionKey]) { + setStore("sessionTabs", sessionKey, { all: [], active: tab }) + } else { + setStore("sessionTabs", sessionKey, "active", tab) + } + }, + close(tab: string) { + const current = store.sessionTabs[sessionKey] + if (!current) return + batch(() => { + setStore( + "sessionTabs", + sessionKey, + "all", + current.all.filter((x) => x !== tab), + ) + if (current.active === tab) { + const index = current.all.findIndex((f) => f === tab) + const previous = current.all[Math.max(0, index - 1)] + setStore("sessionTabs", sessionKey, "active", previous) + } + }) + }, + move(tab: string, to: number) { + const current = store.sessionTabs[sessionKey] + if (!current) return + const index = current.all.findIndex((f) => f === tab) + if (index === -1) return + setStore( + "sessionTabs", + sessionKey, + "all", + produce((opened) => { + opened.splice(to, 0, opened.splice(index, 1)[0]) + }), + ) + }, + } + }, + } + }, +}) diff --git a/packages/desktop/src/context/local.tsx b/packages/app/src/context/local.tsx similarity index 62% rename from packages/desktop/src/context/local.tsx rename to packages/app/src/context/local.tsx index de4202d9a0f..23d706bb010 100644 --- a/packages/desktop/src/context/local.tsx +++ b/packages/app/src/context/local.tsx @@ -1,11 +1,15 @@ import { createStore, produce, reconcile } from "solid-js/store" -import { batch, createEffect, createMemo } from "solid-js" -import { uniqueBy } from "remeda" +import { batch, createMemo } from "solid-js" +import { filter, firstBy, flat, groupBy, mapValues, pipe, uniqueBy, values } from "remeda" import type { FileContent, FileNode, Model, Provider, File as FileStatus } from "@opencode-ai/sdk/v2" import { createSimpleContext } from "@opencode-ai/ui/context" import { useSDK } from "./sdk" import { useSync } from "./sync" -import { base64Encode } from "@/utils" +import { base64Encode } from "@opencode-ai/util/encode" +import { useProviders } from "@/hooks/use-providers" +import { DateTime } from "luxon" +import { persisted } from "@/utils/persist" +import { showToast } from "@opencode-ai/ui/toast" export type LocalFile = FileNode & Partial<{ @@ -25,6 +29,7 @@ export type View = LocalFile["view"] export type LocalModel = Omit & { provider: Provider + latest?: boolean } export type ModelKey = { providerID: string; modelID: string } @@ -36,10 +41,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ init: () => { const sdk = useSDK() const sync = useSync() + const providers = useProviders() function isModelValid(model: ModelKey) { - const provider = sync.data.provider.find((x) => x.id === model.providerID) - return !!provider?.models[model.modelID] + const provider = providers.all().find((x) => x.id === model.providerID) + return ( + !!provider?.models[model.modelID] && + providers + .connected() + .map((p) => p.id) + .includes(model.providerID) + ) } function getFirstValidModel(...modelFns: (() => ModelKey | undefined)[]) { @@ -50,44 +62,43 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } } - // Automatically update model when agent changes - createEffect(() => { - const value = agent.current() - if (value.model) { - if (isModelValid(value.model)) - model.set({ - providerID: value.model.providerID, - modelID: value.model.modelID, - }) - // else - // toast.show({ - // type: "warning", - // message: `Agent ${value.name}'s configured model ${value.model.providerID}/${value.model.modelID} is not valid`, - // duration: 3000, - // }) - } - }) - const agent = (() => { - const list = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent")) + const list = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent" && !x.hidden)) const [store, setStore] = createStore<{ - current: string + current?: string }>({ - current: list()[0].name, + current: list()[0]?.name, }) return { list, current() { - return list().find((x) => x.name === store.current)! + const available = list() + if (available.length === 0) return undefined + return available.find((x) => x.name === store.current) ?? available[0] }, set(name: string | undefined) { - setStore("current", name ?? list()[0].name) + const available = list() + if (available.length === 0) { + setStore("current", undefined) + return + } + if (name && available.some((x) => x.name === name)) { + setStore("current", name) + return + } + setStore("current", available[0].name) }, move(direction: 1 | -1) { - let next = list().findIndex((x) => x.name === store.current) + direction - if (next < 0) next = list().length - 1 - if (next >= list().length) next = 0 - const value = list()[next] + const available = list() + if (available.length === 0) { + setStore("current", undefined) + return + } + let next = available.findIndex((x) => x.name === store.current) + direction + if (next < 0) next = available.length - 1 + if (next >= available.length) next = 0 + const value = available[next] + if (!value) return setStore("current", value.name) if (value.model) model.set({ @@ -99,23 +110,62 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ })() const model = (() => { - const [store, setStore] = createStore<{ + const [store, setStore, _, modelReady] = persisted( + "model.v1", + createStore<{ + user: (ModelKey & { visibility: "show" | "hide"; favorite?: boolean })[] + recent: ModelKey[] + }>({ + user: [], + recent: [], + }), + ) + + const [ephemeral, setEphemeral] = createStore<{ model: Record - recent: ModelKey[] }>({ model: {}, - recent: [], }) - const value = localStorage.getItem("model") - setStore("recent", JSON.parse(value ?? "[]")) - createEffect(() => { - localStorage.setItem("model", JSON.stringify(store.recent)) - }) + const available = createMemo(() => + providers.connected().flatMap((p) => + Object.values(p.models).map((m) => ({ + ...m, + provider: p, + })), + ), + ) + + const latest = createMemo(() => + pipe( + available(), + filter((x) => Math.abs(DateTime.fromISO(x.release_date).diffNow().as("months")) < 6), + groupBy((x) => x.provider.id), + mapValues((models) => + pipe( + models, + groupBy((x) => x.family), + values(), + (groups) => + groups.flatMap((g) => { + const first = firstBy(g, [(x) => x.release_date, "desc"]) + return first ? [{ modelID: first.id, providerID: first.provider.id }] : [] + }), + ), + ), + values(), + flat(), + ), + ) const list = createMemo(() => - sync.data.provider.flatMap((p) => Object.values(p.models).map((m) => ({ ...m, provider: p }) as LocalModel)), + available().map((m) => ({ + ...m, + name: m.name.replace("(latest)", "").trim(), + latest: m.name.includes("(latest)"), + })), ) + const find = (key: ModelKey) => list().find((m) => m.id === key?.modelID && m.provider.id === key.providerID) const fallbackModel = createMemo(() => { @@ -134,21 +184,28 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ return item } } - const provider = sync.data.provider[0] - const model = Object.values(provider.models)[0] - return { - providerID: provider.id, - modelID: model.id, + + for (const p of providers.connected()) { + if (p.id in providers.default()) { + return { + providerID: p.id, + modelID: providers.default()[p.id], + } + } } + + throw new Error("No default model found") }) - const currentModel = createMemo(() => { + const current = createMemo(() => { const a = agent.current() + if (!a) return undefined const key = getFirstValidModel( - () => store.model[a.name], + () => ephemeral.model[a.name], () => a.model, fallbackModel, - )! + ) + if (!key) return undefined return find(key) }) @@ -156,10 +213,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const cycle = (direction: 1 | -1) => { const recentList = recent() - const current = currentModel() - if (!current) return + const currentModel = current() + if (!currentModel) return - const index = recentList.findIndex((x) => x?.provider.id === current.provider.id && x?.id === current.id) + const index = recentList.findIndex( + (x) => x?.provider.id === currentModel.provider.id && x?.id === currentModel.id, + ) if (index === -1) return let next = index + direction @@ -175,14 +234,26 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ }) } + function updateVisibility(model: ModelKey, visibility: "show" | "hide") { + const index = store.user.findIndex((x) => x.modelID === model.modelID && x.providerID === model.providerID) + if (index >= 0) { + setStore("user", index, { visibility }) + } else { + setStore("user", store.user.length, { ...model, visibility }) + } + } + return { - current: currentModel, + ready: modelReady, + current, recent, list, cycle, set(model: ModelKey | undefined, options?: { recent?: boolean }) { batch(() => { - setStore("model", agent.current().name, model ?? fallbackModel()) + const currentAgent = agent.current() + if (currentAgent) setEphemeral("model", currentAgent.name, model ?? fallbackModel()) + if (model) updateVisibility(model, "show") if (options?.recent && model) { const uniq = uniqueBy([model, ...store.recent], (x) => x.providerID + x.modelID) if (uniq.length > 5) uniq.pop() @@ -190,6 +261,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } }) }, + visible(model: ModelKey) { + const user = store.user.find((x) => x.modelID === model.modelID && x.providerID === model.providerID) + return ( + user?.visibility !== "hide" && + (latest().find((x) => x.modelID === model.modelID && x.providerID === model.providerID) || + user?.visibility === "show") + ) + }, + setVisibility(model: ModelKey, visible: boolean) { + updateVisibility(model, visible ? "show" : "hide") + }, } })() @@ -197,11 +279,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const [store, setStore] = createStore<{ node: Record }>({ - node: Object.fromEntries(sync.data.node.map((x) => [x.path, x])), + node: {}, // Object.fromEntries(sync.data.node.map((x) => [x.path, x])), }) - const changeset = createMemo(() => new Set(sync.data.changes.map((f) => f.path))) - const changes = createMemo(() => Array.from(changeset()).sort((a, b) => a.localeCompare(b))) + // const changeset = createMemo(() => new Set(sync.data.changes.map((f) => f.path))) + // const changes = createMemo(() => Array.from(changeset()).sort((a, b) => a.localeCompare(b))) // createEffect((prev: FileStatus[]) => { // const removed = prev.filter((p) => !sync.data.changes.find((c) => c.path === p.path)) @@ -229,16 +311,16 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ // return sync.data.changes // }, sync.data.changes) - const changed = (path: string) => { - const node = store.node[path] - if (node?.status) return true - const set = changeset() - if (set.has(path)) return true - for (const p of set) { - if (p.startsWith(path ? path + "/" : "")) return true - } - return false - } + // const changed = (path: string) => { + // const node = store.node[path] + // if (node?.status) return true + // const set = changeset() + // if (set.has(path)) return true + // for (const p of set) { + // if (p.startsWith(path ? path + "/" : "")) return true + // } + // return false + // } // const resetNode = (path: string) => { // setStore("node", path, { @@ -257,16 +339,26 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const load = async (path: string) => { const relativePath = relative(path) - sdk.client.file.read({ path: relativePath }).then((x) => { - setStore( - "node", - relativePath, - produce((draft) => { - draft.loaded = true - draft.content = x.data - }), - ) - }) + await sdk.client.file + .read({ path: relativePath }) + .then((x) => { + if (!store.node[relativePath]) return + setStore( + "node", + relativePath, + produce((draft) => { + draft.loaded = true + draft.content = x.data + }), + ) + }) + .catch((e) => { + showToast({ + variant: "error", + title: "Failed to load file", + description: e.message, + }) + }) } const fetch = async (path: string) => { @@ -280,7 +372,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const init = async (path: string) => { const relativePath = relative(path) if (!store.node[relativePath]) await fetch(path) - if (store.node[relativePath].loaded) return + if (store.node[relativePath]?.loaded) return return load(relativePath) } @@ -300,22 +392,25 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ context.addActive() if (options?.pinned) setStore("node", path, "pinned", true) if (options?.view && store.node[relativePath].view === undefined) setStore("node", path, "view", options.view) - if (store.node[relativePath].loaded) return + if (store.node[relativePath]?.loaded) return return load(relativePath) } const list = async (path: string) => { - return sdk.client.file.list({ path: path + "/" }).then((x) => { - setStore( - "node", - produce((draft) => { - x.data!.forEach((node) => { - if (node.path in draft) return - draft[node.path] = node - }) - }), - ) - }) + return sdk.client.file + .list({ path: path + "/" }) + .then((x) => { + setStore( + "node", + produce((draft) => { + x.data!.forEach((node) => { + if (node.path in draft) return + draft[node.path] = node + }) + }), + ) + }) + .catch(() => {}) } const searchFiles = (query: string) => sdk.client.find.files({ query, dirs: "false" }).then((x) => x.data!) @@ -328,14 +423,14 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ case "file.watcher.updated": const relativePath = relative(event.properties.file) if (relativePath.startsWith(".git/")) return - load(relativePath) + if (store.node[relativePath]) load(relativePath) break } }) return { node: async (path: string) => { - if (!store.node[path]) { + if (!store.node[path] || !store.node[path].loaded) { await init(path) } return store.node[path] @@ -346,7 +441,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ init, expand(path: string) { setStore("node", path, "expanded", true) - if (store.node[path].loaded) return + if (store.node[path]?.loaded) return setStore("node", path, "loaded", true) list(path) }, @@ -386,8 +481,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ setChangeIndex(path: string, index: number | undefined) { setStore("node", path, "selectedChange", index) }, - changes, - changed, + // changes, + // changed, children(path: string) { return Object.values(store.node).filter( (x) => diff --git a/packages/app/src/context/notification.tsx b/packages/app/src/context/notification.tsx new file mode 100644 index 00000000000..33d72d4f2ee --- /dev/null +++ b/packages/app/src/context/notification.tsx @@ -0,0 +1,134 @@ +import { createStore } from "solid-js/store" +import { createSimpleContext } from "@opencode-ai/ui/context" +import { useGlobalSDK } from "./global-sdk" +import { useGlobalSync } from "./global-sync" +import { usePlatform } from "@/context/platform" +import { Binary } from "@opencode-ai/util/binary" +import { base64Encode } from "@opencode-ai/util/encode" +import { EventSessionError } from "@opencode-ai/sdk/v2" +import { makeAudioPlayer } from "@solid-primitives/audio" +import idleSound from "@opencode-ai/ui/audio/staplebops-01.aac" +import errorSound from "@opencode-ai/ui/audio/nope-03.aac" +import { persisted } from "@/utils/persist" + +type NotificationBase = { + directory?: string + session?: string + metadata?: any + time: number + viewed: boolean +} + +type TurnCompleteNotification = NotificationBase & { + type: "turn-complete" +} + +type ErrorNotification = NotificationBase & { + type: "error" + error: EventSessionError["properties"]["error"] +} + +export type Notification = TurnCompleteNotification | ErrorNotification + +export const { use: useNotification, provider: NotificationProvider } = createSimpleContext({ + name: "Notification", + init: () => { + let idlePlayer: ReturnType | undefined + let errorPlayer: ReturnType | undefined + + try { + idlePlayer = makeAudioPlayer(idleSound) + errorPlayer = makeAudioPlayer(errorSound) + } catch (err) { + console.log("Failed to load audio", err) + } + + const globalSDK = useGlobalSDK() + const globalSync = useGlobalSync() + const platform = usePlatform() + + const [store, setStore, _, ready] = persisted( + "notification.v1", + createStore({ + list: [] as Notification[], + }), + ) + + globalSDK.event.listen((e) => { + const directory = e.name + const event = e.details + const base = { + directory, + time: Date.now(), + viewed: false, + } + switch (event.type) { + case "session.idle": { + const sessionID = event.properties.sessionID + const [syncStore] = globalSync.child(directory) + const match = Binary.search(syncStore.session, sessionID, (s) => s.id) + const session = match.found ? syncStore.session[match.index] : undefined + if (session?.parentID) break + try { + idlePlayer?.play() + } catch {} + setStore("list", store.list.length, { + ...base, + type: "turn-complete", + session: sessionID, + }) + const href = `/${base64Encode(directory)}/session/${sessionID}` + void platform.notify("Response ready", session?.title ?? sessionID, href) + break + } + case "session.error": { + const sessionID = event.properties.sessionID + const [syncStore] = globalSync.child(directory) + const match = sessionID ? Binary.search(syncStore.session, sessionID, (s) => s.id) : undefined + const session = sessionID && match?.found ? syncStore.session[match.index] : undefined + if (session?.parentID) break + try { + errorPlayer?.play() + } catch {} + const error = "error" in event.properties ? event.properties.error : undefined + setStore("list", store.list.length, { + ...base, + type: "error", + session: sessionID ?? "global", + error, + }) + const description = session?.title ?? (typeof error === "string" ? error : "An error occurred") + const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}` + void platform.notify("Session error", description, href) + break + } + } + }) + + return { + ready, + session: { + all(session: string) { + return store.list.filter((n) => n.session === session) + }, + unseen(session: string) { + return store.list.filter((n) => n.session === session && !n.viewed) + }, + markViewed(session: string) { + setStore("list", (n) => n.session === session, "viewed", true) + }, + }, + project: { + all(directory: string) { + return store.list.filter((n) => n.directory === directory) + }, + unseen(directory: string) { + return store.list.filter((n) => n.directory === directory && !n.viewed) + }, + markViewed(directory: string) { + setStore("list", (n) => n.directory === directory, "viewed", true) + }, + }, + } + }, +}) diff --git a/packages/app/src/context/permission.tsx b/packages/app/src/context/permission.tsx new file mode 100644 index 00000000000..6d7b335ad96 --- /dev/null +++ b/packages/app/src/context/permission.tsx @@ -0,0 +1,130 @@ +import { createEffect, createRoot, onCleanup } from "solid-js" +import { createStore } from "solid-js/store" +import { createSimpleContext } from "@opencode-ai/ui/context" +import type { Permission } from "@opencode-ai/sdk/v2/client" +import { persisted } from "@/utils/persist" + +type PermissionsBySession = { + [sessionID: string]: Permission[] +} + +type PermissionRespondFn = (input: { + sessionID: string + permissionID: string + response: "once" | "always" | "reject" +}) => void + +const AUTO_ACCEPT_TYPES = new Set(["edit", "write"]) + +function shouldAutoAccept(perm: Permission) { + return AUTO_ACCEPT_TYPES.has(perm.type) +} + +export const { use: usePermission, provider: PermissionProvider } = createSimpleContext({ + name: "Permission", + init: (props: { permissions: PermissionsBySession; onRespond: PermissionRespondFn }) => { + const [store, setStore, _, ready] = persisted( + "permission.v1", + createStore({ + autoAcceptEdits: {} as Record, + }), + ) + + const responded = new Set() + const watches = new Map void>() + + function respond(perm: Permission) { + if (responded.has(perm.id)) return + responded.add(perm.id) + props.onRespond({ + sessionID: perm.sessionID, + permissionID: perm.id, + response: "once", + }) + } + + function watch(sessionID: string) { + if (watches.has(sessionID)) return + + const dispose = createRoot((dispose) => { + createEffect(() => { + if (!store.autoAcceptEdits[sessionID]) return + + const permissions = props.permissions[sessionID] ?? [] + permissions.length + + for (const perm of permissions) { + if (!shouldAutoAccept(perm)) continue + respond(perm) + } + }) + + return dispose + }) + + watches.set(sessionID, dispose) + } + + function unwatch(sessionID: string) { + const dispose = watches.get(sessionID) + if (!dispose) return + dispose() + watches.delete(sessionID) + } + + createEffect(() => { + if (!ready()) return + + for (const sessionID in store.autoAcceptEdits) { + if (!store.autoAcceptEdits[sessionID]) continue + watch(sessionID) + } + }) + + onCleanup(() => { + for (const dispose of watches.values()) dispose() + watches.clear() + }) + + function enable(sessionID: string) { + setStore("autoAcceptEdits", sessionID, true) + watch(sessionID) + + const permissions = props.permissions[sessionID] ?? [] + for (const perm of permissions) { + if (!shouldAutoAccept(perm)) continue + respond(perm) + } + } + + function disable(sessionID: string) { + setStore("autoAcceptEdits", sessionID, false) + unwatch(sessionID) + } + + return { + get permissions() { + return props.permissions + }, + respond: props.onRespond, + isAutoAccepting(sessionID: string) { + return store.autoAcceptEdits[sessionID] ?? false + }, + toggleAutoAccept(sessionID: string) { + if (store.autoAcceptEdits[sessionID]) { + disable(sessionID) + return + } + + enable(sessionID) + }, + enableAutoAccept(sessionID: string) { + if (store.autoAcceptEdits[sessionID]) return + enable(sessionID) + }, + disableAutoAccept(sessionID: string) { + disable(sessionID) + }, + } + }, +}) diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx new file mode 100644 index 00000000000..b41d6da1e54 --- /dev/null +++ b/packages/app/src/context/platform.tsx @@ -0,0 +1,47 @@ +import { createSimpleContext } from "@opencode-ai/ui/context" +import { AsyncStorage, SyncStorage } from "@solid-primitives/storage" + +export type Platform = { + /** Platform discriminator */ + platform: "web" | "tauri" + + /** App version */ + version?: string + + /** Open a URL in the default browser */ + openLink(url: string): void + + /** Restart the app */ + restart(): Promise + + /** Send a system notification (optional deep link) */ + notify(title: string, description?: string, href?: string): Promise + + /** Open directory picker dialog (native on Tauri, server-backed on web) */ + openDirectoryPickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise + + /** Open native file picker dialog (Tauri only) */ + openFilePickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise + + /** Save file picker dialog (Tauri only) */ + saveFilePickerDialog?(opts?: { title?: string; defaultPath?: string }): Promise + + /** Storage mechanism, defaults to localStorage */ + storage?: (name?: string) => SyncStorage | AsyncStorage + + /** Check for updates (Tauri only) */ + checkUpdate?(): Promise<{ updateAvailable: boolean; version?: string }> + + /** Install updates (Tauri only) */ + update?(): Promise + + /** Fetch override */ + fetch?: typeof fetch +} + +export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({ + name: "Platform", + init: (props: { value: Platform }) => { + return props.value + }, +}) diff --git a/packages/app/src/context/prompt.tsx b/packages/app/src/context/prompt.tsx new file mode 100644 index 00000000000..8d3590cd996 --- /dev/null +++ b/packages/app/src/context/prompt.tsx @@ -0,0 +1,111 @@ +import { createStore } from "solid-js/store" +import { createSimpleContext } from "@opencode-ai/ui/context" +import { batch, createMemo } from "solid-js" +import { useParams } from "@solidjs/router" +import { TextSelection } from "./local" +import { persisted } from "@/utils/persist" + +interface PartBase { + content: string + start: number + end: number +} + +export interface TextPart extends PartBase { + type: "text" +} + +export interface FileAttachmentPart extends PartBase { + type: "file" + path: string + selection?: TextSelection +} + +export interface ImageAttachmentPart { + type: "image" + id: string + filename: string + mime: string + dataUrl: string +} + +export type ContentPart = TextPart | FileAttachmentPart | ImageAttachmentPart +export type Prompt = ContentPart[] + +export const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }] + +export function isPromptEqual(promptA: Prompt, promptB: Prompt): boolean { + if (promptA.length !== promptB.length) return false + for (let i = 0; i < promptA.length; i++) { + const partA = promptA[i] + const partB = promptB[i] + if (partA.type !== partB.type) return false + if (partA.type === "text" && partA.content !== (partB as TextPart).content) { + return false + } + if (partA.type === "file" && partA.path !== (partB as FileAttachmentPart).path) { + return false + } + if (partA.type === "image" && partA.id !== (partB as ImageAttachmentPart).id) { + return false + } + } + return true +} + +function cloneSelection(selection?: TextSelection) { + if (!selection) return undefined + return { ...selection } +} + +function clonePart(part: ContentPart): ContentPart { + if (part.type === "text") return { ...part } + if (part.type === "image") return { ...part } + return { + ...part, + selection: cloneSelection(part.selection), + } +} + +function clonePrompt(prompt: Prompt): Prompt { + return prompt.map(clonePart) +} + +export const { use: usePrompt, provider: PromptProvider } = createSimpleContext({ + name: "Prompt", + init: () => { + const params = useParams() + const name = createMemo(() => `${params.dir}/prompt${params.id ? "/" + params.id : ""}.v1`) + + const [store, setStore, _, ready] = persisted( + name(), + createStore<{ + prompt: Prompt + cursor?: number + }>({ + prompt: clonePrompt(DEFAULT_PROMPT), + cursor: undefined, + }), + ) + + return { + ready, + current: createMemo(() => store.prompt), + cursor: createMemo(() => store.cursor), + dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)), + set(prompt: Prompt, cursorPosition?: number) { + const next = clonePrompt(prompt) + batch(() => { + setStore("prompt", next) + if (cursorPosition !== undefined) setStore("cursor", cursorPosition) + }) + }, + reset() { + batch(() => { + setStore("prompt", clonePrompt(DEFAULT_PROMPT)) + setStore("cursor", 0) + }) + }, + } + }, +}) diff --git a/packages/desktop/src/context/sdk.tsx b/packages/app/src/context/sdk.tsx similarity index 82% rename from packages/desktop/src/context/sdk.tsx rename to packages/app/src/context/sdk.tsx index 764b01f8aa4..4d1c797c9b7 100644 --- a/packages/desktop/src/context/sdk.tsx +++ b/packages/app/src/context/sdk.tsx @@ -1,18 +1,20 @@ import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2/client" import { createSimpleContext } from "@opencode-ai/ui/context" import { createGlobalEmitter } from "@solid-primitives/event-bus" -import { onCleanup } from "solid-js" import { useGlobalSDK } from "./global-sdk" +import { usePlatform } from "./platform" export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ name: "SDK", init: (props: { directory: string }) => { + const platform = usePlatform() const globalSDK = useGlobalSDK() - const abort = new AbortController() const sdk = createOpencodeClient({ baseUrl: globalSDK.url, - signal: abort.signal, + signal: AbortSignal.timeout(1000 * 60 * 10), + fetch: platform.fetch, directory: props.directory, + throwOnError: true, }) const emitter = createGlobalEmitter<{ @@ -23,10 +25,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ emitter.emit(event.type, event) }) - onCleanup(() => { - abort.abort() - }) - return { directory: props.directory, client: sdk, event: emitter, url: globalSDK.url } }, }) diff --git a/packages/app/src/context/server.tsx b/packages/app/src/context/server.tsx new file mode 100644 index 00000000000..7679cb2419c --- /dev/null +++ b/packages/app/src/context/server.tsx @@ -0,0 +1,183 @@ +import { createOpencodeClient } from "@opencode-ai/sdk/v2/client" +import { createSimpleContext } from "@opencode-ai/ui/context" +import { batch, createEffect, createMemo, createResource, createSignal, onCleanup } from "solid-js" +import { createStore } from "solid-js/store" +import { usePlatform } from "@/context/platform" +import { persisted } from "@/utils/persist" + +type StoredProject = { worktree: string; expanded: boolean } + +export function normalizeServerUrl(input: string) { + const trimmed = input.trim() + if (!trimmed) return + const withProtocol = /^https?:\/\//.test(trimmed) ? trimmed : `http://${trimmed}` + const cleaned = withProtocol.replace(/\/+$/, "") + return cleaned.replace(/^(https?:\/\/[^/]+).*/, "$1") +} + +export function serverDisplayName(url: string) { + if (!url) return "" + return url + .replace(/^https?:\/\//, "") + .replace(/\/+$/, "") + .split("/")[0] +} + +function projectsKey(url: string) { + if (!url) return "" + const host = url.replace(/^https?:\/\//, "").split(":")[0] + if (host === "localhost" || host === "127.0.0.1") return "local" + return url +} + +export const { use: useServer, provider: ServerProvider } = createSimpleContext({ + name: "Server", + init: (props: { defaultUrl: string }) => { + const platform = usePlatform() + + const [store, setStore, _, ready] = persisted( + "server.v3", + createStore({ + list: [] as string[], + projects: {} as Record, + }), + ) + + const [active, setActiveRaw] = createSignal("") + + function setActive(input: string) { + const url = normalizeServerUrl(input) + if (!url) return + setActiveRaw(url) + } + + function add(input: string) { + const url = normalizeServerUrl(input) + if (!url) return + + const fallback = normalizeServerUrl(props.defaultUrl) + if (fallback && url === fallback) { + setActiveRaw(url) + return + } + + batch(() => { + if (!store.list.includes(url)) { + setStore("list", store.list.length, url) + } + setActiveRaw(url) + }) + } + + function remove(input: string) { + const url = normalizeServerUrl(input) + if (!url) return + + const list = store.list.filter((x) => x !== url) + const next = active() === url ? (list[0] ?? normalizeServerUrl(props.defaultUrl) ?? "") : active() + + batch(() => { + setStore("list", list) + setActiveRaw(next) + }) + } + + createEffect(() => { + if (!ready()) return + if (active()) return + const url = normalizeServerUrl(props.defaultUrl) + if (!url) return + setActiveRaw(url) + }) + + const isReady = createMemo(() => ready() && !!active()) + + const [healthy, { refetch }] = createResource( + () => active() || undefined, + async (url) => { + if (!url) return + + const sdk = createOpencodeClient({ + baseUrl: url, + fetch: platform.fetch, + signal: AbortSignal.timeout(2000), + }) + return sdk.global + .health() + .then((x) => x.data?.healthy === true) + .catch(() => false) + }, + ) + + createEffect(() => { + if (!active()) return + const interval = setInterval(() => refetch(), 10_000) + onCleanup(() => clearInterval(interval)) + }) + + const origin = createMemo(() => projectsKey(active())) + const projectsList = createMemo(() => store.projects[origin()] ?? []) + + return { + ready: isReady, + healthy, + get url() { + return active() + }, + get name() { + return serverDisplayName(active()) + }, + get list() { + return store.list + }, + setActive, + add, + remove, + projects: { + list: projectsList, + open(directory: string) { + const key = origin() + if (!key) return + const current = store.projects[key] ?? [] + if (current.find((x) => x.worktree === directory)) return + setStore("projects", key, [{ worktree: directory, expanded: true }, ...current]) + }, + close(directory: string) { + const key = origin() + if (!key) return + const current = store.projects[key] ?? [] + setStore( + "projects", + key, + current.filter((x) => x.worktree !== directory), + ) + }, + expand(directory: string) { + const key = origin() + if (!key) return + const current = store.projects[key] ?? [] + const index = current.findIndex((x) => x.worktree === directory) + if (index !== -1) setStore("projects", key, index, "expanded", true) + }, + collapse(directory: string) { + const key = origin() + if (!key) return + const current = store.projects[key] ?? [] + const index = current.findIndex((x) => x.worktree === directory) + if (index !== -1) setStore("projects", key, index, "expanded", false) + }, + move(directory: string, toIndex: number) { + const key = origin() + if (!key) return + const current = store.projects[key] ?? [] + const fromIndex = current.findIndex((x) => x.worktree === directory) + if (fromIndex === -1 || fromIndex === toIndex) return + const result = [...current] + const [item] = result.splice(fromIndex, 1) + result.splice(toIndex, 0, item) + setStore("projects", key, result) + }, + }, + } + }, +}) diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx new file mode 100644 index 00000000000..b45936a9162 --- /dev/null +++ b/packages/app/src/context/sync.tsx @@ -0,0 +1,138 @@ +import { batch, createMemo } from "solid-js" +import { produce, reconcile } from "solid-js/store" +import { Binary } from "@opencode-ai/util/binary" +import { retry } from "@opencode-ai/util/retry" +import { createSimpleContext } from "@opencode-ai/ui/context" +import { useGlobalSync } from "./global-sync" +import { useSDK } from "./sdk" +import type { Message, Part } from "@opencode-ai/sdk/v2/client" + +export const { use: useSync, provider: SyncProvider } = createSimpleContext({ + name: "Sync", + init: () => { + const globalSync = useGlobalSync() + const sdk = useSDK() + const [store, setStore] = globalSync.child(sdk.directory) + const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/") + + return { + data: store, + set: setStore, + get ready() { + return store.ready + }, + get project() { + const match = Binary.search(globalSync.data.project, store.project, (p) => p.id) + if (match.found) return globalSync.data.project[match.index] + return undefined + }, + session: { + get(sessionID: string) { + const match = Binary.search(store.session, sessionID, (s) => s.id) + if (match.found) return store.session[match.index] + return undefined + }, + addOptimisticMessage(input: { + sessionID: string + messageID: string + parts: Part[] + agent: string + model: { providerID: string; modelID: string } + }) { + const message: Message = { + id: input.messageID, + sessionID: input.sessionID, + role: "user", + time: { created: Date.now() }, + agent: input.agent, + model: input.model, + } + setStore( + produce((draft) => { + const messages = draft.message[input.sessionID] + if (!messages) { + draft.message[input.sessionID] = [message] + } else { + const result = Binary.search(messages, input.messageID, (m) => m.id) + messages.splice(result.index, 0, message) + } + draft.part[input.messageID] = input.parts.slice().sort((a, b) => a.id.localeCompare(b.id)) + }), + ) + }, + async sync(sessionID: string, _isRetry = false) { + const [session, messages, todo, diff] = await Promise.all([ + retry(() => sdk.client.session.get({ sessionID })), + retry(() => sdk.client.session.messages({ sessionID, limit: 100 })), + retry(() => sdk.client.session.todo({ sessionID })), + retry(() => sdk.client.session.diff({ sessionID })), + ]) + + batch(() => { + setStore( + "session", + produce((draft) => { + const match = Binary.search(draft, sessionID, (s) => s.id) + if (match.found) { + draft[match.index] = session.data! + return + } + draft.splice(match.index, 0, session.data!) + }), + ) + + setStore("todo", sessionID, reconcile(todo.data ?? [], { key: "id" })) + setStore( + "message", + sessionID, + reconcile( + (messages.data ?? []) + .map((x) => x.info) + .slice() + .sort((a, b) => a.id.localeCompare(b.id)), + { key: "id" }, + ), + ) + + for (const message of messages.data ?? []) { + setStore( + "part", + message.info.id, + reconcile( + message.parts.slice().sort((a, b) => a.id.localeCompare(b.id)), + { key: "id" }, + ), + ) + } + + setStore("session_diff", sessionID, reconcile(diff.data ?? [], { key: "file" })) + }) + }, + fetch: async (count = 10) => { + setStore("limit", (x) => x + count) + await sdk.client.session.list().then((x) => { + const sessions = (x.data ?? []) + .slice() + .sort((a, b) => a.id.localeCompare(b.id)) + .slice(0, store.limit) + setStore("session", reconcile(sessions, { key: "id" })) + }) + }, + more: createMemo(() => store.session.length >= store.limit), + archive: async (sessionID: string) => { + await sdk.client.session.update({ sessionID, time: { archived: Date.now() } }) + setStore( + produce((draft) => { + const match = Binary.search(draft.session, sessionID, (s) => s.id) + if (match.found) draft.session.splice(match.index, 1) + }), + ) + }, + }, + absolute, + get directory() { + return store.path.directory + }, + } + }, +}) diff --git a/packages/app/src/context/terminal.tsx b/packages/app/src/context/terminal.tsx new file mode 100644 index 00000000000..e9a07077cef --- /dev/null +++ b/packages/app/src/context/terminal.tsx @@ -0,0 +1,121 @@ +import { createStore, produce } from "solid-js/store" +import { createSimpleContext } from "@opencode-ai/ui/context" +import { batch, createMemo } from "solid-js" +import { useParams } from "@solidjs/router" +import { useSDK } from "./sdk" +import { persisted } from "@/utils/persist" + +export type LocalPTY = { + id: string + title: string + rows?: number + cols?: number + buffer?: string + scrollY?: number +} + +export const { use: useTerminal, provider: TerminalProvider } = createSimpleContext({ + name: "Terminal", + init: () => { + const sdk = useSDK() + const params = useParams() + const name = createMemo(() => `${params.dir}/terminal${params.id ? "/" + params.id : ""}.v1`) + + const [store, setStore, _, ready] = persisted( + name(), + createStore<{ + active?: string + all: LocalPTY[] + }>({ + all: [], + }), + ) + + return { + ready, + all: createMemo(() => Object.values(store.all)), + active: createMemo(() => store.active), + new() { + sdk.client.pty + .create({ title: `Terminal ${store.all.length + 1}` }) + .then((pty) => { + const id = pty.data?.id + if (!id) return + setStore("all", [ + ...store.all, + { + id, + title: pty.data?.title ?? "Terminal", + }, + ]) + setStore("active", id) + }) + .catch((e) => { + console.error("Failed to create terminal", e) + }) + }, + update(pty: Partial & { id: string }) { + setStore("all", (x) => x.map((x) => (x.id === pty.id ? { ...x, ...pty } : x))) + sdk.client.pty + .update({ + ptyID: pty.id, + title: pty.title, + size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined, + }) + .catch((e) => { + console.error("Failed to update terminal", e) + }) + }, + async clone(id: string) { + const index = store.all.findIndex((x) => x.id === id) + const pty = store.all[index] + if (!pty) return + const clone = await sdk.client.pty + .create({ + title: pty.title, + }) + .catch((e) => { + console.error("Failed to clone terminal", e) + return undefined + }) + if (!clone?.data) return + setStore("all", index, { + ...pty, + ...clone.data, + }) + if (store.active === pty.id) { + setStore("active", clone.data.id) + } + }, + open(id: string) { + setStore("active", id) + }, + async close(id: string) { + batch(() => { + setStore( + "all", + store.all.filter((x) => x.id !== id), + ) + if (store.active === id) { + const index = store.all.findIndex((f) => f.id === id) + const previous = store.all[Math.max(0, index - 1)] + setStore("active", previous?.id) + } + }) + await sdk.client.pty.remove({ ptyID: id }).catch((e) => { + console.error("Failed to close terminal", e) + }) + }, + move(id: string, to: number) { + const index = store.all.findIndex((f) => f.id === id) + if (index === -1) return + setStore( + "all", + produce((all) => { + all.splice(to, 0, all.splice(index, 1)[0]) + }), + ) + }, + } + }, +}) diff --git a/packages/desktop/src/custom-elements.d.ts b/packages/app/src/custom-elements.d.ts similarity index 100% rename from packages/desktop/src/custom-elements.d.ts rename to packages/app/src/custom-elements.d.ts diff --git a/packages/app/src/entry.tsx b/packages/app/src/entry.tsx new file mode 100644 index 00000000000..a915aa25b89 --- /dev/null +++ b/packages/app/src/entry.tsx @@ -0,0 +1,62 @@ +// @refresh reload +import { render } from "solid-js/web" +import { App } from "@/app" +import { Platform, PlatformProvider } from "@/context/platform" +import pkg from "../package.json" + +const root = document.getElementById("root") +if (import.meta.env.DEV && !(root instanceof HTMLElement)) { + throw new Error( + "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?", + ) +} + +const platform: Platform = { + platform: "web", + version: pkg.version, + openLink(url: string) { + window.open(url, "_blank") + }, + restart: async () => { + window.location.reload() + }, + notify: async (title, description, href) => { + if (!("Notification" in window)) return + + const permission = + Notification.permission === "default" + ? await Notification.requestPermission().catch(() => "denied") + : Notification.permission + + if (permission !== "granted") return + + const inView = document.visibilityState === "visible" && document.hasFocus() + if (inView) return + + await Promise.resolve() + .then(() => { + const notification = new Notification(title, { + body: description ?? "", + icon: "https://opencode.ai/favicon-96x96.png", + }) + notification.onclick = () => { + window.focus() + if (href) { + window.history.pushState(null, "", href) + window.dispatchEvent(new PopStateEvent("popstate")) + } + notification.close() + } + }) + .catch(() => undefined) + }, +} + +render( + () => ( + + + + ), + root!, +) diff --git a/packages/desktop/src/env.d.ts b/packages/app/src/env.d.ts similarity index 100% rename from packages/desktop/src/env.d.ts rename to packages/app/src/env.d.ts diff --git a/packages/app/src/hooks/use-providers.ts b/packages/app/src/hooks/use-providers.ts new file mode 100644 index 00000000000..4a73fa05588 --- /dev/null +++ b/packages/app/src/hooks/use-providers.ts @@ -0,0 +1,31 @@ +import { useGlobalSync } from "@/context/global-sync" +import { base64Decode } from "@opencode-ai/util/encode" +import { useParams } from "@solidjs/router" +import { createMemo } from "solid-js" + +export const popularProviders = ["opencode", "anthropic", "github-copilot", "openai", "google", "openrouter", "vercel"] + +export function useProviders() { + const globalSync = useGlobalSync() + const params = useParams() + const currentDirectory = createMemo(() => base64Decode(params.dir ?? "")) + const providers = createMemo(() => { + if (currentDirectory()) { + const [projectStore] = globalSync.child(currentDirectory()) + return projectStore.provider + } + return globalSync.data.provider + }) + const connected = createMemo(() => providers().all.filter((p) => providers().connected.includes(p.id))) + const paid = createMemo(() => + connected().filter((p) => p.id !== "opencode" || Object.values(p.models).find((m) => m.cost?.input)), + ) + const popular = createMemo(() => providers().all.filter((p) => popularProviders.includes(p.id))) + return { + all: createMemo(() => providers().all), + default: createMemo(() => providers().default), + popular, + connected, + paid, + } +} diff --git a/packages/desktop/src/index.css b/packages/app/src/index.css similarity index 100% rename from packages/desktop/src/index.css rename to packages/app/src/index.css diff --git a/packages/app/src/index.ts b/packages/app/src/index.ts new file mode 100644 index 00000000000..cf5be9f512f --- /dev/null +++ b/packages/app/src/index.ts @@ -0,0 +1,2 @@ +export { PlatformProvider, type Platform } from "./context/platform" +export { App } from "./app" diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx new file mode 100644 index 00000000000..473dcd8e10d --- /dev/null +++ b/packages/app/src/pages/directory-layout.tsx @@ -0,0 +1,41 @@ +import { createMemo, Show, type ParentProps } from "solid-js" +import { useParams } from "@solidjs/router" +import { SDKProvider, useSDK } from "@/context/sdk" +import { SyncProvider, useSync } from "@/context/sync" +import { LocalProvider } from "@/context/local" +import { PermissionProvider } from "@/context/permission" +import { base64Decode } from "@opencode-ai/util/encode" +import { DataProvider } from "@opencode-ai/ui/context" +import { iife } from "@opencode-ai/util/iife" + +export default function Layout(props: ParentProps) { + const params = useParams() + const directory = createMemo(() => { + return base64Decode(params.dir!) + }) + return ( + + + + {iife(() => { + const sync = useSync() + const sdk = useSDK() + const respond = (input: { + sessionID: string + permissionID: string + response: "once" | "always" | "reject" + }) => sdk.client.permission.respond(input) + + return ( + + + {props.children} + + + ) + })} + + + + ) +} diff --git a/packages/app/src/pages/error.tsx b/packages/app/src/pages/error.tsx new file mode 100644 index 00000000000..61d2df4f19c --- /dev/null +++ b/packages/app/src/pages/error.tsx @@ -0,0 +1,223 @@ +import { TextField } from "@opencode-ai/ui/text-field" +import { Logo } from "@opencode-ai/ui/logo" +import { Button } from "@opencode-ai/ui/button" +import { Component, Show } from "solid-js" +import { usePlatform } from "@/context/platform" +import { Icon } from "@opencode-ai/ui/icon" + +export type InitError = { + name: string + data: Record +} + +function isInitError(error: unknown): error is InitError { + return ( + typeof error === "object" && + error !== null && + "name" in error && + "data" in error && + typeof (error as InitError).data === "object" + ) +} + +function safeJson(value: unknown): string { + const seen = new WeakSet() + const json = JSON.stringify( + value, + (_key, val) => { + if (typeof val === "bigint") return val.toString() + if (typeof val === "object" && val) { + if (seen.has(val)) return "[Circular]" + seen.add(val) + } + return val + }, + 2, + ) + return json ?? String(value) +} + +function formatInitError(error: InitError): string { + const data = error.data + switch (error.name) { + case "MCPFailed": + return `MCP server "${data.name}" failed. Note, opencode does not support MCP authentication yet.` + case "ProviderAuthError": { + const providerID = typeof data.providerID === "string" ? data.providerID : "unknown" + const message = typeof data.message === "string" ? data.message : safeJson(data.message) + return `Provider authentication failed (${providerID}): ${message}` + } + case "APIError": { + const message = typeof data.message === "string" ? data.message : "API error" + const lines: string[] = [message] + + if (typeof data.statusCode === "number") { + lines.push(`Status: ${data.statusCode}`) + } + + if (typeof data.isRetryable === "boolean") { + lines.push(`Retryable: ${data.isRetryable}`) + } + + if (typeof data.responseBody === "string" && data.responseBody) { + lines.push(`Response body:\n${data.responseBody}`) + } + + return lines.join("\n") + } + case "ProviderModelNotFoundError": { + const { providerID, modelID, suggestions } = data as { + providerID: string + modelID: string + suggestions?: string[] + } + return [ + `Model not found: ${providerID}/${modelID}`, + ...(Array.isArray(suggestions) && suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []), + `Check your config (opencode.json) provider/model names`, + ].join("\n") + } + case "ProviderInitError": { + const providerID = typeof data.providerID === "string" ? data.providerID : "unknown" + return `Failed to initialize provider "${providerID}". Check credentials and configuration.` + } + case "ConfigJsonError": { + const message = typeof data.message === "string" ? data.message : "" + return `Config file at ${data.path} is not valid JSON(C)` + (message ? `: ${message}` : "") + } + case "ConfigDirectoryTypoError": + return `Directory "${data.dir}" in ${data.path} is not valid. Rename the directory to "${data.suggestion}" or remove it. This is a common typo.` + case "ConfigFrontmatterError": + return `Failed to parse frontmatter in ${data.path}:\n${data.message}` + case "ConfigInvalidError": { + const issues = Array.isArray(data.issues) + ? data.issues.map( + (issue: { message: string; path: string[] }) => "↳ " + issue.message + " " + issue.path.join("."), + ) + : [] + const message = typeof data.message === "string" ? data.message : "" + return [`Config file at ${data.path} is invalid` + (message ? `: ${message}` : ""), ...issues].join("\n") + } + case "UnknownError": + return typeof data.message === "string" ? data.message : safeJson(data) + default: + if (typeof data.message === "string") return data.message + return safeJson(data) + } +} + +function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): string { + if (!error) return "Unknown error" + + if (isInitError(error)) { + const message = formatInitError(error) + if (depth > 0 && parentMessage === message) return "" + const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : "" + return indent + `${error.name}\n${message}` + } + + if (error instanceof Error) { + const isDuplicate = depth > 0 && parentMessage === error.message + const parts: string[] = [] + const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : "" + + const header = `${error.name}${error.message ? `: ${error.message}` : ""}` + const stack = error.stack?.trim() + + if (stack) { + const startsWithHeader = stack.startsWith(header) + + if (isDuplicate && startsWithHeader) { + const trace = stack.split("\n").slice(1).join("\n").trim() + if (trace) { + parts.push(indent + trace) + } + } + + if (isDuplicate && !startsWithHeader) { + parts.push(indent + stack) + } + + if (!isDuplicate && startsWithHeader) { + parts.push(indent + stack) + } + + if (!isDuplicate && !startsWithHeader) { + parts.push(indent + `${header}\n${stack}`) + } + } + + if (!stack && !isDuplicate) { + parts.push(indent + header) + } + + if (error.cause) { + const causeResult = formatErrorChain(error.cause, depth + 1, error.message) + if (causeResult) { + parts.push(causeResult) + } + } + + return parts.join("\n\n") + } + + if (typeof error === "string") { + if (depth > 0 && parentMessage === error) return "" + const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : "" + return indent + error + } + + const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : "" + return indent + safeJson(error) +} + +function formatError(error: unknown): string { + return formatErrorChain(error, 0) +} + +interface ErrorPageProps { + error: unknown +} + +export const ErrorPage: Component = (props) => { + const platform = usePlatform() + return ( +
+
+ +
+

Something went wrong

+

An error occurred while loading the application.

+
+ + +
+
+ Please report this error to the OpenCode team + +
+ +

Version: {platform.version}

+
+
+
+
+ ) +} diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx new file mode 100644 index 00000000000..6ff63f809fe --- /dev/null +++ b/packages/app/src/pages/home.tsx @@ -0,0 +1,122 @@ +import { useGlobalSync } from "@/context/global-sync" +import { createMemo, For, Match, Show, Switch } from "solid-js" +import { Button } from "@opencode-ai/ui/button" +import { Logo } from "@opencode-ai/ui/logo" +import { useLayout } from "@/context/layout" +import { useNavigate } from "@solidjs/router" +import { base64Encode } from "@opencode-ai/util/encode" +import { Icon } from "@opencode-ai/ui/icon" +import { usePlatform } from "@/context/platform" +import { DateTime } from "luxon" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { DialogSelectDirectory } from "@/components/dialog-select-directory" +import { DialogSelectServer } from "@/components/dialog-select-server" +import { useServer } from "@/context/server" + +export default function Home() { + const sync = useGlobalSync() + const layout = useLayout() + const platform = usePlatform() + const dialog = useDialog() + const navigate = useNavigate() + const server = useServer() + const homedir = createMemo(() => sync.data.path.home) + + function openProject(directory: string) { + layout.projects.open(directory) + navigate(`/${base64Encode(directory)}`) + } + + async function chooseProject() { + function resolve(result: string | string[] | null) { + if (Array.isArray(result)) { + for (const directory of result) { + openProject(directory) + } + } else if (result) { + openProject(result) + } + } + + if (platform.openDirectoryPickerDialog) { + const result = await platform.openDirectoryPickerDialog?.({ + title: "Open project", + multiple: true, + }) + resolve(result) + } else { + dialog.show( + () => , + () => resolve(null), + ) + } + } + + return ( +
+ + + + 0}> +
+
+
Recent projects
+ +
+
    + (b.time.updated ?? b.time.created) - (a.time.updated ?? a.time.created)) + .slice(0, 5)} + > + {(project) => ( + + )} + +
+
+
+ +
+ +
+
No recent projects
+
Get started by opening a local project
+
+
+ +
+ + +
+ ) +} diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx new file mode 100644 index 00000000000..f9322201a90 --- /dev/null +++ b/packages/app/src/pages/layout.tsx @@ -0,0 +1,1075 @@ +import { + createEffect, + createMemo, + createSignal, + For, + Match, + onCleanup, + onMount, + ParentProps, + Show, + Switch, + untrack, + type JSX, +} from "solid-js" +import { DateTime } from "luxon" +import { A, useNavigate, useParams } from "@solidjs/router" +import { useLayout, getAvatarColors, LocalProject } from "@/context/layout" +import { useGlobalSync } from "@/context/global-sync" +import { base64Decode, base64Encode } from "@opencode-ai/util/encode" +import { Avatar } from "@opencode-ai/ui/avatar" +import { ResizeHandle } from "@opencode-ai/ui/resize-handle" +import { Button } from "@opencode-ai/ui/button" +import { Icon } from "@opencode-ai/ui/icon" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { Tooltip } from "@opencode-ai/ui/tooltip" +import { Collapsible } from "@opencode-ai/ui/collapsible" +import { DiffChanges } from "@opencode-ai/ui/diff-changes" +import { Spinner } from "@opencode-ai/ui/spinner" +import { getFilename } from "@opencode-ai/util/path" +import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" +import { Session } from "@opencode-ai/sdk/v2/client" +import { usePlatform } from "@/context/platform" +import { createStore, produce } from "solid-js/store" +import { + DragDropProvider, + DragDropSensors, + DragOverlay, + SortableProvider, + closestCenter, + createSortable, +} from "@thisbeyond/solid-dnd" +import type { DragEvent } from "@thisbeyond/solid-dnd" +import { useProviders } from "@/hooks/use-providers" +import { showToast, Toast, toaster } from "@opencode-ai/ui/toast" +import { useGlobalSDK } from "@/context/global-sdk" +import { useNotification } from "@/context/notification" +import { Binary } from "@opencode-ai/util/binary" +import { Header } from "@/components/header" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme" +import { DialogSelectProvider } from "@/components/dialog-select-provider" +import { DialogEditProject } from "@/components/dialog-edit-project" +import { DialogSelectServer } from "@/components/dialog-select-server" +import { useCommand, type CommandOption } from "@/context/command" +import { ConstrainDragXAxis } from "@/utils/solid-dnd" +import { DialogSelectDirectory } from "@/components/dialog-select-directory" + +export default function Layout(props: ParentProps) { + const [store, setStore] = createStore({ + lastSession: {} as { [directory: string]: string }, + activeDraggable: undefined as string | undefined, + mobileSidebarOpen: false, + mobileProjectsExpanded: {} as Record, + }) + + const mobileSidebar = { + open: () => store.mobileSidebarOpen, + show: () => setStore("mobileSidebarOpen", true), + hide: () => setStore("mobileSidebarOpen", false), + toggle: () => setStore("mobileSidebarOpen", (x) => !x), + } + + const mobileProjects = { + expanded: (directory: string) => store.mobileProjectsExpanded[directory] ?? true, + expand: (directory: string) => setStore("mobileProjectsExpanded", directory, true), + collapse: (directory: string) => setStore("mobileProjectsExpanded", directory, false), + } + + let scrollContainerRef: HTMLDivElement | undefined + const xlQuery = window.matchMedia("(min-width: 1280px)") + const [isLargeViewport, setIsLargeViewport] = createSignal(xlQuery.matches) + const handleViewportChange = (e: MediaQueryListEvent) => setIsLargeViewport(e.matches) + xlQuery.addEventListener("change", handleViewportChange) + onCleanup(() => xlQuery.removeEventListener("change", handleViewportChange)) + + const params = useParams() + const globalSDK = useGlobalSDK() + const globalSync = useGlobalSync() + const layout = useLayout() + const platform = usePlatform() + const notification = useNotification() + const navigate = useNavigate() + const providers = useProviders() + const dialog = useDialog() + const command = useCommand() + const theme = useTheme() + const availableThemeEntries = createMemo(() => Object.entries(theme.themes())) + const colorSchemeOrder: ColorScheme[] = ["system", "light", "dark"] + const colorSchemeLabel: Record = { + system: "System", + light: "Light", + dark: "Dark", + } + + function cycleTheme(direction = 1) { + const ids = availableThemeEntries().map(([id]) => id) + if (ids.length === 0) return + const currentIndex = ids.indexOf(theme.themeId()) + const nextIndex = currentIndex === -1 ? 0 : (currentIndex + direction + ids.length) % ids.length + const nextThemeId = ids[nextIndex] + theme.setTheme(nextThemeId) + const nextTheme = theme.themes()[nextThemeId] + showToast({ + title: "Theme switched", + description: nextTheme?.name ?? nextThemeId, + }) + } + + function cycleColorScheme(direction = 1) { + const current = theme.colorScheme() + const currentIndex = colorSchemeOrder.indexOf(current) + const nextIndex = + currentIndex === -1 ? 0 : (currentIndex + direction + colorSchemeOrder.length) % colorSchemeOrder.length + const next = colorSchemeOrder[nextIndex] + theme.setColorScheme(next) + showToast({ + title: "Color scheme", + description: colorSchemeLabel[next], + }) + } + + onMount(async () => { + if (platform.checkUpdate && platform.update && platform.restart) { + const { updateAvailable, version } = await platform.checkUpdate() + if (updateAvailable) { + showToast({ + persistent: true, + icon: "download", + title: "Update available", + description: `A new version of OpenCode (${version}) is now available to install.`, + actions: [ + { + label: "Install and restart", + onClick: async () => { + await platform.update!() + await platform.restart!() + }, + }, + { + label: "Not yet", + onClick: "dismiss", + }, + ], + }) + } + } + }) + + onMount(() => { + const seenSessions = new Set() + const toastBySession = new Map() + const unsub = globalSDK.event.listen((e) => { + if (e.details?.type !== "permission.updated") return + const directory = e.name + const permission = e.details.properties + const currentDir = params.dir ? base64Decode(params.dir) : undefined + const currentSession = params.id + const [store] = globalSync.child(directory) + const session = store.session.find((s) => s.id === permission.sessionID) + const sessionTitle = session?.title ?? "New session" + const projectName = getFilename(directory) + const description = `${sessionTitle} in ${projectName} needs permission` + const href = `/${base64Encode(directory)}/session/${permission.sessionID}` + void platform.notify("Permission required", description, href) + + if (directory === currentDir && permission.sessionID === currentSession) return + if (directory === currentDir && session?.parentID === currentSession) return + + const sessionKey = `${directory}:${permission.sessionID}` + if (seenSessions.has(sessionKey)) return + seenSessions.add(sessionKey) + + const toastId = showToast({ + persistent: true, + icon: "checklist", + title: "Permission required", + description, + actions: [ + { + label: "Go to session", + onClick: () => { + navigate(href) + }, + }, + { + label: "Dismiss", + onClick: "dismiss", + }, + ], + }) + toastBySession.set(sessionKey, toastId) + }) + onCleanup(unsub) + + createEffect(() => { + const currentDir = params.dir ? base64Decode(params.dir) : undefined + const currentSession = params.id + if (!currentDir || !currentSession) return + const sessionKey = `${currentDir}:${currentSession}` + const toastId = toastBySession.get(sessionKey) + if (toastId !== undefined) { + toaster.dismiss(toastId) + toastBySession.delete(sessionKey) + seenSessions.delete(sessionKey) + } + const [store] = globalSync.child(currentDir) + const childSessions = store.session.filter((s) => s.parentID === currentSession) + for (const child of childSessions) { + const childKey = `${currentDir}:${child.id}` + const childToastId = toastBySession.get(childKey) + if (childToastId !== undefined) { + toaster.dismiss(childToastId) + toastBySession.delete(childKey) + seenSessions.delete(childKey) + } + } + }) + }) + + function sortSessions(a: Session, b: Session) { + const now = Date.now() + const oneMinuteAgo = now - 60 * 1000 + const aUpdated = a.time.updated ?? a.time.created + const bUpdated = b.time.updated ?? b.time.created + const aRecent = aUpdated > oneMinuteAgo + const bRecent = bUpdated > oneMinuteAgo + if (aRecent && bRecent) return a.id.localeCompare(b.id) + if (aRecent && !bRecent) return -1 + if (!aRecent && bRecent) return 1 + return bUpdated - aUpdated + } + + function scrollToSession(sessionId: string) { + if (!scrollContainerRef) return + const element = scrollContainerRef.querySelector(`[data-session-id="${sessionId}"]`) + if (element) { + element.scrollIntoView({ block: "nearest", behavior: "smooth" }) + } + } + + function projectSessions(directory: string) { + if (!directory) return [] + const sessions = globalSync.child(directory)[0].session.toSorted(sortSessions) + return (sessions ?? []).filter((s) => !s.parentID) + } + + const currentSessions = createMemo(() => { + if (!params.dir) return [] + const directory = base64Decode(params.dir) + return projectSessions(directory) + }) + + function navigateSessionByOffset(offset: number) { + const projects = layout.projects.list() + if (projects.length === 0) return + + const currentDirectory = params.dir ? base64Decode(params.dir) : undefined + const projectIndex = currentDirectory ? projects.findIndex((p) => p.worktree === currentDirectory) : -1 + + if (projectIndex === -1) { + const targetProject = offset > 0 ? projects[0] : projects[projects.length - 1] + if (targetProject) navigateToProject(targetProject.worktree) + return + } + + const sessions = currentSessions() + const sessionIndex = params.id ? sessions.findIndex((s) => s.id === params.id) : -1 + + let targetIndex: number + if (sessionIndex === -1) { + targetIndex = offset > 0 ? 0 : sessions.length - 1 + } else { + targetIndex = sessionIndex + offset + } + + if (targetIndex >= 0 && targetIndex < sessions.length) { + const session = sessions[targetIndex] + navigateToSession(session) + queueMicrotask(() => scrollToSession(session.id)) + return + } + + const nextProjectIndex = projectIndex + (offset > 0 ? 1 : -1) + const nextProject = projects[nextProjectIndex] + if (!nextProject) return + + const nextProjectSessions = projectSessions(nextProject.worktree) + if (nextProjectSessions.length === 0) { + navigateToProject(nextProject.worktree) + return + } + + const targetSession = offset > 0 ? nextProjectSessions[0] : nextProjectSessions[nextProjectSessions.length - 1] + navigate(`/${base64Encode(nextProject.worktree)}/session/${targetSession.id}`) + queueMicrotask(() => scrollToSession(targetSession.id)) + } + + async function archiveSession(session: Session) { + const [store, setStore] = globalSync.child(session.directory) + const sessions = store.session ?? [] + const index = sessions.findIndex((s) => s.id === session.id) + const nextSession = sessions[index + 1] ?? sessions[index - 1] + + await globalSDK.client.session.update({ + directory: session.directory, + sessionID: session.id, + time: { archived: Date.now() }, + }) + setStore( + produce((draft) => { + const match = Binary.search(draft.session, session.id, (s) => s.id) + if (match.found) draft.session.splice(match.index, 1) + }), + ) + if (session.id === params.id) { + if (nextSession) { + navigate(`/${params.dir}/session/${nextSession.id}`) + } else { + navigate(`/${params.dir}/session`) + } + } + } + + command.register(() => { + const commands: CommandOption[] = [ + { + id: "sidebar.toggle", + title: "Toggle sidebar", + category: "View", + keybind: "mod+b", + onSelect: () => layout.sidebar.toggle(), + }, + { + id: "project.open", + title: "Open project", + category: "Project", + keybind: "mod+o", + onSelect: () => chooseProject(), + }, + { + id: "provider.connect", + title: "Connect provider", + category: "Provider", + onSelect: () => connectProvider(), + }, + { + id: "server.switch", + title: "Switch server", + category: "Server", + onSelect: () => openServer(), + }, + { + id: "session.previous", + title: "Previous session", + category: "Session", + keybind: "alt+arrowup", + onSelect: () => navigateSessionByOffset(-1), + }, + { + id: "session.next", + title: "Next session", + category: "Session", + keybind: "alt+arrowdown", + onSelect: () => navigateSessionByOffset(1), + }, + { + id: "session.archive", + title: "Archive session", + category: "Session", + keybind: "mod+shift+backspace", + disabled: !params.dir || !params.id, + onSelect: () => { + const session = currentSessions().find((s) => s.id === params.id) + if (session) archiveSession(session) + }, + }, + { + id: "theme.cycle", + title: "Cycle theme", + category: "Theme", + keybind: "mod+shift+t", + onSelect: () => cycleTheme(1), + }, + ] + + for (const [id, definition] of availableThemeEntries()) { + commands.push({ + id: `theme.set.${id}`, + title: `Use theme: ${definition.name ?? id}`, + category: "Theme", + onSelect: () => theme.commitPreview(), + onHighlight: () => { + theme.previewTheme(id) + return () => theme.cancelPreview() + }, + }) + } + + commands.push({ + id: "theme.scheme.cycle", + title: "Cycle color scheme", + category: "Theme", + keybind: "mod+shift+s", + onSelect: () => cycleColorScheme(1), + }) + + for (const scheme of colorSchemeOrder) { + commands.push({ + id: `theme.scheme.${scheme}`, + title: `Use color scheme: ${colorSchemeLabel[scheme]}`, + category: "Theme", + onSelect: () => theme.commitPreview(), + onHighlight: () => { + theme.previewColorScheme(scheme) + return () => theme.cancelPreview() + }, + }) + } + + return commands + }) + + function connectProvider() { + dialog.show(() => ) + } + + function openServer() { + dialog.show(() => ) + } + + function navigateToProject(directory: string | undefined) { + if (!directory) return + const lastSession = store.lastSession[directory] + navigate(`/${base64Encode(directory)}${lastSession ? `/session/${lastSession}` : ""}`) + mobileSidebar.hide() + } + + function navigateToSession(session: Session | undefined) { + if (!session) return + navigate(`/${params.dir}/session/${session?.id}`) + mobileSidebar.hide() + } + + function openProject(directory: string, navigate = true) { + layout.projects.open(directory) + if (navigate) navigateToProject(directory) + } + + function closeProject(directory: string) { + const index = layout.projects.list().findIndex((x) => x.worktree === directory) + const next = layout.projects.list()[index + 1] + layout.projects.close(directory) + if (next) navigateToProject(next.worktree) + else navigate("/") + } + + async function chooseProject() { + function resolve(result: string | string[] | null) { + if (Array.isArray(result)) { + for (const directory of result) { + openProject(directory, false) + } + navigateToProject(result[0]) + } else if (result) { + openProject(result) + } + } + + if (platform.openDirectoryPickerDialog) { + const result = await platform.openDirectoryPickerDialog?.({ + title: "Open project", + multiple: true, + }) + resolve(result) + } else { + dialog.show( + () => , + () => resolve(null), + ) + } + } + + createEffect(() => { + if (!params.dir || !params.id) return + const directory = base64Decode(params.dir) + const id = params.id + setStore("lastSession", directory, id) + notification.session.markViewed(id) + untrack(() => layout.projects.expand(directory)) + requestAnimationFrame(() => scrollToSession(id)) + }) + + createEffect(() => { + if (isLargeViewport()) { + const sidebarWidth = layout.sidebar.opened() ? layout.sidebar.width() : 48 + document.documentElement.style.setProperty("--dialog-left-margin", `${sidebarWidth}px`) + } else { + document.documentElement.style.setProperty("--dialog-left-margin", "0px") + } + }) + + function getDraggableId(event: unknown): string | undefined { + if (typeof event !== "object" || event === null) return undefined + if (!("draggable" in event)) return undefined + const draggable = (event as { draggable?: { id?: unknown } }).draggable + if (!draggable) return undefined + return typeof draggable.id === "string" ? draggable.id : undefined + } + + function handleDragStart(event: unknown) { + const id = getDraggableId(event) + if (!id) return + setStore("activeDraggable", id) + } + + function handleDragOver(event: DragEvent) { + const { draggable, droppable } = event + if (draggable && droppable) { + const projects = layout.projects.list() + const fromIndex = projects.findIndex((p) => p.worktree === draggable.id.toString()) + const toIndex = projects.findIndex((p) => p.worktree === droppable.id.toString()) + if (fromIndex !== toIndex && toIndex !== -1) { + layout.projects.move(draggable.id.toString(), toIndex) + } + } + } + + function handleDragEnd() { + setStore("activeDraggable", undefined) + } + + const ProjectAvatar = (props: { + project: LocalProject + class?: string + expandable?: boolean + notify?: boolean + }): JSX.Element => { + const notification = useNotification() + const notifications = createMemo(() => notification.project.unseen(props.project.worktree)) + const hasError = createMemo(() => notifications().some((n) => n.type === "error")) + const name = createMemo(() => props.project.name || getFilename(props.project.worktree)) + const mask = "radial-gradient(circle 5px at calc(100% - 2px) 2px, transparent 5px, black 5.5px)" + const opencode = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750" + + return ( +
+ 0 && props.notify ? { "-webkit-mask-image": mask, "mask-image": mask } : undefined + } + /> + + + 0 && props.notify}> +
+ +
+ ) + } + + const ProjectVisual = (props: { project: LocalProject; class?: string }): JSX.Element => { + const name = createMemo(() => props.project.name || getFilename(props.project.worktree)) + const current = createMemo(() => base64Decode(params.dir ?? "")) + return ( + + + + + + + + + ) + } + + const SessionItem = (props: { + session: Session + slug: string + project: LocalProject + mobile?: boolean + }): JSX.Element => { + const notification = useNotification() + const updated = createMemo(() => DateTime.fromMillis(props.session.time.updated)) + const notifications = createMemo(() => notification.session.unseen(props.session.id)) + const hasError = createMemo(() => notifications().some((n) => n.type === "error")) + const hasPermissions = createMemo(() => { + const store = globalSync.child(props.project.worktree)[0] + const permissions = store.permission?.[props.session.id] ?? [] + if (permissions.length > 0) return true + const childSessions = store.session.filter((s) => s.parentID === props.session.id) + for (const child of childSessions) { + const childPermissions = store.permission?.[child.id] ?? [] + if (childPermissions.length > 0) return true + } + return false + }) + const isWorking = createMemo(() => { + if (props.session.id === params.id) return false + if (hasPermissions()) return false + const status = globalSync.child(props.project.worktree)[0].session_status[props.session.id] + return status?.type === "busy" || status?.type === "retry" + }) + return ( + <> +
+ + +
+ + {props.session.title} + + + + ) + } + + const SortableProject = (props: { project: LocalProject; mobile?: boolean }): JSX.Element => { + const sortable = createSortable(props.project.worktree) + const showExpanded = createMemo(() => props.mobile || layout.sidebar.opened()) + const slug = createMemo(() => base64Encode(props.project.worktree)) + const name = createMemo(() => props.project.name || getFilename(props.project.worktree)) + const [store, setProjectStore] = globalSync.child(props.project.worktree) + const sessions = createMemo(() => store.session.toSorted(sortSessions)) + const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID)) + const hasMoreSessions = createMemo(() => store.session.length >= store.limit) + const loadMoreSessions = async () => { + setProjectStore("limit", (limit) => limit + 5) + await globalSync.project.loadSessions(props.project.worktree) + } + const isExpanded = createMemo(() => + props.mobile ? mobileProjects.expanded(props.project.worktree) : props.project.expanded, + ) + const handleOpenChange = (open: boolean) => { + if (props.mobile) { + if (open) mobileProjects.expand(props.project.worktree) + else mobileProjects.collapse(props.project.worktree) + } else { + if (open) layout.projects.expand(props.project.worktree) + else layout.projects.collapse(props.project.worktree) + } + } + return ( + // @ts-ignore +
+ + + +
+ + + + + + + + + + + + +
+ ) + } + + const ProjectDragOverlay = (): JSX.Element => { + const project = createMemo(() => layout.projects.list().find((p) => p.worktree === store.activeDraggable)) + return ( + + {(p) => ( +
+ +
+ )} +
+ ) + } + + const SidebarContent = (sidebarProps: { mobile?: boolean }) => { + const expanded = () => sidebarProps.mobile || layout.sidebar.opened() + return ( + <> +
+ + + Toggle sidebar + {command.keybind("sidebar.toggle")} +
+ } + inactive={expanded()} + > + +
+ + + + +
{ + if (!sidebarProps.mobile) scrollContainerRef = el + }} + class="w-full min-w-8 flex flex-col gap-2 min-h-0 overflow-y-auto no-scrollbar" + > + p.worktree)}> + + {(project) => } + + +
+ + + +
+
+
+ + 0 && !providers.paid().length && expanded()}> +
+
+
Getting started
+
OpenCode includes free models so you can start immediately.
+
Connect any provider to use models, inc. Claude, GPT, Gemini etc.
+
+ + + +
+
+ 0}> + + + + +
+ + Open project + + {command.keybind("project.open")} + +
+ } + inactive={expanded()} + > + + + + + +
+ + ) + } + + return ( +
+
+
+
+ + + + +
+
+
{ + if (e.target === e.currentTarget) mobileSidebar.hide() + }} + /> +
e.stopPropagation()} + > + +
+
+ +
{props.children}
+
+ +
+ ) +} diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx new file mode 100644 index 00000000000..6b9ff9e0811 --- /dev/null +++ b/packages/app/src/pages/session.tsx @@ -0,0 +1,1008 @@ +import { + For, + onCleanup, + onMount, + Show, + Match, + Switch, + createResource, + createMemo, + createEffect, + on, + createRenderEffect, + batch, +} from "solid-js" + +import { Dynamic } from "solid-js/web" +import { useLocal, type LocalFile } from "@/context/local" +import { createStore } from "solid-js/store" +import { PromptInput } from "@/components/prompt-input" +import { DateTime } from "luxon" +import { FileIcon } from "@opencode-ai/ui/file-icon" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { Icon } from "@opencode-ai/ui/icon" +import { Tooltip } from "@opencode-ai/ui/tooltip" +import { DiffChanges } from "@opencode-ai/ui/diff-changes" +import { ResizeHandle } from "@opencode-ai/ui/resize-handle" +import { Tabs } from "@opencode-ai/ui/tabs" +import { useCodeComponent } from "@opencode-ai/ui/context/code" +import { SessionTurn } from "@opencode-ai/ui/session-turn" +import { createAutoScroll } from "@opencode-ai/ui/hooks" +import { SessionMessageRail } from "@opencode-ai/ui/session-message-rail" +import { SessionReview } from "@opencode-ai/ui/session-review" +import { + DragDropProvider, + DragDropSensors, + DragOverlay, + SortableProvider, + closestCenter, + createSortable, +} from "@thisbeyond/solid-dnd" +import type { DragEvent } from "@thisbeyond/solid-dnd" +import type { JSX } from "solid-js" +import { useSync } from "@/context/sync" +import { useTerminal, type LocalPTY } from "@/context/terminal" +import { useLayout } from "@/context/layout" +import { getDirectory, getFilename } from "@opencode-ai/util/path" +import { Terminal } from "@/components/terminal" +import { checksum } from "@opencode-ai/util/encode" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { DialogSelectFile } from "@/components/dialog-select-file" +import { DialogSelectModel } from "@/components/dialog-select-model" +import { DialogSelectMcp } from "@/components/dialog-select-mcp" +import { useCommand } from "@/context/command" +import { useNavigate, useParams } from "@solidjs/router" +import { UserMessage } from "@opencode-ai/sdk/v2" +import { useSDK } from "@/context/sdk" +import { usePrompt } from "@/context/prompt" +import { extractPromptFromParts } from "@/utils/prompt" +import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd" +import { StatusBar } from "@/components/status-bar" +import { SessionMcpIndicator } from "@/components/session-mcp-indicator" +import { SessionLspIndicator } from "@/components/session-lsp-indicator" +import { usePermission } from "@/context/permission" +import { showToast } from "@opencode-ai/ui/toast" + +function same(a: readonly T[], b: readonly T[]) { + if (a === b) return true + if (a.length !== b.length) return false + return a.every((x, i) => x === b[i]) +} + +export default function Page() { + const layout = useLayout() + const local = useLocal() + const sync = useSync() + const terminal = useTerminal() + const dialog = useDialog() + const codeComponent = useCodeComponent() + const command = useCommand() + const params = useParams() + const navigate = useNavigate() + const sdk = useSDK() + const prompt = usePrompt() + + const permission = usePermission() + const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) + const tabs = createMemo(() => layout.tabs(sessionKey())) + const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) + const revertMessageID = createMemo(() => info()?.revert?.messageID) + const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : [])) + const emptyUserMessages: UserMessage[] = [] + const userMessages = createMemo( + () => messages().filter((m) => m.role === "user") as UserMessage[], + emptyUserMessages, + { equals: same }, + ) + const visibleUserMessages = createMemo( + () => { + const revert = revertMessageID() + if (!revert) return userMessages() + return userMessages().filter((m) => m.id < revert) + }, + emptyUserMessages, + { equals: same }, + ) + const lastUserMessage = createMemo(() => visibleUserMessages().at(-1)) + + createEffect( + on( + () => lastUserMessage()?.id, + () => { + const msg = lastUserMessage() + if (!msg) return + if (msg.agent) local.agent.set(msg.agent) + if (msg.model) local.model.set(msg.model) + }, + ), + ) + + const [store, setStore] = createStore({ + clickTimer: undefined as number | undefined, + activeDraggable: undefined as string | undefined, + activeTerminalDraggable: undefined as string | undefined, + userInteracted: false, + stepsExpanded: true, + mobileStepsExpanded: {} as Record, + messageId: undefined as string | undefined, + }) + + const activeMessage = createMemo(() => { + if (!store.messageId) return lastUserMessage() + // If the stored message is no longer visible (e.g., was reverted), fall back to last visible + const found = visibleUserMessages()?.find((m) => m.id === store.messageId) + return found ?? lastUserMessage() + }) + const setActiveMessage = (message: UserMessage | undefined) => { + setStore("messageId", message?.id) + } + + function navigateMessageByOffset(offset: number) { + const msgs = visibleUserMessages() + if (msgs.length === 0) return + + const current = activeMessage() + const currentIndex = current ? msgs.findIndex((m) => m.id === current.id) : -1 + + let targetIndex: number + if (currentIndex === -1) { + targetIndex = offset > 0 ? 0 : msgs.length - 1 + } else { + targetIndex = currentIndex + offset + } + + if (targetIndex < 0 || targetIndex >= msgs.length) return + + setActiveMessage(msgs[targetIndex]) + } + + const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : [])) + + let inputRef!: HTMLDivElement + + createEffect(() => { + if (!params.id) return + sync.session.sync(params.id) + }) + + createEffect(() => { + if (layout.terminal.opened()) { + if (terminal.all().length === 0) { + terminal.new() + } + } + }) + + createEffect( + on( + () => visibleUserMessages().at(-1)?.id, + (lastId, prevLastId) => { + if (lastId && prevLastId && lastId > prevLastId) { + setStore("messageId", undefined) + } + }, + { defer: true }, + ), + ) + + const idle = { type: "idle" as const } + + createEffect( + on( + () => params.id, + (id) => { + const status = sync.data.session_status[id ?? ""] ?? idle + batch(() => { + setStore("userInteracted", false) + setStore("stepsExpanded", status.type !== "idle") + }) + }, + ), + ) + + const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? idle) + + createEffect( + on( + () => status().type, + (type) => { + if (type !== "idle") return + batch(() => { + setStore("userInteracted", false) + setStore("stepsExpanded", false) + }) + }, + { defer: true }, + ), + ) + + const working = createMemo(() => status().type !== "idle" && activeMessage()?.id === lastUserMessage()?.id) + + createRenderEffect((prev) => { + const isWorking = working() + if (!prev && isWorking) { + setStore("stepsExpanded", true) + } + if (prev && !isWorking && !store.userInteracted) { + setStore("stepsExpanded", false) + } + return isWorking + }, working()) + + command.register(() => [ + { + id: "session.new", + title: "New session", + description: "Create a new session", + category: "Session", + keybind: "mod+shift+s", + slash: "new", + onSelect: () => navigate(`/${params.dir}/session`), + }, + { + id: "file.open", + title: "Open file", + description: "Search and open a file", + category: "File", + keybind: "mod+p", + slash: "open", + onSelect: () => dialog.show(() => ), + }, + // { + // id: "theme.toggle", + // title: "Toggle theme", + // description: "Switch between themes", + // category: "View", + // keybind: "ctrl+t", + // slash: "theme", + // onSelect: () => { + // const currentTheme = localStorage.getItem("theme") ?? "oc-1" + // const themes = ["oc-1", "oc-2-paper"] + // const nextTheme = themes[(themes.indexOf(currentTheme) + 1) % themes.length] + // localStorage.setItem("theme", nextTheme) + // document.documentElement.setAttribute("data-theme", nextTheme) + // }, + // }, + { + id: "terminal.toggle", + title: "Toggle terminal", + description: "Show or hide the terminal", + category: "View", + keybind: "ctrl+`", + slash: "terminal", + onSelect: () => layout.terminal.toggle(), + }, + { + id: "review.toggle", + title: "Toggle review", + description: "Show or hide the review panel", + category: "View", + keybind: "mod+shift+r", + onSelect: () => layout.review.toggle(), + }, + { + id: "terminal.new", + title: "New terminal", + description: "Create a new terminal tab", + category: "Terminal", + keybind: "ctrl+shift+`", + onSelect: () => terminal.new(), + }, + { + id: "steps.toggle", + title: "Toggle steps", + description: "Show or hide the steps", + category: "View", + keybind: "mod+e", + slash: "steps", + disabled: !params.id, + onSelect: () => setStore("stepsExpanded", (x) => !x), + }, + { + id: "message.previous", + title: "Previous message", + description: "Go to the previous user message", + category: "Session", + keybind: "mod+arrowup", + disabled: !params.id, + onSelect: () => navigateMessageByOffset(-1), + }, + { + id: "message.next", + title: "Next message", + description: "Go to the next user message", + category: "Session", + keybind: "mod+arrowdown", + disabled: !params.id, + onSelect: () => navigateMessageByOffset(1), + }, + { + id: "model.choose", + title: "Choose model", + description: "Select a different model", + category: "Model", + keybind: "mod+'", + slash: "model", + onSelect: () => dialog.show(() => ), + }, + { + id: "mcp.toggle", + title: "Toggle MCPs", + description: "Toggle MCPs", + category: "MCP", + keybind: "mod+;", + slash: "mcp", + onSelect: () => dialog.show(() => ), + }, + { + id: "agent.cycle", + title: "Cycle agent", + description: "Switch to the next agent", + category: "Agent", + keybind: "mod+.", + slash: "agent", + onSelect: () => local.agent.move(1), + }, + { + id: "agent.cycle.reverse", + title: "Cycle agent backwards", + description: "Switch to the previous agent", + category: "Agent", + keybind: "shift+mod+.", + onSelect: () => local.agent.move(-1), + }, + { + id: "permissions.autoaccept", + title: params.id && permission.isAutoAccepting(params.id) ? "Stop auto-accepting edits" : "Auto-accept edits", + category: "Permissions", + disabled: !params.id, + onSelect: () => { + if (!params.id) return + permission.toggleAutoAccept(params.id) + showToast({ + title: permission.isAutoAccepting(params.id) ? "Auto-accepting edits" : "Stopped auto-accepting edits", + description: permission.isAutoAccepting(params.id) + ? "Edit and write permissions will be automatically approved" + : "Edit and write permissions will require approval", + }) + }, + }, + { + id: "session.undo", + title: "Undo", + description: "Undo the last message", + category: "Session", + slash: "undo", + disabled: !params.id || visibleUserMessages().length === 0, + onSelect: async () => { + const sessionID = params.id + if (!sessionID) return + if (status()?.type !== "idle") { + await sdk.client.session.abort({ sessionID }).catch(() => {}) + } + const revert = info()?.revert?.messageID + // Find the last user message that's not already reverted + const message = userMessages().findLast((x) => !revert || x.id < revert) + if (!message) return + await sdk.client.session.revert({ sessionID, messageID: message.id }) + // Restore the prompt from the reverted message + const parts = sync.data.part[message.id] + if (parts) { + const restored = extractPromptFromParts(parts) + prompt.set(restored) + } + // Navigate to the message before the reverted one (which will be the new last visible message) + const priorMessage = userMessages().findLast((x) => x.id < message.id) + setActiveMessage(priorMessage) + }, + }, + { + id: "session.redo", + title: "Redo", + description: "Redo the last undone message", + category: "Session", + slash: "redo", + disabled: !params.id || !info()?.revert?.messageID, + onSelect: async () => { + const sessionID = params.id + if (!sessionID) return + const revertMessageID = info()?.revert?.messageID + if (!revertMessageID) return + const nextMessage = userMessages().find((x) => x.id > revertMessageID) + if (!nextMessage) { + // Full unrevert - restore all messages and navigate to last + await sdk.client.session.unrevert({ sessionID }) + prompt.reset() + // Navigate to the last message (the one that was at the revert point) + const lastMsg = userMessages().findLast((x) => x.id >= revertMessageID) + setActiveMessage(lastMsg) + return + } + // Partial redo - move forward to next message + await sdk.client.session.revert({ sessionID, messageID: nextMessage.id }) + // Navigate to the message before the new revert point + const priorMsg = userMessages().findLast((x) => x.id < nextMessage.id) + setActiveMessage(priorMsg) + }, + }, + ]) + + const handleKeyDown = (event: KeyboardEvent) => { + const activeElement = document.activeElement as HTMLElement | undefined + if (activeElement) { + const isProtected = activeElement.closest("[data-prevent-autofocus]") + const isInput = /^(INPUT|TEXTAREA|SELECT)$/.test(activeElement.tagName) || activeElement.isContentEditable + if (isProtected || isInput) return + } + if (dialog.active) return + + if (activeElement === inputRef) { + if (event.key === "Escape") inputRef?.blur() + return + } + + if (event.key.length === 1 && event.key !== "Unidentified" && !(event.ctrlKey || event.metaKey)) { + inputRef?.focus() + } + } + + onMount(() => { + document.addEventListener("keydown", handleKeyDown) + }) + + onCleanup(() => { + document.removeEventListener("keydown", handleKeyDown) + }) + + const resetClickTimer = () => { + if (!store.clickTimer) return + clearTimeout(store.clickTimer) + setStore("clickTimer", undefined) + } + + const startClickTimer = () => { + const newClickTimer = setTimeout(() => { + setStore("clickTimer", undefined) + }, 300) + setStore("clickTimer", newClickTimer as unknown as number) + } + + const handleTabClick = async (tab: string) => { + if (store.clickTimer) { + resetClickTimer() + } else { + if (tab.startsWith("file://")) { + local.file.open(tab.replace("file://", "")) + } + startClickTimer() + } + } + + const handleDragStart = (event: unknown) => { + const id = getDraggableId(event) + if (!id) return + setStore("activeDraggable", id) + } + + const handleDragOver = (event: DragEvent) => { + const { draggable, droppable } = event + if (draggable && droppable) { + const currentTabs = tabs().all() + const fromIndex = currentTabs?.indexOf(draggable.id.toString()) + const toIndex = currentTabs?.indexOf(droppable.id.toString()) + if (fromIndex !== toIndex && toIndex !== undefined) { + tabs().move(draggable.id.toString(), toIndex) + } + } + } + + const handleDragEnd = () => { + setStore("activeDraggable", undefined) + } + + const handleTerminalDragStart = (event: unknown) => { + const id = getDraggableId(event) + if (!id) return + setStore("activeTerminalDraggable", id) + } + + const handleTerminalDragOver = (event: DragEvent) => { + const { draggable, droppable } = event + if (draggable && droppable) { + const terminals = terminal.all() + const fromIndex = terminals.findIndex((t: LocalPTY) => t.id === draggable.id.toString()) + const toIndex = terminals.findIndex((t: LocalPTY) => t.id === droppable.id.toString()) + if (fromIndex !== -1 && toIndex !== -1 && fromIndex !== toIndex) { + terminal.move(draggable.id.toString(), toIndex) + } + } + } + + const handleTerminalDragEnd = () => { + setStore("activeTerminalDraggable", undefined) + } + + const SortableTerminalTab = (props: { terminal: LocalPTY }): JSX.Element => { + const sortable = createSortable(props.terminal.id) + return ( + // @ts-ignore +
+
+ 1 && ( + terminal.close(props.terminal.id)} /> + ) + } + > + {props.terminal.title} + +
+
+ ) + } + + const FileVisual = (props: { file: LocalFile; active?: boolean }): JSX.Element => { + return ( +
+ + + {props.file.name} + + +
+ ) + } + + const SortableTab = (props: { + tab: string + onTabClick: (tab: string) => void + onTabClose: (tab: string) => void + }): JSX.Element => { + const sortable = createSortable(props.tab) + const [file] = createResource( + () => props.tab, + async (tab) => { + if (tab.startsWith("file://")) { + return local.file.node(tab.replace("file://", "")) + } + return undefined + }, + ) + return ( + // @ts-ignore +
+
+ + props.onTabClose(props.tab)} /> + + } + hideCloseButton + onClick={() => props.onTabClick(props.tab)} + > + + {(f) => } + + +
+
+ ) + } + + const showTabs = createMemo(() => layout.review.opened() && (diffs().length > 0 || tabs().all().length > 0)) + + const mobileWorking = createMemo(() => status().type !== "idle") + const mobileAutoScroll = createAutoScroll({ + working: mobileWorking, + onUserInteracted: () => setStore("userInteracted", true), + }) + + const MobileTurns = () => ( +
+
+ + {(message) => ( + setStore("mobileStepsExpanded", message.id, (x) => !x)} + onUserInteracted={() => setStore("userInteracted", true)} + classes={{ + root: "min-w-0 w-full relative", + content: + "flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]", + container: "px-4", + }} + /> + )} + +
+
+ ) + + const NewSessionView = () => ( +
+
New session
+
+ +
+ {getDirectory(sync.data.path.directory)} + {getFilename(sync.data.path.directory)} +
+
+ + {(project) => ( +
+ +
+ Last modified  + + {DateTime.fromMillis(project().time.updated ?? project().time.created).toRelative()} + +
+
+ )} +
+
+ ) + + const DesktopSessionContent = () => ( + + +
+ + + setStore("stepsExpanded", (x) => !x)} + onUserInteracted={() => setStore("userInteracted", true)} + classes={{ + root: "pb-20 flex-1 min-w-0", + content: "pb-20", + container: + "w-full " + + (!showTabs() ? "max-w-200 mx-auto px-6" : visibleUserMessages().length > 1 ? "pr-6 pl-18" : "px-6"), + }} + /> + +
+
+ + + +
+ ) + + return ( +
+
+ + +
+ +
+
+ 0}> + + + + Session + + + {diffs().length} Files Changed + + + + + + + + + +
+ +
+
+
+
+
+ { + inputRef = el + }} + /> +
+
+
+ + + +
+ + + + + + {(pty) => ( + + terminal.clone(pty.id)} /> + + )} + + + + + {(draggedId) => { + const pty = createMemo(() => terminal.all().find((t: LocalPTY) => t.id === draggedId())) + return ( + + {(t) => ( +
+ {t().title} +
+ )} +
+ ) + }} +
+
+ +
+ + + + + +
+ ) +} diff --git a/packages/desktop/src/sst-env.d.ts b/packages/app/src/sst-env.d.ts similarity index 100% rename from packages/desktop/src/sst-env.d.ts rename to packages/app/src/sst-env.d.ts diff --git a/packages/desktop/src/utils/dom.ts b/packages/app/src/utils/dom.ts similarity index 100% rename from packages/desktop/src/utils/dom.ts rename to packages/app/src/utils/dom.ts diff --git a/packages/app/src/utils/id.ts b/packages/app/src/utils/id.ts new file mode 100644 index 00000000000..fa27cf4c5f9 --- /dev/null +++ b/packages/app/src/utils/id.ts @@ -0,0 +1,99 @@ +import z from "zod" + +const prefixes = { + session: "ses", + message: "msg", + permission: "per", + user: "usr", + part: "prt", + pty: "pty", +} as const + +const LENGTH = 26 +let lastTimestamp = 0 +let counter = 0 + +type Prefix = keyof typeof prefixes +export namespace Identifier { + export function schema(prefix: Prefix) { + return z.string().startsWith(prefixes[prefix]) + } + + export function ascending(prefix: Prefix, given?: string) { + return generateID(prefix, false, given) + } + + export function descending(prefix: Prefix, given?: string) { + return generateID(prefix, true, given) + } +} + +function generateID(prefix: Prefix, descending: boolean, given?: string): string { + if (!given) { + return create(prefix, descending) + } + + if (!given.startsWith(prefixes[prefix])) { + throw new Error(`ID ${given} does not start with ${prefixes[prefix]}`) + } + + return given +} + +function create(prefix: Prefix, descending: boolean, timestamp?: number): string { + const currentTimestamp = timestamp ?? Date.now() + + if (currentTimestamp !== lastTimestamp) { + lastTimestamp = currentTimestamp + counter = 0 + } + + counter += 1 + + let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter) + + if (descending) { + now = ~now + } + + const timeBytes = new Uint8Array(6) + for (let i = 0; i < 6; i += 1) { + timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff)) + } + + return prefixes[prefix] + "_" + bytesToHex(timeBytes) + randomBase62(LENGTH - 12) +} + +function bytesToHex(bytes: Uint8Array): string { + let hex = "" + for (let i = 0; i < bytes.length; i += 1) { + hex += bytes[i].toString(16).padStart(2, "0") + } + return hex +} + +function randomBase62(length: number): string { + const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + const bytes = getRandomBytes(length) + let result = "" + for (let i = 0; i < length; i += 1) { + result += chars[bytes[i] % 62] + } + return result +} + +function getRandomBytes(length: number): Uint8Array { + const bytes = new Uint8Array(length) + const cryptoObj = typeof globalThis !== "undefined" ? globalThis.crypto : undefined + + if (cryptoObj && typeof cryptoObj.getRandomValues === "function") { + cryptoObj.getRandomValues(bytes) + return bytes + } + + for (let i = 0; i < length; i += 1) { + bytes[i] = Math.floor(Math.random() * 256) + } + + return bytes +} diff --git a/packages/app/src/utils/index.ts b/packages/app/src/utils/index.ts new file mode 100644 index 00000000000..d87053269df --- /dev/null +++ b/packages/app/src/utils/index.ts @@ -0,0 +1 @@ +export * from "./dom" diff --git a/packages/app/src/utils/persist.ts b/packages/app/src/utils/persist.ts new file mode 100644 index 00000000000..12b334f9f02 --- /dev/null +++ b/packages/app/src/utils/persist.ts @@ -0,0 +1,26 @@ +import { usePlatform } from "@/context/platform" +import { makePersisted } from "@solid-primitives/storage" +import { createResource, type Accessor } from "solid-js" +import type { SetStoreFunction, Store } from "solid-js/store" + +type InitType = Promise | string | null +type PersistedWithReady = [Store, SetStoreFunction, InitType, Accessor] + +export function persisted(key: string, store: [Store, SetStoreFunction]): PersistedWithReady { + const platform = usePlatform() + const [state, setState, init] = makePersisted(store, { name: key, storage: platform.storage?.() ?? localStorage }) + + // Create a resource that resolves when the store is initialized + // This integrates with Suspense and provides a ready signal + const isAsync = init instanceof Promise + const [ready] = createResource( + () => init, + async (initValue) => { + if (initValue instanceof Promise) await initValue + return true + }, + { initialValue: !isAsync }, + ) + + return [state, setState, init, () => ready() === true] +} diff --git a/packages/app/src/utils/prompt.ts b/packages/app/src/utils/prompt.ts new file mode 100644 index 00000000000..45c5ce1f3fa --- /dev/null +++ b/packages/app/src/utils/prompt.ts @@ -0,0 +1,47 @@ +import type { Part, TextPart, FilePart } from "@opencode-ai/sdk/v2" +import type { Prompt, FileAttachmentPart } from "@/context/prompt" + +/** + * Extract prompt content from message parts for restoring into the prompt input. + * This is used by undo to restore the original user prompt. + */ +export function extractPromptFromParts(parts: Part[]): Prompt { + const result: Prompt = [] + let position = 0 + + for (const part of parts) { + if (part.type === "text") { + const textPart = part as TextPart + if (!textPart.synthetic && textPart.text) { + result.push({ + type: "text", + content: textPart.text, + start: position, + end: position + textPart.text.length, + }) + position += textPart.text.length + } + } else if (part.type === "file") { + const filePart = part as FilePart + if (filePart.source?.type === "file") { + const path = filePart.source.path + const content = "@" + path + const attachment: FileAttachmentPart = { + type: "file", + path, + content, + start: position, + end: position + content.length, + } + result.push(attachment) + position += content.length + } + } + } + + if (result.length === 0) { + result.push({ type: "text", content: "", start: 0, end: 0 }) + } + + return result +} diff --git a/packages/app/src/utils/solid-dnd.tsx b/packages/app/src/utils/solid-dnd.tsx new file mode 100644 index 00000000000..a634be4b486 --- /dev/null +++ b/packages/app/src/utils/solid-dnd.tsx @@ -0,0 +1,55 @@ +import { useDragDropContext } from "@thisbeyond/solid-dnd" +import { JSXElement } from "solid-js" +import type { Transformer } from "@thisbeyond/solid-dnd" + +export const getDraggableId = (event: unknown): string | undefined => { + if (typeof event !== "object" || event === null) return undefined + if (!("draggable" in event)) return undefined + const draggable = (event as { draggable?: { id?: unknown } }).draggable + if (!draggable) return undefined + return typeof draggable.id === "string" ? draggable.id : undefined +} + +export const ConstrainDragXAxis = (): JSXElement => { + const context = useDragDropContext() + if (!context) return <> + const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context + const transformer: Transformer = { + id: "constrain-x-axis", + order: 100, + callback: (transform) => ({ ...transform, x: 0 }), + } + onDragStart((event) => { + const id = getDraggableId(event) + if (!id) return + addTransformer("draggables", id, transformer) + }) + onDragEnd((event) => { + const id = getDraggableId(event) + if (!id) return + removeTransformer("draggables", id, transformer.id) + }) + return <> +} + +export const ConstrainDragYAxis = (): JSXElement => { + const context = useDragDropContext() + if (!context) return <> + const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context + const transformer: Transformer = { + id: "constrain-y-axis", + order: 100, + callback: (transform) => ({ ...transform, y: 0 }), + } + onDragStart((event) => { + const id = getDraggableId(event) + if (!id) return + addTransformer("draggables", id, transformer) + }) + onDragEnd((event) => { + const id = getDraggableId(event) + if (!id) return + removeTransformer("draggables", id, transformer.id) + }) + return <> +} diff --git a/packages/desktop/src/utils/speech.ts b/packages/app/src/utils/speech.ts similarity index 100% rename from packages/desktop/src/utils/speech.ts rename to packages/app/src/utils/speech.ts diff --git a/packages/tauri/sst-env.d.ts b/packages/app/sst-env.d.ts similarity index 100% rename from packages/tauri/sst-env.d.ts rename to packages/app/sst-env.d.ts diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json new file mode 100644 index 00000000000..e2a27dd5d8a --- /dev/null +++ b/packages/app/tsconfig.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "composite": true, + "target": "ESNext", + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "allowJs": true, + "resolveJsonModule": true, + "strict": true, + "noEmit": false, + "emitDeclarationOnly": true, + "outDir": "node_modules/.ts-dist", + "isolatedModules": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src", "package.json"], + "exclude": ["dist", "ts-dist"] +} diff --git a/packages/app/vite.config.ts b/packages/app/vite.config.ts new file mode 100644 index 00000000000..6a29ae6345e --- /dev/null +++ b/packages/app/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "vite" +import desktopPlugin from "./vite" + +export default defineConfig({ + plugins: [desktopPlugin] as any, + server: { + host: "0.0.0.0", + allowedHosts: true, + port: 3000, + }, + build: { + target: "esnext", + // sourcemap: true, + }, +}) diff --git a/packages/desktop/vite.js b/packages/app/vite.js similarity index 100% rename from packages/desktop/vite.js rename to packages/app/vite.js diff --git a/packages/console/app/.opencode/agent/css.md b/packages/console/app/.opencode/agent/css.md index d0ec43a483b..d5e68c7bf6a 100644 --- a/packages/console/app/.opencode/agent/css.md +++ b/packages/console/app/.opencode/agent/css.md @@ -49,7 +49,7 @@ use data attributes to represent different states of the component } ``` -this will allow jsx to control the syling +this will allow jsx to control the styling avoid selectors that just target an element type like `> span` you should assign it a slot name. it's ok to do this sometimes where it makes sense semantically diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 6190b53d633..7e5b064dcc6 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.0.134", + "version": "1.0.217", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", diff --git a/packages/console/app/src/app.tsx b/packages/console/app/src/app.tsx index bc94b443e95..cde2f01876f 100644 --- a/packages/console/app/src/app.tsx +++ b/packages/console/app/src/app.tsx @@ -3,6 +3,7 @@ import { Router } from "@solidjs/router" import { FileRoutes } from "@solidjs/start/router" import { Suspense } from "solid-js" import { Favicon } from "@opencode-ai/ui/favicon" +import { Font } from "@opencode-ai/ui/font" import "@ibm/plex/css/ibm-plex.css" import "./app.css" @@ -13,8 +14,9 @@ export default function App() { root={(props) => ( opencode - + + {props.children} )} diff --git a/packages/console/app/src/asset/lander/desktop-app-icon.png b/packages/console/app/src/asset/lander/desktop-app-icon.png new file mode 100644 index 00000000000..a35c28f516c Binary files /dev/null and b/packages/console/app/src/asset/lander/desktop-app-icon.png differ diff --git a/packages/console/app/src/asset/lander/opencode-desktop-icon.png b/packages/console/app/src/asset/lander/opencode-desktop-icon.png new file mode 100644 index 00000000000..f2c8d4f5a30 Binary files /dev/null and b/packages/console/app/src/asset/lander/opencode-desktop-icon.png differ diff --git a/packages/console/app/src/asset/lander/opencode-min.mp4 b/packages/console/app/src/asset/lander/opencode-min.mp4 index 47468bedfa9..ffd6c4f7af4 100644 Binary files a/packages/console/app/src/asset/lander/opencode-min.mp4 and b/packages/console/app/src/asset/lander/opencode-min.mp4 differ diff --git a/packages/console/app/src/component/email-signup.tsx b/packages/console/app/src/component/email-signup.tsx index 4943921e75c..65f81b5fc6d 100644 --- a/packages/console/app/src/component/email-signup.tsx +++ b/packages/console/app/src/component/email-signup.tsx @@ -25,11 +25,8 @@ export function EmailSignup() { const submission = useSubmission(emailSignup) return (
-
- -
-

OpenCode will be available on desktop soon

+

Be the first to know when we release new products

Join the waitlist for early access.

diff --git a/packages/console/app/src/component/header.tsx b/packages/console/app/src/component/header.tsx index 06e710a1879..7bfcc782508 100644 --- a/packages/console/app/src/component/header.tsx +++ b/packages/console/app/src/component/header.tsx @@ -34,7 +34,7 @@ const fetchSvgContent = async (svgPath: string): Promise => { } } -export function Header(props: { zen?: boolean }) { +export function Header(props: { zen?: boolean; hideGetStarted?: boolean }) { const navigate = useNavigate() const githubData = createAsync(() => github()) const starCount = createMemo(() => @@ -119,8 +119,8 @@ export function Header(props: { zen?: boolean }) {
@@ -169,6 +169,25 @@ export function Header(props: { zen?: boolean }) { + + {" "} +
  • + {" "} + + {" "} + + {" "} + {" "} + {" "} + Free{" "} + {" "} +
  • +
    diff --git a/packages/console/app/src/component/icon.tsx b/packages/console/app/src/component/icon.tsx index a28fc51a3e0..1225aeb10c8 100644 --- a/packages/console/app/src/component/icon.tsx +++ b/packages/console/app/src/component/icon.tsx @@ -202,6 +202,14 @@ export function IconZai(props: JSX.SvgSVGAttributes) { ) } +export function IconMiniMax(props: JSX.SvgSVGAttributes) { + return ( + + + + ) +} + export function IconGemini(props: JSX.SvgSVGAttributes) { return ( diff --git a/packages/console/app/src/component/legal.tsx b/packages/console/app/src/component/legal.tsx index c055977e324..e971a31e171 100644 --- a/packages/console/app/src/component/legal.tsx +++ b/packages/console/app/src/component/legal.tsx @@ -9,6 +9,12 @@ export function Legal() { Brand + + Privacy + + + Terms + ) } diff --git a/packages/console/app/src/config.ts b/packages/console/app/src/config.ts index a058f6829a3..bf20681ae15 100644 --- a/packages/console/app/src/config.ts +++ b/packages/console/app/src/config.ts @@ -9,8 +9,8 @@ export const config = { github: { repoUrl: "https://github.com/sst/opencode", starsFormatted: { - compact: "35K", - full: "35,000", + compact: "41K", + full: "41,000", }, }, @@ -22,8 +22,8 @@ export const config = { // Static stats (used on landing page) stats: { - contributors: "350", - commits: "5,000", + contributors: "450", + commits: "6,000", monthlyUsers: "400,000", }, } as const diff --git a/packages/console/app/src/entry-server.tsx b/packages/console/app/src/entry-server.tsx index 913c8ca0600..deaadc747de 100644 --- a/packages/console/app/src/entry-server.tsx +++ b/packages/console/app/src/entry-server.tsx @@ -1,6 +1,8 @@ // @refresh reload import { createHandler, StartServer } from "@solidjs/start/server" +const criticalCSS = `[data-component="top"]{min-height:80px;display:flex;align-items:center}` + export default createHandler( () => ( + {assets} diff --git a/packages/console/app/src/lib/github.ts b/packages/console/app/src/lib/github.ts index bc49d2e629d..cc266f58c4d 100644 --- a/packages/console/app/src/lib/github.ts +++ b/packages/console/app/src/lib/github.ts @@ -26,6 +26,7 @@ export const github = query(async () => { release: { name: release.name, url: release.html_url, + tag_name: release.tag_name, }, contributors: contributorCount, } diff --git a/packages/console/app/src/routes/auth/callback.ts b/packages/console/app/src/routes/auth/callback.ts index a793b85962a..2f8781e9882 100644 --- a/packages/console/app/src/routes/auth/callback.ts +++ b/packages/console/app/src/routes/auth/callback.ts @@ -5,28 +5,36 @@ import { useAuthSession } from "~/context/auth.session" export async function GET(input: APIEvent) { const url = new URL(input.request.url) - const code = url.searchParams.get("code") - if (!code) throw new Error("No code found") - const result = await AuthClient.exchange(code, `${url.origin}${url.pathname}`) - if (result.err) { - throw new Error(result.err.message) - } - const decoded = AuthClient.decode(result.tokens.access, {} as any) - if (decoded.err) throw new Error(decoded.err.message) - const session = await useAuthSession() - const id = decoded.subject.properties.accountID - await session.update((value) => { - return { - ...value, - account: { - ...value.account, - [id]: { - id, - email: decoded.subject.properties.email, + try { + const code = url.searchParams.get("code") + if (!code) throw new Error("No code found") + const result = await AuthClient.exchange(code, `${url.origin}${url.pathname}`) + if (result.err) throw new Error(result.err.message) + const decoded = AuthClient.decode(result.tokens.access, {} as any) + if (decoded.err) throw new Error(decoded.err.message) + const session = await useAuthSession() + const id = decoded.subject.properties.accountID + await session.update((value) => { + return { + ...value, + account: { + ...value.account, + [id]: { + id, + email: decoded.subject.properties.email, + }, }, - }, - current: id, - } - }) - return redirect("/auth") + current: id, + } + }) + return redirect("/auth") + } catch (e: any) { + return new Response( + JSON.stringify({ + error: e.message, + cause: Object.fromEntries(url.searchParams.entries()), + }), + { status: 500 }, + ) + } } diff --git a/packages/console/app/src/routes/bench/[id].tsx b/packages/console/app/src/routes/bench/[id].tsx new file mode 100644 index 00000000000..4586eef9bf9 --- /dev/null +++ b/packages/console/app/src/routes/bench/[id].tsx @@ -0,0 +1,365 @@ +import { Title } from "@solidjs/meta" +import { createAsync, query, useParams } from "@solidjs/router" +import { createSignal, For, Show } from "solid-js" +import { Database, desc, eq } from "@opencode-ai/console-core/drizzle/index.js" +import { BenchmarkTable } from "@opencode-ai/console-core/schema/benchmark.sql.js" + +interface TaskSource { + repo: string + from: string + to: string +} + +interface Judge { + score: number + rationale: string + judge: string +} + +interface ScoreDetail { + criterion: string + weight: number + average: number + variance?: number + judges?: Judge[] +} + +interface RunUsage { + input: number + output: number + cost: number +} + +interface Run { + task: string + model: string + agent: string + score: { + final: number + base: number + penalty: number + } + scoreDetails: ScoreDetail[] + usage?: RunUsage + duration?: number +} + +interface Prompt { + commit: string + prompt: string +} + +interface AverageUsage { + input: number + output: number + cost: number +} + +interface Task { + averageScore: number + averageDuration?: number + averageUsage?: AverageUsage + model?: string + agent?: string + summary?: string + runs?: Run[] + task: { + id: string + source: TaskSource + prompts?: Prompt[] + } +} + +interface BenchmarkResult { + averageScore: number + tasks: Task[] +} + +async function getTaskDetail(benchmarkId: string, taskId: string) { + "use server" + const rows = await Database.use((tx) => + tx.select().from(BenchmarkTable).where(eq(BenchmarkTable.id, benchmarkId)).limit(1), + ) + if (!rows[0]) return null + const parsed = JSON.parse(rows[0].result) as BenchmarkResult + const task = parsed.tasks.find((t) => t.task.id === taskId) + return task ?? null +} + +const queryTaskDetail = query(getTaskDetail, "benchmark.task.detail") + +function formatDuration(ms: number): string { + const seconds = Math.floor(ms / 1000) + const minutes = Math.floor(seconds / 60) + const remainingSeconds = seconds % 60 + if (minutes > 0) { + return `${minutes}m ${remainingSeconds}s` + } + return `${remainingSeconds}s` +} + +export default function BenchDetail() { + const params = useParams() + const [benchmarkId, taskId] = (params.id ?? "").split(":") + const task = createAsync(() => queryTaskDetail(benchmarkId, taskId)) + + return ( +
    + Benchmark - {taskId} +
    + Task not found

    }> +
    +
    + Agent: + {task()?.agent ?? "N/A"} +
    +
    + Model: + {task()?.model ?? "N/A"} +
    +
    + Task: + {task()!.task.id} +
    +
    + + + + 0}> +
    + Prompt: + + {(p) => ( +
    +
    Commit: {p.commit.slice(0, 7)}
    +

    {p.prompt}

    +
    + )} +
    +
    +
    + +
    + +
    +
    + Average Duration: + {task()?.averageDuration ? formatDuration(task()!.averageDuration!) : "N/A"} +
    +
    + Average Score: + {task()?.averageScore?.toFixed(3) ?? "N/A"} +
    +
    + Average Cost: + {task()?.averageUsage?.cost ? `$${task()!.averageUsage!.cost.toFixed(4)}` : "N/A"} +
    +
    + + +
    + Summary: +

    {task()!.summary}

    +
    +
    + + 0}> +
    + Runs: + + + + + + + + + {(detail) => ( + + )} + + + + + + {(run, index) => ( + + + + + + + {(detail) => ( + + )} + + + )} + + +
    Run + Score (Base - Penalty) + CostDuration + {detail.criterion} ({detail.weight}) +
    {index() + 1} + {run.score.final.toFixed(3)} ({run.score.base.toFixed(3)} - {run.score.penalty.toFixed(3)}) + + {run.usage?.cost ? `$${run.usage.cost.toFixed(4)}` : "N/A"} + + {run.duration ? formatDuration(run.duration) : "N/A"} + + + {(judge) => ( + + {judge.score === 1 ? "✓" : judge.score === 0 ? "✗" : judge.score} + + )} + +
    + + {(run, index) => ( +
    +

    Run {index() + 1}

    +
    + Score: + {run.score.final.toFixed(3)} (Base: {run.score.base.toFixed(3)} - Penalty:{" "} + {run.score.penalty.toFixed(3)}) +
    + + {(detail) => ( +
    +
    + {detail.criterion} (weight: {detail.weight}){" "} + + {(judge) => ( + + {judge.score === 1 ? "✓" : judge.score === 0 ? "✗" : judge.score} + + )} + +
    + 0}> + + {(judge) => { + const [expanded, setExpanded] = createSignal(false) + return ( +
    +
    setExpanded(!expanded())} + > + {expanded() ? "▼" : "▶"} + + {judge.score === 1 ? "✓" : judge.score === 0 ? "✗" : judge.score} + {" "} + {judge.judge} +
    + +

    + {judge.rationale} +

    +
    +
    + ) + }} +
    +
    +
    + )} +
    +
    + )} +
    +
    +
    + + {(() => { + const [jsonExpanded, setJsonExpanded] = createSignal(false) + return ( +
    + + +
    {JSON.stringify(task(), null, 2)}
    +
    +
    + ) + })()} +
    +
    +
    + ) +} diff --git a/packages/console/app/src/routes/bench/index.tsx b/packages/console/app/src/routes/bench/index.tsx new file mode 100644 index 00000000000..9b8d0b8f24f --- /dev/null +++ b/packages/console/app/src/routes/bench/index.tsx @@ -0,0 +1,86 @@ +import { Title } from "@solidjs/meta" +import { A, createAsync, query } from "@solidjs/router" +import { createMemo, For, Show } from "solid-js" +import { Database, desc } from "@opencode-ai/console-core/drizzle/index.js" +import { BenchmarkTable } from "@opencode-ai/console-core/schema/benchmark.sql.js" + +interface BenchmarkResult { + averageScore: number + tasks: { averageScore: number; task: { id: string } }[] +} + +async function getBenchmarks() { + "use server" + const rows = await Database.use((tx) => + tx.select().from(BenchmarkTable).orderBy(desc(BenchmarkTable.timeCreated)).limit(100), + ) + return rows.map((row) => { + const parsed = JSON.parse(row.result) as BenchmarkResult + const taskScores: Record = {} + for (const t of parsed.tasks) { + taskScores[t.task.id] = t.averageScore + } + return { + id: row.id, + agent: row.agent, + model: row.model, + averageScore: parsed.averageScore, + taskScores, + } + }) +} + +const queryBenchmarks = query(getBenchmarks, "benchmarks.list") + +export default function Bench() { + const benchmarks = createAsync(() => queryBenchmarks()) + + const taskIds = createMemo(() => { + const ids = new Set() + for (const row of benchmarks() ?? []) { + for (const id of Object.keys(row.taskScores)) { + ids.add(id) + } + } + return [...ids].sort() + }) + + return ( +
    + Benchmark +

    Benchmarks

    + + + + + + + {(id) => } + + + + + {(row) => ( + + + + + + {(id) => ( + + )} + + + )} + + +
    AgentModelScore{id}
    {row.agent}{row.model}{row.averageScore.toFixed(3)} + + + {row.taskScores[id]?.toFixed(3)} + + +
    +
    + ) +} diff --git a/packages/console/app/src/routes/bench/submission.ts b/packages/console/app/src/routes/bench/submission.ts new file mode 100644 index 00000000000..94639439b11 --- /dev/null +++ b/packages/console/app/src/routes/bench/submission.ts @@ -0,0 +1,29 @@ +import type { APIEvent } from "@solidjs/start/server" +import { Database } from "@opencode-ai/console-core/drizzle/index.js" +import { BenchmarkTable } from "@opencode-ai/console-core/schema/benchmark.sql.js" +import { Identifier } from "@opencode-ai/console-core/identifier.js" + +interface SubmissionBody { + model: string + agent: string + result: string +} + +export async function POST(event: APIEvent) { + const body = (await event.request.json()) as SubmissionBody + + if (!body.model || !body.agent || !body.result) { + return Response.json({ error: "All fields are required" }, { status: 400 }) + } + + await Database.use((tx) => + tx.insert(BenchmarkTable).values({ + id: Identifier.create("benchmark"), + model: body.model, + agent: body.agent, + result: body.result, + }), + ) + + return Response.json({ success: true }, { status: 200 }) +} diff --git a/packages/console/app/src/routes/brand/index.css b/packages/console/app/src/routes/brand/index.css index d3c0d052374..2bfe5711aa6 100644 --- a/packages/console/app/src/routes/brand/index.css +++ b/packages/console/app/src/routes/brand/index.css @@ -8,7 +8,8 @@ } } -[data-page="enterprise"] { +[data-page="enterprise"], +[data-page="legal"] { --color-background: hsl(0, 20%, 99%); --color-background-weak: hsl(0, 8%, 97%); --color-background-weak-hover: hsl(0, 8%, 94%); @@ -84,7 +85,16 @@ ul { display: flex; justify-content: space-between; + align-items: center; gap: 48px; + + @media (max-width: 55rem) { + gap: 32px; + } + + @media (max-width: 48rem) { + gap: 24px; + } li { display: inline-block; a { @@ -98,6 +108,25 @@ text-underline-offset: 2px; text-decoration-thickness: 1px; } + [data-slot="cta-button"] { + background: var(--color-background-strong); + color: var(--color-text-inverted); + padding: 8px 16px 8px 10px; + border-radius: 4px; + font-weight: 500; + text-decoration: none; + display: flex; + align-items: center; + gap: 8px; + + @media (max-width: 55rem) { + display: none; + } + } + [data-slot="cta-button"]:hover { + background: var(--color-background-strong-hover); + text-decoration: none; + } } } @@ -266,7 +295,7 @@ h1 { font-size: 1.5rem; - font-weight: 500; + font-weight: 700; color: var(--color-text-strong); margin-bottom: 1rem; } diff --git a/packages/console/app/src/routes/download/[platform].ts b/packages/console/app/src/routes/download/[platform].ts new file mode 100644 index 00000000000..427fb132b50 --- /dev/null +++ b/packages/console/app/src/routes/download/[platform].ts @@ -0,0 +1,38 @@ +import { APIEvent } from "@solidjs/start" +import { DownloadPlatform } from "./types" + +const assetNames: Record = { + "darwin-aarch64-dmg": "opencode-desktop-darwin-aarch64.dmg", + "darwin-x64-dmg": "opencode-desktop-darwin-x64.dmg", + "windows-x64-nsis": "opencode-desktop-windows-x64.exe", + "linux-x64-deb": "opencode-desktop-linux-amd64.deb", + "linux-x64-appimage": "opencode-desktop-linux-amd64.AppImage", + "linux-x64-rpm": "opencode-desktop-linux-x86_64.rpm", +} satisfies Record + +// Doing this on the server lets us preserve the original name for platforms we don't care to rename for +const downloadNames: Record = { + "darwin-aarch64-dmg": "OpenCode Desktop.dmg", + "darwin-x64-dmg": "OpenCode Desktop.dmg", + "windows-x64-nsis": "OpenCode Desktop Installer.exe", +} satisfies { [K in DownloadPlatform]?: string } + +export async function GET({ params: { platform } }: APIEvent) { + const assetName = assetNames[platform] + if (!assetName) return new Response("Not Found", { status: 404 }) + + const resp = await fetch(`https://github.com/sst/opencode/releases/latest/download/${assetName}`, { + cf: { + // in case gh releases has rate limits + cacheTtl: 60 * 60 * 24, + cacheEverything: true, + }, + } as any) + + const downloadName = downloadNames[platform] + + const headers = new Headers(resp.headers) + if (downloadName) headers.set("content-disposition", `attachment; filename="${downloadName}"`) + + return new Response(resp.body, { ...resp, headers }) +} diff --git a/packages/console/app/src/routes/download/index.css b/packages/console/app/src/routes/download/index.css new file mode 100644 index 00000000000..5178a6e55b9 --- /dev/null +++ b/packages/console/app/src/routes/download/index.css @@ -0,0 +1,751 @@ +::selection { + background: var(--color-background-interactive); + color: var(--color-text-strong); + + @media (prefers-color-scheme: dark) { + background: var(--color-background-interactive); + color: var(--color-text-inverted); + } +} + +[data-page="download"] { + --color-background: hsl(0, 20%, 99%); + --color-background-weak: hsl(0, 8%, 97%); + --color-background-weak-hover: hsl(0, 8%, 94%); + --color-background-strong: hsl(0, 5%, 12%); + --color-background-strong-hover: hsl(0, 5%, 18%); + --color-background-interactive: hsl(62, 84%, 88%); + --color-background-interactive-weaker: hsl(64, 74%, 95%); + + --color-text: hsl(0, 1%, 39%); + --color-text-weak: hsl(0, 1%, 60%); + --color-text-weaker: hsl(30, 2%, 81%); + --color-text-strong: hsl(0, 5%, 12%); + --color-text-inverted: hsl(0, 20%, 99%); + --color-text-success: hsl(119, 100%, 35%); + + --color-border: hsl(30, 2%, 81%); + --color-border-weak: hsl(0, 1%, 85%); + + --color-icon: hsl(0, 1%, 55%); + --color-success: hsl(142, 76%, 36%); + + background: var(--color-background); + font-family: var(--font-mono); + color: var(--color-text); + padding-bottom: 5rem; + overflow-x: hidden; + + @media (prefers-color-scheme: dark) { + --color-background: hsl(0, 9%, 7%); + --color-background-weak: hsl(0, 6%, 10%); + --color-background-weak-hover: hsl(0, 6%, 15%); + --color-background-strong: hsl(0, 15%, 94%); + --color-background-strong-hover: hsl(0, 15%, 97%); + --color-background-interactive: hsl(62, 100%, 90%); + --color-background-interactive-weaker: hsl(60, 20%, 8%); + + --color-text: hsl(0, 4%, 71%); + --color-text-weak: hsl(0, 2%, 49%); + --color-text-weaker: hsl(0, 3%, 28%); + --color-text-strong: hsl(0, 15%, 94%); + --color-text-inverted: hsl(0, 9%, 7%); + --color-text-success: hsl(119, 60%, 72%); + + --color-border: hsl(0, 3%, 28%); + --color-border-weak: hsl(0, 4%, 23%); + + --color-icon: hsl(10, 3%, 43%); + --color-success: hsl(142, 76%, 46%); + } + + /* Header and Footer styles - copied from enterprise */ + [data-component="top"] { + padding: 24px 5rem; + height: 80px; + position: sticky; + top: 0; + display: flex; + justify-content: space-between; + align-items: center; + background: var(--color-background); + border-bottom: 1px solid var(--color-border-weak); + z-index: 10; + + @media (max-width: 60rem) { + padding: 24px 1.5rem; + } + + img { + height: 34px; + width: auto; + } + + [data-component="nav-desktop"] { + ul { + display: flex; + justify-content: space-between; + align-items: center; + gap: 48px; + + @media (max-width: 55rem) { + gap: 32px; + } + + @media (max-width: 48rem) { + gap: 24px; + } + li { + display: inline-block; + a { + text-decoration: none; + span { + color: var(--color-text-weak); + } + } + a:hover { + text-decoration: underline; + text-underline-offset: 2px; + text-decoration-thickness: 1px; + } + [data-slot="cta-button"] { + background: var(--color-background-strong); + color: var(--color-text-inverted); + padding: 8px 16px; + border-radius: 4px; + font-weight: 500; + text-decoration: none; + + @media (max-width: 55rem) { + display: none; + } + } + [data-slot="cta-button"]:hover { + background: var(--color-background-strong-hover); + text-decoration: none; + } + } + } + + @media (max-width: 40rem) { + display: none; + } + } + + [data-component="nav-mobile"] { + button > svg { + color: var(--color-icon); + } + } + + [data-component="nav-mobile-toggle"] { + border: none; + background: none; + outline: none; + height: 40px; + width: 40px; + cursor: pointer; + margin-right: -8px; + } + + [data-component="nav-mobile-toggle"]:hover { + background: var(--color-background-weak); + } + + [data-component="nav-mobile"] { + display: none; + + @media (max-width: 40rem) { + display: block; + + [data-component="nav-mobile-icon"] { + cursor: pointer; + height: 40px; + width: 40px; + display: flex; + align-items: center; + justify-content: center; + } + + [data-component="nav-mobile-menu-list"] { + position: fixed; + background: var(--color-background); + top: 80px; + left: 0; + right: 0; + height: 100vh; + + ul { + list-style: none; + padding: 20px 0; + + li { + a { + text-decoration: none; + padding: 20px; + display: block; + + span { + color: var(--color-text-weak); + } + } + + a:hover { + background: var(--color-background-weak); + } + } + } + } + } + } + + [data-slot="logo dark"] { + display: none; + } + + @media (prefers-color-scheme: dark) { + [data-slot="logo light"] { + display: none; + } + [data-slot="logo dark"] { + display: block; + } + } + } + + [data-component="footer"] { + border-top: 1px solid var(--color-border-weak); + display: flex; + flex-direction: row; + + @media (max-width: 65rem) { + border-bottom: 1px solid var(--color-border-weak); + } + + [data-slot="cell"] { + flex: 1; + text-align: center; + + a { + text-decoration: none; + padding: 2rem 0; + width: 100%; + display: block; + + span { + color: var(--color-text-weak); + + @media (max-width: 40rem) { + display: none; + } + } + } + + a:hover { + background: var(--color-background-weak); + text-decoration: underline; + text-underline-offset: 2px; + text-decoration-thickness: 1px; + } + } + + [data-slot="cell"] + [data-slot="cell"] { + border-left: 1px solid var(--color-border-weak); + + @media (max-width: 40rem) { + border-left: none; + } + } + + @media (max-width: 25rem) { + flex-wrap: wrap; + + [data-slot="cell"] { + flex: 1 0 100%; + border-left: none; + border-top: 1px solid var(--color-border-weak); + } + + [data-slot="cell"]:nth-child(1) { + border-top: none; + } + } + } + + [data-component="container"] { + max-width: 67.5rem; + margin: 0 auto; + border: 1px solid var(--color-border-weak); + border-top: none; + + @media (max-width: 65rem) { + border: none; + } + } + + [data-component="content"] { + padding: 6rem 5rem; + + @media (max-width: 60rem) { + padding: 4rem 1.5rem; + } + } + + [data-component="legal"] { + color: var(--color-text-weak); + text-align: center; + padding: 2rem 5rem; + display: flex; + gap: 32px; + justify-content: center; + + @media (max-width: 60rem) { + padding: 2rem 1.5rem; + } + + a { + color: var(--color-text-weak); + text-decoration: none; + } + + a:hover { + color: var(--color-text); + text-decoration: underline; + } + } + + /* Download Hero Section */ + [data-component="download-hero"] { + display: grid; + grid-template-columns: 260px 1fr; + gap: 4rem; + padding-bottom: 2rem; + margin-bottom: 4rem; + + @media (max-width: 50rem) { + grid-template-columns: 1fr; + gap: 1.5rem; + padding-bottom: 2rem; + margin-bottom: 2rem; + } + + [data-component="hero-icon"] { + display: flex; + justify-content: flex-end; + align-items: center; + + @media (max-width: 40rem) { + display: none; + } + + [data-slot="icon-placeholder"] { + width: 120px; + height: 120px; + background: var(--color-background-weak); + border: 1px solid var(--color-border-weak); + border-radius: 24px; + + @media (max-width: 50rem) { + width: 80px; + height: 80px; + } + } + + img { + width: 120px; + height: 120px; + border-radius: 24px; + box-shadow: + 0 1.467px 2.847px 0 rgba(0, 0, 0, 0.42), + 0 0.779px 1.512px 0 rgba(0, 0, 0, 0.34), + 0 0.324px 0.629px 0 rgba(0, 0, 0, 0.24); + + @media (max-width: 50rem) { + width: 80px; + height: 80px; + border-radius: 16px; + } + } + + @media (max-width: 50rem) { + justify-content: flex-start; + } + } + + [data-component="hero-text"] { + display: flex; + flex-direction: column; + justify-content: center; + + h1 { + font-size: 1.5rem; + font-weight: 700; + color: var(--color-text-strong); + margin-bottom: 4px; + + @media (max-width: 40rem) { + margin-bottom: 1rem; + } + } + + p { + color: var(--color-text); + margin-bottom: 12px; + + @media (max-width: 40rem) { + margin-bottom: 2.5rem; + line-height: 1.6; + } + } + + [data-component="download-button"] { + padding: 8px 20px 8px 16px; + background: var(--color-background-strong); + color: var(--color-text-inverted); + border: none; + border-radius: 4px; + font-weight: 500; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 10px; + transition: all 0.2s ease; + text-decoration: none; + width: fit-content; + + &:hover:not(:disabled) { + background: var(--color-background-strong-hover); + } + + &:active { + transform: scale(0.98); + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + } + } + } + } + + /* Download Sections */ + [data-component="download-section"] { + display: grid; + grid-template-columns: 260px 1fr; + gap: 4rem; + margin-bottom: 4rem; + + @media (max-width: 50rem) { + grid-template-columns: 1fr; + gap: 1rem; + margin-bottom: 3rem; + } + + &:last-child { + margin-bottom: 0; + } + + [data-component="section-label"] { + font-weight: 500; + color: var(--color-text-strong); + padding-top: 1rem; + + span { + color: var(--color-text-weaker); + } + + @media (max-width: 50rem) { + padding-top: 0; + padding-bottom: 0.5rem; + } + } + + [data-component="section-content"] { + display: flex; + flex-direction: column; + gap: 0; + } + } + + /* CLI Rows */ + button[data-component="cli-row"] { + display: flex; + align-items: center; + gap: 12px; + padding: 1rem 0.5rem 1rem 1.5rem; + margin: 0 -0.5rem 0 -1.5rem; + background: none; + border: none; + border-radius: 4px; + width: calc(100% + 2rem); + text-align: left; + cursor: pointer; + transition: background 0.15s ease; + + &:hover { + background: var(--color-background-weak); + } + + code { + font-family: var(--font-mono); + color: var(--color-text-weak); + + strong { + color: var(--color-text-strong); + font-weight: 500; + } + } + + [data-component="copy-status"] { + display: flex; + align-items: center; + opacity: 0; + transition: opacity 0.15s ease; + color: var(--color-icon); + + svg { + width: 18px; + height: 18px; + } + + [data-slot="copy"] { + display: block; + } + + [data-slot="check"] { + display: none; + } + } + + &:hover [data-component="copy-status"] { + opacity: 1; + } + + &[data-copied] [data-component="copy-status"] { + opacity: 1; + + [data-slot="copy"] { + display: none; + } + + [data-slot="check"] { + display: block; + } + } + } + + /* Download Rows */ + [data-component="download-row"] { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 0.5rem 0.75rem 1.5rem; + margin: 0 -0.5rem 0 -1.5rem; + border-radius: 4px; + transition: background 0.15s ease; + + &:hover { + background: var(--color-background-weak); + } + + [data-component="download-info"] { + display: flex; + align-items: center; + gap: 0.75rem; + + [data-slot="icon"] { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + color: var(--color-icon); + + svg { + width: 20px; + height: 20px; + } + + img { + width: 20px; + height: 20px; + } + } + + span { + color: var(--color-text); + } + } + + [data-component="action-button"] { + padding: 6px 16px; + background: var(--color-background); + color: var(--color-text); + border: 1px solid var(--color-border); + border-radius: 4px; + font-weight: 500; + cursor: pointer; + text-decoration: none; + transition: all 0.2s ease; + + &:hover { + background: var(--color-background-weak); + border-color: var(--color-border); + text-decoration: none; + } + + &:active { + transform: scale(0.98); + } + } + } + + a { + color: var(--color-text-strong); + text-decoration: underline; + text-underline-offset: 2px; + text-decoration-thickness: 1px; + + &:hover { + text-decoration-thickness: 2px; + } + } + + /* Narrow screen font sizes */ + @media (max-width: 40rem) { + [data-component="download-section"] { + [data-component="section-label"] { + font-size: 14px; + } + } + + button[data-component="cli-row"] { + margin: 0; + padding: 1rem 0; + width: 100%; + overflow: hidden; + + code { + font-size: 14px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: block; + max-width: calc(100vw - 80px); + } + + [data-component="copy-status"] { + opacity: 1 !important; + flex-shrink: 0; + } + } + + [data-component="download-row"] { + margin: 0; + padding: 0.75rem 0; + + [data-component="download-info"] span { + font-size: 14px; + } + + [data-component="action-button"] { + font-size: 14px; + padding-left: 8px; + padding-right: 8px; + } + } + } + + @media (max-width: 22.5rem) { + [data-slot="hide-narrow"] { + display: none; + } + } + + /* FAQ Section */ + [data-component="faq"] { + border-top: 1px solid var(--color-border-weak); + padding: 4rem 5rem; + margin-top: 4rem; + + @media (max-width: 60rem) { + padding: 3rem 1.5rem; + margin-top: 3rem; + } + + [data-slot="section-title"] { + margin-bottom: 24px; + + h3 { + font-size: 16px; + font-weight: 700; + color: var(--color-text-strong); + margin-bottom: 12px; + } + } + + ul { + padding: 0; + + li { + list-style: none; + margin-bottom: 24px; + line-height: 200%; + } + } + + [data-slot="faq-question"] { + display: flex; + gap: 16px; + margin-bottom: 8px; + color: var(--color-text-strong); + font-weight: 500; + cursor: pointer; + background: none; + border: none; + padding: 0; + align-items: start; + min-height: 24px; + + svg { + margin-top: 2px; + } + + [data-slot="faq-icon-plus"] { + flex-shrink: 0; + color: var(--color-text-weak); + margin-top: 2px; + + [data-closed] & { + display: block; + } + [data-expanded] & { + display: none; + } + } + [data-slot="faq-icon-minus"] { + flex-shrink: 0; + color: var(--color-text-weak); + margin-top: 2px; + + [data-closed] & { + display: none; + } + [data-expanded] & { + display: block; + } + } + [data-slot="faq-question-text"] { + flex-grow: 1; + text-align: left; + } + } + + [data-slot="faq-answer"] { + margin-left: 40px; + margin-bottom: 32px; + line-height: 200%; + } + } +} diff --git a/packages/console/app/src/routes/download/index.tsx b/packages/console/app/src/routes/download/index.tsx new file mode 100644 index 00000000000..d4de97c68da --- /dev/null +++ b/packages/console/app/src/routes/download/index.tsx @@ -0,0 +1,480 @@ +import "./index.css" +import { Title, Meta, Link } from "@solidjs/meta" +import { A, createAsync, query } from "@solidjs/router" +import { Header } from "~/component/header" +import { Footer } from "~/component/footer" +import { IconCopy, IconCheck } from "~/component/icon" +import { Faq } from "~/component/faq" +import desktopAppIcon from "../../asset/lander/opencode-desktop-icon.png" +import { Legal } from "~/component/legal" +import { config } from "~/config" +import { createSignal, onMount, Show, JSX } from "solid-js" +import { DownloadPlatform } from "./types" + +type OS = "macOS" | "Windows" | "Linux" | null + +function detectOS(): OS { + if (typeof navigator === "undefined") return null + const platform = navigator.platform.toLowerCase() + const userAgent = navigator.userAgent.toLowerCase() + + if (platform.includes("mac") || userAgent.includes("mac")) return "macOS" + if (platform.includes("win") || userAgent.includes("win")) return "Windows" + if (platform.includes("linux") || userAgent.includes("linux")) return "Linux" + return null +} + +function getDownloadPlatform(os: OS): DownloadPlatform { + switch (os) { + case "macOS": + return "darwin-aarch64-dmg" + case "Windows": + return "windows-x64-nsis" + case "Linux": + return "linux-x64-deb" + default: + return "darwin-aarch64-dmg" + } +} + +function getDownloadHref(platform: DownloadPlatform) { + return `/download/${platform}` +} + +function IconDownload(props: JSX.SvgSVGAttributes) { + return ( + + + + ) +} + +function CopyStatus() { + return ( + + + + + ) +} + +export default function Download() { + const [detectedOS, setDetectedOS] = createSignal(null) + + onMount(() => { + setDetectedOS(detectOS()) + }) + + const handleCopyClick = (command: string) => (event: Event) => { + const button = event.currentTarget as HTMLButtonElement + navigator.clipboard.writeText(command) + button.setAttribute("data-copied", "") + setTimeout(() => { + button.removeAttribute("data-copied") + }, 1500) + } + return ( +
    + OpenCode | Download + + +
    +
    + +
    +
    +
    + OpenCode Desktop +
    +
    +

    Download OpenCode

    +

    Available in Beta for macOS, Windows, and Linux

    + + + + Download for {detectedOS()} + + +
    +
    + +
    +
    + [1] OpenCode Terminal +
    +
    + + + + + +
    +
    + +
    +
    + [2] OpenCode Desktop (Beta) +
    +
    + +
    +
    + + + + + + + macOS (Apple Silicon) + +
    + + Download + +
    +
    +
    + + + + + + macOS (Intel) +
    + + Download + +
    +
    +
    + + + + + + + + + + + + + Windows (x64) +
    + + Download + +
    +
    +
    + + + + + + Linux (.deb) +
    + + Download + +
    +
    +
    + + + + + + Linux (.rpm) +
    + + Download + +
    +
    +
    + + + + + + Linux (.AppImage) +
    + + Download + +
    +
    +
    + +
    +
    + [3] OpenCode Extensions +
    +
    +
    +
    + + + + + + + + + + + + + VS Code +
    + + Install + +
    + +
    +
    + + + + + + + + + + + + + Cursor +
    + + Install + +
    + +
    +
    + + + + + + Zed +
    + + Install + +
    + +
    +
    + + + + + + Windsurf +
    + + Install + +
    + +
    +
    + + + + + + VSCodium +
    + + Install + +
    +
    +
    + +
    +
    + [4] OpenCode Integrations +
    +
    +
    +
    + + + + + + GitHub +
    + + Install + +
    + +
    +
    + + + + + + GitLab +
    + + Install + +
    +
    +
    +
    + +
    +
    +

    FAQ

    +
    +
      +
    • + + OpenCode is an open source agent that helps you write and run code with any AI model. It's available as + a terminal-based interface, desktop app, or IDE extension. + +
    • +
    • + + The easiest way to get started is to read the intro. + +
    • +
    • + + Not necessarily, but probably. You'll need an AI subscription if you want to connect OpenCode to a paid + provider, although you can work with{" "} + + local models + {" "} + for free. While we encourage users to use Zen, OpenCode works with all popular + providers such as OpenAI, Anthropic, xAI etc. + +
    • +
    • + + Not anymore! OpenCode is now available as an app for your desktop. + +
    • +
    • + + OpenCode is 100% free to use. Any additional costs will come from your subscription to a model provider. + While OpenCode works with any model provider, we recommend using Zen. + +
    • +
    • + + Your data and information is only stored when you create sharable links in OpenCode. Learn more about{" "} + share pages. + +
    • +
    • + + Yes, OpenCode is fully open source. The source code is public on{" "} + + GitHub + {" "} + under the{" "} + + MIT License + + , meaning anyone can use, modify, or contribute to its development. Anyone from the community can file + issues, submit pull requests, and extend functionality. + +
    • +
    +
    + +
    +
    + +
    + ) +} diff --git a/packages/console/app/src/routes/download/types.ts b/packages/console/app/src/routes/download/types.ts new file mode 100644 index 00000000000..916f97022e3 --- /dev/null +++ b/packages/console/app/src/routes/download/types.ts @@ -0,0 +1,4 @@ +export type DownloadPlatform = + | `darwin-${"x64" | "aarch64"}-dmg` + | "windows-x64-nsis" + | `linux-x64-${"deb" | "rpm" | "appimage"}` diff --git a/packages/console/app/src/routes/enterprise/index.css b/packages/console/app/src/routes/enterprise/index.css index 0178e40a27d..7eebf16ce98 100644 --- a/packages/console/app/src/routes/enterprise/index.css +++ b/packages/console/app/src/routes/enterprise/index.css @@ -84,7 +84,16 @@ ul { display: flex; justify-content: space-between; + align-items: center; gap: 48px; + + @media (max-width: 55rem) { + gap: 32px; + } + + @media (max-width: 48rem) { + gap: 24px; + } li { display: inline-block; a { @@ -98,6 +107,25 @@ text-underline-offset: 2px; text-decoration-thickness: 1px; } + [data-slot="cta-button"] { + background: var(--color-background-strong); + color: var(--color-text-inverted); + padding: 8px 16px 8px 10px; + border-radius: 4px; + font-weight: 500; + text-decoration: none; + display: flex; + align-items: center; + gap: 8px; + + @media (max-width: 55rem) { + display: none; + } + } + [data-slot="cta-button"]:hover { + background: var(--color-background-strong-hover); + text-decoration: none; + } } } @@ -289,7 +317,7 @@ [data-component="enterprise-column-1"] { h1 { font-size: 1.5rem; - font-weight: 500; + font-weight: 700; color: var(--color-text-strong); margin-bottom: 1rem; } @@ -441,7 +469,7 @@ h3 { font-size: 16px; - font-weight: 500; + font-weight: 700; color: var(--color-text-strong); margin-bottom: 12px; } diff --git a/packages/console/app/src/routes/index.css b/packages/console/app/src/routes/index.css index 92de172e12c..f100acf8f75 100644 --- a/packages/console/app/src/routes/index.css +++ b/packages/console/app/src/routes/index.css @@ -16,6 +16,8 @@ --color-background-strong-hover: hsl(0, 5%, 18%); --color-background-interactive: hsl(62, 84%, 88%); --color-background-interactive-weaker: hsl(64, 74%, 95%); + --color-surface-raised-base: hsla(0, 100%, 3%, 0.01); + --color-surface-raised-base-active: hsla(0, 100%, 17%, 0.06); --color-text: hsl(0, 1%, 39%); --color-text-weak: hsl(0, 1%, 60%); @@ -24,7 +26,7 @@ --color-text-inverted: hsl(0, 20%, 99%); --color-border: hsl(30, 2%, 81%); - --color-border-weak: hsl(0, 1%, 85%); + --color-border-weak: hsla(0, 100%, 3%, 0.12); --color-icon: hsl(0, 1%, 55%); } @@ -62,6 +64,14 @@ body { } } +[data-slot="br"] { + display: block; + + @media (max-width: 60rem) { + display: none; + } +} + [data-page="opencode"] { background: var(--color-background); --padding: 5rem; @@ -196,6 +206,7 @@ body { [data-component="top"] { padding: 24px var(--padding); height: 80px; + min-height: 80px; position: sticky; top: 0; display: flex; @@ -215,7 +226,16 @@ body { ul { display: flex; justify-content: space-between; + align-items: center; gap: 48px; + + @media (max-width: 55rem) { + gap: 32px; + } + + @media (max-width: 48rem) { + gap: 24px; + } li { display: inline-block; a { @@ -229,6 +249,25 @@ body { text-underline-offset: var(--space-1); text-decoration-thickness: 1px; } + [data-slot="cta-button"] { + background: var(--color-background-strong); + color: var(--color-text-inverted); + padding: 8px 16px 8px 10px; + border-radius: 4px; + font-weight: 500; + text-decoration: none; + display: flex; + align-items: center; + gap: 8px; + + @media (max-width: 55rem) { + display: none; + } + } + [data-slot="cta-button"]:hover { + background: var(--color-background-strong-hover); + text-decoration: none; + } } } @@ -322,7 +361,7 @@ body { display: flex; flex-direction: column; max-width: 100%; - padding: calc(var(--vertical-padding) * 2) var(--padding); + padding: calc(var(--vertical-padding) * 1.5) var(--padding); @media (max-width: 30rem) { padding: var(--vertical-padding) var(--padding); @@ -426,7 +465,7 @@ body { cursor: pointer; align-items: center; color: var(--color-text); - gap: var(--space-1); + gap: 16px; color: var(--color-text); padding: 8px 16px 8px 8px; border-radius: 4px; @@ -465,6 +504,77 @@ body { } } + [data-component="desktop-app-banner"] { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 32px; + + [data-slot="badge"] { + background: var(--color-background-strong); + color: var(--color-text-inverted); + font-weight: 500; + padding: 4px 8px; + line-height: 1; + flex-shrink: 0; + } + + [data-slot="content"] { + display: flex; + align-items: center; + gap: 4px; + } + + [data-slot="text"] { + color: var(--color-text-strong); + line-height: 1.4; + + @media (max-width: 30.625rem) { + display: none; + } + } + + [data-slot="platforms"] { + @media (max-width: 49.125rem) { + display: none; + } + } + + [data-slot="link"] { + color: var(--color-text-weak); + white-space: nowrap; + text-decoration: none; + + @media (max-width: 30.625rem) { + display: none; + } + } + + [data-slot="link"]:hover { + color: var(--color-text); + text-decoration: underline; + text-underline-offset: 2px; + text-decoration-thickness: 1px; + } + + [data-slot="link-mobile"] { + display: none; + color: var(--color-text-strong); + white-space: nowrap; + text-decoration: none; + + @media (max-width: 30.625rem) { + display: inline; + } + } + + [data-slot="link-mobile"]:hover { + text-decoration: underline; + text-underline-offset: 2px; + text-decoration-thickness: 1px; + } + } + [data-slot="hero-copy"] { [data-slot="releases"] { background: none; @@ -492,7 +602,7 @@ body { h1 { font-size: 38px; color: var(--color-text-strong); - font-weight: 500; + font-weight: 700; margin-bottom: 8px; @media (max-width: 60rem) { @@ -502,7 +612,7 @@ body { p { color: var(--color-text); - margin-bottom: 40px; + margin-bottom: 32px; max-width: 82%; @media (max-width: 50rem) { @@ -518,7 +628,6 @@ body { border-radius: 4px; font-weight: 500; cursor: pointer; - margin-bottom: 80px; display: flex; width: fit-content; gap: 12px; @@ -596,7 +705,7 @@ body { h3 { font-size: 16px; - font-weight: 500; + font-weight: 700; color: var(--color-text-strong); margin-bottom: 12px; } @@ -701,7 +810,7 @@ body { [data-slot="privacy-title"] { h3 { font-size: 16px; - font-weight: 500; + font-weight: 700; color: var(--color-text-strong); margin-bottom: 12px; } @@ -727,7 +836,7 @@ body { [data-slot="zen-cta-copy"] { strong { color: var(--color-text-strong); - font-weight: 500; + font-weight: 700; margin-bottom: 16px; display: block; } diff --git a/packages/console/app/src/routes/index.tsx b/packages/console/app/src/routes/index.tsx index 56f07856224..227021b891b 100644 --- a/packages/console/app/src/routes/index.tsx +++ b/packages/console/app/src/routes/index.tsx @@ -1,6 +1,6 @@ import "./index.css" import { Title, Meta, Link } from "@solidjs/meta" -// import { HttpHeader } from "@solidjs/start" +//import { HttpHeader } from "@solidjs/start" import video from "../asset/lander/opencode-min.mp4" import videoPoster from "../asset/lander/opencode-poster.png" import { IconCopy, IconCheck } from "../component/icon" @@ -52,17 +52,33 @@ export default function Home() {
    +
    + New +
    + + Desktop app available in beta on macOS, Windows, and Linux. + + + Download now + + + Download the desktop beta now + +
    +
    +
    - - What’s new in {release()?.name ?? "the latest release"} - -

    The open source coding agent

    + {/**/} + {/* What’s new in {release()?.name ?? "the latest release"}*/} + {/**/} +

    The open source AI coding agent

    - OpenCode includes free models or connect from any provider to
    - use other models, including Claude, GPT, Gemini and more. + Free models included or connect any model from any provider, including + Claude, GPT, Gemini and more.

    -

    Install and use. No account, no email, and no credit card.

    -

    - Available in terminal, web, and desktop (coming soon). -
    - Extensions for VS Code, Cursor, Windsurf, and more. -

    @@ -157,15 +168,9 @@ export default function Home() {

    What is OpenCode?

    -

    OpenCode is an open source agent that helps you write and run code directly from the terminal.

    +

    OpenCode is an open source agent that helps you write code in your terminal, IDE, or desktop.

      -
    • - [*] -
      - Native TUI A responsive, native, themeable terminal UI -
      -
    • [*]
      @@ -199,7 +204,7 @@ export default function Home() {
    • [*]
      - Any editor OpenCode runs in your terminal, pair it with any IDE + Any editor Available as a terminal interface, desktop app, and IDE extension
    @@ -223,7 +228,7 @@ export default function Home() { [*]

    With over {config.github.starsFormatted.full} GitHub stars,{" "} - {config.stats.contributors} contributors, and almost{" "} + {config.stats.contributors} contributors, and over{" "} {config.stats.commits} commits, OpenCode is used and trusted by over{" "} {config.stats.monthlyUsers} developers every month.

    @@ -651,9 +656,8 @@ export default function Home() {
    • - OpenCode is an open source agent that helps you write and run code directly from the terminal. You can - pair OpenCode with any AI model, and because it’s terminal-based you can pair it with your preferred - code editor. + OpenCode is an open source agent that helps you write and run code with any AI model. It's available + as a terminal-based interface, desktop app, or IDE extension.
    • @@ -663,29 +667,38 @@ export default function Home() {
    • - Not necessarily, but probably. You’ll need an AI subscription if you want to connect OpenCode to a - paid provider, although you can work with{" "} + Not necessarily, OpenCode comes with a set of free models that you can use without creating an + account. Aside from these, you can use any of the popular coding models by creating a{" "} + Zen account. While we encourage users to use Zen, OpenCode also works with all + popular providers such as OpenAI, Anthropic, xAI etc. You can even connect your{" "} local models - {" "} - for free. While we encourage users to use Zen, OpenCode works with all popular - providers such as OpenAI, Anthropic, xAI etc. + + . + +
    • +
    • + + Yes, OpenCode supports subscription plans from all major providers. You can use your Claude Pro/Max, + ChatGPT Plus/Pro, or GitHub Copilot subscriptions. Learn more + .
    • - Yes, for now. We are actively working on a desktop app. Join the waitlist for early access. + Not anymore! OpenCode is now available as an app for your desktop.
    • - OpenCode is 100% free to use. Any additional costs will come from your subscription to a model - provider. While OpenCode works with any model provider, we recommend using Zen. + OpenCode is 100% free to use. It also comes with a set of free models. There might be additional costs + if you connect any other provider.
    • - Your data and information is only stored when you create sharable links in OpenCode. Learn more about{" "} + Your data and information is only stored when you use our free models or create sharable links. Learn + more about our models and{" "} share pages.
    • @@ -745,6 +758,17 @@ export default function Home() { />
    +
    + + + +
    +
    + + + +
    Learn about Zen diff --git a/packages/console/app/src/routes/legal/privacy-policy/index.css b/packages/console/app/src/routes/legal/privacy-policy/index.css new file mode 100644 index 00000000000..dbc9f2aa197 --- /dev/null +++ b/packages/console/app/src/routes/legal/privacy-policy/index.css @@ -0,0 +1,343 @@ +[data-component="privacy-policy"] { + max-width: 800px; + margin: 0 auto; + line-height: 1.7; +} + +[data-component="privacy-policy"] h1 { + font-size: 2rem; + font-weight: 700; + color: var(--color-text-strong); + margin-bottom: 0.5rem; + margin-top: 0; +} + +[data-component="privacy-policy"] .effective-date { + font-size: 0.95rem; + color: var(--color-text-weak); + margin-bottom: 2rem; +} + +[data-component="privacy-policy"] h2 { + font-size: 1.5rem; + font-weight: 600; + color: var(--color-text-strong); + margin-top: 3rem; + margin-bottom: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--color-border-weak); +} + +[data-component="privacy-policy"] h2:first-of-type { + margin-top: 2rem; +} + +[data-component="privacy-policy"] h3 { + font-size: 1.25rem; + font-weight: 600; + color: var(--color-text-strong); + margin-top: 2rem; + margin-bottom: 1rem; +} + +[data-component="privacy-policy"] h4 { + font-size: 1.1rem; + font-weight: 600; + color: var(--color-text-strong); + margin-top: 1.5rem; + margin-bottom: 0.75rem; +} + +[data-component="privacy-policy"] p { + margin-bottom: 1rem; + color: var(--color-text); +} + +[data-component="privacy-policy"] ul, +[data-component="privacy-policy"] ol { + margin-bottom: 1rem; + padding-left: 1.5rem; + color: var(--color-text); +} + +[data-component="privacy-policy"] li { + margin-bottom: 0.5rem; + line-height: 1.7; +} + +[data-component="privacy-policy"] ul ul, +[data-component="privacy-policy"] ul ol, +[data-component="privacy-policy"] ol ul, +[data-component="privacy-policy"] ol ol { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +[data-component="privacy-policy"] a { + color: var(--color-text-strong); + text-decoration: underline; + text-underline-offset: 2px; + text-decoration-thickness: 1px; + word-break: break-word; +} + +[data-component="privacy-policy"] a:hover { + text-decoration-thickness: 2px; +} + +[data-component="privacy-policy"] strong { + font-weight: 600; + color: var(--color-text-strong); +} + +[data-component="privacy-policy"] .table-wrapper { + overflow-x: auto; + margin: 1.5rem 0; +} + +[data-component="privacy-policy"] table { + width: 100%; + border-collapse: collapse; + border: 1px solid var(--color-border); +} + +[data-component="privacy-policy"] th, +[data-component="privacy-policy"] td { + padding: 0.75rem 1rem; + text-align: left; + border: 1px solid var(--color-border); + vertical-align: top; +} + +[data-component="privacy-policy"] th { + background: var(--color-background-weak); + font-weight: 600; + color: var(--color-text-strong); +} + +[data-component="privacy-policy"] td { + color: var(--color-text); +} + +[data-component="privacy-policy"] td ul { + margin: 0; + padding-left: 1.25rem; +} + +[data-component="privacy-policy"] td li { + margin-bottom: 0.25rem; +} + +/* Mobile responsiveness */ +@media (max-width: 60rem) { + [data-component="privacy-policy"] { + padding: 0; + } + + [data-component="privacy-policy"] h1 { + font-size: 1.75rem; + } + + [data-component="privacy-policy"] h2 { + font-size: 1.35rem; + margin-top: 2.5rem; + } + + [data-component="privacy-policy"] h3 { + font-size: 1.15rem; + } + + [data-component="privacy-policy"] h4 { + font-size: 1rem; + } + + [data-component="privacy-policy"] table { + font-size: 0.9rem; + } + + [data-component="privacy-policy"] th, + [data-component="privacy-policy"] td { + padding: 0.5rem 0.75rem; + } +} + +html { + scroll-behavior: smooth; +} + +[data-component="privacy-policy"] [id] { + scroll-margin-top: 100px; +} + +@media print { + @page { + margin: 2cm; + size: letter; + } + + [data-component="top"], + [data-component="footer"], + [data-component="legal"] { + display: none !important; + } + + [data-page="legal"] { + background: white !important; + padding: 0 !important; + } + + [data-component="container"] { + max-width: none !important; + border: none !important; + margin: 0 !important; + } + + [data-component="content"], + [data-component="brand-content"] { + padding: 0 !important; + margin: 0 !important; + } + + [data-component="privacy-policy"] { + max-width: none !important; + margin: 0 !important; + padding: 0 !important; + } + + [data-component="privacy-policy"] * { + color: black !important; + background: transparent !important; + } + + [data-component="privacy-policy"] h1 { + font-size: 24pt; + margin-top: 0; + margin-bottom: 12pt; + page-break-after: avoid; + } + + [data-component="privacy-policy"] h2 { + font-size: 18pt; + border-top: 2pt solid black !important; + padding-top: 12pt; + margin-top: 24pt; + margin-bottom: 8pt; + page-break-after: avoid; + page-break-before: auto; + } + + [data-component="privacy-policy"] h2:first-of-type { + margin-top: 16pt; + } + + [data-component="privacy-policy"] h3 { + font-size: 14pt; + margin-top: 16pt; + margin-bottom: 8pt; + page-break-after: avoid; + } + + [data-component="privacy-policy"] h4 { + font-size: 12pt; + margin-top: 12pt; + margin-bottom: 6pt; + page-break-after: avoid; + } + + [data-component="privacy-policy"] p { + font-size: 11pt; + line-height: 1.5; + margin-bottom: 8pt; + orphans: 3; + widows: 3; + } + + [data-component="privacy-policy"] .effective-date { + font-size: 10pt; + margin-bottom: 16pt; + } + + [data-component="privacy-policy"] ul, + [data-component="privacy-policy"] ol { + margin-bottom: 8pt; + page-break-inside: auto; + } + + [data-component="privacy-policy"] li { + font-size: 11pt; + line-height: 1.5; + margin-bottom: 4pt; + page-break-inside: avoid; + } + + [data-component="privacy-policy"] a { + color: black !important; + text-decoration: underline; + } + + [data-component="privacy-policy"] .table-wrapper { + overflow: visible !important; + margin: 12pt 0; + } + + [data-component="privacy-policy"] table { + border: 2pt solid black !important; + page-break-inside: avoid; + width: 100% !important; + font-size: 10pt; + } + + [data-component="privacy-policy"] th, + [data-component="privacy-policy"] td { + border: 1pt solid black !important; + padding: 6pt 8pt !important; + background: white !important; + } + + [data-component="privacy-policy"] th { + background: #f0f0f0 !important; + font-weight: bold; + page-break-after: avoid; + } + + [data-component="privacy-policy"] tr { + page-break-inside: avoid; + } + + [data-component="privacy-policy"] td ul { + margin: 2pt 0; + padding-left: 12pt; + } + + [data-component="privacy-policy"] td li { + margin-bottom: 2pt; + font-size: 9pt; + } + + [data-component="privacy-policy"] strong { + font-weight: bold; + color: black !important; + } + + [data-component="privacy-policy"] h1, + [data-component="privacy-policy"] h2, + [data-component="privacy-policy"] h3, + [data-component="privacy-policy"] h4 { + page-break-inside: avoid; + page-break-after: avoid; + } + + [data-component="privacy-policy"] h2 + p, + [data-component="privacy-policy"] h3 + p, + [data-component="privacy-policy"] h4 + p, + [data-component="privacy-policy"] h2 + ul, + [data-component="privacy-policy"] h3 + ul, + [data-component="privacy-policy"] h4 + ul { + page-break-before: avoid; + } + + [data-component="privacy-policy"] table, + [data-component="privacy-policy"] .table-wrapper { + page-break-inside: avoid; + } +} diff --git a/packages/console/app/src/routes/legal/privacy-policy/index.tsx b/packages/console/app/src/routes/legal/privacy-policy/index.tsx new file mode 100644 index 00000000000..8b30ba14e0e --- /dev/null +++ b/packages/console/app/src/routes/legal/privacy-policy/index.tsx @@ -0,0 +1,1512 @@ +import "../../brand/index.css" +import "./index.css" +import { Title, Meta, Link } from "@solidjs/meta" +import { Header } from "~/component/header" +import { config } from "~/config" +import { Footer } from "~/component/footer" +import { Legal } from "~/component/legal" + +export default function PrivacyPolicy() { + return ( +
    + OpenCode | Privacy Policy + + +
    +
    + +
    +
    +
    +

    Privacy Policy

    +

    Effective date: Dec 16, 2025

    + +

    + At OpenCode, we take your privacy seriously. Please read this Privacy Policy to learn how we treat your + personal data.{" "} + + By using or accessing our Services in any manner, you acknowledge that you accept the practices and + policies outlined below, and you hereby consent that we will collect, use and disclose your + information as described in this Privacy Policy. + +

    + +

    + Remember that your use of OpenCode is at all times subject to our Terms of Use,{" "} + https://opencode.ai/legal/terms-of-service, which incorporates + this Privacy Policy. Any terms we use in this Policy without defining them have the definitions given to + them in the Terms of Use. +

    + +

    You may print a copy of this Privacy Policy by clicking the print button in your browser.

    + +

    + As we continually work to improve our Services, we may need to change this Privacy Policy from time to + time. We will alert you of material changes by placing a notice on the OpenCode website, by sending you + an email and/or by some other means. Please note that if you've opted not to receive legal notice emails + from us (or you haven't provided us with your email address), those legal notices will still govern your + use of the Services, and you are still responsible for reading and understanding them. If you use the + Services after any changes to the Privacy Policy have been posted, that means you agree to all of the + changes. +

    + +

    Privacy Policy Table of Contents

    + + +

    What this Privacy Policy Covers

    +

    + This Privacy Policy covers how we treat Personal Data that we gather when you access or use our + Services. "Personal Data" means any information that identifies or relates to a particular individual + and also includes information referred to as "personally identifiable information" or "personal + information" under applicable data privacy laws, rules or regulations. This Privacy Policy does not + cover the practices of companies we don't own or control or people we don't manage. +

    + +

    Personal Data

    + +

    Categories of Personal Data We Collect

    +

    + This chart details the categories of Personal Data that we collect and have collected over the past 12 + months: +

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Category of Personal Data (and Examples)Business or Commercial Purpose(s) for CollectionCategories of Third Parties With Whom We Disclose this Personal Data
    + Profile or Contact Data such as first and last name, email, phone number and + mailing address. + +
      +
    • Providing, Customizing and Improving the Services
    • +
    • Marketing the Services
    • +
    • Corresponding with You
    • +
    +
    +
      +
    • Service Providers
    • +
    • Business Partners
    • +
    • Parties You Authorize, Access or Authenticate
    • +
    +
    + Payment Data such as financial account information, payment card type, full + number of payment card, last 4 digits of payment card, bank account information, billing + address, billing phone number and billing email + +
      +
    • Providing, Customizing and Improving the Services
    • +
    • Marketing the Services
    • +
    • Corresponding with You
    • +
    +
    +
      +
    • Service Providers (specifically our payment processing partner)
    • +
    • Business Partners
    • +
    • Parties You Authorize, Access or Authenticate
    • +
    +
    + Device/IP Data such as IP address, device ID, domain server, type of + device/operating system/browser used to access the Services. + +
      +
    • Providing, Customizing and Improving the Services
    • +
    • Marketing the Services
    • +
    • Corresponding with You
    • +
    +
    +
      +
    • None
    • +
    • Service Providers
    • +
    • Business Partners
    • +
    • Parties You Authorize, Access or Authenticate
    • +
    +
    + Other Identifying Information that You Voluntarily Choose to Provide such as + information included in conversations or prompts that you submit to AI + +
      +
    • Providing, Customizing and Improving the Services
    • +
    • Marketing the Services
    • +
    • Corresponding with You
    • +
    +
    +
      +
    • Service Providers
    • +
    • Business Partners
    • +
    • Parties You Authorize, Access or Authenticate
    • +
    +
    +
    + +

    Our Commercial or Business Purposes for Collecting Personal Data

    + +

    Providing, Customizing and Improving the Services

    +
      +
    • Creating and managing your account or other user profiles.
    • +
    • Providing you with the products, services or information you request.
    • +
    • Meeting or fulfilling the reason you provided the information to us.
    • +
    • Providing support and assistance for the Services.
    • +
    • + Improving the Services, including testing, research, internal analytics and product development. +
    • +
    • Doing fraud protection, security and debugging.
    • +
    • + Carrying out other business purposes stated when collecting your Personal Data or as otherwise set + forth in applicable data privacy laws, such as the California Consumer Privacy Act, as amended by the + California Privacy Rights Act of 2020 (the "CCPA"), the Colorado Privacy Act (the "CPA"), the + Connecticut Data Privacy Act (the "CTDPA"), the Delaware Personal Data Privacy Act (the "DPDPA"), the + Iowa Consumer Data Protection Act (the "ICDPA"), the Montana Consumer Data Privacy Act ("MCDPA"), the + Nebraska Data Privacy Act (the "NDPA"), the New Hampshire Privacy Act (the "NHPA"), the New Jersey + Privacy Act (the "NJPA"), the Oregon Consumer Privacy Act ("OCPA"), the Texas Data Privacy and + Security Act ("TDPSA"), the Utah Consumer Privacy Act (the "UCPA"), or the Virginia Consumer Data + Protection Act (the "VCDPA") (collectively, the "State Privacy Laws"). +
    • +
    + +

    Marketing the Services

    +
      +
    • Marketing and selling the Services.
    • +
    + +

    Corresponding with You

    +
      +
    • + Responding to correspondence that we receive from you, contacting you when necessary or requested, and + sending you information about OpenCode. +
    • +
    • Sending emails and other communications according to your preferences.
    • +
    + +

    Other Permitted Purposes for Processing Personal Data

    +

    + In addition, each of the above referenced categories of Personal Data may be collected, used, and + disclosed with the government, including law enforcement, or other parties to meet certain legal + requirements and enforcing legal terms including: fulfilling our legal obligations under applicable law, + regulation, court order or other legal process, such as preventing, detecting and investigating security + incidents and potentially illegal or prohibited activities; protecting the rights, property or safety of + you, OpenCode or another party; enforcing any agreements with you; responding to claims that any posting + or other content violates third-party rights; and resolving disputes. +

    + +

    + We will not collect additional categories of Personal Data or use the Personal Data we collected for + materially different, unrelated or incompatible purposes without providing you notice or obtaining your + consent. +

    + +

    Categories of Sources of Personal Data

    +

    We collect Personal Data about you from the following categories of sources:

    + +

    You

    +
      +
    • + When you provide such information directly to us. +
        +
      • When you create an account or use our interactive tools and Services.
      • +
      • + When you voluntarily provide information in free-form text boxes through the Services or through + responses to surveys or questionnaires. +
      • +
      • When you send us an email or otherwise contact us.
      • +
      +
    • +
    • + When you use the Services and such information is collected automatically. +
        +
      • Through Cookies (defined in the "Tracking Tools and Opt-Out" section below).
      • +
      • + If you download and install certain applications and software we make available, we may receive + and collect information transmitted from your computing device for the purpose of providing you + the relevant Services, such as information regarding when you are logged on and available to + receive updates or alert notices. +
      • +
      +
    • +
    + +

    Public Records

    +
      +
    • From the government.
    • +
    + +

    Third Parties

    +
      +
    • + Vendors +
        +
      • + We may use analytics providers to analyze how you interact and engage with the Services, or third + parties may help us provide you with customer support. +
      • +
      • We may use vendors to obtain information to generate leads and create user profiles.
      • +
      +
    • +
    + +

    How We Disclose Your Personal Data

    +

    + We disclose your Personal Data to the categories of service providers and other parties listed in this + section. Depending on state laws that may be applicable to you, some of these disclosures may constitute + a "sale" of your Personal Data. For more information, please refer to the state-specific sections below. +

    + +

    Service Providers

    +

    + These parties help us provide the Services or perform business functions on our behalf. They include: +

    +
      +
    • Hosting, technology and communication providers.
    • +
    • Analytics providers for web traffic or usage of the site.
    • +
    • Security and fraud prevention consultants.
    • +
    • Support and customer service vendors.
    • +
    + +

    Business Partners

    +

    These parties partner with us in offering various services. They include:

    +
      +
    • Businesses that you have a relationship with.
    • +
    • Companies that we partner with to offer joint promotional offers or opportunities.
    • +
    + +

    Parties You Authorize, Access or Authenticate

    +
      +
    • Home buyers
    • +
    + +

    Legal Obligations

    +

    + We may disclose any Personal Data that we collect with third parties in conjunction with any of the + activities set forth under "Other Permitted Purposes for Processing Personal Data" section above. +

    + +

    Business Transfers

    +

    + All of your Personal Data that we collect may be transferred to a third party if we undergo a merger, + acquisition, bankruptcy or other transaction in which that third party assumes control of our business + (in whole or in part). +

    + +

    Data that is Not Personal Data

    +

    + We may create aggregated, de-identified or anonymized data from the Personal Data we collect, including + by removing information that makes the data personally identifiable to a particular user. We may use + such aggregated, de-identified or anonymized data and disclose it with third parties for our lawful + business purposes, including to analyze, build and improve the Services and promote our business, + provided that we will not disclose such data in a manner that could identify you. +

    + +

    Tracking Tools and Opt-Out

    +

    + The Services use cookies and similar technologies such as pixel tags, web beacons, clear GIFs and + JavaScript (collectively, "Cookies") to enable our servers to recognize your web browser, tell us how + and when you visit and use our Services, analyze trends, learn about our user base and operate and + improve our Services. Cookies are small pieces of data– usually text files – placed on your computer, + tablet, phone or similar device when you use that device to access our Services. We may also supplement + the information we collect from you with information received from third parties, including third + parties that have placed their own Cookies on your device(s). +

    + +

    + Please note that because of our use of Cookies, the Services do not support "Do Not Track" requests sent + from a browser at this time. +

    + +

    We use the following types of Cookies:

    + +
      +
    • + Essential Cookies. Essential Cookies are required for providing you with features or + services that you have requested. For example, certain Cookies enable you to log into secure areas of + our Services. Disabling these Cookies may make certain features and services unavailable. +
    • +
    • + Functional Cookies. Functional Cookies are used to record your choices and settings + regarding our Services, maintain your preferences over time and recognize you when you return to our + Services. These Cookies help us to personalize our content for you, greet you by name and remember + your preferences (for example, your choice of language or region). +
    • +
    • + Performance/Analytical Cookies. Performance/Analytical Cookies allow us to understand + how visitors use our Services. They do this by collecting information about the number of visitors to + the Services, what pages visitors view on our Services and how long visitors are viewing pages on the + Services. Performance/Analytical Cookies also help us measure the performance of our advertising + campaigns in order to help us improve our campaigns and the Services' content for those who engage + with our advertising. For example, Google LLC ("Google") uses cookies in connection with its Google + Analytics services. Google's ability to use and disclose information collected by Google Analytics + about your visits to the Services is subject to the Google Analytics Terms of Use and the Google + Privacy Policy. You have the option to opt-out of Google's use of Cookies by visiting the Google + advertising opt-out page at{" "} + www.google.com/privacy_ads.html or the Google + Analytics Opt-out Browser Add-on at{" "} + https://tools.google.com/dlpage/gaoptout/. +
    • +
    + +

    + You can decide whether or not to accept Cookies through your internet browser's settings. Most browsers + have an option for turning off the Cookie feature, which will prevent your browser from accepting new + Cookies, as well as (depending on the sophistication of your browser software) allow you to decide on + acceptance of each new Cookie in a variety of ways. You can also delete all Cookies that are already on + your device. If you do this, however, you may have to manually adjust some preferences every time you + visit our website and some of the Services and functionalities may not work. +

    + +

    + To find out more information about Cookies generally, including information about how to manage and + delete Cookies, please visit{" "} + http://www.allaboutcookies.org/. +

    + +

    Data Security

    +

    + We seek to protect your Personal Data from unauthorized access, use and disclosure using appropriate + physical, technical, organizational and administrative security measures based on the type of Personal + Data and how we are processing that data. You should also help protect your data by appropriately + selecting and protecting your password and/or other sign-on mechanism; limiting access to your computer + or device and browser; and signing off after you have finished accessing your account. Although we work + to protect the security of your account and other data that we hold in our records, please be aware that + no method of transmitting data over the internet or storing data is completely secure. +

    + +

    Data Retention

    +

    + We retain Personal Data about you for as long as necessary to provide you with our Services or to + perform our business or commercial purposes for collecting your Personal Data. When establishing a + retention period for specific categories of data, we consider who we collected the data from, our need + for the Personal Data, why we collected the Personal Data, and the sensitivity of the Personal Data. In + some cases we retain Personal Data for longer, if doing so is necessary to comply with our legal + obligations, resolve disputes or collect fees owed, or is otherwise permitted or required by applicable + law, rule or regulation. We may further retain information in an anonymous or aggregated form where that + information would not identify you personally. +

    + +

    Personal Data of Children

    +

    + As noted in the Terms of Use, we do not knowingly collect or solicit Personal Data from children under + 18 years of age; if you are a child under the age of 18, please do not attempt to register for or + otherwise use the Services or send us any Personal Data. If we learn we have collected Personal Data + from a child under 18 years of age, we will delete that information as quickly as possible. If you + believe that a child under 18 years of age may have provided Personal Data to us, please contact us at{" "} + contact@anoma.ly. +

    + +

    California Resident Rights

    +

    + If you are a California resident, you have the rights set forth in this section. Please see the + "Exercising Your Rights under the State Privacy Laws" section below for instructions regarding how to + exercise these rights. Please note that we may process Personal Data of our customers' end users or + employees in connection with our provision of certain services to our customers. If we are processing + your Personal Data as a service provider, you should contact the entity that collected your Personal + Data in the first instance to address your rights with respect to such data. Additionally, please note + that these rights are subject to certain conditions and exceptions under applicable law, which may + permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are a California resident, the portion that is more protective of Personal Data shall control to the + extent of such conflict. If you have any questions about this section or whether any of the following + rights apply to you, please contact us at contact@anoma.ly. +

    + +

    Access

    +

    + You have the right to request certain information about our collection and use of your Personal Data. In + response, we will provide you with the following information in the past 12 months: +

    +
      +
    • The categories of Personal Data that we have collected about you.
    • +
    • The categories of sources from which that Personal Data was collected.
    • +
    • The business or commercial purpose for collecting or selling your Personal Data.
    • +
    • The categories of third parties with whom we have shared your Personal Data.
    • +
    • The specific pieces of Personal Data that we have collected about you.
    • +
    + +

    + If we have disclosed your Personal Data to any third parties for a business purpose over the past 12 + months, we will identify the categories of Personal Data shared with each category of third party + recipient. If we have sold your Personal Data over the past 12 months, we will identify the categories + of Personal Data sold to each category of third party recipient. +

    + +

    + You may request the above information beyond the 12-month period, but no earlier than January 1, 2022. + If you do make such a request, we are required to provide that information unless doing so proves + impossible or would involve disproportionate effort. +

    + +

    Deletion

    +

    + You have the right to request that we delete the Personal Data that we have collected from you. Under + the CCPA, this right is subject to certain exceptions: for example, we may need to retain your Personal + Data to provide you with the Services or complete a transaction or other action you have requested, or + if deletion of your Personal Data involves disproportionate effort. If your deletion request is subject + to one of these exceptions, we may deny your deletion request. +

    + +

    Correction

    +

    + You have the right to request that we correct any inaccurate Personal Data we have collected about you. + Under the CCPA, this right is subject to certain exceptions: for example, if we decide, based on the + totality of circumstances related to your Personal Data, that such data is correct. If your correction + request is subject to one of these exceptions, we may deny your request. +

    + +

    Personal Data Sales Opt-Out

    +

    + We will not sell or share your Personal Data, and have not done so over the last 12 months. To our + knowledge, we do not sell or share the Personal Data of minors under 13 years of age or of consumers + under 16 years of age. +

    + +

    Limit the Use of Sensitive Personal Information

    +

    + Consumers have certain rights over the processing of their Sensitive Personal Information. However, we + do not collect Sensitive Personal Information. +

    + +

    We Will Not Discriminate Against You for Exercising Your Rights Under the CCPA

    +

    + We will not discriminate against you for exercising your rights under the CCPA. We will not deny you our + goods or services, charge you different prices or rates, or provide you a lower quality of goods and + services if you exercise your rights under the CCPA. However, we may offer different tiers of our + Services as allowed by applicable data privacy laws (including the CCPA) with varying prices, rates or + levels of quality of the goods or services you receive related to the value of Personal Data that we + receive from you. +

    + +

    Colorado Resident Rights

    +

    + If you are a Colorado resident, you have the rights set forth under the Colorado Privacy Act ("CPA"). + Please see the "Exercising Your Rights under the State Privacy Laws" section below for instructions + regarding how to exercise these rights. Please note that we may process Personal Data of our customers' + end users or employees in connection with our provision of certain services to our customers. If we are + processing your Personal Data as a service provider, you should contact the entity that collected your + Personal Data in the first instance to address your rights with respect to such data. Additionally, + please note that these rights are subject to certain conditions and exceptions under applicable law, + which may permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are a Colorado resident, the portion that is more protective of Personal Data shall control to the + extent of such conflict. If you have any questions about this section or whether any of the following + rights apply to you, please contact us at contact@anoma.ly. +

    + +

    Access and Portability

    +

    + You have the right to request confirmation of whether or not we are processing your Personal Data and to + access and request a copy of your Personal Data in a machine-readable format, to the extent technically + feasible, twice within a calendar year. +

    + +

    Correction

    +

    + You have the right to correct inaccuracies in your Personal Data, to the extent such correction is + appropriate in consideration of the nature of such data and our purposes of processing your Personal + Data. +

    + +

    Deletion

    +

    You have the right to delete Personal Data concerning you.

    + +

    Personal Data Sales Opt-Out

    +

    + We do not currently sell or process for the purposes of targeted advertising your Personal Data as + defined under the CPA. +

    + +

    Profiling Opt-Out

    +

    + We do not process your Personal Data for "Profiling" to make "Decisions" under the CPA. "Profiling" + means any form of automated processing performed on personal data to evaluate, analyze or predict + personal aspects related to an identified or identifiable individual's economic situation, health, + personal preferences, interests, reliability, behavior, location or movements. "Decision" means any + "Decisions that produce legal or similarly significant effects concerning a Consumer," as defined in the + CPA that concern you. +

    + +

    Consent or "Opt-in" Required and How to Withdraw

    +

    + We may seek your consent to collect or process certain Personal Data, including: 1) Sensitive Data, 2) + Personal Data from a known child under 13 years of age, 3) to sell, or process Personal Data for + Targeted Advertising or Profiling after you exercise your right to opt-out, or 4) Personal Data for + Secondary Use. +

    + +

    + If you would like to withdraw your consent, please follow the instructions under the "Exercising Your + Rights under the State Privacy Laws" section. +

    + +

    We Will Not Discriminate Against You

    +

    + We will not process your personal data in violation of state and federal laws that prohibit unlawful + discrimination against consumers. +

    + +

    Connecticut Resident Rights

    +

    + If you are a Connecticut resident, you have the rights set forth under the Connecticut Data Privacy Act + ("CTDPA"). Please see the "Exercising Your Rights under the State Privacy Laws" section below for + instructions regarding how to exercise these rights. Please note that we may process Personal Data of + our customers' end users or employees in connection with our provision of certain services to our + customers. If we are processing your Personal Data as a service provider, you should contact the entity + that collected your Personal Data in the first instance to address your rights with respect to such + data. Additionally, please note that these rights are subject to certain conditions and exceptions under + applicable law, which may permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are a Connecticut resident, the portion that is more protective of Personal Data shall control to the + extent of such conflict. If you have any questions about this section or whether any of the following + rights apply to you, please contact us at contact@anoma.ly. +

    + +

    Access and Portability

    +

    + You have the right to request confirmation of whether or not we are processing your Personal Data and to + access your Personal Data and request a copy of your Personal Data in a machine-readable format, to the + extent technically feasible. +

    + +

    Correction

    +

    + You have the right to correct inaccuracies in your Personal Data, to the extent such correction is + appropriate in consideration of the nature of such data and our purposes of processing your Personal + Data. +

    + +

    Deletion

    +

    You have the right to delete Personal Data you have provided to us or we have obtained about you.

    + +

    Personal Data Sales Opt-Out

    +

    + We do not currently sell or process for the purposes of targeted advertising your Personal Data as + defined under the CTDPA. +

    + +

    Profiling Opt-Out

    +

    + We do not process your Personal Data for "Profiling" as defined under the CTDPA. "Profiling" means any + form of automated processing performed on personal data to evaluate, analyze or predict personal aspects + related to an identified or identifiable individual's economic situation, health, personal preferences, + interests, reliability, behavior, location or movements. +

    + +

    Consent or "Opt-in" Required and How to Withdraw

    +

    + We may seek your consent to collect or process certain Personal Data, including: 1) Sensitive Data, 2) + Sensitive Data from a known child under 13 years of age, or 3) to sell, or process Personal Data for + Targeted Advertising of a consumer at least 13 years of age but younger than 16 years of age. +

    + +

    However, we currently do not collect or process your Personal Data as described above.

    + +

    We Will Not Discriminate Against You for Exercising Your Rights Under the CTDPA

    +

    + We will not discriminate against you for exercising your rights under the CTDPA. We will not deny you + our goods or services, charge you different prices or rates, or provide you a lower quality of goods and + services if you exercise your rights under the CTDPA. However, we may offer different tiers of our + Services as allowed by applicable data privacy laws (including the CTDPA) with varying prices, rates or + levels of quality of the goods or services you receive related to the value of Personal Data that we + receive from you. +

    + +

    Delaware Resident Rights

    +

    + If you are a Delaware resident, you have the rights set forth under the Delaware Personal Data Privacy + Act ("DPDPA"). Please see the "Exercising Your Rights under the State Privacy Laws" section below for + instructions regarding how to exercise these rights. Please note that we may process Personal Data of + our customers' end users or employees in connection with our provision of certain services to our + customers. If we are processing your Personal Data as a service provider, you should contact the entity + that collected your Personal Data in the first instance to address your rights with respect to such + data. Additionally, please note that these rights are subject to certain conditions and exceptions under + applicable law, which may permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are a Delaware resident, the portion that is more protective of Personal Data shall control to the + extent of such conflict. If you have any questions about this section or whether any of the following + rights apply to you, please contact us at contact@anoma.ly. +

    + +

    Access and Portability

    +

    + You have the right to request confirmation of whether or not we are processing your Personal Data and to + access and request a copy of your Personal Data in a machine-readable format, to the extent technically + feasible. +

    + +

    Correction

    +

    + You have the right to correct inaccuracies in your Personal Data, to the extent such correction is + appropriate in consideration of the nature of such data and our purposes of processing your Personal + Data. +

    + +

    Deletion

    +

    You have the right to delete Personal Data you have provided to us or we have obtained about you.

    + +

    Personal Data Sales Opt-Out

    +

    + We do not currently sell or process for the purposes of targeted advertising your Personal Data as + defined under the DPDPA. +

    + +

    Profiling Opt-Out

    +

    + We do not process your Personal Data for "Profiling" to make "Decisions" under the DPDPA. "Profiling" + means any form of automated processing performed on personal data to evaluate, analyze or predict + personal aspects related to an identified or identifiable individual's economic circumstances, health, + personal preferences, interests, reliability, behavior, location or movements. "Decision" means any + "Decisions that produce legal or similarly significant effects concerning a Consumer," as defined in the + DPDPA that concern you. To our knowledge, we do not process the Personal Data of consumers under 18 + years of age for the purpose of Profiling. +

    + +

    Consent or "Opt-in" Required and How to Withdraw

    +

    + We may seek your consent to collect or process certain Personal Data, including: 1) Sensitive Data, 2) + Sensitive Data from a known child under 13 years of age, 3) or to sell, or process Personal Data for + Targeted Advertising, or Profiling of a consumer at least 13 years of age but younger than 18 years of + age. +

    + +

    However, we currently do not collect or process your Personal Data as described above.

    + +

    We Will Not Discriminate Against You

    +

    + We will not discriminate against you for exercising your rights under the DPDPA. We will not deny you + our goods or services, charge you different prices or rates, or provide you a lower quality of goods and + services if you exercise your rights under the DPDPA. However, we may offer different tiers of our + Services as allowed by applicable data privacy laws (including the DPDPA) with varying prices, rates or + levels of quality of the goods or services you receive related to the value of Personal Data that we + receive from you. +

    + +

    Iowa Resident Rights

    +

    + If you are an Iowa resident, you have the rights set forth under the Iowa Consumer Data Protection Act + ("ICDPA"). Please see the "Exercising Your Rights under the State Privacy Laws" section below for + instructions regarding how to exercise these rights. Please note that we may process Personal Data of + our customers' end users or employees in connection with our provision of certain services to our + customers. If we are processing your Personal Data as a service provider, you should contact the entity + that collected your Personal Data in the first instance to address your rights with respect to such + data. Additionally, please note that these rights are subject to certain conditions and exceptions under + applicable law, which may permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are an Iowa resident, the portion that is more protective of Personal Data shall control to the extent + of such conflict. If you have any questions about this section or whether any of the following rights + apply to you, please contact us at contact@anoma.ly. +

    + +

    Access and Portability

    +

    + You have the right to request confirmation of whether or not we are processing your Personal Data and to + access and request a copy of your Personal Data in a machine-readable format, to the extent technically + feasible, twice within a calendar year. +

    + +

    Deletion

    +

    You have the right to delete Personal Data you have provided to us.

    + +

    Opt-Out of Certain Processing Activities

    +
      +
    • Targeted Advertising: We do not process your Personal Data for targeted advertising purposes.
    • +
    • Sale of Personal Data: We do not currently sell your Personal Data as defined under the ICDPA.
    • +
    • Processing of Sensitive Personal Data: We do not process Sensitive Personal Data.
    • +
    + +

    We Will Not Discriminate Against You for Exercising Your Rights Under the ICDPA

    +

    + We will not discriminate against you for exercising your rights under the ICDPA. We will not deny you + our goods or services, charge you different prices or rates, or provide you a lower quality of goods and + services if you exercise your rights under the ICDPA. However, we may offer different tiers of our + Services as allowed by applicable data privacy laws (including the ICDPA) with varying prices, rates or + levels of quality of the goods or services you receive related to the value of Personal Data that we + receive from you. +

    + +

    Montana Resident Rights

    +

    + If you are a Montana resident, you have the rights set forth under the Montana Consumer Data Privacy Act + ("MCDPA"). Please see the "Exercising Your Rights under the State Privacy Laws" section below for + instructions regarding how to exercise these rights. Please note that we may process Personal Data of + our customers' end users or employees in connection with our provision of certain services to our + customers. If we are processing your Personal Data as a service provider, you should contact the entity + that collected your Personal Data in the first instance to address your rights with respect to such + data. Additionally, please note that these rights are subject to certain conditions and exceptions under + applicable law, which may permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are a Montana resident, the portion that is more protective of Personal Data shall control to the extent + of such conflict. If you have any questions about this section or whether any of the following rights + apply to you, please contact us at contact@anoma.ly. +

    + +

    Access and Portability

    +

    + You have the right to request confirmation of whether or not we are processing your Personal Data and to + access your Personal Data and request a copy of your Personal Data in a machine-readable format, to the + extent technically feasible. +

    + +

    Correction

    +

    + You have the right to correct inaccuracies in your Personal Data, to the extent such correction is + appropriate in consideration of the nature of such data and our purposes of processing your Personal + Data. +

    + +

    Deletion

    +

    You have the right to delete Personal Data you have provided to us or we have obtained about you.

    + +

    Personal Data Sales Opt-Out

    +

    + We do not currently sell or process for the purposes of targeted advertising your Personal Data as + defined under the MCDPA. +

    + +

    Profiling Opt-Out

    +

    + We do not process your Personal Data for "Profiling" to make "Decisions" under the MCDPA. "Profiling" + means any form of automated processing performed on personal data to evaluate, analyze or predict + personal aspects related to an identified or identifiable individual's economic circumstances, health, + personal preferences, interests, reliability, behavior, location or movements. "Decision" means any + "Decisions that produce legal or similarly significant effects concerning a Consumer," as defined in the + MCDPA that concern you. To our knowledge, we do not process the Personal Data of consumers under 16 + years of age for the purpose of Profiling. +

    + +

    Consent or "Opt-in" Required and How to Withdraw

    +

    + We may seek your consent to collect or process certain Personal Data, including: 1) Sensitive Data, 2) + Sensitive Data from a known child under 13 years of age, or 3) to sell, or process Personal Data for + Targeted Advertising or Profiling of a consumer at least 13 years of age but younger than 16 years of + age. +

    + +

    However, we currently do not collect or process your Personal Data as described above.

    + +

    We Will Not Discriminate Against You for Exercising Your Rights Under the MCDPA

    +

    + We will not discriminate against you for exercising your rights under the MCDPA. We will not deny you + our goods or services, charge you different prices or rates, or provide you a lower quality of goods and + services if you exercise your rights under the MCDPA. However, we may offer different tiers of our + Services as allowed by applicable data privacy laws (including the MCDPA) with varying prices, rates or + levels of quality of the goods or services you receive related to the value of Personal Data that we + receive from you. +

    + +

    Nebraska Resident Rights

    +

    + If you are a Nebraska resident, you have the rights set forth under the Nebraska Data Privacy Act + ("NDPA"). Please see the "Exercising Your Rights under the State Privacy Laws" section below for + instructions regarding how to exercise these rights. Please note that we may process Personal Data of + our customers' end users or employees in connection with our provision of certain services to our + customers. If we are processing your Personal Data as a service provider, you should contact the entity + that collected your Personal Data in the first instance to address your rights with respect to such + data. Additionally, please note that these rights are subject to certain conditions and exceptions under + applicable law, which may permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are a Nebraska resident, the portion that is more protective of Personal Data shall control to the + extent of such conflict. If you have any questions about this section or whether any of the following + rights apply to you, please contact us at contact@anoma.ly. +

    + +

    Access and Portability

    +

    + You have the right to request confirmation of whether or not we are processing your Personal Data and to + access your Personal Data and request a copy of your Personal Data in a machine-readable format, to the + extent technically feasible, twice within a calendar year. +

    + +

    Correction

    +

    + You have the right to correct inaccuracies in your Personal Data, to the extent such correction is + appropriate in consideration of the nature of such data and our purposes of processing your Personal + Data. +

    + +

    Deletion

    +

    You have the right to delete Personal Data you have provided to us or we have obtained about you.

    + +

    Personal Data Sales Opt-Out

    +

    + We do not currently sell or process for the purposes of targeted advertising your Personal Data as + defined under the NDPA. +

    + +

    Profiling Opt-Out

    +

    + We do not process your Personal Data for "Profiling" to make "Decisions" under the NDPA. "Profiling" + means any form of automated processing performed on personal data to evaluate, analyze or predict + personal aspects related to an identified or identifiable individual's economic circumstances, health, + personal preferences, interests, reliability, behavior, location or movements. "Decision" means any + "Decisions that produce legal or similarly significant effects concerning a Consumer," as defined in the + NDPA that concern you. +

    + +

    Consent or "Opt-in" Required and How to Withdraw

    +

    + We may seek your consent to collect or process certain Personal Data, including: 1) Sensitive Data and + 2) Sensitive Data from a known child under 13 years of age. +

    + +

    However, we currently do not collect or process your Personal Data as described above.

    + +

    We Will Not Discriminate Against You for Exercising Your Rights Under the NDPA

    +

    + We will not discriminate against you for exercising your rights under the NDPA. We will not deny you our + goods or services, charge you different prices or rates, or provide you a lower quality of goods and + services if you exercise your rights under the NDPA. However, we may offer different tiers of our + Services as allowed by applicable data privacy laws (including the NDPA) with varying prices, rates or + levels of quality of the goods or services you receive related to the value of Personal Data that we + receive from you. +

    + +

    New Hampshire Resident Rights

    +

    + If you are a New Hampshire resident, you have the rights set forth under the New Hampshire Privacy Act + ("NHPA"). Please see the "Exercising Your Rights under the State Privacy Laws" section below for + instructions regarding how to exercise these rights. Please note that we may process Personal Data of + our customers' end users or employees in connection with our provision of certain services to our + customers. If we are processing your Personal Data as a service provider, you should contact the entity + that collected your Personal Data in the first instance to address your rights with respect to such + data. Additionally, please note that these rights are subject to certain conditions and exceptions under + applicable law, which may permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are a New Hampshire resident, the portion that is more protective of Personal Data shall control to the + extent of such conflict. If you have any questions about this section or whether any of the following + rights apply to you, please contact us at contact@anoma.ly. +

    + +

    Access and Portability

    +

    + You have the right to request confirmation of whether or not we are processing your Personal Data and to + access your Personal Data and request a copy of your Personal Data in a machine-readable format, to the + extent technically feasible. +

    + +

    Correction

    +

    + You have the right to correct inaccuracies in your Personal Data, to the extent such correction is + appropriate in consideration of the nature of such data and our purposes of processing your Personal + Data. +

    + +

    Deletion

    +

    You have the right to delete Personal Data you have provided to us or we have obtained about you.

    + +

    Personal Data Sales Opt-Out

    +

    + We do not currently sell or process for the purposes of targeted advertising your Personal Data as + defined under the NHPA. +

    + +

    Profiling Opt-Out

    +

    + We do not process your Personal Data for "Profiling" to make "Decisions" under the NHPA. "Profiling" + means any form of automated processing performed on personal data to evaluate, analyze or predict + personal aspects related to an identified or identifiable individual's economic circumstances, health, + personal preferences, interests, reliability, behavior, location or movements. "Decision" means any + "Decisions that produce legal or similarly significant effects concerning a Consumer," as defined in the + NHPA that concern you. +

    + +

    Consent or "Opt-in" Required and How to Withdraw

    +

    + We may seek your consent to collect or process certain Personal Data, including: 1) Sensitive Data and + 2) Sensitive Data from a known child under 13 years of age, 3) or to sell or process Personal Data for + Targeted Advertising of a consumer at least 13 years of age but younger than 16 years of age. +

    + +

    However, we currently do not collect or process your Personal Data as described above.

    + +

    We Will Not Discriminate Against You for Exercising Your Rights Under the NHPA

    +

    + We will not discriminate against you for exercising your rights under the NHPA. We will not deny you our + goods or services, charge you different prices or rates, or provide you a lower quality of goods and + services if you exercise your rights under the NHPA. However, we may offer different tiers of our + Services as allowed by applicable data privacy laws (including the NHPA) with varying prices, rates or + levels of quality of the goods or services you receive related to the value of Personal Data that we + receive from you. +

    + +

    New Jersey Resident Rights

    +

    + If you are a New Jersey resident, you have the rights set forth under the New Jersey Privacy Act + ("NJPA"). Please see the "Exercising Your Rights under the State Privacy Laws" section below for + instructions regarding how to exercise these rights. Please note that we may process Personal Data of + our customers' end users or employees in connection with our provision of certain services to our + customers. If we are processing your Personal Data as a service provider, you should contact the entity + that collected your Personal Data in the first instance to address your rights with respect to such + data. Additionally, please note that these rights are subject to certain conditions and exceptions under + applicable law, which may permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are a New Jersey resident, the portion that is more protective of Personal Data shall control to the + extent of such conflict. If you have any questions about this section or whether any of the following + rights apply to you, please contact us at contact@anoma.ly. +

    + +

    Access and Portability

    +

    + You have the right to request confirmation of whether or not we are processing your Personal Data and to + access and request a copy of your Personal Data in a machine-readable format, to the extent technically + feasible. +

    + +

    Correction

    +

    + You have the right to correct inaccuracies in your Personal Data, to the extent such correction is + appropriate in consideration of the nature of such data and our purposes of processing your Personal + Data. +

    + +

    Deletion

    +

    You have the right to delete Personal Data concerning you.

    + +

    Personal Data Sales Opt-Out

    +

    + We do not currently sell or process for the purposes of targeted advertising your Personal Data as + defined under the NJPA. +

    + +

    Profiling Opt-Out

    +

    + We do not process your Personal Data for "Profiling" to make "Decisions" under the NJPA. "Profiling" + means any form of automated processing performed on personal data to evaluate, analyze or predict + personal aspects related to an identified or identifiable individual's economic circumstances, health, + personal preferences, interests, reliability, behavior, location or movements. "Decision" means any + "Decisions that produce legal or similarly significant effects concerning a Consumer," as defined in the + NJPA that concern you. To our knowledge, we do not process the Personal Data of consumers under 17 years + of age for the purpose of Profiling. +

    + +

    Consent or "Opt-in" Required and How to Withdraw

    +

    + We may seek your consent to collect or process certain Personal Data, including: 1) Sensitive Data, 2) + Sensitive Data from a known child under 13 years of age, 3) or to sell, or process Personal Data for + Targeted Advertising, or Profiling of a consumer at least 13 years of age but younger than 17 years of + age. +

    + +

    However, we currently do not collect or process your Personal Data as described above.

    + +

    We Will Not Discriminate Against You

    +

    + We will not discriminate against you for exercising your rights under the NJPA. We will not deny you our + goods or services, charge you different prices or rates, or provide you a lower quality of goods and + services if you exercise your rights under the NJPA. However, we may offer different tiers of our + Services as allowed by applicable data privacy laws (including the NJPA) with varying prices, rates or + levels of quality of the goods or services you receive related to the value of Personal Data that we + receive from you. +

    + +

    Oregon Resident Rights

    +

    + If you are an Oregon resident, you have the rights set forth under the Oregon Consumer Privacy Act + ("OCPA"). Please see the "Exercising Your Rights under the State Privacy Laws" section below for + instructions regarding how to exercise these rights. Please note that we may process Personal Data of + our customers' end users or employees in connection with our provision of certain services to our + customers. If we are processing your Personal Data as a service provider, you should contact the entity + that collected your Personal Data in the first instance to address your rights with respect to such + data. Additionally, please note that these rights are subject to certain conditions and exceptions under + applicable law, which may permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are an Oregon resident, the portion that is more protective of Personal Data shall control to the extent + of such conflict. If you have any questions about this section or whether any of the following rights + apply to you, please contact us at contact@anoma.ly. +

    + +

    Access and Portability

    +

    + You have the right to request confirmation of whether or not we are processing your Personal Data and to + access and request a copy of your Personal Data, including a list of specific third parties, other than + natural persons, to which we have disclosed your Personal Data or any Personal Data, in a + machine-readable format, to the extent technically feasible. +

    + +

    Correction

    +

    + You have the right to correct inaccuracies in your Personal Data, to the extent such correction is + appropriate in consideration of the nature of such data and our purposes of processing your Personal + Data. +

    + +

    Deletion

    +

    You have the right to delete Personal Data you have provided to us or we have obtained about you.

    + +

    Personal Data Sales Opt-Out

    +

    + We do not currently sell or process for the purposes of targeted advertising your Personal Data as + defined under the OCPA. +

    + +

    Profiling Opt-Out

    +

    + We do not process your Personal Data for "Profiling" to make "Decisions" under the OCPA. "Profiling" + means any form of automated processing performed on personal data to evaluate, analyze or predict + personal aspects related to an identified or identifiable individual's economic circumstances, health, + personal preferences, interests, reliability, behavior, location or movements. "Decision" means any + "Decisions that produce legal or similarly significant effects concerning a Consumer," as defined in the + OCPA that concern you. To our knowledge, we do not process the Personal Data of consumers under 16 years + of age for the purpose of Profiling. +

    + +

    Consent or "Opt-in" Required and How to Withdraw

    +

    + We may seek your consent to collect or process certain Personal Data, including: 1) Sensitive Data, 2) + Sensitive Data from a known child under 13 years of age, or 3) to sell, or process Personal Data for + Targeted Advertising, or Profiling of a consumer at least 13 years of age but younger than 16 years of + age. +

    + +

    However, we currently do not collect or process your Personal Data as described above.

    + +

    We Will Not Discriminate Against You

    +

    + We will not discriminate against you for exercising your rights under the OCPA. We will not deny you our + goods or services, charge you different prices or rates, or provide you a lower quality of goods and + services if you exercise your rights under the OCPA. However, we may offer different tiers of our + Services as allowed by applicable data privacy laws (including the OCPA) with varying prices, rates or + levels of quality of the goods or services you receive related to the value of Personal Data that we + receive from you. +

    + +

    Texas Resident Rights

    +

    + If you are a Texas resident, you have the rights set forth under the Texas Data Privacy and Security Act + ("TDPSA"). Please see the "Exercising Your Rights under the State Privacy Laws" section below for + instructions regarding how to exercise these rights. Please note that we may process Personal Data of + our customers' end users or employees in connection with our provision of certain services to our + customers. If we are processing your Personal Data as a service provider, you should contact the entity + that collected your Personal Data in the first instance to address your rights with respect to such + data. Additionally, please note that these rights are subject to certain conditions and exceptions under + applicable law, which may permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are a Texas resident, the portion that is more protective of Personal Data shall control to the extent + of such conflict. If you have any questions about this section or whether any of the following rights + apply to you, please contact us at contact@anoma.ly. +

    + +

    Access and Portability

    +

    + You have the right to request confirmation of whether or not we are processing your Personal Data and to + access your Personal Data and request a copy of your Personal Data in a machine-readable format, to the + extent technically feasible, twice within a calendar year. +

    + +

    Correction

    +

    + You have the right to correct inaccuracies in your Personal Data, to the extent such correction is + appropriate in consideration of the nature of such data and our purposes of processing your Personal + Data. +

    + +

    Deletion

    +

    You have the right to delete Personal Data you have provided to us or we have obtained about you.

    + +

    Personal Data Sales Opt-Out

    +

    + We do not currently sell or process for the purposes of targeted advertising your Personal Data as + defined under the TDPSA. +

    + +

    Profiling Opt-Out

    +

    + We do not process your Personal Data for "Profiling" as defined under the TDPSA. "Profiling" means any + form of solely automated processing performed on personal data to evaluate, analyze, or predict personal + aspects related to an identified or identifiable individual's economic situation, health, personal + preferences, interests, reliability, behavior, location, or movements. +

    + +

    Consent or "Opt-in" Required and How to Withdraw

    +

    + We may seek your consent to collect or process certain Personal Data, including: 1) Sensitive Data, or + 2) Sensitive Data from a known child under 13 years of age. +

    + +

    However, we currently do not collect or process your Personal Data as described above.

    + +

    We Will Not Discriminate Against You for Exercising Your Rights Under the TDPSA

    +

    + We will not discriminate against you for exercising your rights under the TDPSA. We will not deny you + our goods or services, charge you different prices or rates, or provide you a lower quality of goods and + services if you exercise your rights under the TDPSA. However, we may offer different tiers of our + Services as allowed by applicable data privacy laws (including the TDPSA) with varying prices, rates or + levels of quality of the goods or services you receive related to the value of Personal Data that we + receive from you. +

    + +

    Utah Resident Rights

    +

    + If you are a Utah resident, you have the rights set forth under the Utah Consumer Privacy Act ("UCPA"). + Please see the "Exercising Your Rights under the State Privacy Laws" section below for instructions + regarding how to exercise these rights. Please note that we may process Personal Data of our customers' + end users or employees in connection with our provision of certain services to our customers. If we are + processing your Personal Data as a service provider, you should contact the entity that collected your + Personal Data in the first instance to address your rights with respect to such data. Additionally, + please note that these rights are subject to certain conditions and exceptions under applicable law, + which may permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are a Utah resident, the portion that is more protective of Personal Data shall control to the extent of + such conflict. If you have any questions about this section or whether any of the following rights apply + to you, please contact us at contact@anoma.ly. +

    + +

    Access and Portability

    +

    + You have the right to request confirmation of whether or not we are processing your Personal Data and to + access your Personal Data and request a copy of your Personal Data in a machine-readable format, to the + extent technically feasible. +

    + +

    Deletion

    +

    You have the right to delete Personal Data that you have provided to us.

    + +

    Opt-Out of Certain Processing Activities

    +
      +
    • Targeted Advertising: We do not process your Personal Data for targeted advertising purposes.
    • +
    • Sale of Personal Data: We do not currently sell your Personal Data as defined under the UCPA.
    • +
    • Processing of Sensitive Personal Data: We do not process Sensitive Personal Data.
    • +
    + +

    We Will Not Discriminate Against You for Exercising Your Rights Under the UCPA

    +

    + We will not discriminate against you for exercising your rights under the UCPA. We will not deny you our + goods or services, charge you different prices or rates, or provide you a lower quality of goods and + services if you exercise your rights under the UCPA. However, we may offer different tiers of our + Services as allowed by applicable data privacy laws (including the UCPA) with varying prices, rates or + levels of quality of the goods or services you receive related to the value of Personal Data that we + receive from you. +

    + +

    Virginia Resident Rights

    +

    + If you are a Virginia resident, you have the rights set forth under the Virginia Consumer Data + Protection Act ("VCDPA"). Please see the "Exercising Your Rights under the State Privacy Laws" section + below for instructions regarding how to exercise these rights. Please note that we may process Personal + Data of our customers' end users or employees in connection with our provision of certain services to + our customers. If we are processing your Personal Data as a service provider, you should contact the + entity that collected your Personal Data in the first instance to address your rights with respect to + such data. Additionally, please note that these rights are subject to certain conditions and exceptions + under applicable law, which may permit or require us to deny your request. +

    + +

    + If there are any conflicts between this section and any other provision of this Privacy Policy and you + are a Virginia resident, the portion that is more protective of Personal Data shall control to the + extent of such conflict. If you have any questions about this section or whether any of the following + rights apply to you, please contact us at contact@anoma.ly. +

    + +

    Access and Portability

    +

    + You have the right to request confirmation of whether or not we are processing your Personal Data and to + access your Personal Data, and request a copy of your Personal Data in a machine-readable format, to the + extent technically feasible. +

    + +

    Correction

    +

    + You have the right to correct inaccuracies in your Personal Data, to the extent such correction is + appropriate in consideration of the nature of such data and our purposes of processing your Personal + Data. +

    + +

    Deletion

    +

    You have the right to delete Personal Data you have provided to us or we have obtained about you.

    + +

    Consent or "Opt-in" Required and How to Withdraw

    +

    + We may seek your consent to collect or process certain Personal Data, including: 1) Sensitive Data, or + 2) Sensitive Data from a known child under 13 years of age. +

    + +

    However, we currently do not collect or process Personal data as described above.

    + +

    Opt-Out of Certain Processing Activities

    +
      +
    • Targeted Advertising: We do not process your Personal Data for targeted advertising purposes.
    • +
    • Sale of Personal Data: We do not currently sell your Personal Data as defined under the VDCPA.
    • +
    • + Processing for Profiling Purposes: We do not currently process your Personal Data for the purposes of + profiling. +
    • +
    + +

    + To exercise any of your rights for these certain processing activities, please follow the instructions + under the "Exercising Your Rights under the State Privacy Laws" section. +

    + +

    We Will Not Discriminate Against You for Exercising Your Rights Under the VCDPA

    +

    + We will not discriminate against you for exercising your rights under the VCDPA. We will not deny you + our goods or services, charge you different prices or rates, or provide you a lower quality of goods and + services if you exercise your rights under the VCDPA. However, we may offer different tiers of our + Services as allowed by applicable data privacy laws (including the VCDPA) with varying prices, rates or + levels of quality of the goods or services you receive related to the value of Personal Data that we + receive from you. +

    + +

    Exercising Your Rights under the State Privacy Laws

    +

    + To exercise the rights described in this Privacy Policy, you or, if you are a California, Colorado, + Connecticut, Delaware, Montana, Nebraska, New Hampshire, New Jersey, Oregon or Texas resident, your + Authorized Agent (defined below) must send us a request that (1) provides sufficient information to + allow us to verify that you are the person about whom we have collected Personal Data, and (2) describes + your request in sufficient detail to allow us to understand, evaluate and respond to it. Each request + that meets both of these criteria will be considered a "Valid Request." We may not respond to requests + that do not meet these criteria. We will only use Personal Data provided in a Valid Request to verify + your identity and complete your request. You do not need an account to submit a Valid Request. +

    + +

    + We will work to respond to your Valid Request within the time period required by applicable law. We will + not charge you a fee for making a Valid Request unless your Valid Request(s) is excessive, repetitive or + manifestly unfounded. If we determine that your Valid Request warrants a fee, we will notify you of the + fee and explain that decision before completing your request. +

    + +

    Request to Withdraw Consent to Certain Processing Activities

    +

    + If you are a California resident, you may withdraw your consent allowing us: 1) to sell or share your + Personal Data, by using the following method: +

    + + +

    Request to Access, Delete, or Correct

    +

    + You may submit a Valid Request for any other rights afforded to you in this Privacy Policy by using the + following methods: +

    + + +

    + If you are a California, Colorado, Connecticut, Delaware, Montana, Nebraska, New Hampshire, New Jersey, + Oregon or Texas resident, you may also authorize an agent (an "Authorized Agent") to exercise your + rights on your behalf. To do this, you must provide your Authorized Agent with written permission to + exercise your rights on your behalf, and we may request a copy of this written permission from your + Authorized Agent when they make a request on your behalf. +

    + +

    Appealing a Denial

    +

    + If you are a Colorado, Connecticut, Delaware, Iowa, Montana, Nebraska, New Hampshire, New Jersey, + Oregon, Texas or Virginia resident and we refuse to take action on your request within a reasonable + period of time after receiving your request in accordance with this section, you may appeal our + decision. In such appeal, you must (1) provide sufficient information to allow us to verify that you are + the person about whom the original request pertains and to identify the original request, and (2) + provide a description of the basis of your appeal. Please note that your appeal will be subject to your + rights and obligations afforded to you under the State Privacy Laws (as applicable). We will respond to + your appeal within the time period required under the applicable law. You can submit a Verified Request + to appeal by the following methods: +

    + + +

    + If we deny your appeal, you have the right to contact the Attorney General of your State, including by + the following links: Colorado, Connecticut, Delaware, Iowa, Montana, Nebraska, New Hampshire, New + Jersey, Oregon, Texas and Virginia. +

    + +

    Other State Law Privacy Rights

    + +

    California Resident Rights

    +

    + Under California Civil Code Sections 1798.83-1798.84, California residents are entitled to contact us to + prevent disclosure of Personal Data to third parties for such third parties' direct marketing purposes; + in order to submit such a request, please contact us at{" "} + contact@anoma.ly. +

    + +

    + Your browser may offer you a "Do Not Track" option, which allows you to signal to operators of websites + and web applications and services that you do not wish such operators to track certain of your online + activities over time and across different websites. Our Services do not support Do Not Track requests at + this time. To find out more about "Do Not Track," you can visit{" "} + www.allaboutdnt.com. +

    + +

    Nevada Resident Rights

    +

    + Please note that we do not currently sell your Personal Data as sales are defined in Nevada Revised + Statutes Chapter 603A. +

    + +

    Contact Information

    +

    + If you have any questions or comments about this Privacy Policy, the ways in which we collect and use + your Personal Data or your choices and rights regarding such collection and use, please do not hesitate + to contact us at: +

    + +
    +
    +
    +
    +
    + +
    + ) +} diff --git a/packages/console/app/src/routes/legal/terms-of-service/index.css b/packages/console/app/src/routes/legal/terms-of-service/index.css new file mode 100644 index 00000000000..709b3a9c37e --- /dev/null +++ b/packages/console/app/src/routes/legal/terms-of-service/index.css @@ -0,0 +1,254 @@ +[data-component="terms-of-service"] { + max-width: 800px; + margin: 0 auto; + line-height: 1.7; +} + +[data-component="terms-of-service"] h1 { + font-size: 2rem; + font-weight: 700; + color: var(--color-text-strong); + margin-bottom: 0.5rem; + margin-top: 0; +} + +[data-component="terms-of-service"] .effective-date { + font-size: 0.95rem; + color: var(--color-text-weak); + margin-bottom: 2rem; +} + +[data-component="terms-of-service"] h2 { + font-size: 1.5rem; + font-weight: 600; + color: var(--color-text-strong); + margin-top: 3rem; + margin-bottom: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--color-border-weak); +} + +[data-component="terms-of-service"] h2:first-of-type { + margin-top: 2rem; +} + +[data-component="terms-of-service"] h3 { + font-size: 1.25rem; + font-weight: 600; + color: var(--color-text-strong); + margin-top: 2rem; + margin-bottom: 1rem; +} + +[data-component="terms-of-service"] h4 { + font-size: 1.1rem; + font-weight: 600; + color: var(--color-text-strong); + margin-top: 1.5rem; + margin-bottom: 0.75rem; +} + +[data-component="terms-of-service"] p { + margin-bottom: 1rem; + color: var(--color-text); +} + +[data-component="terms-of-service"] ul, +[data-component="terms-of-service"] ol { + margin-bottom: 1rem; + padding-left: 1.5rem; + color: var(--color-text); +} + +[data-component="terms-of-service"] li { + margin-bottom: 0.5rem; + line-height: 1.7; +} + +[data-component="terms-of-service"] ul ul, +[data-component="terms-of-service"] ul ol, +[data-component="terms-of-service"] ol ul, +[data-component="terms-of-service"] ol ol { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +[data-component="terms-of-service"] a { + color: var(--color-text-strong); + text-decoration: underline; + text-underline-offset: 2px; + text-decoration-thickness: 1px; + word-break: break-word; +} + +[data-component="terms-of-service"] a:hover { + text-decoration-thickness: 2px; +} + +[data-component="terms-of-service"] strong { + font-weight: 600; + color: var(--color-text-strong); +} + +@media (max-width: 60rem) { + [data-component="terms-of-service"] { + padding: 0; + } + + [data-component="terms-of-service"] h1 { + font-size: 1.75rem; + } + + [data-component="terms-of-service"] h2 { + font-size: 1.35rem; + margin-top: 2.5rem; + } + + [data-component="terms-of-service"] h3 { + font-size: 1.15rem; + } + + [data-component="terms-of-service"] h4 { + font-size: 1rem; + } +} + +html { + scroll-behavior: smooth; +} + +[data-component="terms-of-service"] [id] { + scroll-margin-top: 100px; +} + +@media print { + @page { + margin: 2cm; + size: letter; + } + + [data-component="top"], + [data-component="footer"], + [data-component="legal"] { + display: none !important; + } + + [data-page="legal"] { + background: white !important; + padding: 0 !important; + } + + [data-component="container"] { + max-width: none !important; + border: none !important; + margin: 0 !important; + } + + [data-component="content"], + [data-component="brand-content"] { + padding: 0 !important; + margin: 0 !important; + } + + [data-component="terms-of-service"] { + max-width: none !important; + margin: 0 !important; + padding: 0 !important; + } + + [data-component="terms-of-service"] * { + color: black !important; + background: transparent !important; + } + + [data-component="terms-of-service"] h1 { + font-size: 24pt; + margin-top: 0; + margin-bottom: 12pt; + page-break-after: avoid; + } + + [data-component="terms-of-service"] h2 { + font-size: 18pt; + border-top: 2pt solid black !important; + padding-top: 12pt; + margin-top: 24pt; + margin-bottom: 8pt; + page-break-after: avoid; + page-break-before: auto; + } + + [data-component="terms-of-service"] h2:first-of-type { + margin-top: 16pt; + } + + [data-component="terms-of-service"] h3 { + font-size: 14pt; + margin-top: 16pt; + margin-bottom: 8pt; + page-break-after: avoid; + } + + [data-component="terms-of-service"] h4 { + font-size: 12pt; + margin-top: 12pt; + margin-bottom: 6pt; + page-break-after: avoid; + } + + [data-component="terms-of-service"] p { + font-size: 11pt; + line-height: 1.5; + margin-bottom: 8pt; + orphans: 3; + widows: 3; + } + + [data-component="terms-of-service"] .effective-date { + font-size: 10pt; + margin-bottom: 16pt; + } + + [data-component="terms-of-service"] ul, + [data-component="terms-of-service"] ol { + margin-bottom: 8pt; + page-break-inside: auto; + } + + [data-component="terms-of-service"] li { + font-size: 11pt; + line-height: 1.5; + margin-bottom: 4pt; + page-break-inside: avoid; + } + + [data-component="terms-of-service"] a { + color: black !important; + text-decoration: underline; + } + + [data-component="terms-of-service"] strong { + font-weight: bold; + color: black !important; + } + + [data-component="terms-of-service"] h1, + [data-component="terms-of-service"] h2, + [data-component="terms-of-service"] h3, + [data-component="terms-of-service"] h4 { + page-break-inside: avoid; + page-break-after: avoid; + } + + [data-component="terms-of-service"] h2 + p, + [data-component="terms-of-service"] h3 + p, + [data-component="terms-of-service"] h4 + p, + [data-component="terms-of-service"] h2 + ul, + [data-component="terms-of-service"] h3 + ul, + [data-component="terms-of-service"] h4 + ul, + [data-component="terms-of-service"] h2 + ol, + [data-component="terms-of-service"] h3 + ol, + [data-component="terms-of-service"] h4 + ol { + page-break-before: avoid; + } +} diff --git a/packages/console/app/src/routes/legal/terms-of-service/index.tsx b/packages/console/app/src/routes/legal/terms-of-service/index.tsx new file mode 100644 index 00000000000..f0d7be61c06 --- /dev/null +++ b/packages/console/app/src/routes/legal/terms-of-service/index.tsx @@ -0,0 +1,512 @@ +import "../../brand/index.css" +import "./index.css" +import { Title, Meta, Link } from "@solidjs/meta" +import { Header } from "~/component/header" +import { config } from "~/config" +import { Footer } from "~/component/footer" +import { Legal } from "~/component/legal" + +export default function TermsOfService() { + return ( +
    + OpenCode | Terms of Service + + +
    +
    + +
    +
    +
    +

    Terms of Use

    +

    Effective date: Dec 16, 2025

    + +

    + Welcome to OpenCode. Please read on to learn the rules and restrictions that govern your use of OpenCode + (the "Services"). If you have any questions, comments, or concerns regarding these terms or the + Services, please contact us at: +

    + +

    + Email: contact@anoma.ly +

    + +

    + These Terms of Use (the "Terms") are a binding contract between you and{" "} + ANOMALY INNOVATIONS, INC. ("OpenCode," "we" and "us"). Your use of the Services in any + way means that you agree to all of these Terms, and these Terms will remain in effect while you use the + Services. These Terms include the provisions in this document as well as those in the Privacy Policy{" "} + https://opencode.ai/legal/privacy-policy.{" "} + + Your use of or participation in certain Services may also be subject to additional policies, rules + and/or conditions ("Additional Terms"), which are incorporated herein by reference, and you understand + and agree that by using or participating in any such Services, you agree to also comply with these + Additional Terms. + +

    + +

    + Please read these Terms carefully. They cover important information about Services provided to you and + any charges, taxes, and fees we bill you. These Terms include information about{" "} + future changes to these Terms,{" "} + automatic renewals,{" "} + limitations of liability,{" "} + a class action waiver and{" "} + resolution of disputes by arbitration instead of in court.{" "} + + PLEASE NOTE THAT YOUR USE OF AND ACCESS TO OUR SERVICES ARE SUBJECT TO THE FOLLOWING TERMS; IF YOU DO + NOT AGREE TO ALL OF THE FOLLOWING, YOU MAY NOT USE OR ACCESS THE SERVICES IN ANY MANNER. + +

    + +

    + ARBITRATION NOTICE AND CLASS ACTION WAIVER: EXCEPT FOR CERTAIN TYPES OF DISPUTES + DESCRIBED IN THE ARBITRATION AGREEMENT SECTION BELOW, YOU AGREE + THAT DISPUTES BETWEEN YOU AND US WILL BE RESOLVED BY BINDING, INDIVIDUAL ARBITRATION AND YOU WAIVE YOUR + RIGHT TO PARTICIPATE IN A CLASS ACTION LAWSUIT OR CLASS-WIDE ARBITRATION. +

    + +

    What is OpenCode?

    +

    + OpenCode is an AI-powered coding agent that helps you write, understand, and modify code using large + language models. Certain of these large language models are provided by third parties ("Third Party + Models") and certain of these models are provided directly by us if you use the OpenCode Zen paid + offering ("Zen"). Regardless of whether you use Third Party Models or Zen, OpenCode enables you to + access the functionality of models through a coding agent running within your terminal. +

    + +

    Will these Terms ever change?

    +

    + We are constantly trying to improve our Services, so these Terms may need to change along with our + Services. We reserve the right to change the Terms at any time, but if we do, we will place a notice on + our site located at opencode.ai, send you an email, and/or notify you by some other means. +

    + +

    + If you don't agree with the new Terms, you are free to reject them; unfortunately, that means you will + no longer be able to use the Services. If you use the Services in any way after a change to the Terms is + effective, that means you agree to all of the changes. +

    + +

    + Except for changes by us as described here, no other amendment or modification of these Terms will be + effective unless in writing and signed by both you and us. +

    + +

    What about my privacy?

    +

    + OpenCode takes the privacy of its users very seriously. For the current OpenCode Privacy Policy, please + click here{" "} + https://opencode.ai/legal/privacy-policy. +

    + +

    Children's Online Privacy Protection Act

    +

    + The Children's Online Privacy Protection Act ("COPPA") requires that online service providers obtain + parental consent before they knowingly collect personally identifiable information online from children + who are under 13 years of age. We do not knowingly collect or solicit personally identifiable + information from children under 13 years of age; if you are a child under 13 years of age, please do not + attempt to register for or otherwise use the Services or send us any personal information. If we learn + we have collected personal information from a child under 13 years of age, we will delete that + information as quickly as possible. If you believe that a child under 13 years of age may have provided + us personal information, please contact us at contact@anoma.ly. +

    + +

    What are the basics of using OpenCode?

    +

    + You represent and warrant that you are an individual of legal age to form a binding contract (or if not, + you've received your parent's or guardian's permission to use the Services and have gotten your parent + or guardian to agree to these Terms on your behalf). If you're agreeing to these Terms on behalf of an + organization or entity, you represent and warrant that you are authorized to agree to these Terms on + that organization's or entity's behalf and bind them to these Terms (in which case, the references to + "you" and "your" in these Terms, except for in this sentence, refer to that organization or entity). +

    + +

    + You will only use the Services for your own internal use, and not on behalf of or for the benefit of any + third party, and only in a manner that complies with all laws that apply to you. If your use of the + Services is prohibited by applicable laws, then you aren't authorized to use the Services. We can't and + won't be responsible for your using the Services in a way that breaks the law. +

    + +

    Are there restrictions in how I can use the Services?

    +

    + You represent, warrant, and agree that you will not provide or contribute anything, including any + Content (as that term is defined below), to the Services, or otherwise use or interact with the + Services, in a manner that: +

    + +
      +
    1. + infringes or violates the intellectual property rights or any other rights of anyone else (including + OpenCode); +
    2. +
    3. + violates any law or regulation, including, without limitation, any applicable export control laws, + privacy laws or any other purpose not reasonably intended by OpenCode; +
    4. +
    5. + is dangerous, harmful, fraudulent, deceptive, threatening, harassing, defamatory, obscene, or + otherwise objectionable; +
    6. +
    7. automatically or programmatically extracts data or Output (defined below);
    8. +
    9. Represent that the Output was human-generated when it was not;
    10. +
    11. + uses Output to develop artificial intelligence models that compete with the Services or any Third + Party Models; +
    12. +
    13. + attempts, in any manner, to obtain the password, account, or other security information from any other + user; +
    14. +
    15. + violates the security of any computer network, or cracks any passwords or security encryption codes; +
    16. +
    17. + runs Maillist, Listserv, any form of auto-responder or "spam" on the Services, or any processes that + run or are activated while you are not logged into the Services, or that otherwise interfere with the + proper working of the Services (including by placing an unreasonable load on the Services' + infrastructure); +
    18. +
    19. + "crawls," "scrapes," or "spiders" any page, data, or portion of or relating to the Services or Content + (through use of manual or automated means); +
    20. +
    21. copies or stores any significant portion of the Content; or
    22. +
    23. + decompiles, reverse engineers, or otherwise attempts to obtain the source code or underlying ideas or + information of or relating to the Services. +
    24. +
    + +

    + A violation of any of the foregoing is grounds for termination of your right to use or access the + Services. +

    + +

    Who Owns the Services and Content?

    + +

    Our IP

    +

    + We retain all right, title and interest in and to the Services. Except as expressly set forth herein, no + rights to the Services or Third Party Models are granted to you. +

    + +

    Your IP

    +

    + You may provide input to the Services ("Input"), and receive output from the Services based on the Input + ("Output"). Input and Output are collectively "Content." You are responsible for Content, including + ensuring that it does not violate any applicable law or these Terms. You represent and warrant that you + have all rights, licenses, and permissions needed to provide Input to our Services. +

    + +

    + As between you and us, and to the extent permitted by applicable law, you (a) retain your ownership + rights in Input and (b) own the Output. We hereby assign to you all our right, title, and interest, if + any, in and to Output. +

    + +

    + Due to the nature of our Services and artificial intelligence generally, output may not be unique and + other users may receive similar output from our Services. Our assignment above does not extend to other + users' output. +

    + +

    + We use Content to provide our Services, comply with applicable law, enforce our terms and policies, and + keep our Services safe. In addition, if you are using the Services through an unpaid account, we may use + Content to further develop and improve our Services. +

    + +

    + If you use OpenCode with Third Party Models, then your Content will be subject to the data retention + policies of the providers of such Third Party Models. Although we will not retain your Content, we + cannot and do not control the retention practices of Third Party Model providers. You should review the + terms and conditions applicable to any Third Party Model for more information about the data use and + retention policies applicable to such Third Party Models. +

    + +

    What about Third Party Models?

    +

    + The Services enable you to access and use Third Party Models, which are not owned or controlled by + OpenCode. Your ability to access Third Party Models is contingent on you having API keys or otherwise + having the right to access such Third Party Models. +

    + +

    + OpenCode has no control over, and assumes no responsibility for, the content, accuracy, privacy + policies, or practices of any providers of Third Party Models. We encourage you to read the terms and + conditions and privacy policy of each provider of a Third Party Model that you choose to utilize. By + using the Services, you release and hold us harmless from any and all liability arising from your use of + any Third Party Model. +

    + +

    Will OpenCode ever change the Services?

    +

    + We're always trying to improve our Services, so they may change over time. We may suspend or discontinue + any part of the Services, or we may introduce new features or impose limits on certain features or + restrict access to parts or all of the Services. +

    + +

    Do the Services cost anything?

    +

    + The Services may be free or we may charge a fee for using the Services. If you are using a free version + of the Services, we will notify you before any Services you are then using begin carrying a fee, and if + you wish to continue using such Services, you must pay all applicable fees for such Services. Any and + all such charges, fees or costs are your sole responsibility. You should consult with your +

    + +

    Paid Services

    +

    + Certain of our Services, including Zen, may be subject to payments now or in the future (the "Paid + Services"). Please see our Paid Services page https://opencode.ai/zen for a + description of the current Paid Services. Please note that any payment terms presented to you in the + process of using or signing up for a Paid Service are deemed part of these Terms. +

    + +

    Billing

    +

    + We use a third-party payment processor (the "Payment Processor") to bill you through a payment account + linked to your account on the Services (your "Billing Account") for use of the Paid Services. The + processing of payments will be subject to the terms, conditions and privacy policies of the Payment + Processor in addition to these Terms. Currently, we use Stripe, Inc. as our Payment Processor. You can + access Stripe's Terms of Service at{" "} + https://stripe.com/us/checkout/legal and their + Privacy Policy at https://stripe.com/us/privacy. We are not + responsible for any error by, or other acts or omissions of, the Payment Processor. By choosing to use + Paid Services, you agree to pay us, through the Payment Processor, all charges at the prices then in + effect for any use of such Paid Services in accordance with the applicable payment terms, and you + authorize us, through the Payment Processor, to charge your chosen payment provider (your "Payment + Method"). You agree to make payment using that selected Payment Method. We reserve the right to correct + any errors or mistakes that the Payment Processor makes even if it has already requested or received + payment. +

    + +

    Payment Method

    +

    + The terms of your payment will be based on your Payment Method and may be determined by agreements + between you and the financial institution, credit card issuer or other provider of your chosen Payment + Method. If we, through the Payment Processor, do not receive payment from you, you agree to pay all + amounts due on your Billing Account upon demand. +

    + +

    Recurring Billing

    +

    + Some of the Paid Services may consist of an initial period, for which there is a one-time charge, + followed by recurring period charges as agreed to by you. By choosing a recurring payment plan, you + acknowledge that such Services have an initial and recurring payment feature and you accept + responsibility for all recurring charges prior to cancellation. WE MAY SUBMIT PERIODIC CHARGES (E.G., + MONTHLY) WITHOUT FURTHER AUTHORIZATION FROM YOU, UNTIL YOU PROVIDE PRIOR NOTICE (RECEIPT OF WHICH IS + CONFIRMED BY US) THAT YOU HAVE TERMINATED THIS AUTHORIZATION OR WISH TO CHANGE YOUR PAYMENT METHOD. SUCH + NOTICE WILL NOT AFFECT CHARGES SUBMITTED BEFORE WE REASONABLY COULD ACT. TO TERMINATE YOUR AUTHORIZATION + OR CHANGE YOUR PAYMENT METHOD, GO TO ACCOUNT SETTINGS{" "} + https://opencode.ai/auth. +

    + +

    Free Trials and Other Promotions

    +

    + Any free trial or other promotion that provides access to a Paid Service must be used within the + specified time of the trial. You must stop using a Paid Service before the end of the trial period in + order to avoid being charged for that Paid Service. If you cancel prior to the end of the trial period + and are inadvertently charged for a Paid Service, please contact us at{" "} + contact@anoma.ly. +

    + +

    What if I want to stop using the Services?

    +

    + You're free to do that at any time; please refer to our Privacy Policy{" "} + https://opencode.ai/legal/privacy-policy, as well as the licenses + above, to understand how we treat information you provide to us after you have stopped using our + Services. +

    + +

    + OpenCode is also free to terminate (or suspend access to) your use of the Services for any reason in our + discretion, including your breach of these Terms. OpenCode has the sole right to decide whether you are + in violation of any of the restrictions set forth in these Terms. +

    + +

    + Provisions that, by their nature, should survive termination of these Terms shall survive termination. + By way of example, all of the following will survive termination: any obligation you have to pay us or + indemnify us, any limitations on our liability, any terms regarding ownership or intellectual property + rights, and terms regarding disputes between us, including without limitation the arbitration agreement. +

    + +

    What else do I need to know?

    + +

    Warranty Disclaimer

    +

    + OpenCode and its licensors, suppliers, partners, parent, subsidiaries or affiliated entities, and each + of their respective officers, directors, members, employees, consultants, contract employees, + representatives and agents, and each of their respective successors and assigns (OpenCode and all such + parties together, the "OpenCode Parties") make no representations or warranties concerning the Services, + including without limitation regarding any Content contained in or accessed through the Services, and + the OpenCode Parties will not be responsible or liable for the accuracy, copyright compliance, legality, + or decency of material contained in or accessed through the Services or any claims, actions, suits + procedures, costs, expenses, damages or liabilities arising out of use of, or in any way related to your + participation in, the Services. The OpenCode Parties make no representations or warranties regarding + suggestions or recommendations of services or products offered or purchased through or in connection + with the Services. THE SERVICES AND CONTENT ARE PROVIDED BY OPENCODE (AND ITS LICENSORS AND SUPPLIERS) + ON AN "AS-IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT + LIMITATION, IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, + OR THAT USE OF THE SERVICES WILL BE UNINTERRUPTED OR ERROR-FREE. SOME STATES DO NOT ALLOW LIMITATIONS ON + HOW LONG AN IMPLIED WARRANTY LASTS, SO THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU. +

    + +

    Limitation of Liability

    +

    + TO THE FULLEST EXTENT ALLOWED BY APPLICABLE LAW, UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY + (INCLUDING, WITHOUT LIMITATION, TORT, CONTRACT, STRICT LIABILITY, OR OTHERWISE) SHALL ANY OF THE + OPENCODE PARTIES BE LIABLE TO YOU OR TO ANY OTHER PERSON FOR (A) ANY INDIRECT, SPECIAL, INCIDENTAL, + PUNITIVE OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING DAMAGES FOR LOST PROFITS, BUSINESS + INTERRUPTION, LOSS OF DATA, LOSS OF GOODWILL, WORK STOPPAGE, ACCURACY OF RESULTS, OR COMPUTER FAILURE OR + MALFUNCTION, (B) ANY SUBSTITUTE GOODS, SERVICES OR TECHNOLOGY, (C) ANY AMOUNT, IN THE AGGREGATE, IN + EXCESS OF THE GREATER OF (I) ONE-HUNDRED ($100) DOLLARS OR (II) THE AMOUNTS PAID AND/OR PAYABLE BY YOU + TO OPENCODE IN CONNECTION WITH THE SERVICES IN THE TWELVE (12) MONTH PERIOD PRECEDING THIS APPLICABLE + CLAIM OR (D) ANY MATTER BEYOND OUR REASONABLE CONTROL. SOME STATES DO NOT ALLOW THE EXCLUSION OR + LIMITATION OF INCIDENTAL OR CONSEQUENTIAL OR CERTAIN OTHER DAMAGES, SO THE ABOVE LIMITATION AND + EXCLUSIONS MAY NOT APPLY TO YOU. +

    + +

    Indemnity

    +

    + You agree to indemnify and hold the OpenCode Parties harmless from and against any and all claims, + liabilities, damages (actual and consequential), losses and expenses (including attorneys' fees) arising + from or in any way related to any claims relating to (a) your use of the Services, and (b) your + violation of these Terms. In the event of such a claim, suit, or action ("Claim"), we will attempt to + provide notice of the Claim to the contact information we have for your account (provided that failure + to deliver such notice shall not eliminate or reduce your indemnification obligations hereunder). +

    + +

    Assignment

    +

    + You may not assign, delegate or transfer these Terms or your rights or obligations hereunder, or your + Services account, in any way (by operation of law or otherwise) without OpenCode's prior written + consent. We may transfer, assign, or delegate these Terms and our rights and obligations without + consent. +

    + +

    Choice of Law

    +

    + These Terms are governed by and will be construed under the Federal Arbitration Act, applicable federal + law, and the laws of the State of Delaware, without regard to the conflicts of laws provisions thereof. +

    + +

    Arbitration Agreement

    +

    + Please read the following ARBITRATION AGREEMENT carefully because it requires you to arbitrate certain + disputes and claims with OpenCode and limits the manner in which you can seek relief from OpenCode. Both + you and OpenCode acknowledge and agree that for the purposes of any dispute arising out of or relating + to the subject matter of these Terms, OpenCode's officers, directors, employees and independent + contractors ("Personnel") are third-party beneficiaries of these Terms, and that upon your acceptance of + these Terms, Personnel will have the right (and will be deemed to have accepted the right) to enforce + these Terms against you as the third-party beneficiary hereof. +

    + +

    Arbitration Rules; Applicability of Arbitration Agreement

    +

    + The parties shall use their best efforts to settle any dispute, claim, question, or disagreement arising + out of or relating to the subject matter of these Terms directly through good-faith negotiations, which + shall be a precondition to either party initiating arbitration. If such negotiations do not resolve the + dispute, it shall be finally settled by binding arbitration in New Castle County, Delaware. The + arbitration will proceed in the English language, in accordance with the JAMS Streamlined Arbitration + Rules and Procedures (the "Rules") then in effect, by one commercial arbitrator with substantial + experience in resolving intellectual property and commercial contract disputes. The arbitrator shall be + selected from the appropriate list of JAMS arbitrators in accordance with such Rules. Judgment upon the + award rendered by such arbitrator may be entered in any court of competent jurisdiction. +

    + +

    Costs of Arbitration

    +

    + The Rules will govern payment of all arbitration fees. OpenCode will pay all arbitration fees for claims + less than seventy-five thousand ($75,000) dollars. OpenCode will not seek its attorneys' fees and costs + in arbitration unless the arbitrator determines that your claim is frivolous. +

    + +

    Small Claims Court; Infringement

    +

    + Either you or OpenCode may assert claims, if they qualify, in small claims court in New Castle County, + Delaware or any United States county where you live or work. Furthermore, notwithstanding the foregoing + obligation to arbitrate disputes, each party shall have the right to pursue injunctive or other + equitable relief at any time, from any court of competent jurisdiction, to prevent the actual or + threatened infringement, misappropriation or violation of a party's copyrights, trademarks, trade + secrets, patents or other intellectual property rights. +

    + +

    Waiver of Jury Trial

    +

    + YOU AND OPENCODE WAIVE ANY CONSTITUTIONAL AND STATUTORY RIGHTS TO GO TO COURT AND HAVE A TRIAL IN FRONT + OF A JUDGE OR JURY. You and OpenCode are instead choosing to have claims and disputes resolved by + arbitration. Arbitration procedures are typically more limited, more efficient, and less costly than + rules applicable in court and are subject to very limited review by a court. In any litigation between + you and OpenCode over whether to vacate or enforce an arbitration award, YOU AND OPENCODE WAIVE ALL + RIGHTS TO A JURY TRIAL, and elect instead to have the dispute be resolved by a judge. +

    + +

    Waiver of Class or Consolidated Actions

    +

    + ALL CLAIMS AND DISPUTES WITHIN THE SCOPE OF THIS ARBITRATION AGREEMENT MUST BE ARBITRATED OR LITIGATED + ON AN INDIVIDUAL BASIS AND NOT ON A CLASS BASIS. CLAIMS OF MORE THAN ONE CUSTOMER OR USER CANNOT BE + ARBITRATED OR LITIGATED JOINTLY OR CONSOLIDATED WITH THOSE OF ANY OTHER CUSTOMER OR USER. If however, + this waiver of class or consolidated actions is deemed invalid or unenforceable, neither you nor + OpenCode is entitled to arbitration; instead all claims and disputes will be resolved in a court as set + forth in (g) below. +

    + +

    Opt-out

    +

    + You have the right to opt out of the provisions of this Section by sending written notice of your + decision to opt out to the following address: [ADDRESS], [CITY], Canada [ZIP CODE] postmarked within + thirty (30) days of first accepting these Terms. You must include (i) your name and residence address, + (ii) the email address and/or telephone number associated with your account, and (iii) a clear statement + that you want to opt out of these Terms' arbitration agreement. +

    + +

    Exclusive Venue

    +

    + If you send the opt-out notice in (f), and/or in any circumstances where the foregoing arbitration + agreement permits either you or OpenCode to litigate any dispute arising out of or relating to the + subject matter of these Terms in court, then the foregoing arbitration agreement will not apply to + either party, and both you and OpenCode agree that any judicial proceeding (other than small claims + actions) will be brought in the state or federal courts located in, respectively, New Castle County, + Delaware, or the federal district in which that county falls. +

    + +

    Severability

    +

    + If the prohibition against class actions and other claims brought on behalf of third parties contained + above is found to be unenforceable, then all of the preceding language in this Arbitration Agreement + section will be null and void. This arbitration agreement will survive the termination of your + relationship with OpenCode. +

    + +

    Miscellaneous

    +

    + You will be responsible for paying, withholding, filing, and reporting all taxes, duties, and other + governmental assessments associated with your activity in connection with the Services, provided that + the OpenCode may, in its sole discretion, do any of the foregoing on your behalf or for itself as it + sees fit. The failure of either you or us to exercise, in any way, any right herein shall not be deemed + a waiver of any further rights hereunder. If any provision of these Terms are found to be unenforceable + or invalid, that provision will be limited or eliminated, to the minimum extent necessary, so that these + Terms shall otherwise remain in full force and effect and enforceable. You and OpenCode agree that these + Terms are the complete and exclusive statement of the mutual understanding between you and OpenCode, and + that these Terms supersede and cancel all previous written and oral agreements, communications and other + understandings relating to the subject matter of these Terms. You hereby acknowledge and agree that you + are not an employee, agent, partner, or joint venture of OpenCode, and you do not have any authority of + any kind to bind OpenCode in any respect whatsoever. +

    + +

    + Except as expressly set forth in the section above regarding the arbitration agreement, you and OpenCode + agree there are no third-party beneficiaries intended under these Terms. +

    +
    +
    +
    +
    +
    + +
    + ) +} diff --git a/packages/console/app/src/routes/t/[...path].tsx b/packages/console/app/src/routes/t/[...path].tsx new file mode 100644 index 00000000000..b877a8d58aa --- /dev/null +++ b/packages/console/app/src/routes/t/[...path].tsx @@ -0,0 +1,20 @@ +import type { APIEvent } from "@solidjs/start/server" + +async function handler(evt: APIEvent) { + const req = evt.request.clone() + const url = new URL(req.url) + const targetUrl = `https://enterprise.opencode.ai/${url.pathname}${url.search}` + const response = await fetch(targetUrl, { + method: req.method, + headers: req.headers, + body: req.body, + }) + return response +} + +export const GET = handler +export const POST = handler +export const PUT = handler +export const DELETE = handler +export const OPTIONS = handler +export const PATCH = handler diff --git a/packages/console/app/src/routes/workspace/[id]/model-section.tsx b/packages/console/app/src/routes/workspace/[id]/model-section.tsx index 30815336d66..38813d276b5 100644 --- a/packages/console/app/src/routes/workspace/[id]/model-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/model-section.tsx @@ -9,6 +9,7 @@ import { IconAlibaba, IconAnthropic, IconGemini, + IconMiniMax, IconMoonshotAI, IconOpenAI, IconStealth, @@ -23,6 +24,7 @@ const getModelLab = (modelId: string) => { if (modelId.startsWith("kimi")) return "Moonshot AI" if (modelId.startsWith("glm")) return "Z.ai" if (modelId.startsWith("qwen")) return "Alibaba" + if (modelId.startsWith("minimax")) return "MiniMax" if (modelId.startsWith("grok")) return "xAI" return "Stealth" } @@ -35,7 +37,7 @@ const getModelsInfo = query(async (workspaceID: string) => { .filter(([id, _model]) => !["claude-3-5-haiku"].includes(id)) .filter(([id, _model]) => !id.startsWith("alpha-")) .sort(([idA, modelA], [idB, modelB]) => { - const priority = ["big-pickle", "grok", "claude", "gpt", "gemini"] + const priority = ["big-pickle", "minimax", "grok", "claude", "gpt", "gemini"] const getPriority = (id: string) => { const index = priority.findIndex((p) => id.startsWith(p)) return index === -1 ? Infinity : index @@ -43,9 +45,12 @@ const getModelsInfo = query(async (workspaceID: string) => { const pA = getPriority(idA) const pB = getPriority(idB) if (pA !== pB) return pA - pB - return modelA.name.localeCompare(modelB.name) + + const modelAName = Array.isArray(modelA) ? modelA[0].name : modelA.name + const modelBName = Array.isArray(modelB) ? modelB[0].name : modelB.name + return modelAName.localeCompare(modelBName) }) - .map(([id, model]) => ({ id, name: model.name })), + .map(([id, model]) => ({ id, name: Array.isArray(model) ? model[0].name : model.name })), disabled: await Model.listDisabled(), } }, workspaceID) @@ -126,6 +131,8 @@ export function ModelSection() { return case "xAI": return + case "MiniMax": + return default: return } diff --git a/packages/console/app/src/routes/zen/index.css b/packages/console/app/src/routes/zen/index.css index fbdd15306b2..5055bac2acd 100644 --- a/packages/console/app/src/routes/zen/index.css +++ b/packages/console/app/src/routes/zen/index.css @@ -147,7 +147,16 @@ body { ul { display: flex; justify-content: space-between; + align-items: center; gap: 48px; + + @media (max-width: 55rem) { + gap: 32px; + } + + @media (max-width: 48rem) { + gap: 24px; + } li { display: inline-block; a { @@ -161,6 +170,22 @@ body { text-underline-offset: var(--space-1); text-decoration-thickness: 1px; } + [data-slot="cta-button"] { + background: var(--color-background-strong); + color: var(--color-text-inverted); + padding: 8px 16px; + border-radius: 4px; + font-weight: 500; + text-decoration: none; + + @media (max-width: 55rem) { + display: none; + } + } + [data-slot="cta-button"]:hover { + background: var(--color-background-strong-hover); + text-decoration: none; + } } } @@ -280,7 +305,7 @@ body { h1 { font-size: 28px; color: var(--color-text-strong); - font-weight: 500; + font-weight: 700; margin-bottom: 16px; display: block; @@ -369,7 +394,7 @@ body { h3 { font-size: 16px; - font-weight: 500; + font-weight: 700; color: var(--color-text-strong); margin-bottom: 12px; } @@ -442,7 +467,7 @@ body { [data-slot="privacy-title"] { h3 { font-size: 16px; - font-weight: 500; + font-weight: 700; color: var(--color-text); margin-bottom: 12px; } diff --git a/packages/console/app/src/routes/zen/index.tsx b/packages/console/app/src/routes/zen/index.tsx index 7fd393962d6..5708c238cde 100644 --- a/packages/console/app/src/routes/zen/index.tsx +++ b/packages/console/app/src/routes/zen/index.tsx @@ -1,7 +1,7 @@ import "./index.css" import { createAsync, query, redirect } from "@solidjs/router" import { Title, Meta, Link } from "@solidjs/meta" -// import { HttpHeader } from "@solidjs/start" +//import { HttpHeader } from "@solidjs/start" import zenLogoLight from "../../asset/zen-ornate-light.svg" import { config } from "~/config" import zenLogoDark from "../../asset/zen-ornate-dark.svg" @@ -38,7 +38,7 @@ export default function Home() {
    -
    +
    diff --git a/packages/console/app/src/routes/zen/util/dataDumper.ts b/packages/console/app/src/routes/zen/util/dataDumper.ts index 155cc6c58b6..b852ca0b5c0 100644 --- a/packages/console/app/src/routes/zen/util/dataDumper.ts +++ b/packages/console/app/src/routes/zen/util/dataDumper.ts @@ -19,17 +19,23 @@ export function createDataDumper(sessionId: string, requestId: string, projectId if (!data.modelName) return const timestamp = new Date().toISOString().replace(/[^0-9]/g, "") + const year = timestamp.substring(0, 4) + const month = timestamp.substring(4, 6) + const day = timestamp.substring(6, 8) + const hour = timestamp.substring(8, 10) + const minute = timestamp.substring(10, 12) + const second = timestamp.substring(12, 14) waitUntil( - Resource.ZenData.put( - `data/${data.modelName}/${sessionId}/${requestId}.json`, + Resource.ZenDataNew.put( + `data/${data.modelName}/${year}/${month}/${day}/${hour}/${minute}/${second}/${requestId}.json`, JSON.stringify({ timestamp, ...data }), ), ) waitUntil( - Resource.ZenData.put( - `meta/${data.modelName}/${timestamp}/${requestId}.json`, + Resource.ZenDataNew.put( + `meta/${data.modelName}/${sessionId}/${requestId}.json`, JSON.stringify({ timestamp, ...metadata }), ), ) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index e34704f98ed..70d2f55160c 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -57,15 +57,17 @@ export async function handler( const sessionId = input.request.headers.get("x-opencode-session") ?? "" const requestId = input.request.headers.get("x-opencode-request") ?? "" const projectId = input.request.headers.get("x-opencode-project") ?? "" + const ocClient = input.request.headers.get("x-opencode-client") ?? "" logger.metric({ is_tream: isStream, session: sessionId, request: requestId, + client: ocClient, }) const zenData = ZenData.list() const modelInfo = validateModel(zenData, model) const dataDumper = createDataDumper(sessionId, requestId, projectId) - const trialLimiter = createTrialLimiter(modelInfo.trial?.limit, ip) + const trialLimiter = createTrialLimiter(modelInfo.trial, ip, ocClient) const isTrial = await trialLimiter?.isTrial() const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip) await rateLimiter?.check() @@ -110,13 +112,23 @@ export async function handler( headers.delete("content-length") headers.delete("x-opencode-request") headers.delete("x-opencode-session") + headers.delete("x-opencode-project") + headers.delete("x-opencode-client") return headers })(), body: reqBody, }) // Try another provider => stop retrying if using fallback provider - if (res.status !== 200 && modelInfo.fallbackProvider && providerInfo.id !== modelInfo.fallbackProvider) { + if ( + res.status !== 200 && + // ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found. + res.status !== 404 && + // ie. cannot change codex model providers mid-session + !modelInfo.stickyProvider && + modelInfo.fallbackProvider && + providerInfo.id !== modelInfo.fallbackProvider + ) { return retriableRequest({ excludeProviders: [...retry.excludeProviders, providerInfo.id], retryCount: retry.retryCount + 1, @@ -135,6 +147,9 @@ export async function handler( // Store sticky provider await stickyTracker?.set(providerInfo.id) + // Temporarily change 404 to 400 status code b/c solid start automatically override 404 response + const resStatus = res.status === 404 ? 400 : res.status + // Scrub response headers const resHeaders = new Headers() const keepHeaders = ["content-type", "cache-control"] @@ -160,7 +175,7 @@ export async function handler( await trackUsage(authInfo, modelInfo, providerInfo, tokensInfo) await reload(authInfo) return new Response(body, { - status: res.status, + status: resStatus, statusText: res.statusText, headers: resHeaders, }) @@ -238,7 +253,7 @@ export async function handler( }) return new Response(stream, { - status: res.status, + status: resStatus, statusText: res.statusText, headers: resHeaders, }) @@ -286,11 +301,14 @@ export async function handler( } function validateModel(zenData: ZenData, reqModel: string) { - if (!(reqModel in zenData.models)) { - throw new ModelError(`Model ${reqModel} not supported`) - } + if (!(reqModel in zenData.models)) throw new ModelError(`Model ${reqModel} not supported`) + const modelId = reqModel as keyof typeof zenData.models - const modelData = zenData.models[modelId] + const modelData = Array.isArray(zenData.models[modelId]) + ? zenData.models[modelId].find((model) => opts.format === model.formatFilter) + : zenData.models[modelId] + + if (!modelData) throw new ModelError(`Model ${reqModel} not supported for format ${opts.format}`) logger.metric({ model: modelId }) @@ -588,7 +606,7 @@ export async function handler( tx .update(KeyTable) .set({ timeUsed: sql`now()` }) - .where(eq(KeyTable.id, authInfo.apiKeyId)), + .where(and(eq(KeyTable.workspaceID, authInfo.workspaceID), eq(KeyTable.id, authInfo.apiKeyId))), ) } diff --git a/packages/console/app/src/routes/zen/util/trialLimiter.ts b/packages/console/app/src/routes/zen/util/trialLimiter.ts index 15561c9f6b7..531e5cf0c30 100644 --- a/packages/console/app/src/routes/zen/util/trialLimiter.ts +++ b/packages/console/app/src/routes/zen/util/trialLimiter.ts @@ -1,12 +1,18 @@ import { Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js" import { IpTable } from "@opencode-ai/console-core/schema/ip.sql.js" import { UsageInfo } from "./provider/provider" +import { ZenData } from "@opencode-ai/console-core/model.js" -export function createTrialLimiter(limit: number | undefined, ip: string) { - if (!limit) return +export function createTrialLimiter(trial: ZenData.Trial | undefined, ip: string, client: string) { + if (!trial) return if (!ip) return - let trial: boolean + const limit = + trial.limits.find((limit) => limit.client === client)?.limit ?? + trial.limits.find((limit) => limit.client === undefined)?.limit + if (!limit) return + + let _isTrial: boolean return { isTrial: async () => { @@ -20,11 +26,11 @@ export function createTrialLimiter(limit: number | undefined, ip: string) { .then((rows) => rows[0]), ) - trial = (data?.usage ?? 0) < limit - return trial + _isTrial = (data?.usage ?? 0) < limit + return _isTrial }, track: async (usageInfo: UsageInfo) => { - if (!trial) return + if (!_isTrial) return const usage = usageInfo.inputTokens + usageInfo.outputTokens + diff --git a/packages/console/app/src/style/token/font.css b/packages/console/app/src/style/token/font.css index 67143e66260..844677b5fa8 100644 --- a/packages/console/app/src/style/token/font.css +++ b/packages/console/app/src/style/token/font.css @@ -15,6 +15,7 @@ body { --font-size-9xl: 8rem; --font-mono: - "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + "Berkeley Mono", "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; --font-sans: var(--font-mono); } diff --git a/packages/console/core/migrations/0039_striped_forge.sql b/packages/console/core/migrations/0039_striped_forge.sql new file mode 100644 index 00000000000..ad823197fb1 --- /dev/null +++ b/packages/console/core/migrations/0039_striped_forge.sql @@ -0,0 +1,12 @@ +CREATE TABLE `benchmark` ( + `id` varchar(30) NOT NULL, + `time_created` timestamp(3) NOT NULL DEFAULT (now()), + `time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + `time_deleted` timestamp(3), + `model` varchar(64) NOT NULL, + `agent` varchar(64) NOT NULL, + `result` mediumtext NOT NULL, + CONSTRAINT `benchmark_id_pk` PRIMARY KEY(`id`) +); +--> statement-breakpoint +CREATE INDEX `time_created` ON `benchmark` (`time_created`); \ No newline at end of file diff --git a/packages/console/core/migrations/meta/0039_snapshot.json b/packages/console/core/migrations/meta/0039_snapshot.json new file mode 100644 index 00000000000..ba34f1ac490 --- /dev/null +++ b/packages/console/core/migrations/meta/0039_snapshot.json @@ -0,0 +1,1053 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "49a1ac05-78ab-4aae-908e-d4aeeb8196fc", + "prevId": "9d5d9885-7ec5-45f6-ac53-45a8e25dede7", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "account_id_pk": { + "name": "account_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "auth": { + "name": "auth", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "enum('email','github','google')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "provider": { + "name": "provider", + "columns": ["provider", "subject"], + "isUnique": true + }, + "account_id": { + "name": "account_id", + "columns": ["account_id"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "auth_id_pk": { + "name": "auth_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "benchmark": { + "name": "benchmark", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agent": { + "name": "agent", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "result": { + "name": "result", + "type": "mediumtext", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "time_created": { + "name": "time_created", + "columns": ["time_created"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "benchmark_id_pk": { + "name": "benchmark_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "billing": { + "name": "billing", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_id": { + "name": "payment_method_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_type": { + "name": "payment_method_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_last4": { + "name": "payment_method_last4", + "type": "varchar(4)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "balance": { + "name": "balance", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monthly_limit": { + "name": "monthly_limit", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "monthly_usage": { + "name": "monthly_usage", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_monthly_usage_updated": { + "name": "time_monthly_usage_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reload": { + "name": "reload", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reload_trigger": { + "name": "reload_trigger", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reload_amount": { + "name": "reload_amount", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reload_error": { + "name": "reload_error", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_reload_error": { + "name": "time_reload_error", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_reload_locked_till": { + "name": "time_reload_locked_till", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "global_customer_id": { + "name": "global_customer_id", + "columns": ["customer_id"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "billing_workspace_id_id_pk": { + "name": "billing_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "payment": { + "name": "payment", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoice_id": { + "name": "invoice_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_id": { + "name": "payment_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_refunded": { + "name": "time_refunded", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "payment_workspace_id_id_pk": { + "name": "payment_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "usage": { + "name": "usage", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reasoning_tokens": { + "name": "reasoning_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_read_tokens": { + "name": "cache_read_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_5m_tokens": { + "name": "cache_write_5m_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_1h_tokens": { + "name": "cache_write_1h_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_id": { + "name": "key_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_workspace_id_id_pk": { + "name": "usage_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ip": { + "name": "ip", + "columns": { + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage": { + "name": "usage", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "ip_ip_pk": { + "name": "ip_ip_pk", + "columns": ["ip"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "key": { + "name": "key", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_used": { + "name": "time_used", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "global_key": { + "name": "global_key", + "columns": ["key"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "key_workspace_id_id_pk": { + "name": "key_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "model": { + "name": "model", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "model_workspace_model": { + "name": "model_workspace_model", + "columns": ["workspace_id", "model"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "model_workspace_id_id_pk": { + "name": "model_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "provider": { + "name": "provider", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials": { + "name": "credentials", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "workspace_provider": { + "name": "workspace_provider", + "columns": ["workspace_id", "provider"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "provider_workspace_id_id_pk": { + "name": "provider_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_seen": { + "name": "time_seen", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "enum('admin','member')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monthly_limit": { + "name": "monthly_limit", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "monthly_usage": { + "name": "monthly_usage", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_monthly_usage_updated": { + "name": "time_monthly_usage_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_account_id": { + "name": "user_account_id", + "columns": ["workspace_id", "account_id"], + "isUnique": true + }, + "user_email": { + "name": "user_email", + "columns": ["workspace_id", "email"], + "isUnique": true + }, + "global_account_id": { + "name": "global_account_id", + "columns": ["account_id"], + "isUnique": false + }, + "global_email": { + "name": "global_email", + "columns": ["email"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_workspace_id_id_pk": { + "name": "user_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "slug": { + "name": "slug", + "columns": ["slug"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "workspace_id": { + "name": "workspace_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} diff --git a/packages/console/core/migrations/meta/_journal.json b/packages/console/core/migrations/meta/_journal.json index 8a1a38551fb..e96bf52ed09 100644 --- a/packages/console/core/migrations/meta/_journal.json +++ b/packages/console/core/migrations/meta/_journal.json @@ -274,6 +274,13 @@ "when": 1764110043942, "tag": "0038_famous_magik", "breakpoints": true + }, + { + "idx": 39, + "version": "5", + "when": 1766946179892, + "tag": "0039_striped_forge", + "breakpoints": true } ] } diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 5b6a977fc84..285f0dea12e 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.0.134", + "version": "1.0.217", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/core/script/promote-models.ts b/packages/console/core/script/promote-models.ts index 0ff859df8ed..766fe6604c5 100755 --- a/packages/console/core/script/promote-models.ts +++ b/packages/console/core/script/promote-models.ts @@ -16,16 +16,22 @@ const value1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[ const value2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1] const value3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1] const value4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1] +const value5 = lines.find((line) => line.startsWith("ZEN_MODELS5"))?.split("=")[1] +const value6 = lines.find((line) => line.startsWith("ZEN_MODELS6"))?.split("=")[1] if (!value1) throw new Error("ZEN_MODELS1 not found") if (!value2) throw new Error("ZEN_MODELS2 not found") if (!value3) throw new Error("ZEN_MODELS3 not found") if (!value4) throw new Error("ZEN_MODELS4 not found") +if (!value5) throw new Error("ZEN_MODELS5 not found") +if (!value6) throw new Error("ZEN_MODELS6 not found") // validate value -ZenData.validate(JSON.parse(value1 + value2 + value3 + value4)) +ZenData.validate(JSON.parse(value1 + value2 + value3 + value4 + value5 + value6)) // update the secret await $`bun sst secret set ZEN_MODELS1 ${value1} --stage ${stage}` await $`bun sst secret set ZEN_MODELS2 ${value2} --stage ${stage}` await $`bun sst secret set ZEN_MODELS3 ${value3} --stage ${stage}` await $`bun sst secret set ZEN_MODELS4 ${value4} --stage ${stage}` +await $`bun sst secret set ZEN_MODELS5 ${value5} --stage ${stage}` +await $`bun sst secret set ZEN_MODELS6 ${value6} --stage ${stage}` diff --git a/packages/console/core/script/pull-models.ts b/packages/console/core/script/pull-models.ts index a89e3951c8d..80b1037b68e 100755 --- a/packages/console/core/script/pull-models.ts +++ b/packages/console/core/script/pull-models.ts @@ -16,16 +16,21 @@ const value1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[ const value2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1] const value3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1] const value4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1] +const value5 = lines.find((line) => line.startsWith("ZEN_MODELS5"))?.split("=")[1] +const value6 = lines.find((line) => line.startsWith("ZEN_MODELS6"))?.split("=")[1] if (!value1) throw new Error("ZEN_MODELS1 not found") if (!value2) throw new Error("ZEN_MODELS2 not found") if (!value3) throw new Error("ZEN_MODELS3 not found") if (!value4) throw new Error("ZEN_MODELS4 not found") - +if (!value5) throw new Error("ZEN_MODELS5 not found") +if (!value6) throw new Error("ZEN_MODELS6 not found") // validate value -ZenData.validate(JSON.parse(value1 + value2 + value3 + value4)) +ZenData.validate(JSON.parse(value1 + value2 + value3 + value4 + value5 + value6)) // update the secret await $`bun sst secret set ZEN_MODELS1 ${value1}` await $`bun sst secret set ZEN_MODELS2 ${value2}` await $`bun sst secret set ZEN_MODELS3 ${value3}` await $`bun sst secret set ZEN_MODELS4 ${value4}` +await $`bun sst secret set ZEN_MODELS5 ${value5}` +await $`bun sst secret set ZEN_MODELS6 ${value6}` diff --git a/packages/console/core/script/update-models.ts b/packages/console/core/script/update-models.ts index a8523a5f209..7872e2330fd 100755 --- a/packages/console/core/script/update-models.ts +++ b/packages/console/core/script/update-models.ts @@ -14,15 +14,21 @@ const oldValue1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("= const oldValue2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1] const oldValue3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1] const oldValue4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1] +const oldValue5 = lines.find((line) => line.startsWith("ZEN_MODELS5"))?.split("=")[1] +const oldValue6 = lines.find((line) => line.startsWith("ZEN_MODELS6"))?.split("=")[1] if (!oldValue1) throw new Error("ZEN_MODELS1 not found") if (!oldValue2) throw new Error("ZEN_MODELS2 not found") if (!oldValue3) throw new Error("ZEN_MODELS3 not found") if (!oldValue4) throw new Error("ZEN_MODELS4 not found") +if (!oldValue5) throw new Error("ZEN_MODELS5 not found") +if (!oldValue6) throw new Error("ZEN_MODELS6 not found") // store the prettified json to a temp file const filename = `models-${Date.now()}.json` const tempFile = Bun.file(path.join(os.tmpdir(), filename)) -await tempFile.write(JSON.stringify(JSON.parse(oldValue1 + oldValue2 + oldValue3 + oldValue4), null, 2)) +await tempFile.write( + JSON.stringify(JSON.parse(oldValue1 + oldValue2 + oldValue3 + oldValue4 + oldValue5 + oldValue6), null, 2), +) console.log("tempFile", tempFile.name) // open temp file in vim and read the file on close @@ -31,12 +37,17 @@ const newValue = JSON.stringify(JSON.parse(await tempFile.text())) ZenData.validate(JSON.parse(newValue)) // update the secret -const chunk = Math.ceil(newValue.length / 4) +const chunk = Math.ceil(newValue.length / 6) const newValue1 = newValue.slice(0, chunk) const newValue2 = newValue.slice(chunk, chunk * 2) const newValue3 = newValue.slice(chunk * 2, chunk * 3) -const newValue4 = newValue.slice(chunk * 3) +const newValue4 = newValue.slice(chunk * 3, chunk * 4) +const newValue5 = newValue.slice(chunk * 4, chunk * 5) +const newValue6 = newValue.slice(chunk * 5) + await $`bun sst secret set ZEN_MODELS1 ${newValue1}` await $`bun sst secret set ZEN_MODELS2 ${newValue2}` await $`bun sst secret set ZEN_MODELS3 ${newValue3}` await $`bun sst secret set ZEN_MODELS4 ${newValue4}` +await $`bun sst secret set ZEN_MODELS5 ${newValue5}` +await $`bun sst secret set ZEN_MODELS6 ${newValue6}` diff --git a/packages/console/core/src/identifier.ts b/packages/console/core/src/identifier.ts index 8fdf79cde28..f94765ec702 100644 --- a/packages/console/core/src/identifier.ts +++ b/packages/console/core/src/identifier.ts @@ -5,6 +5,7 @@ export namespace Identifier { const prefixes = { account: "acc", auth: "aut", + benchmark: "ben", billing: "bil", key: "key", model: "mod", diff --git a/packages/console/core/src/model.ts b/packages/console/core/src/model.ts index 47ba3e9d838..5a4a6e3b6f0 100644 --- a/packages/console/core/src/model.ts +++ b/packages/console/core/src/model.ts @@ -9,7 +9,17 @@ import { Resource } from "@opencode-ai/console-resource" export namespace ZenData { const FormatSchema = z.enum(["anthropic", "google", "openai", "oa-compat"]) + const TrialSchema = z.object({ + provider: z.string(), + limits: z.array( + z.object({ + limit: z.number(), + client: z.enum(["cli", "desktop"]).optional(), + }), + ), + }) export type Format = z.infer + export type Trial = z.infer const ModelCostSchema = z.object({ input: z.number(), @@ -26,12 +36,7 @@ export namespace ZenData { allowAnonymous: z.boolean().optional(), byokProvider: z.enum(["openai", "anthropic", "google"]).optional(), stickyProvider: z.boolean().optional(), - trial: z - .object({ - limit: z.number(), - provider: z.string(), - }) - .optional(), + trial: TrialSchema.optional(), rateLimit: z.number().optional(), fallbackProvider: z.string().optional(), providers: z.array( @@ -53,7 +58,7 @@ export namespace ZenData { }) const ModelsSchema = z.object({ - models: z.record(z.string(), ModelSchema), + models: z.record(z.string(), z.union([ModelSchema, z.array(ModelSchema.extend({ formatFilter: FormatSchema }))])), providers: z.record(z.string(), ProviderSchema), }) @@ -63,7 +68,12 @@ export namespace ZenData { export const list = fn(z.void(), () => { const json = JSON.parse( - Resource.ZEN_MODELS1.value + Resource.ZEN_MODELS2.value + Resource.ZEN_MODELS3.value + Resource.ZEN_MODELS4.value, + Resource.ZEN_MODELS1.value + + Resource.ZEN_MODELS2.value + + Resource.ZEN_MODELS3.value + + Resource.ZEN_MODELS4.value + + Resource.ZEN_MODELS5.value + + Resource.ZEN_MODELS6.value, ) return ModelsSchema.parse(json) }) diff --git a/packages/console/core/src/schema/benchmark.sql.ts b/packages/console/core/src/schema/benchmark.sql.ts new file mode 100644 index 00000000000..8d435eddfd8 --- /dev/null +++ b/packages/console/core/src/schema/benchmark.sql.ts @@ -0,0 +1,14 @@ +import { index, mediumtext, mysqlTable, primaryKey, varchar } from "drizzle-orm/mysql-core" +import { id, timestamps } from "../drizzle/types" + +export const BenchmarkTable = mysqlTable( + "benchmark", + { + id: id(), + ...timestamps, + model: varchar("model", { length: 64 }).notNull(), + agent: varchar("agent", { length: 64 }).notNull(), + result: mediumtext("result").notNull(), + }, + (table) => [primaryKey({ columns: [table.id] }), index("time_created").on(table.timeCreated)], +) diff --git a/packages/console/core/sst-env.d.ts b/packages/console/core/sst-env.d.ts index 0b09bfd0cd8..ff2c614b3ac 100644 --- a/packages/console/core/sst-env.d.ts +++ b/packages/console/core/sst-env.d.ts @@ -42,18 +42,10 @@ declare module "sst" { "type": "sst.sst.Linkable" "username": string } - "Desktop": { - "type": "sst.cloudflare.StaticSite" - "url": string - } "EMAILOCTOPUS_API_KEY": { "type": "sst.sst.Secret" "value": string } - "Enterprise": { - "type": "sst.cloudflare.SolidStart" - "url": string - } "GITHUB_APP_ID": { "type": "sst.sst.Secret" "value": string @@ -94,10 +86,18 @@ declare module "sst" { "type": "sst.sst.Linkable" "value": string } + "Teams": { + "type": "sst.cloudflare.SolidStart" + "url": string + } "Web": { "type": "sst.cloudflare.Astro" "url": string } + "WebApp": { + "type": "sst.cloudflare.StaticSite" + "url": string + } "ZEN_MODELS1": { "type": "sst.sst.Secret" "value": string @@ -114,6 +114,14 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "ZEN_MODELS5": { + "type": "sst.sst.Secret" + "value": string + } + "ZEN_MODELS6": { + "type": "sst.sst.Secret" + "value": string + } } } // cloudflare @@ -128,6 +136,7 @@ declare module "sst" { "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "ZenData": cloudflare.R2Bucket + "ZenDataNew": cloudflare.R2Bucket } } diff --git a/packages/console/function/package.json b/packages/console/function/package.json index fc8b9f29515..b35e540977d 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.0.134", + "version": "1.0.217", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/function/src/auth.ts b/packages/console/function/src/auth.ts index 742e0d567ce..082564b21ce 100644 --- a/packages/console/function/src/auth.ts +++ b/packages/console/function/src/auth.ts @@ -123,7 +123,11 @@ export default { }, }).then((x) => x.json())) as any subject = user.id.toString() - email = emails.find((x: any) => x.primary && x.verified)?.email + + const primaryEmail = emails.find((x: any) => x.primary) + if (!primaryEmail) throw new Error("No primary email found for GitHub user") + if (!primaryEmail.verified) throw new Error("Primary email for GitHub user not verified") + email = primaryEmail.email } else if (response.provider === "google") { if (!response.id.email_verified) throw new Error("Google email not verified") subject = response.id.sub as string diff --git a/packages/console/function/sst-env.d.ts b/packages/console/function/sst-env.d.ts index 0b09bfd0cd8..ff2c614b3ac 100644 --- a/packages/console/function/sst-env.d.ts +++ b/packages/console/function/sst-env.d.ts @@ -42,18 +42,10 @@ declare module "sst" { "type": "sst.sst.Linkable" "username": string } - "Desktop": { - "type": "sst.cloudflare.StaticSite" - "url": string - } "EMAILOCTOPUS_API_KEY": { "type": "sst.sst.Secret" "value": string } - "Enterprise": { - "type": "sst.cloudflare.SolidStart" - "url": string - } "GITHUB_APP_ID": { "type": "sst.sst.Secret" "value": string @@ -94,10 +86,18 @@ declare module "sst" { "type": "sst.sst.Linkable" "value": string } + "Teams": { + "type": "sst.cloudflare.SolidStart" + "url": string + } "Web": { "type": "sst.cloudflare.Astro" "url": string } + "WebApp": { + "type": "sst.cloudflare.StaticSite" + "url": string + } "ZEN_MODELS1": { "type": "sst.sst.Secret" "value": string @@ -114,6 +114,14 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "ZEN_MODELS5": { + "type": "sst.sst.Secret" + "value": string + } + "ZEN_MODELS6": { + "type": "sst.sst.Secret" + "value": string + } } } // cloudflare @@ -128,6 +136,7 @@ declare module "sst" { "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "ZenData": cloudflare.R2Bucket + "ZenDataNew": cloudflare.R2Bucket } } diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 36faf76ecac..23bd13d88f4 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.0.134", + "version": "1.0.217", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/console/resource/sst-env.d.ts b/packages/console/resource/sst-env.d.ts index 0b09bfd0cd8..ff2c614b3ac 100644 --- a/packages/console/resource/sst-env.d.ts +++ b/packages/console/resource/sst-env.d.ts @@ -42,18 +42,10 @@ declare module "sst" { "type": "sst.sst.Linkable" "username": string } - "Desktop": { - "type": "sst.cloudflare.StaticSite" - "url": string - } "EMAILOCTOPUS_API_KEY": { "type": "sst.sst.Secret" "value": string } - "Enterprise": { - "type": "sst.cloudflare.SolidStart" - "url": string - } "GITHUB_APP_ID": { "type": "sst.sst.Secret" "value": string @@ -94,10 +86,18 @@ declare module "sst" { "type": "sst.sst.Linkable" "value": string } + "Teams": { + "type": "sst.cloudflare.SolidStart" + "url": string + } "Web": { "type": "sst.cloudflare.Astro" "url": string } + "WebApp": { + "type": "sst.cloudflare.StaticSite" + "url": string + } "ZEN_MODELS1": { "type": "sst.sst.Secret" "value": string @@ -114,6 +114,14 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "ZEN_MODELS5": { + "type": "sst.sst.Secret" + "value": string + } + "ZEN_MODELS6": { + "type": "sst.sst.Secret" + "value": string + } } } // cloudflare @@ -128,6 +136,7 @@ declare module "sst" { "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "ZenData": cloudflare.R2Bucket + "ZenDataNew": cloudflare.R2Bucket } } diff --git a/packages/desktop/.gitignore b/packages/desktop/.gitignore index 4a20d55a70d..a547bf36d8d 100644 --- a/packages/desktop/.gitignore +++ b/packages/desktop/.gitignore @@ -1 +1,24 @@ -src/assets/theme.css +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 6a176453668..b381dcf5bf4 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -1,34 +1,7 @@ -## Usage +# Tauri + Vanilla TS -Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`. +This template should help get you started developing with Tauri in vanilla HTML, CSS and Typescript. -This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template. +## Recommended IDE Setup -```bash -$ npm install # or pnpm install or yarn install -``` - -### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) - -## Available Scripts - -In the project directory, you can run: - -### `npm run dev` or `npm start` - -Runs the app in the development mode.
    -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
    - -### `npm run build` - -Builds the app for production to the `dist` folder.
    -It correctly bundles Solid in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
    -Your app is ready to be deployed! - -## Deployment - -You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.) +- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) diff --git a/packages/desktop/index.html b/packages/desktop/index.html index 0ac3d566d2a..83826b602ca 100644 --- a/packages/desktop/index.html +++ b/packages/desktop/index.html @@ -1,5 +1,5 @@ - + @@ -13,16 +13,41 @@ - - - + + -
    +
    diff --git a/packages/desktop/package.json b/packages/desktop/package.json index a71a33fe8da..5d114263eb0 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,59 +1,38 @@ { "name": "@opencode-ai/desktop", - "version": "1.0.134", - "description": "", + "private": true, + "version": "1.0.217", "type": "module", - "exports": { - ".": "./src/index.tsx", - "./vite": "./vite.js" - }, "scripts": { - "typecheck": "tsgo --noEmit", - "start": "vite", + "typecheck": "tsgo -b", + "predev": "bun ./scripts/predev.ts", "dev": "vite", - "build": "vite build", - "serve": "vite preview" + "build": "bun run typecheck && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@opencode-ai/app": "workspace:*", + "@solid-primitives/storage": "catalog:", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "~2", + "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-os": "~2", + "@tauri-apps/plugin-notification": "~2", + "@tauri-apps/plugin-process": "~2", + "@tauri-apps/plugin-shell": "~2", + "@tauri-apps/plugin-store": "~2", + "@tauri-apps/plugin-updater": "~2", + "@tauri-apps/plugin-http": "~2", + "@tauri-apps/plugin-window-state": "~2", + "solid-js": "catalog:" }, - "license": "MIT", "devDependencies": { - "@happy-dom/global-registrator": "20.0.11", - "@tailwindcss/vite": "catalog:", - "@tsconfig/bun": "1.0.9", + "@actions/artifact": "4.0.0", + "@tauri-apps/cli": "^2", "@types/bun": "catalog:", - "@types/luxon": "catalog:", - "@types/node": "catalog:", "@typescript/native-preview": "catalog:", - "typescript": "catalog:", - "vite": "catalog:", - "vite-plugin-icons-spritesheet": "3.0.1", - "vite-plugin-solid": "catalog:" - }, - "dependencies": { - "@kobalte/core": "catalog:", - "@opencode-ai/sdk": "workspace:*", - "@opencode-ai/ui": "workspace:*", - "@opencode-ai/util": "workspace:*", - "@shikijs/transformers": "3.9.2", - "@solid-primitives/active-element": "2.1.3", - "@solid-primitives/event-bus": "1.1.2", - "@solid-primitives/resize-observer": "2.1.3", - "@solid-primitives/scroll": "2.1.3", - "@solid-primitives/storage": "4.3.3", - "@solid-primitives/websocket": "1.3.1", - "@solidjs/meta": "catalog:", - "@solidjs/router": "catalog:", - "@thisbeyond/solid-dnd": "0.7.5", - "diff": "catalog:", - "fuzzysort": "catalog:", - "ghostty-web": "0.3.0", - "luxon": "catalog:", - "marked": "16.2.0", - "marked-shiki": "1.2.1", - "remeda": "catalog:", - "shiki": "3.9.2", - "solid-js": "catalog:", - "solid-list": "catalog:", - "tailwindcss": "catalog:", - "virtua": "catalog:" + "typescript": "~5.6.2", + "vite": "catalog:" } } diff --git a/packages/tauri/scripts/copy-bundles.ts b/packages/desktop/scripts/copy-bundles.ts similarity index 100% rename from packages/tauri/scripts/copy-bundles.ts rename to packages/desktop/scripts/copy-bundles.ts diff --git a/packages/tauri/scripts/predev.ts b/packages/desktop/scripts/predev.ts similarity index 64% rename from packages/tauri/scripts/predev.ts rename to packages/desktop/scripts/predev.ts index bd9320a4a1c..21821519770 100644 --- a/packages/tauri/scripts/predev.ts +++ b/packages/desktop/scripts/predev.ts @@ -1,4 +1,3 @@ -import * as fs from "node:fs/promises" import { $ } from "bun" import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils" @@ -9,9 +8,6 @@ const sidecarConfig = getCurrentSidecar(RUST_TARGET) const binaryPath = `../opencode/dist/${sidecarConfig.ocBinary}/bin/opencode` -if (!(await fs.exists(binaryPath))) { - console.log("opencode binary not found, building...") - await $`cd ../opencode && bun run build --single` -} +await $`cd ../opencode && bun run build --single` await copyBinaryToSidecarFolder(binaryPath, RUST_TARGET) diff --git a/packages/desktop/scripts/prepare.ts b/packages/desktop/scripts/prepare.ts new file mode 100755 index 00000000000..495a0baea42 --- /dev/null +++ b/packages/desktop/scripts/prepare.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env bun +import { $ } from "bun" + +import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils" + +const sidecarConfig = getCurrentSidecar() + +const dir = "src-tauri/target/opencode-binaries" + +await $`mkdir -p ${dir}` +await $`gh run download ${Bun.env.GITHUB_RUN_ID} -n opencode-cli`.cwd(dir) + +await copyBinaryToSidecarFolder( + `${dir}/${sidecarConfig.ocBinary}/bin/opencode${process.platform === "win32" ? ".exe" : ""}`, +) diff --git a/packages/tauri/scripts/utils.ts b/packages/desktop/scripts/utils.ts similarity index 84% rename from packages/tauri/scripts/utils.ts rename to packages/desktop/scripts/utils.ts index b2885d00a84..885d0afce89 100644 --- a/packages/tauri/scripts/utils.ts +++ b/packages/desktop/scripts/utils.ts @@ -21,6 +21,11 @@ export const SIDECAR_BINARIES: Array<{ rustTarget: string; ocBinary: string; ass ocBinary: "opencode-linux-x64", assetExt: "tar.gz", }, + { + rustTarget: "aarch64-unknown-linux-gnu", + ocBinary: "opencode-linux-arm64", + assetExt: "tar.gz", + }, ] export const RUST_TARGET = Bun.env.RUST_TARGET @@ -36,7 +41,7 @@ export function getCurrentSidecar(target = RUST_TARGET) { export async function copyBinaryToSidecarFolder(source: string, target = RUST_TARGET) { await $`mkdir -p src-tauri/sidecars` - const dest = `src-tauri/sidecars/opencode-${target}${process.platform === "win32" ? ".exe" : ""}` + const dest = `src-tauri/sidecars/opencode-cli-${target}${process.platform === "win32" ? ".exe" : ""}` await $`cp ${source} ${dest}` console.log(`Copied ${source} to ${dest}`) diff --git a/packages/tauri/src-tauri/.gitignore b/packages/desktop/src-tauri/.gitignore similarity index 100% rename from packages/tauri/src-tauri/.gitignore rename to packages/desktop/src-tauri/.gitignore diff --git a/packages/tauri/src-tauri/Cargo.lock b/packages/desktop/src-tauri/Cargo.lock similarity index 90% rename from packages/tauri/src-tauri/Cargo.lock rename to packages/desktop/src-tauri/Cargo.lock index aaa7429eb26..11afce91e25 100644 --- a/packages/tauri/src-tauri/Cargo.lock +++ b/packages/desktop/src-tauri/Cargo.lock @@ -56,6 +56,27 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "wl-clipboard-rs", + "x11rb", +] + [[package]] name = "ashpd" version = "0.11.0" @@ -349,6 +370,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.0" @@ -486,6 +513,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "combine" version = "4.6.7" @@ -517,10 +553,39 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ + "percent-encoding", "time", "version_check", ] +[[package]] +name = "cookie_store" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -544,7 +609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ "bitflags 2.10.0", - "core-foundation", + "core-foundation 0.10.1", "core-graphics-types", "foreign-types", "libc", @@ -557,7 +622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.10.0", - "core-foundation", + "core-foundation 0.10.1", "libc", ] @@ -594,6 +659,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -676,6 +747,12 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + [[package]] name = "deranged" version = "0.5.5" @@ -802,6 +879,15 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -927,6 +1013,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "event-listener" version = "5.4.1" @@ -954,6 +1046,26 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -991,6 +1103,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.5" @@ -1007,6 +1125,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.5.0" @@ -1256,6 +1380,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix", + "windows-link 0.2.1", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -1442,12 +1576,51 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.12.1", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -1540,6 +1713,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -1587,9 +1761,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1623,7 +1799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" dependencies = [ "byteorder", - "png", + "png 0.17.16", ] [[package]] @@ -1734,6 +1910,20 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png 0.18.0", + "tiff", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1987,6 +2177,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -2014,6 +2210,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mac-notification-sys" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fd3f75411f4725061682ed91f131946e912859d0044d39c4ec0aac818d7621" +dependencies = [ + "cc", + "objc2 0.6.3", + "objc2-foundation 0.3.2", + "time", +] + [[package]] name = "markup5ever" version = "0.14.1" @@ -2093,6 +2301,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "muda" version = "0.17.1" @@ -2108,7 +2326,7 @@ dependencies = [ "objc2-core-foundation", "objc2-foundation 0.3.2", "once_cell", - "png", + "png 0.17.16", "serde", "thiserror 2.0.17", "windows-sys 0.60.2", @@ -2169,6 +2387,29 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "notify-rust" +version = "4.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6442248665a5aa2514e794af3b39661a8e73033b1cc5e59899e1276117ee4400" +dependencies = [ + "futures-lite", + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2309,6 +2550,16 @@ dependencies = [ "objc2-foundation 0.3.2", ] +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-core-text" version = "0.3.2" @@ -2440,6 +2691,7 @@ checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ "bitflags 2.10.0", "objc2 0.6.3", + "objc2-core-foundation", "objc2-foundation 0.3.2", ] @@ -2461,8 +2713,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ "bitflags 2.10.0", + "block2 0.6.2", "objc2 0.6.3", + "objc2-cloud-kit", + "objc2-core-data", "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2 0.6.3", "objc2-foundation 0.3.2", ] @@ -2504,16 +2775,25 @@ dependencies = [ name = "opencode-desktop" version = "0.0.0" dependencies = [ + "gtk", "listeners", "serde", "serde_json", "tauri", "tauri-build", + "tauri-plugin-clipboard-manager", "tauri-plugin-dialog", + "tauri-plugin-http", + "tauri-plugin-notification", "tauri-plugin-opener", + "tauri-plugin-os", + "tauri-plugin-process", "tauri-plugin-shell", + "tauri-plugin-store", "tauri-plugin-updater", + "tauri-plugin-window-state", "tokio", + "webkit2gtk", ] [[package]] @@ -2532,6 +2812,22 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_info" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c39b5918402d564846d5aba164c09a66cc88d232179dfd3e3c619a25a268392" +dependencies = [ + "android_system_properties", + "log", + "nix", + "objc2 0.6.3", + "objc2-foundation 0.3.2", + "objc2-ui-kit", + "serde", + "windows-sys 0.61.2", +] + [[package]] name = "os_pipe" version = "1.2.3" @@ -2622,6 +2918,17 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap 2.12.1", +] + [[package]] name = "phf" version = "0.8.0" @@ -2811,6 +3118,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.10.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.11.0" @@ -2923,6 +3243,37 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + +[[package]] +name = "pxfm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.37.5" @@ -3204,8 +3555,12 @@ checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64 0.22.1", "bytes", + "cookie", + "cookie_store", + "encoding_rs", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -3214,6 +3569,7 @@ dependencies = [ "hyper-util", "js-sys", "log", + "mime", "percent-encoding", "pin-project-lite", "quinn", @@ -3869,6 +4225,36 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3890,7 +4276,7 @@ checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" dependencies = [ "bitflags 2.10.0", "block2 0.6.2", - "core-foundation", + "core-foundation 0.10.1", "core-graphics", "crossbeam-channel", "dispatch", @@ -4034,7 +4420,7 @@ dependencies = [ "ico", "json-patch", "plist", - "png", + "png 0.17.16", "proc-macro2", "quote", "semver", @@ -4081,6 +4467,21 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-clipboard-manager" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206dc20af4ed210748ba945c2774e60fd0acd52b9a73a028402caf809e9b6ecf" +dependencies = [ + "arboard", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", +] + [[package]] name = "tauri-plugin-dialog" version = "2.4.2" @@ -4121,6 +4522,49 @@ dependencies = [ "url", ] +[[package]] +name = "tauri-plugin-http" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00685aceab12643cf024f712ab0448ba8fcadf86f2391d49d2e5aa732aacc70" +dependencies = [ + "bytes", + "cookie_store", + "data-url", + "http", + "regex", + "reqwest", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.17", + "tokio", + "url", + "urlpattern", +] + +[[package]] +name = "tauri-plugin-notification" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc" +dependencies = [ + "log", + "notify-rust", + "rand 0.9.2", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "time", + "url", +] + [[package]] name = "tauri-plugin-opener" version = "2.5.2" @@ -4143,6 +4587,34 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-os" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f08346c8deb39e96f86973da0e2d76cbb933d7ac9b750f6dc4daf955a6f997" +dependencies = [ + "gethostname", + "log", + "os_info", + "serde", + "serde_json", + "serialize-to-javascript", + "sys-locale", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", +] + +[[package]] +name = "tauri-plugin-process" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a" +dependencies = [ + "tauri", + "tauri-plugin", +] + [[package]] name = "tauri-plugin-shell" version = "2.3.3" @@ -4164,6 +4636,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "tauri-plugin-store" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a77036340a97eb5bbe1b3209c31e5f27f75e6f92a52fd9dd4b211ef08bf310" +dependencies = [ + "dunce", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "tokio", + "tracing", +] + [[package]] name = "tauri-plugin-updater" version = "2.9.0" @@ -4196,6 +4684,21 @@ dependencies = [ "zip", ] +[[package]] +name = "tauri-plugin-window-state" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73736611e14142408d15353e21e3cca2f12a3cfb523ad0ce85999b6d2ef1a704" +dependencies = [ + "bitflags 2.10.0", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", +] + [[package]] name = "tauri-runtime" version = "2.9.1" @@ -4297,6 +4800,18 @@ dependencies = [ "toml 0.9.8", ] +[[package]] +name = "tauri-winrt-notification" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" +dependencies = [ + "quick-xml 0.37.5", + "thiserror 2.0.17", + "windows", + "windows-version", +] + [[package]] name = "tempfile" version = "3.23.0" @@ -4361,6 +4876,20 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.44" @@ -4429,10 +4958,22 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "socket2", + "tokio-macros", "tracing", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -4644,12 +5185,23 @@ dependencies = [ "objc2-core-graphics", "objc2-foundation 0.3.2", "once_cell", - "png", + "png 0.17.16", "serde", "thiserror 2.0.17", "windows-sys 0.60.2", ] +[[package]] +name = "tree_magic_mini" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" +dependencies = [ + "memchr", + "nom", + "petgraph", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -4967,6 +5519,19 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + [[package]] name = "wayland-scanner" version = "0.31.7" @@ -5098,6 +5663,12 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "winapi" version = "0.3.9" @@ -5247,6 +5818,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -5566,6 +6148,24 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wl-clipboard-rs" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3" +dependencies = [ + "libc", + "log", + "os_pipe", + "rustix", + "thiserror 2.0.17", + "tree_magic_mini", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] + [[package]] name = "writeable" version = "0.6.2" @@ -5638,6 +6238,23 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + [[package]] name = "xattr" version = "1.6.1" @@ -5825,6 +6442,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" version = "5.8.0" diff --git a/packages/tauri/src-tauri/Cargo.toml b/packages/desktop/src-tauri/Cargo.toml similarity index 69% rename from packages/tauri/src-tauri/Cargo.toml rename to packages/desktop/src-tauri/Cargo.toml index dd893d6e3f9..b7c238f064f 100644 --- a/packages/tauri/src-tauri/Cargo.toml +++ b/packages/desktop/src-tauri/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "opencode-desktop" version = "0.0.0" -description = "A Tauri App" -authors = ["you"] +description = "The open source AI coding agent" +authors = ["Anomaly Innovations"] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -23,8 +23,19 @@ tauri-plugin-opener = "2" tauri-plugin-shell = "2" tauri-plugin-dialog = "2" tauri-plugin-updater = "2" +tauri-plugin-process = "2" +tauri-plugin-store = "2" +tauri-plugin-window-state = "2" +tauri-plugin-clipboard-manager = "2" +tauri-plugin-http = "2" +tauri-plugin-notification = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = "1.48.0" listeners = "0.3" +tauri-plugin-os = "2" + +[target.'cfg(target_os = "linux")'.dependencies] +gtk = "0.18.2" +webkit2gtk = "=2.0.1" diff --git a/packages/desktop/src-tauri/assets/nsis-header.bmp b/packages/desktop/src-tauri/assets/nsis-header.bmp new file mode 100644 index 00000000000..27480b5f7cb Binary files /dev/null and b/packages/desktop/src-tauri/assets/nsis-header.bmp differ diff --git a/packages/desktop/src-tauri/assets/nsis-sidebar.bmp b/packages/desktop/src-tauri/assets/nsis-sidebar.bmp new file mode 100644 index 00000000000..a476f2f2ac2 Binary files /dev/null and b/packages/desktop/src-tauri/assets/nsis-sidebar.bmp differ diff --git a/packages/tauri/src-tauri/build.rs b/packages/desktop/src-tauri/build.rs similarity index 100% rename from packages/tauri/src-tauri/build.rs rename to packages/desktop/src-tauri/build.rs diff --git a/packages/desktop/src-tauri/capabilities/default.json b/packages/desktop/src-tauri/capabilities/default.json new file mode 100644 index 00000000000..1b305aebeeb --- /dev/null +++ b/packages/desktop/src-tauri/capabilities/default.json @@ -0,0 +1,28 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": ["main"], + "permissions": [ + "core:default", + "opener:default", + "core:window:allow-start-dragging", + "core:webview:allow-set-webview-zoom", + "core:window:allow-is-focused", + "core:window:allow-show", + "core:window:allow-unminimize", + "core:window:allow-set-focus", + "shell:default", + "updater:default", + "dialog:default", + "process:default", + "store:default", + "window-state:default", + "os:default", + "notification:default", + { + "identifier": "http:default", + "allow": [{ "url": "http://*" }, { "url": "https://*" }, { "url": "http://*:*/*" }] + } + ] +} diff --git a/packages/tauri/src-tauri/entitlements.plist b/packages/desktop/src-tauri/entitlements.plist similarity index 100% rename from packages/tauri/src-tauri/entitlements.plist rename to packages/desktop/src-tauri/entitlements.plist diff --git a/packages/desktop/src-tauri/icons/README.md b/packages/desktop/src-tauri/icons/README.md new file mode 100644 index 00000000000..db86593cc37 --- /dev/null +++ b/packages/desktop/src-tauri/icons/README.md @@ -0,0 +1,11 @@ +# Tauri Icons + +Here's the process I've been using to create icons: + +- Save source image as `app-icon.png` in `packages/desktop` +- `cd` to `src-tauri` +- Run `bun tauri icons -o icons/{environment}` +- Use [Image2Icon](https://img2icnsapp.com/)'s 'Big Sur Icon' preset to generate an `icon.icns` file and place it in the appropriate icons folder + +The Image2Icon step is necessary as the `icon.icns` generated by `app-icon.png` does not apply the shadow/padding expected by macOS, +so app icons appear larger than expected. diff --git a/packages/desktop/src-tauri/icons/dev/128x128.png b/packages/desktop/src-tauri/icons/dev/128x128.png new file mode 100644 index 00000000000..d7fc4db1498 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/128x128.png differ diff --git a/packages/desktop/src-tauri/icons/dev/128x128@2x.png b/packages/desktop/src-tauri/icons/dev/128x128@2x.png new file mode 100644 index 00000000000..59188230647 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/128x128@2x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/32x32.png b/packages/desktop/src-tauri/icons/dev/32x32.png new file mode 100644 index 00000000000..53925cc4f54 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/32x32.png differ diff --git a/packages/desktop/src-tauri/icons/dev/64x64.png b/packages/desktop/src-tauri/icons/dev/64x64.png new file mode 100644 index 00000000000..a88ef15c64a Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/64x64.png differ diff --git a/packages/desktop/src-tauri/icons/dev/Square107x107Logo.png b/packages/desktop/src-tauri/icons/dev/Square107x107Logo.png new file mode 100644 index 00000000000..0de29ec828c Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/Square107x107Logo.png differ diff --git a/packages/desktop/src-tauri/icons/dev/Square142x142Logo.png b/packages/desktop/src-tauri/icons/dev/Square142x142Logo.png new file mode 100644 index 00000000000..af62e8e1e9e Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/Square142x142Logo.png differ diff --git a/packages/desktop/src-tauri/icons/dev/Square150x150Logo.png b/packages/desktop/src-tauri/icons/dev/Square150x150Logo.png new file mode 100644 index 00000000000..2b19dc39cc0 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/Square150x150Logo.png differ diff --git a/packages/desktop/src-tauri/icons/dev/Square284x284Logo.png b/packages/desktop/src-tauri/icons/dev/Square284x284Logo.png new file mode 100644 index 00000000000..eda6d9901f3 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/Square284x284Logo.png differ diff --git a/packages/desktop/src-tauri/icons/dev/Square30x30Logo.png b/packages/desktop/src-tauri/icons/dev/Square30x30Logo.png new file mode 100644 index 00000000000..dad821ba84f Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/Square30x30Logo.png differ diff --git a/packages/desktop/src-tauri/icons/dev/Square310x310Logo.png b/packages/desktop/src-tauri/icons/dev/Square310x310Logo.png new file mode 100644 index 00000000000..555b3b19790 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/Square310x310Logo.png differ diff --git a/packages/desktop/src-tauri/icons/dev/Square44x44Logo.png b/packages/desktop/src-tauri/icons/dev/Square44x44Logo.png new file mode 100644 index 00000000000..9f8ad001f78 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/Square44x44Logo.png differ diff --git a/packages/desktop/src-tauri/icons/dev/Square71x71Logo.png b/packages/desktop/src-tauri/icons/dev/Square71x71Logo.png new file mode 100644 index 00000000000..43feb784882 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/Square71x71Logo.png differ diff --git a/packages/desktop/src-tauri/icons/dev/Square89x89Logo.png b/packages/desktop/src-tauri/icons/dev/Square89x89Logo.png new file mode 100644 index 00000000000..628cc597f06 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/Square89x89Logo.png differ diff --git a/packages/desktop/src-tauri/icons/dev/StoreLogo.png b/packages/desktop/src-tauri/icons/dev/StoreLogo.png new file mode 100644 index 00000000000..8d3aa53cffd Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/StoreLogo.png differ diff --git a/packages/tauri/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/packages/desktop/src-tauri/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from packages/tauri/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml rename to packages/desktop/src-tauri/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000000..b355e37fea6 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..c33f8713bc1 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000000..04e37aa6544 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000000..98e53cd220a Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..40fe6e37863 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000000..4814f1ddf5b Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..608493283e4 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..898066a3fc0 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..64035c0f3c4 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..f47691bf428 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..dba6f5635b7 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..764702604e3 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000000..2e8430a604c Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..db953d128c6 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..d5c9ba6a8d1 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/packages/tauri/src-tauri/icons/android/values/ic_launcher_background.xml b/packages/desktop/src-tauri/icons/dev/android/values/ic_launcher_background.xml similarity index 100% rename from packages/tauri/src-tauri/icons/android/values/ic_launcher_background.xml rename to packages/desktop/src-tauri/icons/dev/android/values/ic_launcher_background.xml diff --git a/packages/desktop/src-tauri/icons/dev/icon.icns b/packages/desktop/src-tauri/icons/dev/icon.icns new file mode 100644 index 00000000000..d73a94904ad Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/icon.icns differ diff --git a/packages/desktop/src-tauri/icons/dev/icon.ico b/packages/desktop/src-tauri/icons/dev/icon.ico new file mode 100644 index 00000000000..bec385d9aad Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/icon.ico differ diff --git a/packages/desktop/src-tauri/icons/dev/icon.png b/packages/desktop/src-tauri/icons/dev/icon.png new file mode 100644 index 00000000000..6de37ea2942 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/icon.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@1x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@1x.png new file mode 100644 index 00000000000..0e823043e76 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@1x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x-1.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 00000000000..54e4b2aacab Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x-1.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x.png new file mode 100644 index 00000000000..54e4b2aacab Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@3x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@3x.png new file mode 100644 index 00000000000..645b01561a1 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@3x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@1x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@1x.png new file mode 100644 index 00000000000..054225c6e9f Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@1x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x-1.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 00000000000..0b1b2e0b7fe Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x-1.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x.png new file mode 100644 index 00000000000..0b1b2e0b7fe Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@3x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@3x.png new file mode 100644 index 00000000000..d2c42592b3d Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@3x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@1x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@1x.png new file mode 100644 index 00000000000..54e4b2aacab Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@1x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x-1.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 00000000000..471ed2eec34 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x-1.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x.png new file mode 100644 index 00000000000..471ed2eec34 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@3x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@3x.png new file mode 100644 index 00000000000..1a490cbf16f Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@3x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-512@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-512@2x.png new file mode 100644 index 00000000000..f53b404e5f8 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-512@2x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@2x.png new file mode 100644 index 00000000000..1a490cbf16f Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@2x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@3x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@3x.png new file mode 100644 index 00000000000..bdc759eefe2 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@3x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@1x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@1x.png new file mode 100644 index 00000000000..d22096a2dfe Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@1x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@2x.png new file mode 100644 index 00000000000..d675773d17e Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@2x.png differ diff --git a/packages/desktop/src-tauri/icons/dev/ios/AppIcon-83.5x83.5@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 00000000000..31698afce20 Binary files /dev/null and b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/128x128.png b/packages/desktop/src-tauri/icons/prod/128x128.png new file mode 100644 index 00000000000..caf7b02eb36 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/128x128.png differ diff --git a/packages/desktop/src-tauri/icons/prod/128x128@2x.png b/packages/desktop/src-tauri/icons/prod/128x128@2x.png new file mode 100644 index 00000000000..47fe4c61ea2 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/128x128@2x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/32x32.png b/packages/desktop/src-tauri/icons/prod/32x32.png new file mode 100644 index 00000000000..5868bcc9331 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/32x32.png differ diff --git a/packages/desktop/src-tauri/icons/prod/64x64.png b/packages/desktop/src-tauri/icons/prod/64x64.png new file mode 100644 index 00000000000..1ed7425d855 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/64x64.png differ diff --git a/packages/desktop/src-tauri/icons/prod/Square107x107Logo.png b/packages/desktop/src-tauri/icons/prod/Square107x107Logo.png new file mode 100644 index 00000000000..1db249bf720 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/Square107x107Logo.png differ diff --git a/packages/desktop/src-tauri/icons/prod/Square142x142Logo.png b/packages/desktop/src-tauri/icons/prod/Square142x142Logo.png new file mode 100644 index 00000000000..1961c340816 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/Square142x142Logo.png differ diff --git a/packages/desktop/src-tauri/icons/prod/Square150x150Logo.png b/packages/desktop/src-tauri/icons/prod/Square150x150Logo.png new file mode 100644 index 00000000000..abc507347e6 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/Square150x150Logo.png differ diff --git a/packages/desktop/src-tauri/icons/prod/Square284x284Logo.png b/packages/desktop/src-tauri/icons/prod/Square284x284Logo.png new file mode 100644 index 00000000000..51e2a1b9fea Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/Square284x284Logo.png differ diff --git a/packages/desktop/src-tauri/icons/prod/Square30x30Logo.png b/packages/desktop/src-tauri/icons/prod/Square30x30Logo.png new file mode 100644 index 00000000000..066a1fd0c87 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/Square30x30Logo.png differ diff --git a/packages/desktop/src-tauri/icons/prod/Square310x310Logo.png b/packages/desktop/src-tauri/icons/prod/Square310x310Logo.png new file mode 100644 index 00000000000..2a85c8e9522 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/Square310x310Logo.png differ diff --git a/packages/desktop/src-tauri/icons/prod/Square44x44Logo.png b/packages/desktop/src-tauri/icons/prod/Square44x44Logo.png new file mode 100644 index 00000000000..c855b80632b Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/Square44x44Logo.png differ diff --git a/packages/desktop/src-tauri/icons/prod/Square71x71Logo.png b/packages/desktop/src-tauri/icons/prod/Square71x71Logo.png new file mode 100644 index 00000000000..c8168f71119 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/Square71x71Logo.png differ diff --git a/packages/desktop/src-tauri/icons/prod/Square89x89Logo.png b/packages/desktop/src-tauri/icons/prod/Square89x89Logo.png new file mode 100644 index 00000000000..19ec1777dec Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/Square89x89Logo.png differ diff --git a/packages/desktop/src-tauri/icons/prod/StoreLogo.png b/packages/desktop/src-tauri/icons/prod/StoreLogo.png new file mode 100644 index 00000000000..3fd053d349a Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/StoreLogo.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml b/packages/desktop/src-tauri/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000000..2ffbf24b689 --- /dev/null +++ b/packages/desktop/src-tauri/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000000..4f3ea0e3672 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..7db80699bcc Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000000..a54ebe65286 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000000..9337ccfa3fc Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..0bfc1082e68 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000000..5b02ec732e3 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..322aeaeaaad Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..ca1e336cc34 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..f7111079920 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..287a6b500b6 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..9d3d06a867b Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..d4b6fde1b81 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000000..bde8d75967a Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000000..03df7809da9 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000000..62363be0470 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/packages/desktop/src-tauri/icons/prod/android/values/ic_launcher_background.xml b/packages/desktop/src-tauri/icons/prod/android/values/ic_launcher_background.xml new file mode 100644 index 00000000000..ea9c223a6cb --- /dev/null +++ b/packages/desktop/src-tauri/icons/prod/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/packages/desktop/src-tauri/icons/prod/icon.icns b/packages/desktop/src-tauri/icons/prod/icon.icns new file mode 100644 index 00000000000..be910ad5f93 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/icon.icns differ diff --git a/packages/desktop/src-tauri/icons/prod/icon.ico b/packages/desktop/src-tauri/icons/prod/icon.ico new file mode 100644 index 00000000000..ff88d21e4c5 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/icon.ico differ diff --git a/packages/desktop/src-tauri/icons/prod/icon.png b/packages/desktop/src-tauri/icons/prod/icon.png new file mode 100644 index 00000000000..0ecbb6d5f8f Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/icon.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@1x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@1x.png new file mode 100644 index 00000000000..eb137e164af Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@1x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x-1.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 00000000000..aa76ab10bae Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x-1.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x.png new file mode 100644 index 00000000000..aa76ab10bae Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@3x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@3x.png new file mode 100644 index 00000000000..c58ea3d49bd Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@3x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@1x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@1x.png new file mode 100644 index 00000000000..0eeb4d9bf9e Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@1x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x-1.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 00000000000..32601c70a14 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x-1.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x.png new file mode 100644 index 00000000000..32601c70a14 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@3x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@3x.png new file mode 100644 index 00000000000..a372c4a111f Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@3x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@1x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@1x.png new file mode 100644 index 00000000000..aa76ab10bae Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@1x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x-1.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 00000000000..e82ce2765f1 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x-1.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x.png new file mode 100644 index 00000000000..e82ce2765f1 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@3x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@3x.png new file mode 100644 index 00000000000..15ad5936285 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@3x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-512@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-512@2x.png new file mode 100644 index 00000000000..2260671c001 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-512@2x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@2x.png new file mode 100644 index 00000000000..15ad5936285 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@2x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@3x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@3x.png new file mode 100644 index 00000000000..5c66bd3b181 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@3x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@1x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@1x.png new file mode 100644 index 00000000000..a5b05f3b50f Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@1x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@2x.png new file mode 100644 index 00000000000..9c0615d411d Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@2x.png differ diff --git a/packages/desktop/src-tauri/icons/prod/ios/AppIcon-83.5x83.5@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 00000000000..6b792b36ad3 Binary files /dev/null and b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs new file mode 100644 index 00000000000..46c0ab256db --- /dev/null +++ b/packages/desktop/src-tauri/src/lib.rs @@ -0,0 +1,309 @@ +mod window_customizer; + +use std::{ + collections::VecDeque, + net::{SocketAddr, TcpListener}, + sync::{Arc, Mutex}, + time::{Duration, Instant}, +}; +use tauri::{AppHandle, LogicalSize, Manager, RunEvent, WebviewUrl, WebviewWindow, path::BaseDirectory}; +use tauri_plugin_clipboard_manager::ClipboardExt; +use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult}; +use tauri_plugin_shell::process::{CommandChild, CommandEvent}; +use tauri_plugin_shell::ShellExt; +use tokio::net::TcpSocket; + +use crate::window_customizer::PinchZoomDisablePlugin; + +#[derive(Clone)] +struct ServerState(Arc>>); + +#[derive(Clone)] +struct LogState(Arc>>); + +const MAX_LOG_ENTRIES: usize = 200; + +#[tauri::command] +fn kill_sidecar(app: AppHandle) { + let Some(server_state) = app.try_state::() else { + println!("Server not running"); + return; + }; + + let Some(server_state) = server_state + .0 + .lock() + .expect("Failed to acquire mutex lock") + .take() + else { + println!("Server state missing"); + return; + }; + + let _ = server_state.kill(); + + println!("Killed server"); +} + +#[tauri::command] +async fn copy_logs_to_clipboard(app: AppHandle) -> Result<(), String> { + let log_state = app.try_state::().ok_or("Log state not found")?; + + let logs = log_state + .0 + .lock() + .map_err(|_| "Failed to acquire log lock")?; + + let log_text = logs.iter().cloned().collect::>().join(""); + + app.clipboard() + .write_text(log_text) + .map_err(|e| format!("Failed to copy to clipboard: {}", e))?; + + Ok(()) +} + +#[tauri::command] +async fn get_logs(app: AppHandle) -> Result { + let log_state = app.try_state::().ok_or("Log state not found")?; + + let logs = log_state + .0 + .lock() + .map_err(|_| "Failed to acquire log lock")?; + + Ok(logs.iter().cloned().collect::>().join("")) +} + +fn get_sidecar_port() -> u32 { + option_env!("OPENCODE_PORT") + .map(|s| s.to_string()) + .or_else(|| std::env::var("OPENCODE_PORT").ok()) + .and_then(|port_str| port_str.parse().ok()) + .unwrap_or_else(|| { + TcpListener::bind("127.0.0.1:0") + .expect("Failed to bind to find free port") + .local_addr() + .expect("Failed to get local address") + .port() + }) as u32 +} + +fn get_user_shell() -> String { + std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string()) +} + +fn spawn_sidecar(app: &AppHandle, port: u32) -> CommandChild { + let log_state = app.state::(); + let log_state_clone = log_state.inner().clone(); + + let state_dir = app + .path() + .resolve("", BaseDirectory::AppLocalData) + .expect("Failed to resolve app local data dir"); + + #[cfg(target_os = "windows")] + let (mut rx, child) = app + .shell() + .sidecar("opencode-cli") + .unwrap() + .env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true") + .env("OPENCODE_CLIENT", "desktop") + .env("XDG_STATE_HOME", &state_dir) + .args(["serve", &format!("--port={port}")]) + .spawn() + .expect("Failed to spawn opencode"); + + #[cfg(not(target_os = "windows"))] + let (mut rx, child) = { + let sidecar_path = tauri::utils::platform::current_exe() + .expect("Failed to get current exe") + .parent() + .expect("Failed to get parent dir") + .join("opencode-cli"); + let shell = get_user_shell(); + app.shell() + .command(&shell) + .env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true") + .env("OPENCODE_CLIENT", "desktop") + .env("XDG_STATE_HOME", &state_dir) + .args([ + "-il", + "-c", + &format!("{} serve --port={}", sidecar_path.display(), port), + ]) + .spawn() + .expect("Failed to spawn opencode") + }; + + tauri::async_runtime::spawn(async move { + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Stdout(line_bytes) => { + let line = String::from_utf8_lossy(&line_bytes); + print!("{line}"); + + // Store log in shared state + if let Ok(mut logs) = log_state_clone.0.lock() { + logs.push_back(format!("[STDOUT] {}", line)); + // Keep only the last MAX_LOG_ENTRIES + while logs.len() > MAX_LOG_ENTRIES { + logs.pop_front(); + } + } + } + CommandEvent::Stderr(line_bytes) => { + let line = String::from_utf8_lossy(&line_bytes); + eprint!("{line}"); + + // Store log in shared state + if let Ok(mut logs) = log_state_clone.0.lock() { + logs.push_back(format!("[STDERR] {}", line)); + // Keep only the last MAX_LOG_ENTRIES + while logs.len() > MAX_LOG_ENTRIES { + logs.pop_front(); + } + } + } + _ => {} + } + } + }); + + child +} + +async fn is_server_running(port: u32) -> bool { + TcpSocket::new_v4() + .unwrap() + .connect(SocketAddr::new( + "127.0.0.1".parse().expect("Failed to parse IP"), + port as u16, + )) + .await + .is_ok() +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + let updater_enabled = option_env!("TAURI_SIGNING_PRIVATE_KEY").is_some(); + + let mut builder = tauri::Builder::default() + .plugin(tauri_plugin_os::init()) + .plugin(tauri_plugin_window_state::Builder::new().build()) + .plugin(tauri_plugin_store::Builder::new().build()) + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_process::init()) + .plugin(tauri_plugin_opener::init()) + .plugin(tauri_plugin_clipboard_manager::init()) + .plugin(tauri_plugin_http::init()) + .plugin(tauri_plugin_notification::init()) + .plugin(PinchZoomDisablePlugin) + .invoke_handler(tauri::generate_handler![ + kill_sidecar, + copy_logs_to_clipboard, + get_logs + ]) + .setup(move |app| { + let app = app.handle().clone(); + + // Initialize log state + app.manage(LogState(Arc::new(Mutex::new(VecDeque::new())))); + + tauri::async_runtime::spawn(async move { + let port = get_sidecar_port(); + + let should_spawn_sidecar = !is_server_running(port).await; + + let child = if should_spawn_sidecar { + let child = spawn_sidecar(&app, port); + + let timestamp = Instant::now(); + loop { + if timestamp.elapsed() > Duration::from_secs(7) { + let res = app.dialog() + .message("Failed to spawn OpenCode Server. Copy logs using the button below and send them to the team for assistance.") + .title("Startup Failed") + .buttons(MessageDialogButtons::OkCancelCustom("Copy Logs And Exit".to_string(), "Exit".to_string())) + .blocking_show_with_result(); + + if matches!(&res, MessageDialogResult::Custom(name) if name == "Copy Logs And Exit") { + match copy_logs_to_clipboard(app.clone()).await { + Ok(()) => println!("Logs copied to clipboard successfully"), + Err(e) => println!("Failed to copy logs to clipboard: {}", e), + } + } + + app.exit(1); + + return; + } + + tokio::time::sleep(Duration::from_millis(10)).await; + + if is_server_running(port).await { + // give the server a little bit more time to warm up + tokio::time::sleep(Duration::from_millis(10)).await; + + break; + } + } + + println!("Server ready after {:?}", timestamp.elapsed()); + + Some(child) + } else { + None + }; + + let primary_monitor = app.primary_monitor().ok().flatten(); + let size = primary_monitor + .map(|m| m.size().to_logical(m.scale_factor())) + .unwrap_or(LogicalSize::new(1920, 1080)); + + let mut window_builder = + WebviewWindow::builder(&app, "main", WebviewUrl::App("/".into())) + .title("OpenCode") + .inner_size(size.width as f64, size.height as f64) + .decorations(true) + .zoom_hotkeys_enabled(true) + .disable_drag_drop_handler() + .initialization_script(format!( + r#" + window.__OPENCODE__ ??= {{}}; + window.__OPENCODE__.updaterEnabled = {updater_enabled}; + window.__OPENCODE__.port = {port}; + "# + )); + + #[cfg(target_os = "macos")] + { + window_builder = window_builder + .title_bar_style(tauri::TitleBarStyle::Overlay) + .hidden_title(true); + } + + window_builder.build().expect("Failed to create window"); + + app.manage(ServerState(Arc::new(Mutex::new(child)))); + }); + + Ok(()) + }); + + if updater_enabled { + builder = builder.plugin(tauri_plugin_updater::Builder::new().build()); + } + + builder + .build(tauri::generate_context!()) + .expect("error while running tauri application") + .run(|app, event| { + if let RunEvent::Exit = event { + println!("Received Exit"); + + kill_sidecar(app.clone()); + } + }); +} diff --git a/packages/desktop/src-tauri/src/main.rs b/packages/desktop/src-tauri/src/main.rs new file mode 100644 index 00000000000..b215f8c55a9 --- /dev/null +++ b/packages/desktop/src-tauri/src/main.rs @@ -0,0 +1,63 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +// borrowed from https://github.com/skyline69/balatro-mod-manager +#[cfg(target_os = "linux")] +fn configure_display_backend() -> Option { + use std::env; + + let set_env_if_absent = |key: &str, value: &str| { + if env::var_os(key).is_none() { + // Safety: called during startup before any threads are spawned, so mutating the + // process environment is safe. + unsafe { env::set_var(key, value) }; + } + }; + + let on_wayland = env::var_os("WAYLAND_DISPLAY").is_some() + || matches!( + env::var("XDG_SESSION_TYPE"), + Ok(v) if v.eq_ignore_ascii_case("wayland") + ); + if !on_wayland { + return None; + } + + // Allow users to explicitly keep Wayland if they know their setup is stable. + let allow_wayland = matches!( + env::var("OC_ALLOW_WAYLAND"), + Ok(v) if matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes") + ); + if allow_wayland { + return Some("Wayland session detected; respecting OC_ALLOW_WAYLAND=1".into()); + } + + // Prefer XWayland when available to avoid Wayland protocol errors seen during startup. + if env::var_os("DISPLAY").is_some() { + set_env_if_absent("WINIT_UNIX_BACKEND", "x11"); + set_env_if_absent("GDK_BACKEND", "x11"); + set_env_if_absent("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); + return Some( + "Wayland session detected; forcing X11 backend to avoid compositor protocol errors. \ + Set OC_ALLOW_WAYLAND=1 to keep native Wayland." + .into(), + ); + } + + set_env_if_absent("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); + Some( + "Wayland session detected without X11; leaving Wayland enabled (set WINIT_UNIX_BACKEND/GDK_BACKEND manually if needed)." + .into(), + ) +} + +fn main() { + #[cfg(target_os = "linux")] + { + if let Some(backend_note) = configure_display_backend() { + eprintln!("{backend_note:?}"); + } + } + + opencode_lib::run() +} diff --git a/packages/desktop/src-tauri/src/window_customizer.rs b/packages/desktop/src-tauri/src/window_customizer.rs new file mode 100644 index 00000000000..cd42fd0299c --- /dev/null +++ b/packages/desktop/src-tauri/src/window_customizer.rs @@ -0,0 +1,34 @@ +use tauri::{plugin::Plugin, Manager, Runtime, Window}; + +pub struct PinchZoomDisablePlugin; + +impl Default for PinchZoomDisablePlugin { + fn default() -> Self { + Self + } +} + +impl Plugin for PinchZoomDisablePlugin { + fn name(&self) -> &'static str { + "Does not matter here" + } + + fn window_created(&mut self, window: Window) { + let Some(webview_window) = window.get_webview_window(window.label()) else { + return; + }; + + let _ = webview_window.with_webview(|_webview| { + #[cfg(target_os = "linux")] + unsafe { + use gtk::glib::ObjectExt; + use gtk::GestureZoom; + use webkit2gtk::glib::gobject_ffi; + + if let Some(data) = _webview.inner().data::("wk-view-zoom-gesture") { + gobject_ffi::g_signal_handlers_destroy(data.as_ptr().cast()); + } + } + }); + } +} diff --git a/packages/desktop/src-tauri/tauri.conf.json b/packages/desktop/src-tauri/tauri.conf.json new file mode 100644 index 00000000000..bcb067a3207 --- /dev/null +++ b/packages/desktop/src-tauri/tauri.conf.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "OpenCode Dev", + "identifier": "ai.opencode.desktop.dev", + "mainBinaryName": "OpenCode", + "version": "../package.json", + "build": { + "beforeDevCommand": "bun run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "bun run build", + "frontendDist": "../dist" + }, + "app": { + "withGlobalTauri": true, + "security": { + "csp": null + }, + "macOSPrivateApi": true + }, + "bundle": { + "icon": [ + "icons/dev/32x32.png", + "icons/dev/128x128.png", + "icons/dev/128x128@2x.png", + "icons/dev/icon.icns", + "icons/dev/icon.ico" + ], + "active": true, + "targets": ["deb", "rpm", "dmg", "nsis", "app", "appimage"], + "externalBin": ["sidecars/opencode-cli"], + "macOS": { + "entitlements": "./entitlements.plist" + }, + "windows": { + "nsis": { + "installerIcon": "icons/dev/icon.ico", + "headerImage": "assets/nsis-header.bmp", + "sidebarImage": "assets/nsis-sidebar.bmp" + } + } + } +} diff --git a/packages/desktop/src-tauri/tauri.prod.conf.json b/packages/desktop/src-tauri/tauri.prod.conf.json new file mode 100644 index 00000000000..7894b8ab207 --- /dev/null +++ b/packages/desktop/src-tauri/tauri.prod.conf.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "OpenCode", + "identifier": "ai.opencode.desktop", + "bundle": { + "createUpdaterArtifacts": true, + "icon": [ + "icons/prod/32x32.png", + "icons/prod/128x128.png", + "icons/prod/128x128@2x.png", + "icons/prod/icon.icns", + "icons/prod/icon.ico" + ], + "windows": { + "nsis": { + "installerIcon": "icons/prod/icon.ico" + } + } + }, + "plugins": { + "updater": { + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEYwMDM5Nzg5OUMzOUExMDQKUldRRW9UbWNpWmNEOENYT01CV0lhOXR1UFhpaXJsK1Z3aU9lZnNtNzE0TDROWVMwVW9XQnFOelkK", + "endpoints": ["https://github.com/sst/opencode/releases/latest/download/latest.json"] + } + } +} diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx deleted file mode 100644 index dd1e00c6f5d..00000000000 --- a/packages/desktop/src/components/prompt-input.tsx +++ /dev/null @@ -1,557 +0,0 @@ -import { useFilteredList } from "@opencode-ai/ui/hooks" -import { createEffect, on, Component, Show, For, onMount, onCleanup, Switch, Match } from "solid-js" -import { createStore } from "solid-js/store" -import { createFocusSignal } from "@solid-primitives/active-element" -import { useLocal } from "@/context/local" -import { DateTime } from "luxon" -import { ContentPart, DEFAULT_PROMPT, isPromptEqual, Prompt, useSession } from "@/context/session" -import { useSDK } from "@/context/sdk" -import { useNavigate } from "@solidjs/router" -import { useSync } from "@/context/sync" -import { FileIcon } from "@opencode-ai/ui/file-icon" -import { SelectDialog } from "@opencode-ai/ui/select-dialog" -import { Button } from "@opencode-ai/ui/button" -import { Icon } from "@opencode-ai/ui/icon" -import { Tooltip } from "@opencode-ai/ui/tooltip" -import { IconButton } from "@opencode-ai/ui/icon-button" -import { Select } from "@opencode-ai/ui/select" -import { getDirectory, getFilename } from "@opencode-ai/util/path" - -interface PromptInputProps { - class?: string - ref?: (el: HTMLDivElement) => void -} - -export const PromptInput: Component = (props) => { - const navigate = useNavigate() - const sdk = useSDK() - const sync = useSync() - const local = useLocal() - const session = useSession() - let editorRef!: HTMLDivElement - - const [store, setStore] = createStore<{ - popoverIsOpen: boolean - }>({ - popoverIsOpen: false, - }) - - createEffect(() => { - session.id - editorRef.focus() - }) - - const isFocused = createFocusSignal(() => editorRef) - - const handlePaste = (event: ClipboardEvent) => { - event.preventDefault() - event.stopPropagation() - // @ts-expect-error - const plainText = (event.clipboardData || window.clipboardData)?.getData("text/plain") ?? "" - addPart({ type: "text", content: plainText, start: 0, end: 0 }) - } - - onMount(() => { - editorRef.addEventListener("paste", handlePaste) - }) - onCleanup(() => { - editorRef.removeEventListener("paste", handlePaste) - }) - - createEffect(() => { - if (isFocused()) { - handleInput() - } else { - setStore("popoverIsOpen", false) - } - }) - - const handleFileSelect = (path: string | undefined) => { - if (!path) return - addPart({ type: "file", path, content: "@" + getFilename(path), start: 0, end: 0 }) - } - - const { flat, active, onInput, onKeyDown, refetch } = useFilteredList({ - items: local.file.searchFilesAndDirectories, - key: (x) => x, - onSelect: handleFileSelect, - }) - - createEffect(() => { - local.model.recent() - refetch() - }) - - createEffect( - on( - () => session.prompt.current(), - (currentParts) => { - const domParts = parseFromDOM() - if (isPromptEqual(currentParts, domParts)) return - - const selection = window.getSelection() - let cursorPosition: number | null = null - if (selection && selection.rangeCount > 0 && editorRef.contains(selection.anchorNode)) { - cursorPosition = getCursorPosition(editorRef) - } - - editorRef.innerHTML = "" - currentParts.forEach((part) => { - if (part.type === "text") { - editorRef.appendChild(document.createTextNode(part.content)) - } else if (part.type === "file") { - const pill = document.createElement("span") - pill.textContent = part.content - pill.setAttribute("data-type", "file") - pill.setAttribute("data-path", part.path) - pill.setAttribute("contenteditable", "false") - pill.style.userSelect = "text" - pill.style.cursor = "default" - editorRef.appendChild(pill) - } - }) - - if (cursorPosition !== null) { - setCursorPosition(editorRef, cursorPosition) - } - }, - ), - ) - - const parseFromDOM = (): Prompt => { - const newParts: Prompt = [] - let position = 0 - editorRef.childNodes.forEach((node) => { - if (node.nodeType === Node.TEXT_NODE) { - if (node.textContent) { - const content = node.textContent - newParts.push({ type: "text", content, start: position, end: position + content.length }) - position += content.length - } - } else if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).dataset.type) { - switch ((node as HTMLElement).dataset.type) { - case "file": - const content = node.textContent! - newParts.push({ - type: "file", - path: (node as HTMLElement).dataset.path!, - content, - start: position, - end: position + content.length, - }) - position += content.length - break - default: - break - } - } - }) - if (newParts.length === 0) newParts.push(...DEFAULT_PROMPT) - return newParts - } - - const handleInput = () => { - const rawParts = parseFromDOM() - const cursorPosition = getCursorPosition(editorRef) - const rawText = rawParts.map((p) => p.content).join("") - - const atMatch = rawText.substring(0, cursorPosition).match(/@(\S*)$/) - if (atMatch) { - onInput(atMatch[1]) - setStore("popoverIsOpen", true) - } else if (store.popoverIsOpen) { - setStore("popoverIsOpen", false) - } - - session.prompt.set(rawParts, cursorPosition) - } - - const addPart = (part: ContentPart) => { - const selection = window.getSelection() - if (!selection || selection.rangeCount === 0) return - - const cursorPosition = getCursorPosition(editorRef) - const prompt = session.prompt.current() - const rawText = prompt.map((p) => p.content).join("") - const textBeforeCursor = rawText.substring(0, cursorPosition) - const atMatch = textBeforeCursor.match(/@(\S*)$/) - - if (part.type === "file") { - const pill = document.createElement("span") - pill.textContent = part.content - pill.setAttribute("data-type", "file") - pill.setAttribute("data-path", part.path) - pill.setAttribute("contenteditable", "false") - pill.style.userSelect = "text" - pill.style.cursor = "default" - - const gap = document.createTextNode(" ") - const range = selection.getRangeAt(0) - - if (atMatch) { - // let node: Node | null = range.startContainer - // let offset = range.startOffset - let runningLength = 0 - - const walker = document.createTreeWalker(editorRef, NodeFilter.SHOW_TEXT, null) - let currentNode = walker.nextNode() - while (currentNode) { - const textContent = currentNode.textContent || "" - if (runningLength + textContent.length >= atMatch.index!) { - const localStart = atMatch.index! - runningLength - const localEnd = cursorPosition - runningLength - if (currentNode === range.startContainer || runningLength + textContent.length >= cursorPosition) { - range.setStart(currentNode, localStart) - range.setEnd(currentNode, Math.min(localEnd, textContent.length)) - break - } - } - runningLength += textContent.length - currentNode = walker.nextNode() - } - } - - range.deleteContents() - range.insertNode(gap) - range.insertNode(pill) - range.setStartAfter(gap) - range.collapse(true) - selection.removeAllRanges() - selection.addRange(range) - } else if (part.type === "text") { - const textNode = document.createTextNode(part.content) - const range = selection.getRangeAt(0) - range.deleteContents() - range.insertNode(textNode) - range.setStartAfter(textNode) - range.collapse(true) - selection.removeAllRanges() - selection.addRange(range) - } - - handleInput() - setStore("popoverIsOpen", false) - } - - const abort = () => - sdk.client.session.abort({ - sessionID: session.id!, - }) - - const handleKeyDown = (event: KeyboardEvent) => { - if (store.popoverIsOpen && (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter")) { - onKeyDown(event) - event.preventDefault() - return - } - if (event.key === "Enter" && !event.shiftKey) { - handleSubmit(event) - } - if (event.key === "Escape") { - if (store.popoverIsOpen) { - setStore("popoverIsOpen", false) - } else if (session.working()) { - abort() - } - } - } - - const handleSubmit = async (event: Event) => { - event.preventDefault() - const prompt = session.prompt.current() - const text = prompt.map((part) => part.content).join("") - if (text.trim().length === 0) { - if (session.working()) abort() - return - } - - let existing = session.info() - if (!existing) { - const created = await sdk.client.session.create() - existing = created.data ?? undefined - if (existing) navigate(existing.id) - } - if (!existing) return - - // if (!session.id) { - // session.layout.setOpenedTabs( - // session.layout.copyTabs("", session.id) - // } - - const toAbsolutePath = (path: string) => (path.startsWith("/") ? path : sync.absolute(path)) - const attachments = prompt.filter((part) => part.type === "file") - - // const activeFile = local.context.active() - // if (activeFile) { - // registerAttachment( - // activeFile.path, - // activeFile.selection, - // activeFile.name ?? formatAttachmentLabel(activeFile.path, activeFile.selection), - // ) - // } - - // for (const contextFile of local.context.all()) { - // registerAttachment( - // contextFile.path, - // contextFile.selection, - // formatAttachmentLabel(contextFile.path, contextFile.selection), - // ) - // } - - const attachmentParts = attachments.map((attachment) => { - const absolute = toAbsolutePath(attachment.path) - const query = attachment.selection - ? `?start=${attachment.selection.startLine}&end=${attachment.selection.endLine}` - : "" - return { - type: "file" as const, - mime: "text/plain", - url: `file://${absolute}${query}`, - filename: getFilename(attachment.path), - source: { - type: "file" as const, - text: { - value: attachment.content, - start: attachment.start, - end: attachment.end, - }, - path: absolute, - }, - } - }) - - session.layout.setActiveTab(undefined) - session.messages.setActive(undefined) - // Clear the editor DOM directly to ensure it's empty - editorRef.innerHTML = "" - session.prompt.set([{ type: "text", content: "", start: 0, end: 0 }], 0) - - sdk.client.session.prompt({ - sessionID: existing.id, - agent: local.agent.current()!.name, - model: { - modelID: local.model.current()!.id, - providerID: local.model.current()!.provider.id, - }, - parts: [ - { - type: "text", - text, - }, - ...attachmentParts, - ], - }) - } - - return ( -
    - -
    - 0} fallback={
    No matching files
    }> - - {(i) => ( - - )} - -
    -
    -
    - -
    -
    { - editorRef = el - props.ref?.(el) - }} - contenteditable="true" - onInput={handleInput} - onKeyDown={handleKeyDown} - classList={{ - "w-full px-5 py-3 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true, - "[&>[data-type=file]]:text-icon-info-active": true, - }} - /> - -
    - Plan and build anything -
    -
    -
    -
    -
    - getFilename(project.directory))} - current={getFilename(currentDirectory())} - class="text-14-regular text-text-base" - variant="ghost" - /> -
    /
    -