diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000000..a9d0ab3ca13 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,39 @@ +# Git +.git + +# IDE +.idea +.vscode +*.swp +*~ + +# Build artifacts (we build fresh in Docker) +node_modules +dist +ts-dist +.turbo +target + +# Development files +.DS_Store +.opencode +.worktrees +.sst +.env +playground +tmp +.serena +refs +Session.vim +opencode.json +a.out +.scripts + +# Documentation (not needed for build) +docs/ +CONTEXT/ +specs/ +logs/ + +# Tauri (native desktop app - not needed for Docker) +packages/desktop/src-tauri/target diff --git a/.github/ISSUE_TEMPLATE/upstream-sync-conflict.md b/.github/ISSUE_TEMPLATE/upstream-sync-conflict.md new file mode 100644 index 00000000000..5a61d71e995 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/upstream-sync-conflict.md @@ -0,0 +1,70 @@ +--- +name: Upstream Sync Conflict +about: Automatically created when upstream sync encounters conflicts +title: "[Upstream Sync] Merge conflict with {{ tag }}" +labels: upstream-sync, needs-manual-review +assignees: "" +--- + +## Upstream Sync Conflict Report + +**Trigger**: Upstream sync at {{ timestamp }} +**Upstream Tag**: {{ tag }} +**Upstream SHA**: {{ upstream_sha }} +**Integration SHA**: {{ integration_sha }} + +### Conflicting Files + +{{ conflict_list }} + +### Recommended Actions + +1. Checkout integration branch locally +2. Run: `git fetch origin && git merge origin/dev` +3. Resolve conflicts manually +4. Run validation: + ```bash + bun install + bun turbo typecheck + bun turbo test + ``` +5. Push resolved integration branch +6. Close this issue + +### Resolution Strategies + +| File Pattern | Resolution Strategy | +| ------------------------------- | ----------------------------------------------- | +| `bun.lock` | Regenerate from merged manifest: `bun install` | +| `*.md` (docs) | Accept upstream: `git checkout --theirs ` | +| `package.json` | Manual review required | +| `.github/*` (workflow configs) | Keep ours: `git checkout --ours ` | +| Shared code with custom changes | Manual review required | + +### Manual Sync Commands + +```bash +git fetch origin +git checkout integration +git merge origin/dev + +# Resolve conflicts... + +bun install +bun turbo typecheck +bun turbo test +bun turbo build + +git add . +git commit -m "sync: resolve conflicts with {{ tag }}" +git push origin integration +``` + +### Logs + +
+Merge output + +{{ merge_output }} + +
diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml index cba04faccef..2c4e04ef5e9 100644 --- a/.github/actions/setup-bun/action.yml +++ b/.github/actions/setup-bun/action.yml @@ -3,6 +3,11 @@ description: "Setup Bun with caching and install dependencies" runs: using: "composite" steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + - name: Setup Bun uses: oven-sh/setup-bun@v2 with: diff --git a/.github/last-synced-tag b/.github/last-synced-tag new file mode 100644 index 00000000000..2d4c31f2342 --- /dev/null +++ b/.github/last-synced-tag @@ -0,0 +1 @@ +v1.1.42 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 25466a63e06..00000000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: deploy - -on: - push: - branches: - - dev - - production - workflow_dispatch: - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - deploy: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v3 - - - uses: ./.github/actions/setup-bun - - - uses: actions/setup-node@v4 - with: - node-version: "24" - - - run: bun sst deploy --stage=${{ github.ref_name }} - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }} - PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }} - STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }} diff --git a/.github/workflows/docs-update.yml b/.github/workflows/docs-update.yml deleted file mode 100644 index a8dd2ae4f2b..00000000000 --- a/.github/workflows/docs-update.yml +++ /dev/null @@ -1,72 +0,0 @@ -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 deleted file mode 100644 index 53aa2a725eb..00000000000 --- a/.github/workflows/duplicate-issues.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Duplicate Issue Detection - -on: - issues: - types: [opened] - -jobs: - check-duplicates: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - uses: ./.github/actions/setup-bun - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Check for duplicate issues - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENCODE_PERMISSION: | - { - "bash": { - "*": "deny", - "gh issue*": "allow" - }, - "webfetch": "deny" - } - run: | - opencode run -m opencode/claude-haiku-4-5 "A new issue has been created:' - - Issue number: - ${{ github.event.issue.number }} - - Lookup this issue and search through existing issues (excluding #${{ github.event.issue.number }}) in this repository to find any potential duplicates of this new issue. - Consider: - 1. Similar titles or descriptions - 2. Same error messages or symptoms - 3. Related functionality or components - 4. Similar feature requests - - If you find any potential duplicates, please comment on the new issue with: - - A brief explanation of why it might be a duplicate - - Links to the potentially duplicate issues - - A suggestion to check those issues first - - Use this format for the comment: - 'This issue might be a duplicate of existing issues. Please check: - - #[issue_number]: [brief description of similarity] - - Feel free to ignore if none of these address your specific case.' - - Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, please add a comment mentioning the pinned keybinds issue #4997: - 'For keybind-related issues, please also check our pinned keybinds documentation: #4997' - - If no clear duplicates are found, do not comment." diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml deleted file mode 100644 index 29cc9895393..00000000000 --- a/.github/workflows/generate.yml +++ /dev/null @@ -1,51 +0,0 @@ -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 deleted file mode 100644 index 62577ecf00e..00000000000 --- a/.github/workflows/notify-discord.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: discord - -on: - release: - types: [released] # fires when a draft release is published - -jobs: - notify: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - name: Send nicely-formatted embed to Discord - uses: SethCohen/github-releases-to-discord@v1 - with: - webhook_url: ${{ secrets.DISCORD_WEBHOOK }} diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml index 76e75fcaefb..646b9c92007 100644 --- a/.github/workflows/opencode.yml +++ b/.github/workflows/opencode.yml @@ -1,4 +1,4 @@ -name: opencode +name: shuvcode on: issue_comment: @@ -7,28 +7,36 @@ on: types: [created] jobs: - opencode: + shuvcode: if: | - contains(github.event.comment.body, ' /oc') || - startsWith(github.event.comment.body, '/oc') || - contains(github.event.comment.body, ' /opencode') || - startsWith(github.event.comment.body, '/opencode') + contains(github.event.comment.body, '/shuv') || + contains(github.event.comment.body, '/shuvcode') || + contains(github.event.comment.body, '/oc') || + contains(github.event.comment.body, '/opencode') runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: id-token: write - contents: read - pull-requests: read - issues: read + contents: write + pull-requests: write + issues: write steps: - name: Checkout repository uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Checkout integration branch for upstream-sync issues (non-PR issues) + - name: Checkout integration branch for sync issues + if: ${{ !github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'upstream-sync') }} + run: git checkout integration - uses: ./.github/actions/setup-bun - - name: Run opencode - uses: anomalyco/opencode/github@latest + - name: Run shuvcode + uses: Latitudes-Dev/shuvcode/github@integration env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - OPENCODE_PERMISSION: '{"bash": "deny"}' + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + OPENCODE_PERMISSION: '{"external_directory": "allow"}' with: - model: opencode/claude-opus-4-5 + model: anthropic/claude-opus-4-5 diff --git a/.github/workflows/publish-github-action.yml b/.github/workflows/publish-github-action.yml deleted file mode 100644 index d2789373a34..00000000000 --- a/.github/workflows/publish-github-action.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: publish-github-action - -on: - workflow_dispatch: - push: - tags: - - "github-v*.*.*" - - "!github-v1" - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -permissions: - contents: write - -jobs: - publish: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - run: git fetch --force --tags - - - name: Publish - run: | - git config --global user.email "opencode@sst.dev" - git config --global user.name "opencode" - ./script/publish - working-directory: ./github diff --git a/.github/workflows/publish-vscode.yml b/.github/workflows/publish-vscode.yml deleted file mode 100644 index f49a1057807..00000000000 --- a/.github/workflows/publish-vscode.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: publish-vscode - -on: - workflow_dispatch: - push: - tags: - - "vscode-v*.*.*" - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -permissions: - contents: write - -jobs: - publish: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - uses: ./.github/actions/setup-bun - - - run: git fetch --force --tags - - run: bun install -g @vscode/vsce - - - name: Install extension dependencies - run: bun install - working-directory: ./sdks/vscode - - - name: Publish - run: | - ./script/publish - working-directory: ./sdks/vscode - env: - VSCE_PAT: ${{ secrets.VSCE_PAT }} - OPENVSX_TOKEN: ${{ secrets.OPENVSX_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8d7a823b144..3c059cf0afc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,10 +2,6 @@ name: publish run-name: "${{ format('release {0}', inputs.bump) }}" on: - push: - branches: - - dev - - snapshot-* workflow_dispatch: inputs: bump: @@ -20,8 +16,15 @@ on: description: "Override version (optional)" required: false type: string + publishDesktop: + description: "Publish desktop (tauri) artifacts" + required: false + type: boolean + default: false -concurrency: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.version || inputs.bump }} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.version || inputs.bump }} + cancel-in-progress: false permissions: id-token: write @@ -31,7 +34,7 @@ permissions: jobs: publish: runs-on: blacksmith-4vcpu-ubuntu-2404 - if: github.repository == 'anomalyco/opencode' + if: github.repository == 'Latitudes-Dev/shuvcode' steps: - uses: actions/checkout@v3 with: @@ -65,9 +68,9 @@ jobs: - name: Setup Git Identity run: | - git config --global user.email "opencode@sst.dev" - git config --global user.name "opencode" - git remote set-url origin https://x-access-token:${{ secrets.SST_GITHUB_TOKEN }}@github.com/${{ github.repository }} + git config --global user.email "shuvcode@latitudes.dev" + git config --global user.name "shuvcode" + git remote set-url origin https://x-access-token:${{ secrets.PAT_TOKEN }}@github.com/${{ github.repository }} - name: Publish id: publish @@ -77,7 +80,9 @@ jobs: OPENCODE_VERSION: ${{ inputs.version }} OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} AUR_KEY: ${{ secrets.AUR_KEY }} - GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + GH_TOKEN: ${{ secrets.PAT_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_CONFIG_PROVENANCE: false - uses: actions/upload-artifact@v4 @@ -92,6 +97,7 @@ jobs: publish-tauri: needs: publish + if: inputs.publishDesktop == 'true' continue-on-error: false strategy: fail-fast: false @@ -161,7 +167,7 @@ jobs: env: OPENCODE_VERSION: ${{ needs.publish.outputs.version }} NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} - GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} AUR_KEY: ${{ secrets.AUR_KEY }} OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} RUST_TARGET: ${{ matrix.settings.target }} @@ -209,7 +215,7 @@ jobs: needs: - publish - publish-tauri - if: needs.publish.outputs.tag + if: needs.publish.outputs.tag && (needs.publish-tauri.result == 'success' || needs.publish-tauri.result == 'skipped') runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - uses: actions/checkout@v3 @@ -226,12 +232,12 @@ jobs: 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" + git config --global user.email "shuvcode@latitudes.dev" + git config --global user.name "shuvcode" 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 }} + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} diff --git a/.github/workflows/release-github-action.yml b/.github/workflows/release-github-action.yml index 3f5caa55c8d..46ef7af90f8 100644 --- a/.github/workflows/release-github-action.yml +++ b/.github/workflows/release-github-action.yml @@ -1,13 +1,16 @@ name: release-github-action on: - push: - branches: - - dev - paths: - - "github/**" + workflow_dispatch: +# push: +# branches: +# - integration +# paths: +# - "github/**" -concurrency: ${{ github.workflow }}-${{ github.ref }} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false permissions: contents: write @@ -24,6 +27,6 @@ jobs: - name: Release run: | - git config --global user.email "opencode@sst.dev" - git config --global user.name "opencode" + git config --global user.email "shuvcode@latitudes.dev" + git config --global user.name "shuvcode" ./github/script/release diff --git a/.github/workflows/release-vscode-extension.yml b/.github/workflows/release-vscode-extension.yml new file mode 100644 index 00000000000..8c08954ddc1 --- /dev/null +++ b/.github/workflows/release-vscode-extension.yml @@ -0,0 +1,93 @@ +name: release-vscode-extension + +on: + workflow_dispatch: + inputs: + minor: + description: "Bump minor version (resets patch to 0)" + required: false + type: boolean + default: false + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write + +jobs: + release: + runs-on: blacksmith-4vcpu-ubuntu-2404 + if: github.repository == 'Latitudes-Dev/shuvcode' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - run: git fetch --force --tags + + - uses: ./.github/actions/setup-bun + + - name: Setup Git Identity + run: | + git config --global user.email "shuvcode@latitudes.dev" + git config --global user.name "shuvcode" + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} + + - name: Create Release Tag + id: release + working-directory: sdks/vscode + run: | + if [ "${{ inputs.minor }}" = "true" ]; then + ./script/release --minor + else + ./script/release + fi + + # Get the new tag + latest_tag=$(git tag --sort=committerdate | grep -E '^vscode-v[0-9]+\.[0-9]+\.[0-9]+$' | tail -1) + echo "tag=$latest_tag" >> $GITHUB_OUTPUT + version=$(echo "$latest_tag" | sed 's/^vscode-v//') + echo "version=$version" >> $GITHUB_OUTPUT + + - name: Install VS Code Extension Dependencies + working-directory: sdks/vscode + run: bun install + + - name: Build Extension + working-directory: sdks/vscode + run: bun run package + + - name: Package VSIX + working-directory: sdks/vscode + run: | + mkdir -p dist + npx @vscode/vsce package --no-git-tag-version --no-update-package-json --no-dependencies --skip-license -o "dist/shuvcode-${{ steps.release.outputs.version }}.vsix" "${{ steps.release.outputs.version }}" + + - name: Create GitHub Release + run: | + # Create release if it doesn't exist + if ! gh release view ${{ steps.release.outputs.tag }} --repo ${{ github.repository }} > /dev/null 2>&1; then + gh release create ${{ steps.release.outputs.tag }} \ + --repo ${{ github.repository }} \ + --title "VS Code Extension ${{ steps.release.outputs.version }}" \ + --notes "shuvcode VS Code Extension v${{ steps.release.outputs.version }}" + fi + + # Upload VSIX asset + gh release upload ${{ steps.release.outputs.tag }} \ + sdks/vscode/dist/shuvcode-${{ steps.release.outputs.version }}.vsix \ + --repo ${{ github.repository }} \ + --clobber + env: + GH_TOKEN: ${{ github.token }} + + - name: Summary + run: | + echo "## VS Code Extension Released" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** ${{ steps.release.outputs.tag }}" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.release.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Download:** https://github.com/${{ github.repository }}/releases/tag/${{ steps.release.outputs.tag }}" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index 93b01bafa2b..e469a71df4b 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -8,7 +8,9 @@ jobs: check-guidelines: if: | github.event.issue.pull_request && - startsWith(github.event.comment.body, '/review') && + (startsWith(github.event.comment.body, '/review') || + startsWith(github.event.comment.body, '/shuv') || + startsWith(github.event.comment.body, '/shuvcode')) && contains(fromJson('["OWNER","MEMBER"]'), github.event.comment.author_association) runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: @@ -31,8 +33,10 @@ jobs: - uses: ./.github/actions/setup-bun - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash + - name: Install shuvcode + run: | + # Install shuvcode from fork releases + curl -fsSL https://raw.githubusercontent.com/Latitudes-Dev/shuvcode/integration/install | bash - name: Get PR details id: pr-details @@ -51,7 +55,7 @@ jobs: PR_TITLE: ${{ steps.pr-details.outputs.title }} run: | PR_BODY=$(jq -r .body pr_data.json) - opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${PR_TITLE}' + shuvcode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${PR_TITLE}' ${{ steps.pr-number.outputs.number }} diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml new file mode 100644 index 00000000000..8269b1d1483 --- /dev/null +++ b/.github/workflows/snapshot.yml @@ -0,0 +1,99 @@ +name: snapshot + +on: + workflow_dispatch: + workflow_run: + workflows: ["test"] + types: + - completed + branches: + - integration + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write + id-token: write + +jobs: + publish: + runs-on: blacksmith-4vcpu-ubuntu-2404 + # Only run if tests passed (workflow_run) or manual dispatch ON INTEGRATION BRANCH + # Also skip release commits to prevent infinite loop + if: | + (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/integration') || + (github.event_name == 'workflow_run' && + github.event.workflow_run.conclusion == 'success' && + !startsWith(github.event.workflow_run.head_commit.message, 'release:') && + !startsWith(github.event.workflow_run.head_commit.message, 'chore:')) + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + token: ${{ secrets.PAT_TOKEN }} + # Checkout the branch by name so git branch --show-current works + # For workflow_run, use head_branch; for workflow_dispatch, use ref + ref: ${{ github.event.workflow_run.head_branch || github.ref }} + + - run: git fetch --force --tags + + - uses: actions/setup-go@v5 + with: + go-version: ">=1.24.0" + cache: true + cache-dependency-path: go.sum + + - uses: actions/setup-node@v4 + with: + node-version: "22" + registry-url: "https://registry.npmjs.org" + + - uses: ./.github/actions/setup-bun + + - name: Configure Git + run: | + git config --global user.email "shuvcode@latitudes.dev" + git config --global user.name "shuvcode" + + - name: Publish + id: publish + run: | + ./script/publish.ts + env: + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish Release (undraft) + if: success() && steps.publish.outputs.tagName + run: gh release edit ${{ steps.publish.outputs.tagName }} --draft=false + env: + GH_TOKEN: ${{ secrets.PAT_TOKEN }} + + # - name: Post to Discord + # if: success() && steps.publish.outputs.tagName + # continue-on-error: true + # run: ./script/discord-notify.ts + # env: + # DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} + # DISCORD_THREAD_ID: ${{ secrets.DISCORD_THREAD_ID }} + # RELEASE_VERSION: ${{ steps.publish.outputs.tagName }} + # RELEASE_CHANGELOG: ${{ steps.publish.outputs.changelog }} + - name: Deploy Desktop to Cloudflare + continue-on-error: true + run: | + bunx sst deploy --stage production --config sst.desktop.config.ts + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_DEFAULT_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + + - name: Deploy Share to Cloudflare + continue-on-error: true + run: | + bunx sst deploy --stage production --config sst.share.config.ts + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_DEFAULT_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml deleted file mode 100644 index 824733901d6..00000000000 --- a/.github/workflows/stats.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: stats - -on: - schedule: - - cron: "0 12 * * *" # Run daily at 12:00 UTC - workflow_dispatch: # Allow manual trigger - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - stats: - if: github.repository == 'anomalyco/opencode' - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: write - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Run stats script - run: bun script/stats.ts - - - name: Commit stats - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add STATS.md - git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)" - git push - env: - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} diff --git a/.github/workflows/sync-zed-extension.yml b/.github/workflows/sync-zed-extension.yml deleted file mode 100644 index f14487cde97..00000000000 --- a/.github/workflows/sync-zed-extension.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: "sync-zed-extension" - -on: - workflow_dispatch: - release: - types: [published] - -jobs: - zed: - name: Release Zed Extension - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: ./.github/actions/setup-bun - - - name: Get version tag - id: get_tag - run: | - if [ "${{ github.event_name }}" = "release" ]; then - TAG="${{ github.event.release.tag_name }}" - else - TAG=$(git tag --list 'v[0-9]*.*' --sort=-version:refname | head -n 1) - fi - echo "tag=${TAG}" >> $GITHUB_OUTPUT - echo "Using tag: ${TAG}" - - - name: Sync Zed extension - run: | - ./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }} - env: - ZED_EXTENSIONS_PAT: ${{ secrets.ZED_EXTENSIONS_PAT }} - ZED_PR_PAT: ${{ secrets.ZED_PR_PAT }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d95de94d232..7dc68e15fda 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,7 @@ on: push: branches: - dev + - integration pull_request: workflow_dispatch: jobs: @@ -45,22 +46,15 @@ jobs: run: ${{ matrix.settings.playwright }} - name: Set OS-specific paths + if: runner.os != 'Windows' run: | - if [ "${{ runner.os }}" = "Windows" ]; then - printf '%s\n' "OPENCODE_E2E_ROOT=${{ runner.temp }}\\opencode-e2e" >> "$GITHUB_ENV" - printf '%s\n' "OPENCODE_TEST_HOME=${{ runner.temp }}\\opencode-e2e\\home" >> "$GITHUB_ENV" - printf '%s\n' "XDG_DATA_HOME=${{ runner.temp }}\\opencode-e2e\\share" >> "$GITHUB_ENV" - printf '%s\n' "XDG_CACHE_HOME=${{ runner.temp }}\\opencode-e2e\\cache" >> "$GITHUB_ENV" - printf '%s\n' "XDG_CONFIG_HOME=${{ runner.temp }}\\opencode-e2e\\config" >> "$GITHUB_ENV" - printf '%s\n' "XDG_STATE_HOME=${{ runner.temp }}\\opencode-e2e\\state" >> "$GITHUB_ENV" - else - printf '%s\n' "OPENCODE_E2E_ROOT=${{ runner.temp }}/opencode-e2e" >> "$GITHUB_ENV" - printf '%s\n' "OPENCODE_TEST_HOME=${{ runner.temp }}/opencode-e2e/home" >> "$GITHUB_ENV" - printf '%s\n' "XDG_DATA_HOME=${{ runner.temp }}/opencode-e2e/share" >> "$GITHUB_ENV" - printf '%s\n' "XDG_CACHE_HOME=${{ runner.temp }}/opencode-e2e/cache" >> "$GITHUB_ENV" - printf '%s\n' "XDG_CONFIG_HOME=${{ runner.temp }}/opencode-e2e/config" >> "$GITHUB_ENV" - printf '%s\n' "XDG_STATE_HOME=${{ runner.temp }}/opencode-e2e/state" >> "$GITHUB_ENV" - fi + printf '%s\n' "OPENCODE_E2E_ROOT=${{ runner.temp }}/opencode-e2e" >> "$GITHUB_ENV" + printf '%s\n' "OPENCODE_TEST_HOME=${{ runner.temp }}/opencode-e2e/home" >> "$GITHUB_ENV" + printf '%s\n' "XDG_DATA_HOME=${{ runner.temp }}/opencode-e2e/share" >> "$GITHUB_ENV" + printf '%s\n' "XDG_CACHE_HOME=${{ runner.temp }}/opencode-e2e/cache" >> "$GITHUB_ENV" + printf '%s\n' "XDG_CONFIG_HOME=${{ runner.temp }}/opencode-e2e/config" >> "$GITHUB_ENV" + printf '%s\n' "XDG_STATE_HOME=${{ runner.temp }}/opencode-e2e/state" >> "$GITHUB_ENV" + printf '%s\n' "MODELS_DEV_API_JSON=${{ github.workspace }}/packages/opencode/test/tool/fixtures/models-api.json" >> "$GITHUB_ENV" - name: Seed opencode data if: matrix.settings.name != 'windows' diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml deleted file mode 100644 index 6e150957291..00000000000 --- a/.github/workflows/triage.yml +++ /dev/null @@ -1,37 +0,0 @@ -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/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml deleted file mode 100644 index 011e23f5f6f..00000000000 --- a/.github/workflows/typecheck.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: typecheck - -on: - pull_request: - branches: [dev] - workflow_dispatch: - -jobs: - typecheck: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Run typecheck - run: bun typecheck diff --git a/.github/workflows/upstream-sync.yml b/.github/workflows/upstream-sync.yml new file mode 100644 index 00000000000..6f3fb7f68b2 --- /dev/null +++ b/.github/workflows/upstream-sync.yml @@ -0,0 +1,755 @@ +name: Upstream Sync + +on: + repository_dispatch: + types: [upstream-release] + workflow_dispatch: + inputs: + tag: + description: "Tag to sync (leave empty for latest)" + type: string + required: false + +concurrency: + group: upstream-sync + cancel-in-progress: false + +permissions: + contents: write + issues: write + +jobs: + resolve-tag: + runs-on: blacksmith-2vcpu-ubuntu-2404 + outputs: + latest_tag: ${{ steps.resolve.outputs.latest_tag }} + latest_sha: ${{ steps.resolve.outputs.latest_sha }} + should_sync: ${{ steps.resolve.outputs.should_sync }} + steps: + - name: Resolve tag and SHA + id: resolve + env: + GITHUB_TOKEN: ${{ github.token }} + INPUT_TAG: ${{ github.event.client_payload.tag || inputs.tag }} + run: | + set -euo pipefail + + api() { + curl -fsSL -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $GITHUB_TOKEN" "$1" + } + + # Use provided tag or fetch latest + if [ -n "${INPUT_TAG:-}" ]; then + LATEST_TAG="$INPUT_TAG" + else + RELEASE=$(api "https://api.github.com/repos/sst/opencode/releases/latest") + LATEST_TAG=$(echo "$RELEASE" | jq -r '.tag_name') + fi + + if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then + echo "Unable to determine tag" >&2 + exit 1 + fi + + # Check if already synced (early exit to prevent duplicate work) + LAST_SYNCED=$(api "https://api.github.com/repos/${{ github.repository }}/contents/.github/last-synced-tag?ref=integration" | jq -r '.content' | base64 -d | tr -d '\n' || echo "") + if [ "$LATEST_TAG" = "$LAST_SYNCED" ]; then + echo "Tag $LATEST_TAG already synced, skipping" + echo "latest_tag=$LATEST_TAG" >> "$GITHUB_OUTPUT" + echo "latest_sha=" >> "$GITHUB_OUTPUT" + echo "should_sync=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Resolve tag to commit SHA + TAG_REF=$(api "https://api.github.com/repos/sst/opencode/git/ref/tags/$LATEST_TAG") + REF_TYPE=$(echo "$TAG_REF" | jq -r '.object.type') + REF_SHA=$(echo "$TAG_REF" | jq -r '.object.sha') + + if [ "$REF_TYPE" = "tag" ]; then + TAG_OBJECT=$(api "https://api.github.com/repos/sst/opencode/git/tags/$REF_SHA") + LATEST_SHA=$(echo "$TAG_OBJECT" | jq -r '.object.sha') + else + LATEST_SHA="$REF_SHA" + fi + + echo "Resolved tag: $LATEST_TAG -> $LATEST_SHA" + echo "latest_tag=$LATEST_TAG" >> "$GITHUB_OUTPUT" + echo "latest_sha=$LATEST_SHA" >> "$GITHUB_OUTPUT" + echo "should_sync=true" >> "$GITHUB_OUTPUT" + + merge-upstream: + needs: resolve-tag + if: needs.resolve-tag.outputs.should_sync == 'true' + runs-on: blacksmith-4vcpu-ubuntu-2404 + outputs: + merge_status: ${{ steps.merge.outputs.status }} + conflict_files: ${{ steps.merge.outputs.conflict_files }} + steps: + - name: Checkout integration branch + uses: actions/checkout@v4 + with: + ref: integration + fetch-depth: 0 + token: ${{ secrets.PAT_TOKEN }} + + - name: Setup Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Add upstream remote + run: | + git remote add upstream https://github.com/sst/opencode.git 2>/dev/null || true + + - name: Fetch upstream tag + run: | + git fetch upstream tag ${{ needs.resolve-tag.outputs.latest_tag }} --no-tags + + - name: Attempt merge + id: merge + run: | + # Attempt merge with no-commit to detect conflicts first + if git merge ${{ needs.resolve-tag.outputs.latest_tag }} --no-commit --no-ff 2>&1; then + echo "Merge successful, no conflicts" + # Check if there are changes to commit + if git diff --cached --quiet; then + echo "Already up to date, nothing to merge" + echo "status=up-to-date" >> $GITHUB_OUTPUT + else + git commit -m "sync: merge upstream ${{ needs.resolve-tag.outputs.latest_tag }} into integration" + echo "status=success" >> $GITHUB_OUTPUT + fi + else + # Check for conflicts + CONFLICTS=$(git diff --name-only --diff-filter=U) + if [ -n "$CONFLICTS" ]; then + echo "Conflicts detected in: $CONFLICTS" + echo "status=conflict" >> $GITHUB_OUTPUT + echo "conflict_files<> $GITHUB_OUTPUT + echo "$CONFLICTS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # Attempt auto-resolution for known patterns + UNRESOLVED="" + for file in $CONFLICTS; do + # Skip files in .gitignore (like .opencode/) - reset them instead + if git check-ignore -q "$file" 2>/dev/null; then + echo "Skipping ignored file: $file (resetting to HEAD)" + git checkout HEAD -- "$file" 2>/dev/null || git rm --cached -f "$file" 2>/dev/null || true + continue + fi + + case "$file" in + bun.lock) + echo "Auto-resolving bun.lock by accepting upstream..." + git checkout --theirs bun.lock 2>/dev/null || true + git add bun.lock + ;; + *.md) + echo "Auto-resolving $file (accepting upstream)..." + git checkout --theirs "$file" + git add "$file" + ;; + *) + UNRESOLVED="$UNRESOLVED $file" + ;; + esac + done + + # Check if all conflicts were resolved + REMAINING=$(git diff --name-only --diff-filter=U) + if [ -z "$REMAINING" ]; then + echo "All conflicts auto-resolved" + git commit -m "sync: merge upstream ${{ needs.resolve-tag.outputs.latest_tag }} into integration (auto-resolved conflicts)" + echo "status=auto-resolved" >> $GITHUB_OUTPUT + else + echo "Unresolved conflicts remain: $REMAINING" + git merge --abort + echo "status=needs-manual" >> $GITHUB_OUTPUT + fi + else + echo "Merge failed for unknown reason" + git merge --abort 2>/dev/null || true + echo "status=failed" >> $GITHUB_OUTPUT + fi + fi + + - name: Push integration branch + if: steps.merge.outputs.status == 'success' || steps.merge.outputs.status == 'auto-resolved' + run: | + git push origin integration + + - name: Update last synced tag marker + if: steps.merge.outputs.status == 'success' || steps.merge.outputs.status == 'auto-resolved' || steps.merge.outputs.status == 'up-to-date' + run: | + echo "${{ needs.resolve-tag.outputs.latest_tag }}" > .github/last-synced-tag + git add .github/last-synced-tag + git commit -m "sync: record last synced tag ${{ needs.resolve-tag.outputs.latest_tag }}" || true + git push origin integration || true + + validate: + needs: [resolve-tag, merge-upstream] + if: needs.merge-upstream.outputs.merge_status == 'success' || needs.merge-upstream.outputs.merge_status == 'auto-resolved' || needs.merge-upstream.outputs.merge_status == 'up-to-date' + runs-on: blacksmith-4vcpu-ubuntu-2404 + outputs: + validation_status: ${{ steps.validate.outputs.status }} + validation_error: ${{ steps.validate.outputs.error }} + steps: + - name: Checkout integration branch + uses: actions/checkout@v4 + with: + ref: integration + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + + - name: Install dependencies + run: bun install + + - name: Run validation + id: validate + run: | + set +e + + echo "Running typecheck..." + bun turbo typecheck + if [ $? -ne 0 ]; then + echo "status=failed" >> $GITHUB_OUTPUT + echo "error=typecheck failed" >> $GITHUB_OUTPUT + exit 1 + fi + + echo "Running tests..." + bun turbo test + if [ $? -ne 0 ]; then + echo "status=failed" >> $GITHUB_OUTPUT + echo "error=tests failed" >> $GITHUB_OUTPUT + exit 1 + fi + + echo "Running build..." + bun turbo build + if [ $? -ne 0 ]; then + echo "status=failed" >> $GITHUB_OUTPUT + echo "error=build failed" >> $GITHUB_OUTPUT + exit 1 + fi + + echo "status=success" >> $GITHUB_OUTPUT + + create-issue: + needs: [resolve-tag, merge-upstream, validate] + if: | + always() && + (needs.merge-upstream.outputs.merge_status == 'needs-manual' || + needs.merge-upstream.outputs.merge_status == 'failed' || + needs.validate.outputs.validation_status == 'failed') + runs-on: blacksmith-4vcpu-ubuntu-2404 + outputs: + issue_number: ${{ steps.create-conflict-issue.outputs.issue_number || steps.create-validation-issue.outputs.issue_number }} + steps: + - name: Checkout integration branch + uses: actions/checkout@v4 + with: + ref: integration + + - name: Create conflict issue + id: create-conflict-issue + if: needs.merge-upstream.outputs.merge_status == 'needs-manual' + uses: actions/github-script@v7 + with: + script: | + const conflictFiles = `${{ needs.merge-upstream.outputs.conflict_files }}`; + const latestTag = `${{ needs.resolve-tag.outputs.latest_tag }}`; + const latestSha = `${{ needs.resolve-tag.outputs.latest_sha }}`; + const issueTitle = `[Upstream Sync] Merge conflict with ${latestTag}`; + + // Check if an issue with this title already exists + const existingIssues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: 'upstream-sync' + }); + + const duplicate = existingIssues.data.find(issue => issue.title === issueTitle); + if (duplicate) { + console.log(`Issue already exists: #${duplicate.number}`); + core.setOutput('issue_number', duplicate.number); + core.setOutput('is_duplicate', 'true'); + return; + } + + const body = `## Upstream Sync Conflict Report + + **Trigger**: Upstream sync at ${new Date().toISOString()} + **Upstream Tag**: ${latestTag} + **Upstream SHA**: ${latestSha} + + ### Conflicting Files + + \`\`\` + ${conflictFiles} + \`\`\` + + ### Recommended Actions + + 1. Checkout integration branch locally + 2. Run: \`git fetch upstream && git merge ${latestTag}\` + 3. Resolve conflicts manually + 4. Run validation: \`bun install && bun turbo typecheck && bun turbo test\` + 5. Push resolved integration branch + 6. Close this issue + + ### Manual Sync Commands + + \`\`\`bash + git remote add upstream https://github.com/sst/opencode.git 2>/dev/null || true + git fetch upstream --tags + git checkout integration + git merge ${latestTag} + # Resolve conflicts... + bun install + bun turbo typecheck + bun turbo test + git push origin integration + \`\`\` + `; + + const issue = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: issueTitle, + body: body, + labels: ['upstream-sync', 'needs-manual-review'] + }); + + core.setOutput('issue_number', issue.data.number); + core.setOutput('is_duplicate', 'false'); + + - name: Create validation failure issue + id: create-validation-issue + if: needs.validate.outputs.validation_status == 'failed' + uses: actions/github-script@v7 + with: + script: | + const latestTag = `${{ needs.resolve-tag.outputs.latest_tag }}`; + const error = `${{ needs.validate.outputs.validation_error }}`; + const issueTitle = `[Upstream Sync] Validation failed after merging ${latestTag}`; + + // Check if an issue with this title already exists + const existingIssues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: 'upstream-sync' + }); + + const duplicate = existingIssues.data.find(issue => issue.title === issueTitle); + if (duplicate) { + console.log(`Issue already exists: #${duplicate.number}`); + core.setOutput('issue_number', duplicate.number); + core.setOutput('is_duplicate', 'true'); + return; + } + + const body = `## Upstream Sync Validation Failure + + **Trigger**: Upstream sync at ${new Date().toISOString()} + **Upstream Tag**: ${latestTag} + **Error**: ${error} + + ### What happened + + The upstream sync merged successfully, but post-merge validation failed. + + ### Recommended Actions + + 1. Check the [workflow run](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}) for detailed logs + 2. Checkout integration branch locally + 3. Run the failing validation step locally to debug + 4. Fix any issues and push to integration + + ### Validation Commands + + \`\`\`bash + git fetch origin + git checkout integration + bun install + bun turbo typecheck + bun turbo test + bun turbo build + \`\`\` + `; + + const issue = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: issueTitle, + body: body, + labels: ['upstream-sync', 'needs-manual-review'] + }); + + core.setOutput('issue_number', issue.data.number); + core.setOutput('is_duplicate', 'false'); + + close-issues-on-success: + needs: [resolve-tag, merge-upstream, validate] + if: | + always() && + needs.validate.outputs.validation_status == 'success' && + (needs.merge-upstream.outputs.merge_status == 'success' || needs.merge-upstream.outputs.merge_status == 'auto-resolved') + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - name: Close related upstream-sync issues + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.PAT_TOKEN }} + script: | + const latestTag = `${{ needs.resolve-tag.outputs.latest_tag }}`; + + // Find all open upstream-sync issues + const issues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: 'upstream-sync' + }); + + for (const issue of issues.data) { + // Close issues related to this tag or older + if (issue.title.includes('[Upstream Sync]')) { + console.log(`Closing issue #${issue.number}: ${issue.title}`); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `✅ Automatically closed - upstream sync to ${latestTag} completed successfully.\n\nValidation passed: typecheck, tests, and build all succeeded.` + }); + + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed', + state_reason: 'completed' + }); + } + } + + trigger-opencode: + needs: [resolve-tag, merge-upstream, create-issue] + if: | + always() && + needs.create-issue.outputs.issue_number != '' + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - name: Checkout integration branch + uses: actions/checkout@v4 + with: + ref: integration + + - name: Read fork features + id: fork-features + run: | + if [ -f "script/sync/fork-features.json" ]; then + # Extract features as a formatted list for the prompt + FEATURES=$(cat script/sync/fork-features.json | jq -r '.features[] | "- PR #\(.pr): \(.title) (@\(.author))\n Files: \(.files | join(", "))\n Description: \(.description)"') + echo "features<> $GITHUB_OUTPUT + echo "$FEATURES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # Extract just the file paths for conflict matching + FILES=$(cat script/sync/fork-features.json | jq -r '[.features[].files[]] | unique | .[]') + echo "files<> $GITHUB_OUTPUT + echo "$FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + else + echo "features=" >> $GITHUB_OUTPUT + echo "files=" >> $GITHUB_OUTPUT + fi + + - name: Trigger opencode to resolve conflicts + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.PAT_TOKEN }} + script: | + const issueNumber = parseInt(`${{ needs.create-issue.outputs.issue_number }}`); + const latestTag = `${{ needs.resolve-tag.outputs.latest_tag }}`; + const conflictFiles = `${{ needs.merge-upstream.outputs.conflict_files }}`; + const mergeStatus = `${{ needs.merge-upstream.outputs.merge_status }}`; + const forkFeatures = `${{ steps.fork-features.outputs.features }}`; + const forkFeatureFiles = `${{ steps.fork-features.outputs.files }}`; + + // Build the opencode prompt based on the issue type + let prompt; + + if (mergeStatus === 'needs-manual') { + prompt = `/opencode + + ## Task: Resolve Upstream Merge Conflicts + + Sync the shuvcode fork from upstream sst/opencode tag **${latestTag}**, resolve all merge conflicts, and complete the integration. + + ### Repository Context + - **Upstream**: \`sst/opencode\` (original project) + - **Fork**: \`Latitudes-Dev/shuvcode\` (customized downstream fork) + - **Integration branch**: \`integration\` (where we merge upstream changes) + + ### Conflicting Files + \`\`\` + ${conflictFiles} + \`\`\` + + ### CRITICAL: Fork Feature Files (MUST PRESERVE) + + This fork contains features from upstream PRs that have NOT yet been merged upstream. + These features MUST be preserved during the merge. The fork-features.json file at + \`script/sync/fork-features.json\` contains the authoritative list. + + **Merged PRs in this fork:** + ${forkFeatures} + + **Fork feature files that require special handling:** + \`\`\` + ${forkFeatureFiles} + \`\`\` + + **IMPORTANT**: When any of these files have conflicts: + 1. Do NOT simply accept upstream changes - this will DELETE our features + 2. Carefully merge to preserve BOTH upstream improvements AND our fork features + 3. Read the file content before and after to verify features are preserved + 4. Test that the feature still works after merging + + ### Conflict Resolution Strategy + + Follow these resolution patterns for each file type: + + | File Pattern | Resolution | + |--------------|------------| + | Fork feature files (see above) | **MERGE CAREFULLY** - preserve our features while integrating upstream changes | + | \`STATS.md\`, \`nix/hashes.json\` | Accept upstream (\`git checkout --theirs\`) | + | \`bun.lock\` | Delete and regenerate with \`bun install\` | + | \`packages/opencode/script/publish.ts\` | Keep ours (fork-specific publishing) | + | \`packages/opencode/script/build.ts\` | Keep ours (shuvcode naming) | + | \`packages/opencode/bin/opencode\` | Keep ours (shuvcode binary lookup) | + | \`packages/script/src/index.ts\` | Keep ours (version logic) | + | \`.github/*\` workflow configs | Keep ours (fork-specific workflows) | + | \`README.md\` | Keep ours (fork-specific documentation with PR table) | + + ### Step-by-Step Instructions + + 1. **Review fork-features.json first**: + \`\`\`bash + cat script/sync/fork-features.json + \`\`\` + This file lists all PRs and their files that MUST be preserved. + + 2. **Setup upstream remote and fetch**: + \`\`\`bash + git remote add upstream https://github.com/sst/opencode.git 2>/dev/null || true + git fetch upstream --tags + \`\`\` + + 3. **Start the merge**: + \`\`\`bash + git merge ${latestTag} --no-commit --no-ff + \`\`\` + + 4. **List conflicts and identify fork feature files**: + \`\`\`bash + git diff --name-only --diff-filter=U + \`\`\` + Cross-reference with fork-features.json to identify critical files. + + 5. **For fork feature file conflicts**: Read both versions, understand the changes, and manually merge to preserve our features while integrating upstream improvements. + + 6. **For each resolved file**: + \`\`\`bash + git add + \`\`\` + + 7. **Verify no conflicts remain**: + \`\`\`bash + git diff --name-only --diff-filter=U # Should be empty + \`\`\` + + 8. **Regenerate lockfile** (if bun.lock was conflicted): + \`\`\`bash + rm -f bun.lock + bun install + git add bun.lock + \`\`\` + + 9. **Commit the merge**: + \`\`\`bash + git commit -m "sync: merge upstream ${latestTag} into integration + + Resolved conflicts: + - + + Preserved fork features: + - " + \`\`\` + + 10. **Update sync marker**: + \`\`\`bash + echo "${latestTag}" > .github/last-synced-tag + git add .github/last-synced-tag + git commit -m "sync: record last synced tag ${latestTag}" + \`\`\` + + 11. **Run validation**: + \`\`\`bash + bun install + bun turbo typecheck + bun turbo test + \`\`\` + + 12. **Push branch to origin**: + \`\`\`bash + git push origin HEAD + \`\`\` + + 13. **Create or find existing PR**: + \`\`\`bash + # Check if PR already exists for this branch + EXISTING_PR=$(gh pr list --head "$(git branch --show-current)" --json number --jq '.[0].number' --repo Latitudes-Dev/shuvcode 2>/dev/null || echo "") + if [ -n "$EXISTING_PR" ]; then + echo "PR #$EXISTING_PR already exists" + PR_NUMBER=$EXISTING_PR + else + # Create PR targeting integration branch + PR_URL=$(gh pr create --title "sync: merge upstream ${latestTag}" --body "Resolves conflicts from upstream ${latestTag} merge. Closes #${issueNumber}" --base integration --repo Latitudes-Dev/shuvcode) + PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') + echo "Created PR #$PR_NUMBER" + fi + \`\`\` + + 14. **Wait for CI checks to pass, then merge the PR**: + \`\`\`bash + # Wait for checks and merge (auto-merge when checks pass) + gh pr merge "$PR_NUMBER" --squash --auto --delete-branch --repo Latitudes-Dev/shuvcode + \`\`\` + + 15. **Close this issue** after PR is merged: + \`\`\`bash + gh issue close ${issueNumber} --comment "Resolved - merged upstream ${latestTag} via PR #$PR_NUMBER" --repo Latitudes-Dev/shuvcode + \`\`\` + + ### Important Notes + - **Never force-push** to integration branch + - **Always preserve fork-specific changes** in publish.ts, build.ts, bin/opencode + - **CRITICAL**: Always preserve fork feature files listed in fork-features.json + - If validation fails, fix the issues before pushing + - The PR will be auto-merged once CI checks pass, triggering the snapshot workflow + `; + } else { + // Validation failure case + prompt = `/opencode + + ## Task: Fix Validation Failures After Upstream Merge + + The upstream merge of **${latestTag}** succeeded, but post-merge validation failed. + + ### Fork Features Context + + This fork contains features from upstream PRs that have NOT yet been merged upstream. + Review \`script/sync/fork-features.json\` to understand what features must be preserved. + Validation failures may be related to conflicts between upstream changes and our fork features. + + **Merged PRs in this fork:** + ${forkFeatures} + + ### Instructions + + 1. **Review fork-features.json**: + \`\`\`bash + cat script/sync/fork-features.json + \`\`\` + + 2. **Pull the latest integration branch**: + \`\`\`bash + git fetch origin + git checkout integration + git pull origin integration + \`\`\` + + 3. **Install dependencies**: + \`\`\`bash + bun install + \`\`\` + + 4. **Run validation and identify failures**: + \`\`\`bash + bun turbo typecheck + bun turbo test + bun turbo build + \`\`\` + + 5. **Fix any type errors, test failures, or build issues** + - If failures are in fork feature files, ensure you preserve the feature functionality + - Cross-reference with fork-features.json to understand what each file should contain + + 6. **Commit fixes**: + \`\`\`bash + git add . + git commit -m "fix: resolve validation issues after merging ${latestTag}" + \`\`\` + + 7. **Re-run validation to confirm fixes**: + \`\`\`bash + bun turbo typecheck && bun turbo test && bun turbo build + \`\`\` + + 8. **Push branch to origin**: + \`\`\`bash + git push origin HEAD + \`\`\` + + 9. **Create or find existing PR**: + \`\`\`bash + # Check if PR already exists for this branch + EXISTING_PR=$(gh pr list --head "$(git branch --show-current)" --json number --jq '.[0].number' --repo Latitudes-Dev/shuvcode 2>/dev/null || echo "") + if [ -n "$EXISTING_PR" ]; then + echo "PR #$EXISTING_PR already exists" + PR_NUMBER=$EXISTING_PR + else + # Create PR targeting integration branch + PR_URL=$(gh pr create --title "fix: resolve validation issues after ${latestTag}" --body "Fixes validation failures from upstream ${latestTag} merge. Closes #${issueNumber}" --base integration --repo Latitudes-Dev/shuvcode) + PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') + echo "Created PR #$PR_NUMBER" + fi + \`\`\` + + 10. **Wait for CI checks to pass, then merge the PR**: + \`\`\`bash + # Wait for checks and merge (auto-merge when checks pass) + gh pr merge "$PR_NUMBER" --squash --auto --delete-branch --repo Latitudes-Dev/shuvcode + \`\`\` + + 11. **Close this issue** after PR is merged: + \`\`\`bash + gh issue close ${issueNumber} --comment "Fixed validation issues after merging ${latestTag} via PR #$PR_NUMBER" --repo Latitudes-Dev/shuvcode + \`\`\` + `; + } + + // Post the comment to trigger opencode + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body: prompt + }); + + console.log(`Posted opencode trigger comment on issue #${issueNumber}`); diff --git a/.opencode/agent/docs.md b/.opencode/agent/docs.md deleted file mode 100644 index 21cfc6a16e0..00000000000 --- a/.opencode/agent/docs.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -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 -avoid repeating the title of the page, should be 5-10 words long - -Chunks of text should not be more than 2 sentences long - -Each section is separated by a divider of 3 dashes - -The section titles are short with only the first letter of the word capitalized - -The section titles are in the imperative mood - -The section titles should not repeat the term used in the page title, for -example, if the page title is "Models", avoid using a section title like "Add -new models". This might be unavoidable in some cases, but try to avoid it. - -Check out the /packages/web/src/content/docs/docs/index.mdx as an example. - -For JS or TS code snippets remove trailing semicolons and any trailing commas -that might not be needed. - -If you are making a commit prefix the commit message with `docs:` diff --git a/.opencode/agent/triage.md b/.opencode/agent/triage.md deleted file mode 100644 index 5d1147a8859..00000000000 --- a/.opencode/agent/triage.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -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" or "opencode black". - -If the issue doesn't have "zen" or "opencode black" 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 deleted file mode 100644 index 8260029195e..00000000000 --- a/.opencode/command/commit.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -description: git commit and push -model: opencode/glm-4.7 -subtask: true ---- - -commit and push - -make sure it includes a prefix like -docs: -tui: -core: -ci: -ignore: -wip: - -For anything in the packages/web use the docs: prefix. - -For anything in the packages/app use the ignore: prefix. - -prefer to explain WHY something was done from an end user perspective instead of -WHAT was done. - -do not do generic messages like "improved agent experience" be very specific -about what user facing changes were made - -if there are changes do a git pull --rebase -if there are conflicts DO NOT FIX THEM. notify me and I will fix them - -## GIT DIFF - -!`git diff` - -## GIT DIFF --cached - -!`git diff --cached` - -## GIT STATUS --short - -!`git status --short` diff --git a/.opencode/command/issues.md b/.opencode/command/issues.md deleted file mode 100644 index 75b59616743..00000000000 --- a/.opencode/command/issues.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -description: "find issue(s) on github" -model: opencode/claude-haiku-4-5 ---- - -Search through existing issues in anomalyco/opencode using the gh cli to find issues matching this query: - -$ARGUMENTS - -Consider: - -1. Similar titles or descriptions -2. Same error messages or symptoms -3. Related functionality or components -4. Similar feature requests - -Please list any matching issues with: - -- Issue number and title -- Brief explanation of why it matches the query -- Link to the issue - -If no clear matches are found, say so. diff --git a/.opencode/command/rmslop.md b/.opencode/command/rmslop.md deleted file mode 100644 index 02c9fc0844a..00000000000 --- a/.opencode/command/rmslop.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -description: Remove AI code slop ---- - -Check the diff against dev, and remove all AI generated slop introduced in this branch. - -This includes: - -- Extra comments that a human wouldn't add or is inconsistent with the rest of the file -- Extra defensive checks or try/catch blocks that are abnormal for that area of the codebase (especially if called by trusted / validated codepaths) -- Casts to any to get around type issues -- Any other style that is inconsistent with the file -- Unnecessary emoji usage - -Report at the end with only a 1-3 sentence summary of what you changed diff --git a/.opencode/command/spellcheck.md b/.opencode/command/spellcheck.md deleted file mode 100644 index 0abf23c4fd0..00000000000 --- a/.opencode/command/spellcheck.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -description: spellcheck all markdown file changes ---- - -Look at all the unstaged changes to markdown (.md, .mdx) files, pull out the lines that have changed, and check for spelling and grammar errors. diff --git a/.opencode/env.d.ts b/.opencode/env.d.ts deleted file mode 100644 index f2b13a934c4..00000000000 --- a/.opencode/env.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "*.txt" { - const content: string - export default content -} diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc deleted file mode 100644 index c3f0b7070d1..00000000000 --- a/.opencode/opencode.jsonc +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "https://opencode.ai/config.json", - // "plugin": ["opencode-openai-codex-auth"], - // "enterprise": { - // "url": "https://enterprise.dev.opencode.ai", - // }, - "provider": { - "opencode": { - "options": {}, - }, - }, - "mcp": { - "context7": { - "type": "remote", - "url": "https://mcp.context7.com/mcp", - }, - }, - "tools": { - "github-triage": false, - "github-pr-search": false, - }, -} diff --git a/.opencode/themes/mytheme.json b/.opencode/themes/mytheme.json deleted file mode 100644 index e444de807c6..00000000000 --- a/.opencode/themes/mytheme.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "$schema": "https://opencode.ai/theme.json", - "defs": { - "nord0": "#2E3440", - "nord1": "#3B4252", - "nord2": "#434C5E", - "nord3": "#4C566A", - "nord4": "#D8DEE9", - "nord5": "#E5E9F0", - "nord6": "#ECEFF4", - "nord7": "#8FBCBB", - "nord8": "#88C0D0", - "nord9": "#81A1C1", - "nord10": "#5E81AC", - "nord11": "#BF616A", - "nord12": "#D08770", - "nord13": "#EBCB8B", - "nord14": "#A3BE8C", - "nord15": "#B48EAD" - }, - "theme": { - "primary": { - "dark": "nord8", - "light": "nord10" - }, - "secondary": { - "dark": "nord9", - "light": "nord9" - }, - "accent": { - "dark": "nord7", - "light": "nord7" - }, - "error": { - "dark": "nord11", - "light": "nord11" - }, - "warning": { - "dark": "nord12", - "light": "nord12" - }, - "success": { - "dark": "nord14", - "light": "nord14" - }, - "info": { - "dark": "nord8", - "light": "nord10" - }, - "text": { - "dark": "nord4", - "light": "nord0" - }, - "textMuted": { - "dark": "nord3", - "light": "nord1" - }, - "background": { - "dark": "nord0", - "light": "nord6" - }, - "backgroundPanel": { - "dark": "nord1", - "light": "nord5" - }, - "backgroundElement": { - "dark": "nord1", - "light": "nord4" - }, - "border": { - "dark": "nord2", - "light": "nord3" - }, - "borderActive": { - "dark": "nord3", - "light": "nord2" - }, - "borderSubtle": { - "dark": "nord2", - "light": "nord3" - }, - "diffAdded": { - "dark": "nord14", - "light": "nord14" - }, - "diffRemoved": { - "dark": "nord11", - "light": "nord11" - }, - "diffContext": { - "dark": "nord3", - "light": "nord3" - }, - "diffHunkHeader": { - "dark": "nord3", - "light": "nord3" - }, - "diffHighlightAdded": { - "dark": "nord14", - "light": "nord14" - }, - "diffHighlightRemoved": { - "dark": "nord11", - "light": "nord11" - }, - "diffAddedBg": { - "dark": "#3B4252", - "light": "#E5E9F0" - }, - "diffRemovedBg": { - "dark": "#3B4252", - "light": "#E5E9F0" - }, - "diffContextBg": { - "dark": "nord1", - "light": "nord5" - }, - "diffLineNumber": { - "dark": "nord2", - "light": "nord4" - }, - "diffAddedLineNumberBg": { - "dark": "#3B4252", - "light": "#E5E9F0" - }, - "diffRemovedLineNumberBg": { - "dark": "#3B4252", - "light": "#E5E9F0" - }, - "markdownText": { - "dark": "nord4", - "light": "nord0" - }, - "markdownHeading": { - "dark": "nord8", - "light": "nord10" - }, - "markdownLink": { - "dark": "nord9", - "light": "nord9" - }, - "markdownLinkText": { - "dark": "nord7", - "light": "nord7" - }, - "markdownCode": { - "dark": "nord14", - "light": "nord14" - }, - "markdownBlockQuote": { - "dark": "nord3", - "light": "nord3" - }, - "markdownEmph": { - "dark": "nord12", - "light": "nord12" - }, - "markdownStrong": { - "dark": "nord13", - "light": "nord13" - }, - "markdownHorizontalRule": { - "dark": "nord3", - "light": "nord3" - }, - "markdownListItem": { - "dark": "nord8", - "light": "nord10" - }, - "markdownListEnumeration": { - "dark": "nord7", - "light": "nord7" - }, - "markdownImage": { - "dark": "nord9", - "light": "nord9" - }, - "markdownImageText": { - "dark": "nord7", - "light": "nord7" - }, - "markdownCodeBlock": { - "dark": "nord4", - "light": "nord0" - }, - "syntaxComment": { - "dark": "nord3", - "light": "nord3" - }, - "syntaxKeyword": { - "dark": "nord9", - "light": "nord9" - }, - "syntaxFunction": { - "dark": "nord8", - "light": "nord8" - }, - "syntaxVariable": { - "dark": "nord7", - "light": "nord7" - }, - "syntaxString": { - "dark": "nord14", - "light": "nord14" - }, - "syntaxNumber": { - "dark": "nord15", - "light": "nord15" - }, - "syntaxType": { - "dark": "nord7", - "light": "nord7" - }, - "syntaxOperator": { - "dark": "nord9", - "light": "nord9" - }, - "syntaxPunctuation": { - "dark": "nord4", - "light": "nord0" - } - } -} diff --git a/.opencode/tool/github-triage.ts b/.opencode/tool/github-triage.ts deleted file mode 100644 index 1e216f1c8da..00000000000 --- a/.opencode/tool/github-triage.ts +++ /dev/null @@ -1,90 +0,0 @@ -/// -// 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 = "anomalyco" - 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 deleted file mode 100644 index 4c46a72c162..00000000000 --- a/.opencode/tool/github-triage.txt +++ /dev/null @@ -1,88 +0,0 @@ -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 c3f8e50d05c..395e87c9e3d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,32 @@ +## IMPORTANT + +- This is a FORK of sst/opencode - the fork repo is Latitudes-Dev/shuvcode +- NEVER create PRs against upstream (sst/opencode) +- ALWAYS use `--repo Latitudes-Dev/shuvcode` when creating PRs with `gh` +- All PRs should target the fork repository, not upstream + +## Debugging + +- To test opencode in the `packages/opencode` directory you can run `bun dev` - To regenerate the JavaScript SDK, run `./packages/sdk/js/script/build.ts`. - ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE. - The default branch in this repo is `dev`. +## Testing + +- Avoid testing logic directly inside Solid.js `.tsx` files if they import JSX runtimes, as `bun test` may fail with `jsxDEV` errors. +- Separate pure logic into `.ts` files (e.g., `theme-utils.ts`) and test those instead. + +## Upstream Merge Operations + +When merging upstream tags (e.g., v1.1.1): + +1. Use `git merge --no-commit` to start merge without auto-commit +2. List conflicts: `git diff --name-only --diff-filter=U` +3. Cannot commit plan updates mid-merge - all conflict resolution must complete first +4. For files deleted in fork but modified upstream (delete/modify conflicts), decide per-file: + - `.opencode/*` files are upstream-specific, delete them: `git rm ` + ## Style Guide - Keep things in one function unless composable or reusable diff --git a/CONTEXT/PLAN-mobile-command-qr-code-2026-01-14.md b/CONTEXT/PLAN-mobile-command-qr-code-2026-01-14.md new file mode 100644 index 00000000000..c340b011eef --- /dev/null +++ b/CONTEXT/PLAN-mobile-command-qr-code-2026-01-14.md @@ -0,0 +1,470 @@ +# Plan: Add `/mobile` Built-in Command for QR Code Session URL + +**Created:** 2026-01-14 +**Status:** Draft +**Scope:** Add a new `/mobile` slash command that generates a QR code with the URL to open the currently running session in the web UI + +--- + +## Overview + +Add a built-in `/mobile` command that allows users to quickly open their current session on a mobile device by scanning a QR code displayed in the terminal. This enables seamless cross-device session handoff. + +### User Story + +As a developer using Shuvcode in my terminal, I want to quickly continue my session on my mobile device without manually typing URLs, so that I can review/monitor AI responses while away from my desk. + +--- + +## Technical Context + +### Command System Architecture + +The codebase has two types of slash commands: + +1. **Template Commands** (`type: "template"`) - AI-processed commands defined in `Command.state` +2. **Plugin Commands** (`type: "plugin"`) - Execute arbitrary code via `plugin.command` hook + +For `/mobile`, we need a **plugin command** since it requires: + +- Accessing the server URL programmatically +- Generating a QR code (no AI processing needed) +- Rendering QR output inside the TUI + +### Key Files + +| File | Purpose | +| ---------------------------------------------------- | ------------------------------------------ | +| `packages/opencode/src/command/index.ts` | Command listing + plugin command discovery | +| `packages/opencode/src/session/prompt.ts:1657-1699` | Plugin command execution flow | +| `packages/opencode/src/server/server.ts:69-71` | `Server.url()` function | +| `packages/opencode/src/server/server.ts:2826-2838` | Web UI proxy (`app.shuv.ai`) | +| `packages/opencode/src/cli/cmd/tui/event.ts` | TuiEvent definitions | +| `packages/opencode/src/cli/cmd/tui/app.tsx` | TUI event handling + dialogs | +| `packages/opencode/src/cli/cmd/tui/ui/dialog-qr.tsx` | QR dialog UI | +| `packages/app/src/app.tsx:31-35` | Web UI route `/:dir/session/:id?` | +| `packages/app/src/pages/directory-layout.tsx:15-17` | Base64 decode for `:dir` | +| `packages/util/src/encode.ts:1-5` | `base64Encode` helper | +| `packages/sdk/js/src/v2/client.ts:8-28` | SDK v2 client + directory header | +| `packages/plugin/src/index.ts:225-236` | `plugin.command` hook interface | + +### Server URL Access + +```typescript +// packages/opencode/src/server/server.ts:66-71 +let _url: URL | undefined + +export function url(): URL { + return _url ?? new URL("http://localhost:4096") +} +``` + +### Plugin Command Interface + +```typescript +// packages/plugin/src/index.ts:225-236 +"plugin.command"?: { + [name: string]: { + description: string + aliases?: string[] + sessionOnly?: boolean + execute(input: { + sessionID: string + arguments: string + client: ReturnType + }): Promise + } +} +``` + +## Review Updates (Required before implementation) + +- Use the web UI route `/:dir/session/:id` with a base64-encoded directory slug (`base64Encode`) to match the router decode logic. +- Render the QR inside the TUI via a new event/modal instead of `console.log()` to avoid corrupting the OpenTUI screen. +- Use the SDK v2 client with the `directory` header (or publish `TuiEvent.ToastShow` via `Bus`) since v2 `tui.showToast` does not accept a `body` wrapper. +- Build URLs against the server origin (the server proxies `https://app.shuv.ai` for the web UI), not a separate UI host. +- Plugin commands are auto-discovered from `plugin.command` hooks; skip edits to `packages/opencode/src/command/index.ts`. +- Detect `localhost`/`0.0.0.0` hostnames and warn or resolve a LAN IP (see `packages/opencode/src/cli/cmd/web.ts`). + +--- + +## Implementation Decisions + +### Decision 1: Command Type + +**Choice:** Plugin command (not template) + +**Rationale:** + +- No AI processing needed - purely programmatic +- Requires access to server URL + directory context +- Needs to render a QR code inside the TUI + +### Decision 2: QR Code Library + +**Recommended:** `uqr` from unjs + +**Alternatives Evaluated:** + +| Library | Size | Terminal Support | Notes | +| --------------- | ----- | ------------------ | ----------------------------------------- | +| `uqr` | ~5KB | ANSI, Unicode, SVG | Modern, tree-shakable, active maintenance | +| `qrcode` | ~30KB | UTF-8, terminal | Mature, CLI support, larger bundle | +| `@paulmillr/qr` | ~8KB | ASCII, GIF | Fast benchmarks, minimal deps | + +**Rationale for `uqr`:** + +- Zero dependencies +- Multiple output formats (ANSI for color terminals, Unicode for basic terminals) +- Active maintenance by unjs team +- Small bundle size +- Works in any JavaScript runtime (Node, Bun, Deno) + +**GitHub:** https://github.com/unjs/uqr + +### Decision 3: Output Method + +**Choice:** TUI modal/event + toast fallback + +**Approach:** + +1. Generate QR code as ANSI/Unicode string +2. Publish a new `TuiEvent.QrShow` (or similar) so OpenTUI can render a modal/panel with the QR string +3. Show a toast notification with the URL (for copy/paste fallback) + +**Notes:** Avoid direct `console.log()` because it can corrupt the OpenTUI renderer. + +### Decision 4: URL Construction + +The session URL format depends on deployment context: + +| Context | URL Pattern | +| ---------- | --------------------------------------------------------------- | +| Local TUI | `http://localhost:{port}/{base64Dir}/session/{sessionID}` | +| Local Web | Same as server URL (server proxies `app.shuv.ai` for UI assets) | +| Remote/LAN | `http://{host}:{port}/{base64Dir}/session/{sessionID}` | + +**Implementation:** Use `Server.url()` (or `input.serverUrl`) as base and append `/${base64Encode(directory)}/session/${sessionID}`. + +Web UI session routes (from `packages/app/src/app.tsx:31-35` + `packages/app/src/pages/directory-layout.tsx:15-17`): + +``` +/:dir/session/:id? +``` + +For direct session access, construct: `{serverUrl}/{base64Dir}/session/{sessionID}` + +--- + +## External References + +### QR Code Library - uqr + +**GitHub Repository:** https://github.com/unjs/uqr + +**Key APIs:** + +```typescript +import { encode, renderANSI, renderUnicode, renderUnicodeCompact } from "uqr" + +// Generate QR code data +const qr = encode(text, options) + +// Render to ANSI (color terminals) +const ansi = renderANSI(text, options) + +// Render to Unicode (wide compatibility) +const unicode = renderUnicode(text, options) + +// Render compact Unicode (smaller output) +const compact = renderUnicodeCompact(text, options) +``` + +**Installation:** + +```bash +bun add uqr +``` + +--- + +## Implementation Plan + +### Phase 1: Core Implementation + +#### Task 1.1: Add uqr dependency + +- [ ] Add `uqr` to `packages/opencode/package.json` +- [ ] Run `bun install` to update lockfile +- [ ] Verify import works: `import { renderUnicode } from 'uqr'` + +**File:** `packages/opencode/package.json` + +#### Task 1.2: Create mobile command plugin module + +- [ ] Create new file `packages/opencode/src/plugin/mobile.ts` +- [ ] Implement QR code generation + URL builder helpers in `packages/opencode/src/plugin/mobile-utils.ts` (ANSI then Unicode fallback) +- [ ] Export internal plugin with `plugin.command` hook + +**File to create:** `packages/opencode/src/plugin/mobile.ts` + +```typescript +// Skeleton structure +import { createOpencodeClient } from "@opencode-ai/sdk/v2/client" +import { buildSessionUrl, renderQrCode } from "./mobile-utils" +import { Bus } from "../bus" +import { TuiEvent } from "../cli/cmd/tui/event" +import type { Hooks, PluginInput } from "@opencode-ai/plugin" + +export const MobilePlugin = async (input: PluginInput): Promise => ({ + "plugin.command": { + mobile: { + description: "Show QR code to open session on mobile device", + aliases: ["qr"], + sessionOnly: true, + execute: async ({ sessionID }) => { + // Implementation here + }, + }, + }, +}) +``` + +#### Task 1.3: Register internal plugin + +- [ ] Import `MobilePlugin` in `packages/opencode/src/plugin/index.ts` +- [ ] Add `MobilePlugin` to the `INTERNAL_PLUGINS` array (plugin commands are auto-discovered) + +#### Task 1.4: Implement execute function + +- [ ] Build the server base URL from `input.serverUrl` +- [ ] Base64-encode `input.directory` for the `:dir` slug +- [ ] Generate QR code using uqr +- [ ] Publish a TUI event/modal payload containing the QR string + URL +- [ ] Show a toast with the plain URL using the SDK v2 client + +**Implementation details:** + +```typescript +execute: async ({ sessionID }) => { + const client = createOpencodeClient({ + baseUrl: input.serverUrl.toString(), + directory: input.directory, + }) + const sessionUrl = buildSessionUrl(input.serverUrl, input.directory, sessionID) + const qrCode = renderQrCode(sessionUrl) + + await Bus.publish(TuiEvent.QrShow, { + url: sessionUrl, + qr: qrCode, + }) + + await client.tui.showToast({ + title: "Mobile Access", + message: sessionUrl, + variant: "info", + duration: 10000, + }) +} +``` + +### Phase 2: Integration + +#### Task 2.1: Add TUI QR event + UI + +- [ ] Add `TuiEvent.QrShow` schema in `packages/opencode/src/cli/cmd/tui/event.ts` (e.g., `{ url: string; qr: string }`) +- [ ] Create `packages/opencode/src/cli/cmd/tui/ui/dialog-qr.tsx` to render the QR string +- [ ] Handle `TuiEvent.QrShow` in `packages/opencode/src/cli/cmd/tui/app.tsx` by opening the dialog + +#### Task 2.2: Register internal plugin + +- [ ] Import `MobilePlugin` in `packages/opencode/src/plugin/index.ts` +- [ ] Add `MobilePlugin` to the `INTERNAL_PLUGINS` array + +#### Task 2.3: Network accessibility warning + +- [ ] If `input.serverUrl.hostname` is `localhost`/`127.0.0.1`/`0.0.0.0`, warn via toast and suggest `--hostname 0.0.0.0` +- [ ] Optionally resolve LAN IPs (reuse logic from `packages/opencode/src/cli/cmd/web.ts`) + +#### Task 2.4: Regenerate SDK types (if needed) + +- [ ] If TUI event schemas change OpenAPI output, run `./script/generate.ts` to refresh SDK types + +### Phase 3: Testing + +#### Task 3.1: Add unit tests + +- [ ] Create `packages/opencode/test/command/mobile.test.ts` +- [ ] Test URL construction (base64 dir slug + session ID) +- [ ] Test QR code generation +- [ ] Test TUI event payload for QR display +- [ ] Test command execution flow +- [ ] Keep tests in `.ts` by extracting helpers (avoid JSX runtime in tests) + +**Reference test pattern:** `packages/opencode/test/command/plugin-commands.test.ts` + +#### Task 3.2: Manual testing + +- [ ] Run `bun run dev` in `packages/opencode` to start the TUI (`bun run dev -- --hostname 0.0.0.0` for LAN) +- [ ] Execute `/mobile` command in TUI +- [ ] Verify QR dialog renders without corrupting the screen +- [ ] Scan QR code with mobile device +- [ ] Verify session opens correctly in browser + +### Phase 4: Polish + +#### Task 4.1: Handle edge cases + +- [ ] Missing or invalid session ID (show error toast) +- [ ] Missing directory context (show error toast) +- [ ] Terminal doesn't support ANSI (use Unicode fallback) +- [ ] LAN/remote access considerations + +#### Task 4.2: Add network accessibility check (optional enhancement) + +- [ ] Detect if server is bound to localhost only +- [ ] Warn user if QR code won't work from other devices +- [ ] Suggest using `--hostname 0.0.0.0` for LAN access + +--- + +## File Changes Summary + +### New Files + +| File | Description | +| ---------------------------------------------------- | --------------------- | +| `packages/opencode/src/plugin/mobile.ts` | Mobile command plugin | +| `packages/opencode/src/plugin/mobile-utils.ts` | URL/QR helpers | +| `packages/opencode/src/cli/cmd/tui/ui/dialog-qr.tsx` | TUI QR dialog | +| `packages/opencode/test/command/mobile.test.ts` | Unit tests | + +### Modified Files + +| File | Changes | +| -------------------------------------------- | ---------------------------------- | +| `packages/opencode/package.json` | Add `uqr` dependency | +| `packages/opencode/src/plugin/index.ts` | Register `MobilePlugin` internally | +| `packages/opencode/src/cli/cmd/tui/event.ts` | Add QR TUI event schema | +| `packages/opencode/src/cli/cmd/tui/app.tsx` | Handle QR event in OpenTUI | + +--- + +## API Reference + +### uqr Library Usage + +```typescript +import { renderANSI, renderUnicode, renderUnicodeCompact } from 'uqr' + +// Options interface +interface QROptions { + ecc?: 'L' | 'M' | 'Q' | 'H' // Error correction level + border?: number // Border size in modules +} + +// ANSI output (color terminals) +renderANSI(text: string, options?: QROptions): string + +// Unicode output (wide compatibility) +renderUnicode(text: string, options?: QROptions): string + +// Compact Unicode (half-height characters) +renderUnicodeCompact(text: string, options?: QROptions): string +``` + +### SDK Client TUI Methods + +```typescript +// Show toast notification (SDK v2) +client.tui.showToast({ + title?: string, + message: string, + variant: 'info' | 'success' | 'warning' | 'error', + duration?: number, // milliseconds + directory?: string +}) +``` + +--- + +## Validation Criteria + +### Functional Requirements + +- [ ] `/mobile` command is recognized and executable +- [ ] `/qr` alias works (if implemented) +- [ ] QR dialog renders correctly in the TUI +- [ ] QR code encodes correct session URL (with base64 dir slug) +- [ ] Scanning QR code opens session in mobile browser +- [ ] Toast notification shows URL for manual copy +- [ ] Command only works when session exists (`sessionOnly: true`) + +### Non-Functional Requirements + +- [ ] QR code renders within 100ms +- [ ] No additional runtime dependencies beyond `uqr` +- [ ] Works in both color and monochrome terminals +- [ ] Bundle size increase < 10KB + +### Error Handling + +- [ ] Graceful fallback when terminal doesn't support ANSI +- [ ] Clear error message when no session or directory exists +- [ ] Handles server URL edge cases (localhost/LAN warnings) + +--- + +## Future Enhancements (Out of Scope) + +1. **Copy-to-Clipboard** - Add a button to copy the URL to clipboard +2. **Expiring URLs** - Generate time-limited session tokens for security +3. **Deep Linking** - Support mobile app deep links (if native app exists) +4. **Share Modal Integration** - Add QR code tab to existing share functionality +5. **mDNS Discovery** - Auto-detect server on local network + +--- + +## Risk Assessment + +| Risk | Likelihood | Impact | Mitigation | +| ------------------------------------ | ---------- | ------ | ---------------------------------------- | +| Terminal doesn't render QR correctly | Medium | Low | Provide Unicode fallback, show plain URL | +| TUI event not rendered | Medium | Medium | Add QR event schema + dialog handler | +| Server bound to localhost | High | Medium | Warn user, document `--hostname` flag | +| Large QR code for long URLs | Low | Low | Use compact rendering, error correction | +| uqr library issues | Low | Medium | Library is well-maintained by unjs | + +--- + +## Dependencies + +### Internal Dependencies + +- `packages/opencode/src/server/server.ts` - Server.url() + web UI proxy routing +- `packages/opencode/src/plugin/index.ts` - Internal plugin loading +- `packages/opencode/src/cli/cmd/tui/event.ts` - TUI event schema (add QR event) +- `packages/opencode/src/cli/cmd/tui/app.tsx` - TUI event handling +- `packages/opencode/src/cli/cmd/tui/ui/dialog-qr.tsx` - QR dialog component +- `packages/util/src/encode.ts` - `base64Encode` for `:dir` slug +- `packages/app/src/app.tsx` + `packages/app/src/pages/directory-layout.tsx` - web UI routing +- `packages/sdk/js/src/v2/client.ts` - SDK v2 client with `directory` header + +### External Dependencies + +- `uqr` - QR code generation (new dependency) + +--- + +## References + +- **uqr GitHub:** https://github.com/unjs/uqr +- **Web UI routing:** `packages/app/src/app.tsx:31-35` +- **Dir slug decode:** `packages/app/src/pages/directory-layout.tsx:15-17` +- **Base64 encode helper:** `packages/util/src/encode.ts:1-5` +- **Server URL implementation:** `packages/opencode/src/server/server.ts:66-71` +- **Web UI proxy:** `packages/opencode/src/server/server.ts:2826-2838` +- **Plugin command test:** `packages/opencode/test/command/plugin-commands.test.ts` +- **TuiEvent definitions:** `packages/opencode/src/cli/cmd/tui/event.ts` +- **SDK v2 client:** `packages/sdk/js/src/v2/client.ts:8-28` +- **Plugin hook interface:** `packages/plugin/src/index.ts:225-236` diff --git a/LOCAL_TAURI_PUBLISH.md b/LOCAL_TAURI_PUBLISH.md new file mode 100644 index 00000000000..b182fe4d61a --- /dev/null +++ b/LOCAL_TAURI_PUBLISH.md @@ -0,0 +1,74 @@ +# Local Tauri Publish (Shuvcode) + +This guide is local-only: build, sign, and publish the desktop app without GitHub Actions. + +## 1) Prerequisites + +- Bun and Rust installed (host triple in `rustc -vV`). +- Tauri CLI available via `bun run tauri` in `packages/desktop`. +- Linux-only bundling dependencies for AppImage: + - Install `fuse2` (or set `APPIMAGE_EXTRACT_AND_RUN=1` to avoid FUSE). + - Ensure `glibc`, `gtk3`, `webkit2gtk`, and related system libs are installed. + +## 2) Branding + updater config you must own + +- Set the updater public key to your Shuvcode key in `packages/desktop/src-tauri/tauri.prod.conf.json`. +- Confirm updater endpoint uses your repo: `https://github.com/Latitudes-Dev/shuvcode/releases/latest/download/latest.json`. +- Ensure bundle identifiers are correct: + - Dev: `dev.shuvcode.desktop.dev` + - Prod: `dev.shuvcode.desktop` + +## 3) Generate signing keys (one-time) + +Run locally: + +```bash +bun run --cwd packages/desktop tauri signer generate -w ./shuvcode-private.key +``` + +- The command prints a public key; copy that into `plugins.updater.pubkey` in `packages/desktop/src-tauri/tauri.prod.conf.json`. +- Store the private key securely. If you set a password, also store it. + +## 4) Local build workflow (per release) + +```bash +export RUST_TARGET=x86_64-unknown-linux-gnu +bun run --cwd packages/desktop predev +bun run --cwd packages/desktop build +TAURI_SIGNING_PRIVATE_KEY="$(cat ./shuvcode-private.key)" \ +TAURI_SIGNING_PRIVATE_KEY_PASSWORD="" \ +bun run --cwd packages/desktop tauri build +``` + +Outputs appear in: + +- Bundles: `packages/desktop/src-tauri/target/release/bundle/` +- App binary: `packages/desktop/src-tauri/target/release/Shuvcode` + +## 5) Publish locally (no CI) + +You have two viable local publish paths: + +### Option A: GitHub Releases (local upload) + +- Create a release and upload bundle artifacts + `latest.json` (updater manifest). +- Use `gh release create --repo Latitudes-Dev/shuvcode` from your machine. + +### Option B: Self-hosted updater + +- Host the full contents of `bundle/` plus `latest.json` on your own server. +- Update `plugins.updater.endpoints` to your hosting URL. + +## 6) Known local issues + +- AppImage bundling failed locally with `failed to run linuxdeploy`. + - linuxdeploy’s embedded `strip` fails on `.relr.dyn` sections; try `NO_STRIP=1` or use a newer linuxdeploy build that understands RELR. + - Install `fuse2` (or set `APPIMAGE_EXTRACT_AND_RUN=1`) plus `squashfs-tools` and `patchelf`. + - Re-run `bun run --cwd packages/desktop tauri build` after adjusting linuxdeploy/strip. + +## 7) Validation checklist + +- Launch `Shuvcode` binary, verify UI loads. +- Confirm sidecar starts (CLI server is reachable on the injected port). +- Run in-app update check; ensure it hits your Shuvcode release endpoint. +- Verify installed bundle name and identifier for each OS. diff --git a/README.md b/README.md index d0acb758d9f..a55fbc50182 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,228 @@ +

shuvcode +

+

A fork of opencode - The AI coding agent built for the terminal.

- - - - - OpenCode logo - - + npm + GitHub release

-

The open source AI coding agent.

+ +--- + +## Screenshots + +### Web App + +

+ Desktop session with diff viewer +

+ +_Web session view with chat, session sidebar, and real-time code diff review_ + +### Mobile PWA +

- Discord - npm - Build status + Mobile recent projects + Mobile sidebar menu + Mobile AI terminal view

- English | - 简体中文 | - 繁體中文 | - 한국어 | - Deutsch | - Español | - Français | - Italiano | - Dansk | - 日本語 | - Polski | - Русский | - العربية | - Norsk | - Português (Brasil) + Mobile commit summary + Mobile git clone dialog + Mobile theme selector

-[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai) +_Mobile PWA: Recent projects, sidebar menu, AI chat with terminal, commit summary, git clone dialog, and theme selector_ --- -### Installation +## Installation ```bash -# YOLO -curl -fsSL https://opencode.ai/install | bash - -# Package managers -npm i -g opencode-ai@latest # or bun/pnpm/yarn -scoop install opencode # Windows -choco install opencode # Windows -brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date) -brew install opencode # macOS and Linux (official brew formula, updated less) -paru -S opencode-bin # Arch Linux -mise use -g opencode # Any OS -nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch +# curl install +curl -fsSL https://shuv.ai/install | bash + +# npm +npm i -g shuvcode@latest # or bun/pnpm/yarn ``` -> [!TIP] -> Remove versions older than 0.1.x before installing. +--- -### Desktop App (BETA) +## About -OpenCode is also available as a desktop application. Download directly from the [releases page](https://github.com/anomalyco/opencode/releases) or [opencode.ai/download](https://opencode.ai/download). +This fork serves as an integration testing ground for upstream PRs before they are merged into the main opencode repository. We merge, test, and validate promising features and fixes to help ensure quality contributions to the upstream project. -| 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 -# Windows (Scoop) -scoop bucket add extras; scoop install extras/opencode-desktop +## Merged PRs (Pending Upstream) + +The following PRs have been merged into this fork and are awaiting merge into upstream: + +| PR | Title | Author | Status | Description | +| ---------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------ | -------- | ------------------------------------------------------------------------ | +| [#6476](https://github.com/sst/opencode/pull/6476) | Edit suggested changes before applying | [@dmmulroy](https://github.com/dmmulroy) | Open | Press 'e' to edit AI suggestions in your editor before accepting | +| [#6507](https://github.com/sst/opencode/pull/6507) | Optimize Ripgrep.tree() (109x faster) | [@Karavil](https://github.com/Karavil) | Open | 109x performance improvement for large repos by streaming ripgrep output | +| [#5432](https://github.com/sst/opencode/pull/5432) | Stream grep output to prevent OOM | [@Hona](https://github.com/Hona) | Open | Stream ripgrep output in grep tool to prevent memory exhaustion | +| [#6360](https://github.com/sst/opencode/pull/6360) | Desktop: Edit Project | [@dbpolito](https://github.com/dbpolito) | Merged | Edit project name, icon color, and custom icon image in desktop sidebar | +| [#6368](https://github.com/sst/opencode/pull/6368) | Desktop: Sidebar subsessions support | [@dbpolito](https://github.com/dbpolito) | Open | Expand/collapse subsessions in sidebar with chevron indicators | +| [#6372](https://github.com/sst/opencode/pull/6372) | Desktop: Image Preview and Dedupe | [@dbpolito](https://github.com/dbpolito) | Merged | Click user attachments to preview images, dedupe file uploads | +| [#4898](https://github.com/sst/opencode/pull/4898) | Search in messages | [@OpeOginni](https://github.com/OpeOginni) | Open | Ctrl+ / to search through session messages with highlighting | +| [#4791](https://github.com/sst/opencode/pull/4791) | Bash output with ANSI | [@remorses](https://github.com/remorses) | Open | Full terminal emulation for bash output with color support | +| [#4900](https://github.com/sst/opencode/pull/4900) | Double Ctrl+C to exit | [@AmineGuitouni](https://github.com/AmineGuitouni) | Open | Require double Ctrl+C within 2 seconds to prevent accidental exits | +| [#4709](https://github.com/sst/opencode/pull/4709) | Live token usage during streaming | [@arsham](https://github.com/arsham) | Open | Real-time token tracking and display during model responses | +| [#4865](https://github.com/sst/opencode/pull/4865) | Subagents sidebar with clickable navigation | [@franlol](https://github.com/franlol) | Open | Show subagents in sidebar with click-to-navigate and parent keybind | +| [#4515](https://github.com/sst/opencode/pull/4515) | Show plugins in /status | [@spoons-and-mirrors](https://github.com/spoons-and-mirrors) | Merged | Display configured plugins in /status dialog alongside MCP/LSP servers | +| [#4411](https://github.com/sst/opencode/pull/4411) | Plugin Commands | [@spoons-and-mirrors](https://github.com/spoons-and-mirrors) | Open | Register custom `/commands` from plugins with aliases and sessionOnly | +| [#5958](https://github.com/sst/opencode/pull/5958) | AskQuestion Tool | [@iljod](https://github.com/iljod) | Open | Interactive tool for AI to collect user input via TUI/web wizard dialogs | +| [#5508](https://github.com/sst/opencode/pull/5508) | Cache management command | [@JosXa](https://github.com/JosXa) | Open | `opencode cache info` and `opencode cache clean` for plugin cache mgmt | +| [#5873](https://github.com/sst/opencode/pull/5873) | IDE integration UX improvements | [@tofunori](https://github.com/tofunori) | Open | Selection in footer, synthetic context, home screen IDE status | +| [#5917](https://github.com/sst/opencode/pull/5917) | Draggable sidebar resize | [@agustif](https://github.com/agustif) | Open | Click and drag the sidebar border to resize, width persisted to KV store | +| [#5968](https://github.com/sst/opencode/pull/5968) | Better styling for small screens | [@rekram1-node](https://github.com/rekram1-node) | Reverted | Responsive TUI layout hiding elements on short/narrow terminals | +| [#140](https://github.com/Latitudes-Dev/shuvcode/pull/140) | Toggle transparent background | [@JosXa](https://github.com/JosXa) | Open | Command palette toggle for transparent TUI background on any theme | + +_Last updated: 2026-01-07_ + +**Note:** Granular File Permissions (ariane-emory) was removed in v1.1.1 integration - upstream now provides similar functionality via PermissionNext. + +--- + +## Feature Highlights + +### Custom Server URL Settings + +Configure a custom API server URL for the desktop app: + +- **Settings dialog**: Access via command palette (Cmd/Ctrl+K → Settings) +- **URL validation**: Real-time validation with connection testing +- **Persistence**: Saved to localStorage, survives browser refresh +- **Error recovery**: Configure server URL directly from connection error pages + +Useful for self-hosted deployments or development environments. + +--- + +### GitHub App Integration + +The fork includes a dedicated GitHub App (`shuvcode-agent`) for GitHub Actions automation: + +- **Automatic PR reviews**: Trigger with `/shuvcode` or `/shuv` comments +- **Token exchange**: Secure OIDC-based authentication for CI workflows +- **Installation**: Run `shuvcode github install` to add the app to your repos + +The API is deployed to `api.shuv.ai` with Cloudflare Durable Objects for session sync. + +--- + +### Enhanced Create Project Dialog + +The "Add Project" dialog now has three tabs: + +- **Add Existing**: Browse and search folders from $HOME with fuzzy search +- **Create New**: Directory picker + project name field with path validation +- **Git Clone**: Clone from URL (coming soon) + +Features git repo detection, existing project badges, and keyboard navigation. + +--- + +### Desktop PWA Mobile Support + +The desktop web app now fully supports mobile devices as a Progressive Web App (PWA): + +- **Dynamic island handling**: Proper background color fills the notch/dynamic island area on newer iPhones +- **Mobile menu**: Full-screen navigation overlay accessible via hamburger button +- **Review overlay**: Access session changes and file viewer on mobile via the "Review" button in the header +- **Split/inline diff toggle**: Switch between side-by-side and inline diff views in the review panel +- **Responsive layout**: Timeline rail hidden on mobile, session pane takes full width + +Install as PWA on iOS: Open in Safari → Share → Add to Home Screen + +--- + +### IDE Integration (Cursor/VSCode) + +Connect to Cursor, VSCode, or other supported IDEs for enhanced workflow: + +- **Live text selection** from your editor is displayed in the TUI footer +- **Selection context** is automatically included in prompts (invisible to you, but sent to the model) +- **IDE status** shown on the home screen footer +- **Diff view** support for file edits (open diffs directly in your IDE) + +Configure in `opencode.json`: + +```jsonc +{ + "ide": { + "lockfile_dir": "~/.cursor/opencode/", + "auth_header_name": "x-opencode-auth", + }, +} ``` -#### Installation Directory +Supported IDEs: Cursor, VSCode, VSCode Insiders, VSCodium, Windsurf -The install script respects the following priority order for the installation path: +--- -1. `$OPENCODE_INSTALL_DIR` - Custom installation directory -2. `$XDG_BIN_DIR` - XDG Base Directory Specification compliant path -3. `$HOME/bin` - Standard user binary directory (if exists or can be created) -4. `$HOME/.opencode/bin` - Default fallback +### Add Existing Project Dialog -```bash -# Examples -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 +The desktop "Create project" button now opens an improved "Add Project" dialog with two tabs: + +- **Add Existing**: Browse and search folders from your home directory with fuzzy search, see git repo indicators, and add existing projects with one click +- **Create New**: Original path input for creating new project directories + +The folder browser scans up to 2 levels deep from `$HOME`, prioritizes git repositories, and shows which folders are already added as projects. + +--- + +### Desktop Image Preview + +The desktop file viewer now displays actual image previews for PNG, JPG, GIF, and WEBP files instead of showing raw base64 text. Images are centered and scaled to fit within the viewport with scrolling support for large images. SVG files are excluded from image preview and render as syntax-highlighted XML code. + +--- + +### TUI Spinner Styles + +Choose from 60+ animated spinner styles for tool execution indicators. Access via the command palette with `Change spinner style`. Your selection is persisted across sessions. + +Available styles include braille patterns, block animations, geometric shapes, and creative concepts like moon phases, clock sweeps, and bouncing balls. + +You can also adjust the animation speed via `Change spinner speed` in the command palette. Options range from 20ms (fastest) to 500ms (slowest), with 60ms as the default. + +--- + +### TUI Layout Density + +The TUI automatically adapts its vertical spacing for small terminals (< 28 rows). Configure via `tui.density`: + +- `auto` (default): Switches to compact mode on small terminals +- `comfortable`: Standard spacing with footer and hints +- `compact`: Reduced padding, hides footer and secondary hints + +Toggle density from the command palette or set in config: + +```jsonc +{ + "tui": { + "density": "auto", + }, +} ``` +--- + +### AskQuestion Tool + +The AI can pause and ask structured questions via a wizard UI. Available in both TUI and web app. This tool is enabled by default. + +Features: + +- Wizard-style multi-question dialogs with single/multi-select options +- Custom text input for freeform responses +- Keyboard navigation (1-8 quick select, Tab between questions, Enter to confirm) +- Works across TUI and web app with session resume support + +--- + ### Agents OpenCode includes two built-in agents you can switch between with the `Tab` key. @@ -104,6 +238,25 @@ This is used internally and can be invoked using `@general` in messages. Learn more about [agents](https://opencode.ai/docs/agents). +### Sessions Sidebar + +A NERDTree-style sidebar for managing sessions. Toggle with `ctrl+n`. + +| Key | Action | +| -------------- | ---------------------------- | +| `j/k` or `↑/↓` | Move cursor | +| `Enter` or `o` | Open session / Toggle expand | +| `O` | Expand all children | +| `x` | Collapse parent | +| `X` | Collapse all | +| `p` | Go to parent | +| `g/G` | Jump to top/bottom | +| `n` | New session | +| `r` | Rename session | +| `d` | Delete session | +| `?` | Show help | +| `q` or `Esc` | Close sidebar | + ### Documentation For more info on how to configure OpenCode [**head over to our docs**](https://opencode.ai/docs). diff --git a/README.th.md b/README.th.md new file mode 100644 index 00000000000..a4b306a6c4a --- /dev/null +++ b/README.th.md @@ -0,0 +1,134 @@ +

+ + + + + OpenCode logo + + +

+

เอเจนต์การเขียนโค้ดด้วย AI แบบโอเพนซอร์ส

+

+ Discord + npm + สถานะการสร้าง +

+ +

+ English | + 简体中文 | + 繁體中文 | + 한국어 | + Deutsch | + Español | + Français | + Italiano | + Dansk | + 日本語 | + Polski | + Русский | + العربية | + Norsk | + Português (Brasil) | + ไทย +

+ +[![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 install opencode # Windows +choco install opencode # Windows +brew install anomalyco/tap/opencode # macOS และ Linux (แนะนำ อัปเดตเสมอ) +brew install opencode # macOS และ Linux (brew formula อย่างเป็นทางการ อัปเดตน้อยกว่า) +paru -S opencode-bin # Arch Linux +mise use -g opencode # ระบบปฏิบัติการใดก็ได้ +nix run nixpkgs#opencode # หรือ github:anomalyco/opencode สำหรับสาขาพัฒนาล่าสุด +``` + +> [!TIP] +> ลบเวอร์ชันที่เก่ากว่า 0.1.x ก่อนติดตั้ง + +### แอปพลิเคชันเดสก์ท็อป (เบต้า) + +OpenCode มีให้ใช้งานเป็นแอปพลิเคชันเดสก์ท็อป ดาวน์โหลดโดยตรงจาก [หน้ารุ่น](https://github.com/anomalyco/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) +brew install --cask opencode-desktop +# Windows (Scoop) +scoop bucket add extras; scoop install extras/opencode-desktop +``` + +#### ไดเรกทอรีการติดตั้ง + +สคริปต์การติดตั้งจะใช้ลำดับความสำคัญตามเส้นทางการติดตั้ง: + +1. `$OPENCODE_INSTALL_DIR` - ไดเรกทอรีการติดตั้งที่กำหนดเอง +2. `$XDG_BIN_DIR` - เส้นทางที่สอดคล้องกับ XDG Base Directory Specification +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 +``` + +### เอเจนต์ + +OpenCode รวมเอเจนต์ในตัวสองตัวที่คุณสามารถสลับได้ด้วยปุ่ม `Tab` + +- **build** - เอเจนต์เริ่มต้น มีสิทธิ์เข้าถึงแบบเต็มสำหรับงานพัฒนา +- **plan** - เอเจนต์อ่านอย่างเดียวสำหรับการวิเคราะห์และการสำรวจโค้ด + - ปฏิเสธการแก้ไขไฟล์โดยค่าเริ่มต้น + - ขอสิทธิ์ก่อนเรียกใช้คำสั่ง bash + - เหมาะสำหรับสำรวจโค้ดเบสที่ไม่คุ้นเคยหรือวางแผนการเปลี่ยนแปลง + +นอกจากนี้ยังมีเอเจนต์ย่อย **general** สำหรับการค้นหาที่ซับซ้อนและงานหลายขั้นตอน +ใช้ภายในและสามารถเรียกใช้ได้โดยใช้ `@general` ในข้อความ + +เรียนรู้เพิ่มเติมเกี่ยวกับ [เอเจนต์](https://opencode.ai/docs/agents) + +### เอกสารประกอบ + +สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีกำหนดค่า OpenCode [**ไปที่เอกสารของเรา**](https://opencode.ai/docs) + +### การมีส่วนร่วม + +หากคุณสนใจที่จะมีส่วนร่วมใน OpenCode โปรดอ่าน [เอกสารการมีส่วนร่วม](./CONTRIBUTING.md) ก่อนส่ง Pull Request + +### การสร้างบน OpenCode + +หากคุณทำงานในโปรเจกต์ที่เกี่ยวข้องกับ OpenCode และใช้ "opencode" เป็นส่วนหนึ่งของชื่อ เช่น "opencode-dashboard" หรือ "opencode-mobile" โปรดเพิ่มหมายเหตุใน README ของคุณเพื่อชี้แจงว่าไม่ได้สร้างโดยทีม OpenCode และไม่ได้เกี่ยวข้องกับเราในทางใด + +### คำถามที่พบบ่อย + +#### ต่างจาก Claude Code อย่างไร? + +คล้ายกับ Claude Code มากในแง่ความสามารถ นี่คือความแตกต่างหลัก: + +- โอเพนซอร์ส 100% +- ไม่ผูกมัดกับผู้ให้บริการใดๆ แม้ว่าเราจะแนะนำโมเดลที่เราจัดหาให้ผ่าน [OpenCode Zen](https://opencode.ai/zen) OpenCode สามารถใช้กับ Claude, OpenAI, Google หรือแม้กระทั่งโมเดลในเครื่องได้ เมื่อโมเดลพัฒนาช่องว่างระหว่างพวกมันจะปิดลงและราคาจะลดลง ดังนั้นการไม่ผูกมัดกับผู้ให้บริการจึงสำคัญ +- รองรับ LSP ใช้งานได้ทันทีหลังการติดตั้งโดยไม่ต้องปรับแต่งหรือเปลี่ยนแปลงฟังก์ชันการทำงานใด ๆ +- เน้นที่ TUI OpenCode สร้างโดยผู้ใช้ neovim และผู้สร้าง [terminal.shop](https://terminal.shop) เราจะผลักดันขีดจำกัดของสิ่งที่เป็นไปได้ในเทอร์มินัล +- สถาปัตยกรรมไคลเอนต์/เซิร์ฟเวอร์ ตัวอย่างเช่น อาจอนุญาตให้ OpenCode ทำงานบนคอมพิวเตอร์ของคุณ ในขณะที่คุณสามารถขับเคลื่อนจากระยะไกลผ่านแอปมือถือ หมายความว่า TUI frontend เป็นหนึ่งในไคลเอนต์ที่เป็นไปได้เท่านั้น + +--- + +**ร่วมชุมชนของเรา** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode) diff --git a/STATS.md b/STATS.md index f00ebd72a3a..01be7f3d20c 100644 --- a/STATS.md +++ b/STATS.md @@ -213,3 +213,4 @@ | 2026-01-25 | 6,639,082 (+268,063) | 2,187,853 (+30,983) | 8,826,935 (+299,046) | | 2026-01-26 | 6,941,620 (+302,538) | 2,232,115 (+44,262) | 9,173,735 (+346,800) | | 2026-01-27 | 7,208,093 (+266,473) | 2,280,762 (+48,647) | 9,488,855 (+315,120) | +| 2026-01-28 | 7,489,370 (+281,277) | 2,314,849 (+34,087) | 9,804,219 (+315,364) | diff --git a/TAURI_DESKTOP_FOLLOWUPS.md b/TAURI_DESKTOP_FOLLOWUPS.md new file mode 100644 index 00000000000..65ecd6b8799 --- /dev/null +++ b/TAURI_DESKTOP_FOLLOWUPS.md @@ -0,0 +1,10 @@ +# Shuvcode Desktop (Tauri) Follow-ups + +- Update the Tauri updater public key to the Shuvcode signing key in `packages/desktop/src-tauri/tauri.prod.conf.json`. +- Confirm the fork’s release workflow uploads `latest.json` and uses the Shuvcode repo endpoint for updater artifacts. +- Ensure the CI artifact name for the sidecar is `shuvcode-cli` (matches `packages/desktop/scripts/prepare.ts`). +- Verify all sidecar binaries exist for targets in `packages/desktop/scripts/utils.ts` (especially Linux arm64). +- Validate bundle naming in CI now that the product name is Shuvcode (script expects `Shuvcode*` in `packages/desktop/scripts/copy-bundles.ts`). +- Resolve AppImage bundling on Linux: linuxdeploy’s embedded `strip` fails on `.relr.dyn` sections; try `NO_STRIP=1` or a newer linuxdeploy build, or wrap linuxdeploy to use `/usr/bin/strip`. +- Check macOS/Windows signing identities and entitlements to match the new bundle identifiers (`dev.shuvcode.desktop`). +- Run a full desktop smoke test: `bun run predev` then `bun run tauri dev`, confirm sidecar launch + update flow. diff --git a/bun.lock b/bun.lock index 1ceeb929321..a46562a7fc9 100644 --- a/bun.lock +++ b/bun.lock @@ -23,7 +23,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -68,12 +68,13 @@ "typescript": "catalog:", "vite": "catalog:", "vite-plugin-icons-spritesheet": "3.0.1", + "vite-plugin-pwa": "1.2.0", "vite-plugin-solid": "catalog:", }, }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -107,7 +108,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -134,7 +135,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -158,7 +159,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -182,7 +183,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -212,7 +213,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -241,7 +242,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -257,7 +258,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.1.39", + "version": "1.1.42", "bin": { "opencode": "./bin/opencode", }, @@ -313,6 +314,7 @@ "decimal.js": "10.5.0", "diff": "catalog:", "fuzzysort": "3.1.0", + "ghostty-opentui": "1.3.7", "gray-matter": "4.0.3", "hono": "catalog:", "hono-openapi": "catalog:", @@ -361,7 +363,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -375,13 +377,16 @@ }, "packages/script": { "name": "@opencode-ai/script", + "dependencies": { + "semver": "^7.6.0", + }, "devDependencies": { "@types/bun": "catalog:", }, }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.1.39", + "version": "1.1.42", "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", @@ -392,7 +397,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -405,7 +410,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -447,7 +452,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "zod": "catalog:", }, @@ -458,7 +463,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.1.39", + "version": "1.1.42", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -494,6 +499,7 @@ "tree-sitter-bash", ], "patchedDependencies": { + "ghostty-opentui@1.3.7": "patches/ghostty-opentui@1.3.7.patch", "ghostty-web@0.3.0": "patches/ghostty-web@0.3.0.patch", }, "overrides": { @@ -604,6 +610,8 @@ "@anycable/core": ["@anycable/core@0.9.2", "", { "dependencies": { "nanoevents": "^7.0.1" } }, "sha512-x5ZXDcW/N4cxWl93CnbHs/u7qq4793jS2kNPWm+duPrXlrva+ml2ZGT7X9tuOBKzyIHf60zWCdIK7TUgMPAwXA=="], + "@apideck/better-ajv-errors": ["@apideck/better-ajv-errors@0.3.6", "", { "dependencies": { "json-schema": "^0.4.0", "jsonpointer": "^5.0.0", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA=="], + "@astrojs/cloudflare": ["@astrojs/cloudflare@12.6.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "@astrojs/underscore-redirects": "1.0.0", "@cloudflare/workers-types": "^4.20250507.0", "tinyglobby": "^0.2.13", "vite": "^6.3.5", "wrangler": "^4.14.1" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-xhJptF5tU2k5eo70nIMyL1Udma0CqmUEnGSlGyFflLqSY82CRQI6nWZ/xZt0ZvmXuErUjIx0YYQNfZsz5CNjLQ=="], "@astrojs/compiler": ["@astrojs/compiler@2.13.0", "", {}, "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw=="], @@ -748,6 +756,10 @@ "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], + "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.6", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA=="], + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], @@ -760,7 +772,9 @@ "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], - "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + "@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="], + + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="], "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], @@ -770,22 +784,146 @@ "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + "@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ=="], + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q=="], + + "@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA=="], + + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA=="], + + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g=="], + + "@babel/plugin-proposal-private-property-in-object": ["@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w=="], + + "@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw=="], + + "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw=="], + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w=="], "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ=="], + "@babel/plugin-syntax-unicode-sets-regex": ["@babel/plugin-syntax-unicode-sets-regex@7.18.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg=="], + + "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="], + + "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA=="], + + "@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g=="], + + "@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg=="], + + "@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw=="], + + "@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw=="], + + "@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ=="], + + "@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-replace-supers": "^7.28.6", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q=="], + + "@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/template": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ=="], + + "@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw=="], + + "@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg=="], + + "@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q=="], + + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA=="], + + "@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A=="], + + "@babel/plugin-transform-explicit-resource-management": ["@babel/plugin-transform-explicit-resource-management@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg=="], + + "@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw=="], + + "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ=="], + + "@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw=="], + + "@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.27.1", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ=="], + + "@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw=="], + + "@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA=="], + + "@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A=="], + + "@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ=="], + + "@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA=="], + "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="], + "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.28.5", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew=="], + + "@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w=="], + + "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng=="], + + "@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ=="], + + "@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg=="], + + "@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w=="], + + "@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.28.6", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA=="], + + "@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng=="], + + "@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ=="], + + "@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w=="], + + "@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg=="], + + "@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg=="], + + "@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA=="], + + "@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ=="], + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw=="], + + "@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg=="], + + "@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw=="], + + "@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ=="], + + "@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA=="], + + "@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g=="], + + "@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg=="], + + "@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw=="], + "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA=="], + "@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg=="], + + "@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A=="], + + "@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw=="], + + "@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q=="], + + "@babel/preset-env": ["@babel/preset-env@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.28.6", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-class-static-block": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.28.6", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw=="], + + "@babel/preset-modules": ["@babel/preset-modules@0.1.6-no-external-plugins", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA=="], + "@babel/preset-typescript": ["@babel/preset-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ=="], "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], @@ -1446,6 +1584,14 @@ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], + "@rollup/plugin-babel": ["@rollup/plugin-babel@5.3.1", "", { "dependencies": { "@babel/helper-module-imports": "^7.10.4", "@rollup/pluginutils": "^3.1.0" }, "peerDependencies": { "@babel/core": "^7.0.0", "@types/babel__core": "^7.1.9", "rollup": "^1.20.0||^2.0.0" }, "optionalPeers": ["@types/babel__core"] }, "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q=="], + + "@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@15.3.1", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA=="], + + "@rollup/plugin-replace": ["@rollup/plugin-replace@2.4.2", "", { "dependencies": { "@rollup/pluginutils": "^3.1.0", "magic-string": "^0.25.7" }, "peerDependencies": { "rollup": "^1.20.0 || ^2.0.0" } }, "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg=="], + + "@rollup/plugin-terser": ["@rollup/plugin-terser@0.4.4", "", { "dependencies": { "serialize-javascript": "^6.0.1", "smob": "^1.0.0", "terser": "^5.17.4" }, "peerDependencies": { "rollup": "^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A=="], + "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.3", "", { "os": "android", "cpu": "arm" }, "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w=="], @@ -1684,6 +1830,8 @@ "@stripe/stripe-js": ["@stripe/stripe-js@8.6.1", "", {}, "sha512-UJ05U2062XDgydbUcETH1AoRQLNhigQ2KmDn1BG8sC3xfzu6JKg95Qt6YozdzFpxl1Npii/02m2LEWFt1RYjVA=="], + "@surma/rollup-plugin-off-main-thread": ["@surma/rollup-plugin-off-main-thread@2.2.3", "", { "dependencies": { "ejs": "^3.1.6", "json5": "^2.2.0", "magic-string": "^0.25.0", "string.prototype.matchall": "^4.0.6" } }, "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ=="], + "@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="], "@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="], @@ -1852,6 +2000,8 @@ "@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="], + "@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="], + "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], "@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="], @@ -2002,6 +2152,8 @@ "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="], + "autoprefixer": ["autoprefixer@10.4.22", "", { "dependencies": { "browserslist": "^4.27.0", "caniuse-lite": "^1.0.30001754", "fraction.js": "^5.3.4", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], @@ -2026,6 +2178,12 @@ "babel-plugin-module-resolver": ["babel-plugin-module-resolver@5.0.2", "", { "dependencies": { "find-babel-config": "^2.1.1", "glob": "^9.3.3", "pkg-up": "^3.1.0", "reselect": "^4.1.7", "resolve": "^1.22.8" } }, "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg=="], + "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.15", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw=="], + + "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="], + + "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.6", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A=="], + "babel-preset-solid": ["babel-preset-solid@1.9.10", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.10" }, "optionalPeers": ["solid-js"] }, "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ=="], "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], @@ -2194,6 +2352,8 @@ "common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], + "common-tags": ["common-tags@1.8.2", "", {}, "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA=="], + "compress-commons": ["compress-commons@6.0.2", "", { "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg=="], "condense-newlines": ["condense-newlines@0.2.1", "", { "dependencies": { "extend-shallow": "^2.0.1", "is-whitespace": "^0.3.0", "kind-of": "^3.0.2" } }, "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg=="], @@ -2216,6 +2376,8 @@ "cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="], + "core-js-compat": ["core-js-compat@3.48.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q=="], + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], @@ -2230,6 +2392,8 @@ "crossws": ["crossws@0.4.1", "", { "peerDependencies": { "srvx": ">=0.7.1" }, "optionalPeers": ["srvx"] }, "sha512-E7WKBcHVhAVrY6JYD5kteNqVq1GSZxqGrdSiwXR9at+XHi43HJoCQKXcCczR5LBnBquFZPsB3o7HklulKoBU5w=="], + "crypto-random-string": ["crypto-random-string@2.0.0", "", {}, "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="], + "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], "css-selector-parser": ["css-selector-parser@3.2.0", "", {}, "sha512-L1bdkNKUP5WYxiW5dW6vA2hd3sL8BdRNLy2FCX0rLVise4eNw9nBdeBuJHxlELieSE2H1f6bYQFfwVUwWCV9rQ=="], @@ -2342,6 +2506,8 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], + "electron-to-chromium": ["electron-to-chromium@1.5.259", "", {}, "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ=="], "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], @@ -2414,6 +2580,8 @@ "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], @@ -2456,6 +2624,8 @@ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], "fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="], @@ -2468,6 +2638,8 @@ "file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="], + "filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "finalhandler": ["finalhandler@1.3.1", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" } }, "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ=="], @@ -2506,7 +2678,7 @@ "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], - "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-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], @@ -2540,6 +2712,8 @@ "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + "get-own-enumerable-property-symbols": ["get-own-enumerable-property-symbols@3.0.2", "", {}, "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g=="], + "get-port": ["get-port@7.1.0", "", {}, "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw=="], "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], @@ -2550,6 +2724,8 @@ "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], + "ghostty-opentui": ["ghostty-opentui@1.3.7", "", { "dependencies": { "strip-ansi": "^7.1.2" }, "peerDependencies": { "@opentui/core": "*" }, "optionalPeers": ["@opentui/core"] }, "sha512-tXlVrFKMiS+VNm48OwQtCefP7i85o04wv2S/NPR5bIzv4yAl2//q7CBa8JEv9bL+5jpZsfMm6z8VJGrTq6Xjvg=="], + "ghostty-web": ["ghostty-web@0.3.0", "", {}, "sha512-SAdSHWYF20GMZUB0n8kh1N6Z4ljMnuUqT8iTB2n5FAPswEV10MejEpLlhW/769GL5+BQa1NYwEg9y/XCckV5+A=="], "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], @@ -2686,6 +2862,8 @@ "iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], + "idb": ["idb@7.1.1", "", {}, "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="], + "ieee754": ["ieee754@1.1.13", "", {}, "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="], "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], @@ -2764,12 +2942,16 @@ "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], + "is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="], + "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], + "is-obj": ["is-obj@1.0.1", "", {}, "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg=="], + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], @@ -2778,6 +2960,8 @@ "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], + "is-regexp": ["is-regexp@1.0.0", "", {}, "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA=="], + "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], @@ -2816,6 +3000,8 @@ "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="], + "jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="], + "jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="], "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], @@ -2854,6 +3040,8 @@ "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + "jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="], + "jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="], "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], @@ -2878,6 +3066,8 @@ "leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="], + "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], + "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], @@ -2908,6 +3098,8 @@ "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], + "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], @@ -2926,6 +3118,8 @@ "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], + "loglevelnext": ["loglevelnext@6.0.0", "", {}, "sha512-FDl1AI2sJGjHHG3XKJd6sG3/6ncgiGCQ0YkW46nxe7SfqQq6hujd9CvFXIXtkGBUN83KPZ2KSOJK8q5P0bSSRQ=="], "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], @@ -3338,6 +3532,8 @@ "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=="], + "pretty-bytes": ["pretty-bytes@6.1.1", "", {}, "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="], + "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], @@ -3368,6 +3564,8 @@ "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], + "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], "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=="], @@ -3414,6 +3612,10 @@ "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], + "regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="], + + "regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + "regex": ["regex@6.0.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA=="], "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], @@ -3422,6 +3624,12 @@ "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], + "regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="], + + "regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + "rehype": ["rehype@13.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", "rehype-stringify": "^10.0.0", "unified": "^11.0.0" } }, "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A=="], "rehype-autolink-headings": ["rehype-autolink-headings@7.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-heading-rank": "^3.0.0", "hast-util-is-element": "^3.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw=="], @@ -3520,6 +3728,8 @@ "seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="], + "serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], + "seroval": ["seroval@1.3.2", "", {}, "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ=="], "seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="], @@ -3568,6 +3778,8 @@ "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + "smob": ["smob@1.5.0", "", {}, "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig=="], + "smol-toml": ["smol-toml@1.5.2", "", {}, "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ=="], "socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="], @@ -3588,12 +3800,14 @@ "solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="], - "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + "sourcemap-codec": ["sourcemap-codec@1.4.8", "", {}, "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="], + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], @@ -3644,6 +3858,8 @@ "string-width-cjs": ["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=="], + "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], + "string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="], "string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="], @@ -3654,12 +3870,16 @@ "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + "stringify-object": ["stringify-object@3.3.0", "", { "dependencies": { "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", "is-regexp": "^1.0.0" } }, "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw=="], + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="], + "strip-comments": ["strip-comments@2.0.1", "", {}, "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw=="], + "strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="], "stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="], @@ -3690,6 +3910,10 @@ "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=="], + "temp-dir": ["temp-dir@2.0.0", "", {}, "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg=="], + + "tempy": ["tempy@0.6.0", "", { "dependencies": { "is-stream": "^2.0.0", "temp-dir": "^2.0.0", "type-fest": "^0.16.0", "unique-string": "^2.0.0" } }, "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw=="], + "terracotta": ["terracotta@1.0.6", "", { "dependencies": { "solid-use": "^0.9.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-yVrmT/Lg6a3tEbeYEJH8ksb1PYkR5FA9k5gr1TchaSNIiA2ZWs5a+koEbePXwlBP0poaV7xViZ/v50bQFcMgqw=="], "terser": ["terser@5.44.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw=="], @@ -3800,14 +4024,24 @@ "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], + "unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="], + + "unicode-match-property-ecmascript": ["unicode-match-property-ecmascript@2.0.0", "", { "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" } }, "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q=="], + + "unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + "unicode-properties": ["unicode-properties@1.4.1", "", { "dependencies": { "base64-js": "^1.3.0", "unicode-trie": "^2.0.0" } }, "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg=="], + "unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="], + "unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="], "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], "unifont": ["unifont@0.5.2", "", { "dependencies": { "css-tree": "^3.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.0" } }, "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg=="], + "unique-string": ["unique-string@2.0.0", "", { "dependencies": { "crypto-random-string": "^2.0.0" } }, "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg=="], + "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], @@ -3840,6 +4074,8 @@ "unzip-stream": ["unzip-stream@0.3.4", "", { "dependencies": { "binary": "^0.3.0", "mkdirp": "^0.5.1" } }, "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw=="], + "upath": ["upath@1.2.0", "", {}, "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="], + "update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], "url": ["url@0.10.3", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="], @@ -3874,6 +4110,8 @@ "vite-plugin-icons-spritesheet": ["vite-plugin-icons-spritesheet@3.0.1", "", { "dependencies": { "chalk": "^5.4.1", "glob": "^11.0.1", "node-html-parser": "^7.0.1", "tinyexec": "^0.3.2" }, "peerDependencies": { "vite": ">=5.2.0" } }, "sha512-Cr0+Z6wRMwSwKisWW9PHeTjqmQFv0jwRQQMc3YgAhAgZEe03j21el0P/CA31KN/L5eiL1LhR14VTXl96LetonA=="], + "vite-plugin-pwa": ["vite-plugin-pwa@1.2.0", "", { "dependencies": { "debug": "^4.3.6", "pretty-bytes": "^6.1.1", "tinyglobby": "^0.2.10", "workbox-build": "^7.4.0", "workbox-window": "^7.4.0" }, "peerDependencies": { "@vite-pwa/assets-generator": "^1.0.0", "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@vite-pwa/assets-generator"] }, "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw=="], + "vite-plugin-solid": ["vite-plugin-solid@2.11.10", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw=="], "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=="], @@ -3912,6 +4150,38 @@ "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], + "workbox-background-sync": ["workbox-background-sync@7.4.0", "", { "dependencies": { "idb": "^7.0.1", "workbox-core": "7.4.0" } }, "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w=="], + + "workbox-broadcast-update": ["workbox-broadcast-update@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA=="], + + "workbox-build": ["workbox-build@7.4.0", "", { "dependencies": { "@apideck/better-ajv-errors": "^0.3.1", "@babel/core": "^7.24.4", "@babel/preset-env": "^7.11.0", "@babel/runtime": "^7.11.2", "@rollup/plugin-babel": "^5.2.0", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-replace": "^2.4.1", "@rollup/plugin-terser": "^0.4.3", "@surma/rollup-plugin-off-main-thread": "^2.2.3", "ajv": "^8.6.0", "common-tags": "^1.8.0", "fast-json-stable-stringify": "^2.1.0", "fs-extra": "^9.0.1", "glob": "^11.0.1", "lodash": "^4.17.20", "pretty-bytes": "^5.3.0", "rollup": "^2.79.2", "source-map": "^0.8.0-beta.0", "stringify-object": "^3.3.0", "strip-comments": "^2.0.1", "tempy": "^0.6.0", "upath": "^1.2.0", "workbox-background-sync": "7.4.0", "workbox-broadcast-update": "7.4.0", "workbox-cacheable-response": "7.4.0", "workbox-core": "7.4.0", "workbox-expiration": "7.4.0", "workbox-google-analytics": "7.4.0", "workbox-navigation-preload": "7.4.0", "workbox-precaching": "7.4.0", "workbox-range-requests": "7.4.0", "workbox-recipes": "7.4.0", "workbox-routing": "7.4.0", "workbox-strategies": "7.4.0", "workbox-streams": "7.4.0", "workbox-sw": "7.4.0", "workbox-window": "7.4.0" } }, "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA=="], + + "workbox-cacheable-response": ["workbox-cacheable-response@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ=="], + + "workbox-core": ["workbox-core@7.4.0", "", {}, "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ=="], + + "workbox-expiration": ["workbox-expiration@7.4.0", "", { "dependencies": { "idb": "^7.0.1", "workbox-core": "7.4.0" } }, "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw=="], + + "workbox-google-analytics": ["workbox-google-analytics@7.4.0", "", { "dependencies": { "workbox-background-sync": "7.4.0", "workbox-core": "7.4.0", "workbox-routing": "7.4.0", "workbox-strategies": "7.4.0" } }, "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ=="], + + "workbox-navigation-preload": ["workbox-navigation-preload@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w=="], + + "workbox-precaching": ["workbox-precaching@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0", "workbox-routing": "7.4.0", "workbox-strategies": "7.4.0" } }, "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg=="], + + "workbox-range-requests": ["workbox-range-requests@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw=="], + + "workbox-recipes": ["workbox-recipes@7.4.0", "", { "dependencies": { "workbox-cacheable-response": "7.4.0", "workbox-core": "7.4.0", "workbox-expiration": "7.4.0", "workbox-precaching": "7.4.0", "workbox-routing": "7.4.0", "workbox-strategies": "7.4.0" } }, "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ=="], + + "workbox-routing": ["workbox-routing@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ=="], + + "workbox-strategies": ["workbox-strategies@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg=="], + + "workbox-streams": ["workbox-streams@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0", "workbox-routing": "7.4.0" } }, "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg=="], + + "workbox-sw": ["workbox-sw@7.4.0", "", {}, "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw=="], + + "workbox-window": ["workbox-window@7.4.0", "", { "dependencies": { "@types/trusted-types": "^2.0.2", "workbox-core": "7.4.0" } }, "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw=="], + "workerd": ["workerd@1.20251118.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251118.0", "@cloudflare/workerd-darwin-arm64": "1.20251118.0", "@cloudflare/workerd-linux-64": "1.20251118.0", "@cloudflare/workerd-linux-arm64": "1.20251118.0", "@cloudflare/workerd-windows-64": "1.20251118.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Om5ns0Lyx/LKtYI04IV0bjIrkBgoFNg0p6urzr2asekJlfP18RqFzyqMFZKf0i9Gnjtz/JfAS/Ol6tjCe5JJsQ=="], "wrangler": ["wrangler@4.50.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.11", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251118.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20251118.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251118.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-+nuZuHZxDdKmAyXOSrHlciGshCoAPiy5dM+t6mEohWm7HpXvTHmWQGUf/na9jjWlWJHCJYOWzkA1P5HBJqrIEA=="], @@ -4022,6 +4292,8 @@ "@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.9", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.5", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.13.0", "smol-toml": "^1.4.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-hX2cLC/KW74Io1zIbn92kI482j9J7LleBLGCVU9EP3BeH5MVrnFawOnqD0t/q6D1Z+ZNeQG2gNKMslCcO36wng=="], + "@astrojs/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "@astrojs/sitemap/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@astrojs/solid-js/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=="], @@ -4074,8 +4346,192 @@ "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@babel/helper-create-regexp-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-define-polyfill-provider/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/helper-remap-async-to-generator/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/helper-replace-supers/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/helper-wrap-function/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/helper-wrap-function/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/helper-wrap-function/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-bugfix-safari-class-field-initializer-scope/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-syntax-import-assertions/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-syntax-import-attributes/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-syntax-unicode-sets-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-arrow-functions/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-async-generator-functions/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-async-generator-functions/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-async-to-generator/@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/plugin-transform-async-to-generator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-block-scoped-functions/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-block-scoping/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="], + + "@babel/plugin-transform-class-properties/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="], + + "@babel/plugin-transform-class-static-block/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-classes/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/plugin-transform-classes/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-classes/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-computed-properties/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-computed-properties/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-destructuring/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-destructuring/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-dotall-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-duplicate-keys/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-dynamic-import/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-explicit-resource-management/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-exponentiation-operator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-export-namespace-from/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-for-of/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-function-name/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/plugin-transform-function-name/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-function-name/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-json-strings/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-literals/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-logical-assignment-operators/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-member-expression-literals/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/plugin-transform-modules-amd/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-modules-systemjs/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-modules-systemjs/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/plugin-transform-modules-umd/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-new-target/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-nullish-coalescing-operator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-numeric-separator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-object-rest-spread/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/plugin-transform-object-rest-spread/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-object-rest-spread/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-object-super/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-optional-catch-binding/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-optional-chaining/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-parameters/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="], + + "@babel/plugin-transform-private-methods/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="], + + "@babel/plugin-transform-private-property-in-object/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-property-literals/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-regenerator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-regexp-modifiers/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-reserved-words/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-shorthand-properties/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-spread/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-sticky-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-template-literals/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-typeof-symbol/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-unicode-escapes/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-unicode-property-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-unicode-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/plugin-transform-unicode-sets-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/preset-env/@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], + + "@babel/preset-env/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/preset-env/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/preset-env/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="], + + "@babel/preset-env/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/preset-modules/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + "@bufbuild/protoplugin/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="], "@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=="], @@ -4142,6 +4598,8 @@ "@jsx-email/doiuse-email/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="], + "@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "@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=="], "@modelcontextprotocol/sdk/jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], @@ -4234,6 +4692,18 @@ "@protobuf-ts/plugin/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="], + "@rollup/plugin-babel/@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@rollup/plugin-babel/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="], + + "@rollup/plugin-babel/rollup": ["rollup@2.79.2", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ=="], + + "@rollup/plugin-replace/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="], + + "@rollup/plugin-replace/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], + + "@rollup/plugin-replace/rollup": ["rollup@2.79.2", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ=="], + "@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=="], @@ -4272,6 +4742,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=="], + "@surma/rollup-plugin-off-main-thread/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], + "@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=="], @@ -4316,6 +4788,10 @@ "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=="], + "babel-plugin-polyfill-corejs2/@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], + + "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "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=="], @@ -4332,6 +4808,8 @@ "condense-newlines/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="], + "core-js-compat/browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "dot-prop/type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="], @@ -4352,6 +4830,10 @@ "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=="], + "esbuild-plugin-copy/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=="], + + "estree-util-to-js/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], "express/cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="], @@ -4364,6 +4846,8 @@ "fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], @@ -4482,6 +4966,8 @@ "sitemap/sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="], + "source-map/whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="], + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="], @@ -4498,6 +4984,10 @@ "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "tempy/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "tempy/type-fest": ["type-fest@0.16.0", "", {}, "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg=="], + "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], "token-types/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], @@ -4522,6 +5012,10 @@ "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "workbox-build/pretty-bytes": ["pretty-bytes@5.6.0", "", {}, "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="], + + "workbox-build/rollup": ["rollup@2.79.2", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ=="], + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "wrap-ansi-cjs/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=="], @@ -4588,6 +5082,178 @@ "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], + + "@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/helper-replace-supers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/helper-replace-supers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/helper-replace-supers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/helper-replace-supers/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/helper-replace-supers/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/helper-wrap-function/@babel/template/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/helper-wrap-function/@babel/template/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/helper-wrap-function/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/helper-wrap-function/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/helper-wrap-function/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-async-to-generator/@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-async-to-generator/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/plugin-transform-classes/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], + + "@babel/plugin-transform-classes/@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "@babel/plugin-transform-classes/@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/plugin-transform-classes/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-classes/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-classes/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-classes/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-classes/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-computed-properties/@babel/template/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-computed-properties/@babel/template/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-computed-properties/@babel/template/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-destructuring/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-destructuring/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-destructuring/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-destructuring/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-destructuring/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-function-name/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], + + "@babel/plugin-transform-function-name/@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "@babel/plugin-transform-function-name/@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/plugin-transform-function-name/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-function-name/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-function-name/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-function-name/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-function-name/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-object-rest-spread/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], + + "@babel/plugin-transform-object-rest-spread/@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "@babel/plugin-transform-object-rest-spread/@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/preset-env/@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "@babel/preset-env/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -4846,6 +5512,22 @@ "@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=="], + "@rollup/plugin-babel/@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@rollup/plugin-babel/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@rollup/plugin-babel/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="], + + "@rollup/plugin-babel/@rollup/pluginutils/estree-walker": ["estree-walker@1.0.1", "", {}, "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="], + + "@rollup/plugin-babel/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "@rollup/plugin-replace/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="], + + "@rollup/plugin-replace/@rollup/pluginutils/estree-walker": ["estree-walker@1.0.1", "", {}, "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="], + + "@rollup/plugin-replace/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], @@ -4956,6 +5638,14 @@ "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + "core-js-compat/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.9.17", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ=="], + + "core-js-compat/browserslist/caniuse-lite": ["caniuse-lite@1.0.30001766", "", {}, "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA=="], + + "core-js-compat/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.278", "", {}, "sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw=="], + + "core-js-compat/browserslist/update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], @@ -5050,6 +5740,10 @@ "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "source-map/whatwg-url/tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], + + "source-map/whatwg-url/webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "tw-to-css/tailwindcss/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=="], @@ -5102,6 +5796,92 @@ "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="], + "@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "@babel/plugin-transform-async-to-generator/@babel/helper-module-imports/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-async-to-generator/@babel/helper-module-imports/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-async-to-generator/@babel/helper-module-imports/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-async-to-generator/@babel/helper-module-imports/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-classes/@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "@babel/plugin-transform-function-name/@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-object-rest-spread/@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/preset-env/@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "@babel/preset-env/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/preset-env/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + "@jsx-email/cli/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "@jsx-email/cli/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -5178,6 +5958,14 @@ "@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + "@rollup/plugin-babel/@babel/helper-module-imports/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@rollup/plugin-babel/@babel/helper-module-imports/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@rollup/plugin-babel/@babel/helper-module-imports/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@rollup/plugin-babel/@babel/helper-module-imports/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + "@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=="], @@ -5232,6 +6020,8 @@ "rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "source-map/whatwg-url/tr46/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "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=="], @@ -5288,6 +6078,18 @@ "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="], + "@babel/preset-env/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@babel/preset-env/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/preset-env/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/preset-env/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/preset-env/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/preset-env/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + "@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="], diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000000..38764c56b57 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,34 @@ +# Docker Compose for running shuvcode +# +# Quick start: +# cd docker && docker compose up +# +# This builds everything from source and starts the server at http://localhost:4096 + +services: + shuvcode: + build: + context: .. + dockerfile: packages/opencode/Dockerfile + ports: + - "4096:4096" + volumes: + # Mount a workspace directory for the agent to work in + - ./workspace:/workspace + # Persist opencode data (sessions, config, etc.) + - shuvcode-data:/root/.opencode + environment: + # API keys - set these in your environment or .env file + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + - OPENAI_API_KEY=${OPENAI_API_KEY:-} + - OPENCODE_API_KEY=${OPENCODE_API_KEY:-} + # Git user config - set these to use your own identity for commits + - GIT_AUTHOR_NAME=${GIT_AUTHOR_NAME:-shuvcode} + - GIT_AUTHOR_EMAIL=${GIT_AUTHOR_EMAIL:-shuvcode@localhost} + - GIT_COMMITTER_NAME=${GIT_COMMITTER_NAME:-shuvcode} + - GIT_COMMITTER_EMAIL=${GIT_COMMITTER_EMAIL:-shuvcode@localhost} + working_dir: /workspace + restart: unless-stopped + +volumes: + shuvcode-data: diff --git a/docs/ci-runner-discussion.md b/docs/ci-runner-discussion.md new file mode 100644 index 00000000000..8bda0760ecf --- /dev/null +++ b/docs/ci-runner-discussion.md @@ -0,0 +1,90 @@ +# CI Runner Configuration - December 2024 + +## Context + +The `shuvcode` repository is a public fork of `sst/opencode` that automatically syncs upstream releases, resolves merge conflicts (via OpenCode agent), and publishes to npm as `shuvcode`. + +## Current State + +### Upstream Sync Pipeline (Fully Automated) + +- Detects new upstream releases every 5 minutes +- Attempts automatic merge +- Creates GitHub issues for conflicts/validation failures +- Triggers OpenCode agent to resolve issues automatically +- Auto-closes issues when sync succeeds +- Publishes to npm via snapshot workflow + +### CI Runner Configuration + +**Decision: Use Blacksmith runners via Latitudes-Dev organization** + +All workflows now use hardcoded `blacksmith-4vcpu-ubuntu-2404` runners. + +| Runner Type | Status | Notes | +| ------------- | ------- | ---------------------------------- | +| Blacksmith | Active | Fast execution, no queue delays | +| GitHub-hosted | Removed | Was slow (20+ min queue times) | +| Self-hosted | Removed | Security concerns for public repos | + +### Migration Completed + +- [x] Repository transferred to `Latitudes-Dev/shuvcode` +- [x] Blacksmith enabled for the organization +- [x] All workflows updated to use Blacksmith runners +- [x] Self-hosted runner files removed (security) +- [x] Secrets configured in new organization + +## Workflows + +All workflows use `runs-on: blacksmith-4vcpu-ubuntu-2404`: + +| Workflow | Purpose | +| ------------------- | -------------------------------- | +| `format.yml` | Code formatting checks | +| `test.yml` | Run tests | +| `typecheck.yml` | TypeScript type checking | +| `snapshot.yml` | Publish `shuvcode` to npm | +| `upstream-sync.yml` | Sync from sst/opencode | +| `opencode.yml` | AI agent for conflict resolution | + +SST-only workflows (deploy, publish, extensions, stats, notifications) were removed. + +## Commands Reference + +### Check Workflow Status + +```bash +gh run list --repo Latitudes-Dev/shuvcode --limit 10 +gh run view --repo Latitudes-Dev/shuvcode +``` + +### Trigger Upstream Sync Manually + +```bash +gh workflow run upstream-sync.yml --repo Latitudes-Dev/shuvcode -f force_sync=true +``` + +### View Open Issues + +```bash +gh issue list --repo Latitudes-Dev/shuvcode --state open --label upstream-sync +``` + +## Related Files + +- `.github/workflows/upstream-sync.yml` - Main sync workflow +- `.github/workflows/opencode.yml` - OpenCode agent trigger +- `.github/workflows/snapshot.yml` - Publish workflow + +## Historical Notes + +### Why Not Self-Hosted Runners? + +Self-hosted runners on public repos pose security risks: anyone can fork the repo, create a PR with malicious workflow code, and execute arbitrary code on your server. PR-triggered workflows (format, test, typecheck) would be vulnerable. + +### Why Blacksmith? + +- Fast execution (no 20+ minute queue delays) +- Hosted service (no security concerns) +- Requires GitHub Organization (hence migration to Latitudes-Dev) diff --git a/github/action.yml b/github/action.yml index 8652bb8c151..b282b59a007 100644 --- a/github/action.yml +++ b/github/action.yml @@ -1,5 +1,5 @@ -name: "opencode GitHub Action" -description: "Run opencode in GitHub Actions workflows" +name: "shuvcode GitHub Action" +description: "Run shuvcode in GitHub Actions workflows" branding: icon: "code" color: "orange" @@ -27,7 +27,7 @@ inputs: default: "false" mentions: - description: "Comma-separated list of trigger phrases (case-insensitive). Defaults to '/opencode,/oc'" + description: "Comma-separated list of trigger phrases (case-insensitive). Defaults to '/shuvcode,/shuv,/opencode,/oc'" required: false oidc_base_url: @@ -37,33 +37,33 @@ inputs: runs: using: "composite" steps: - - name: Get opencode version + - name: Get shuvcode version id: version shell: bash run: | - VERSION=$(curl -sf https://api.github.com/repos/anomalyco/opencode/releases/latest | grep -o '"tag_name": *"[^"]*"' | cut -d'"' -f4) + VERSION=$(curl -sf https://api.github.com/repos/Latitudes-Dev/shuvcode/releases/latest | grep -o '"tag_name": *"[^"]*"' | cut -d'"' -f4) echo "version=${VERSION:-latest}" >> $GITHUB_OUTPUT - - name: Cache opencode + - name: Cache shuvcode id: cache uses: actions/cache@v4 with: - path: ~/.opencode/bin - key: opencode-${{ runner.os }}-${{ runner.arch }}-${{ steps.version.outputs.version }} + path: ~/.shuvcode/bin + key: shuvcode-${{ runner.os }}-${{ runner.arch }}-${{ steps.version.outputs.version }} - - name: Install opencode + - name: Install shuvcode if: steps.cache.outputs.cache-hit != 'true' shell: bash - run: curl -fsSL https://opencode.ai/install | bash + run: curl -fsSL https://raw.githubusercontent.com/Latitudes-Dev/shuvcode/integration/install | bash - - name: Add opencode to PATH + - name: Add shuvcode to PATH shell: bash - run: echo "$HOME/.opencode/bin" >> $GITHUB_PATH + run: echo "$HOME/.shuvcode/bin" >> $GITHUB_PATH - - name: Run opencode + - name: Run shuvcode shell: bash - id: run_opencode - run: opencode github run + id: run_shuvcode + run: shuvcode github run env: MODEL: ${{ inputs.model }} AGENT: ${{ inputs.agent }} diff --git a/github/index.ts b/github/index.ts index 73378894cd3..31394b9fc98 100644 --- a/github/index.ts +++ b/github/index.ts @@ -231,7 +231,7 @@ function createOpencode() { const host = "127.0.0.1" const port = 4096 const url = `http://${host}:${port}` - const proc = spawn(`opencode`, [`serve`, `--hostname=${host}`, `--port=${port}`]) + const proc = spawn(`shuvcode`, [`serve`, `--hostname=${host}`, `--port=${port}`]) const client = createOpencodeClient({ baseUrl: url }) return { @@ -243,8 +243,8 @@ function createOpencode() { function assertPayloadKeyword() { const payload = useContext().payload as IssueCommentEvent | PullRequestReviewCommentEvent const body = payload.comment.body.trim() - if (!body.match(/(?:^|\s)(?:\/opencode|\/oc)(?=$|\s)/)) { - throw new Error("Comments must mention `/opencode` or `/oc`") + if (!body.match(/(?:^|\s)(?:\/shuvcode|\/shuv|\/opencode|\/oc)(?=$|\s)/)) { + throw new Error("Comments must mention `/shuvcode`, `/shuv`, `/opencode`, or `/oc`") } } @@ -362,7 +362,7 @@ function useIssueId() { } function useShareUrl() { - return isMock() ? "https://dev.opencode.ai" : "https://opencode.ai" + return isMock() ? "https://share.dev.shuv.ai" : "https://share.shuv.ai" } async function getAccessToken() { @@ -373,7 +373,7 @@ async function getAccessToken() { let response if (isMock()) { - response = await fetch("https://api.opencode.ai/exchange_github_app_token_with_pat", { + response = await fetch("https://api.shuv.ai/exchange_github_app_token_with_pat", { method: "POST", headers: { Authorization: `Bearer ${useEnvMock().mockToken}`, @@ -381,8 +381,8 @@ async function getAccessToken() { body: JSON.stringify({ owner: repo.owner, repo: repo.repo }), }) } else { - const oidcToken = await core.getIDToken("opencode-github-action") - response = await fetch("https://api.opencode.ai/exchange_github_app_token", { + const oidcToken = await core.getIDToken("shuvcode-github-action") + response = await fetch("https://api.shuv.ai/exchange_github_app_token", { method: "POST", headers: { Authorization: `Bearer ${oidcToken}`, @@ -417,19 +417,19 @@ async function getUserPrompt() { let prompt = (() => { const body = payload.comment.body.trim() - if (body === "/opencode" || body === "/oc") { + if (body === "/shuvcode" || body === "/shuv" || body === "/opencode" || body === "/oc") { if (reviewContext) { return `Review this code change and suggest improvements for the commented lines:\n\nFile: ${reviewContext.file}\nLines: ${reviewContext.line}\n\n${reviewContext.diffHunk}` } return "Summarize this thread" } - if (body.includes("/opencode") || body.includes("/oc")) { + if (body.includes("/shuvcode") || body.includes("/shuv") || body.includes("/opencode") || body.includes("/oc")) { if (reviewContext) { return `${body}\n\nContext: You are reviewing a comment on file "${reviewContext.file}" at line ${reviewContext.line}.\n\nDiff context:\n${reviewContext.diffHunk}` } return body } - throw new Error("Comments must mention `/opencode` or `/oc`") + throw new Error("Comments must mention `/shuvcode`, `/shuv`, `/opencode`, or `/oc`") })() // Handle images @@ -663,8 +663,8 @@ async function configureGit(appToken: string) { await $`git config --local --unset-all ${config}` await $`git config --local ${config} "AUTHORIZATION: basic ${newCredentials}"` - await $`git config --global user.name "opencode-agent[bot]"` - await $`git config --global user.email "opencode-agent[bot]@users.noreply.github.com"` + await $`git config --global user.name "shuvcode-agent[bot]"` + await $`git config --global user.email "shuvcode-agent[bot]@users.noreply.github.com"` } async function restoreGitConfig() { @@ -710,7 +710,7 @@ function generateBranchName(type: "issue" | "pr") { .replace(/\.\d{3}Z/, "") .split("T") .join("") - return `opencode/${type}${useIssueId()}-${timestamp}` + return `shuvcode/${type}${useIssueId()}-${timestamp}` } async function pushToNewBranch(summary: string, branch: string) { @@ -823,7 +823,7 @@ function footer(opts?: { image?: boolean }) { return `${titleAlt}\n` })() - const shareUrl = shareId ? `[opencode session](${useShareUrl()}/s/${shareId})  |  ` : "" + const shareUrl = shareId ? `[shuvcode session](${useShareUrl()}/s/${shareId})  |  ` : "" return `\n\n${image}${shareUrl}[github run](${useEnvRunUrl()})` } diff --git a/install b/install index 22b7ca39ed7..c209e35480d 100755 --- a/install +++ b/install @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -APP=opencode +APP=shuvcode MUTED='\033[0;2m' RED='\033[0;31m' @@ -16,19 +16,16 @@ Usage: install.sh [options] Options: -h, --help Display this help message -v, --version Install a specific version (e.g., 1.0.180) - -b, --binary Install from a local binary instead of downloading --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 - ./install --binary /path/to/opencode EOF } requested_version=${VERSION:-} no_modify_path=false -binary_path="" while [[ $# -gt 0 ]]; do case "$1" in @@ -45,15 +42,6 @@ while [[ $# -gt 0 ]]; do exit 1 fi ;; - -b|--binary) - if [[ -n "${2:-}" ]]; then - binary_path="$2" - shift 2 - else - echo -e "${RED}Error: --binary requires a path argument${NC}" - exit 1 - fi - ;; --no-modify-path) no_modify_path=true shift @@ -65,128 +53,119 @@ while [[ $# -gt 0 ]]; do esac done -INSTALL_DIR=$HOME/.opencode/bin -mkdir -p "$INSTALL_DIR" +raw_os=$(uname -s) +os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]') +case "$raw_os" in + Darwin*) os="darwin" ;; + Linux*) os="linux" ;; + MINGW*|MSYS*|CYGWIN*) os="windows" ;; +esac -# If --binary is provided, skip all download/detection logic -if [ -n "$binary_path" ]; then - if [ ! -f "$binary_path" ]; then - echo -e "${RED}Error: Binary not found at ${binary_path}${NC}" - exit 1 - fi - specific_version="local" -else - raw_os=$(uname -s) - os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]') - case "$raw_os" in - Darwin*) os="darwin" ;; - Linux*) os="linux" ;; - MINGW*|MSYS*|CYGWIN*) os="windows" ;; - esac +arch=$(uname -m) +if [[ "$arch" == "aarch64" ]]; then + arch="arm64" +fi +if [[ "$arch" == "x86_64" ]]; then + arch="x64" +fi - arch=$(uname -m) - if [[ "$arch" == "aarch64" ]]; then - arch="arm64" - fi - if [[ "$arch" == "x86_64" ]]; then - arch="x64" - fi +if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then + rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0) + if [ "$rosetta_flag" = "1" ]; then + arch="arm64" + fi +fi - if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then - rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0) - if [ "$rosetta_flag" = "1" ]; then - arch="arm64" - fi - fi +combo="$os-$arch" +case "$combo" in + linux-x64|linux-arm64|darwin-x64|darwin-arm64|windows-x64) + ;; + *) + echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}" + exit 1 + ;; +esac - combo="$os-$arch" - case "$combo" in - linux-x64|linux-arm64|darwin-x64|darwin-arm64|windows-x64) - ;; - *) - echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}" - exit 1 - ;; - esac +archive_ext=".zip" +if [ "$os" = "linux" ]; then + archive_ext=".tar.gz" +fi - archive_ext=".zip" - if [ "$os" = "linux" ]; then - archive_ext=".tar.gz" +is_musl=false +if [ "$os" = "linux" ]; then + if [ -f /etc/alpine-release ]; then + is_musl=true + fi + + if command -v ldd >/dev/null 2>&1; then + if ldd --version 2>&1 | grep -qi musl; then + is_musl=true fi + fi +fi - is_musl=false - if [ "$os" = "linux" ]; then - if [ -f /etc/alpine-release ]; then - is_musl=true - fi +needs_baseline=false +if [ "$arch" = "x64" ]; then + if [ "$os" = "linux" ]; then + if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then + needs_baseline=true + fi + fi - if command -v ldd >/dev/null 2>&1; then - if ldd --version 2>&1 | grep -qi musl; then - is_musl=true - fi - fi + if [ "$os" = "darwin" ]; then + avx2=$(sysctl -n hw.optional.avx2_0 2>/dev/null || echo 0) + if [ "$avx2" != "1" ]; then + needs_baseline=true fi + fi +fi - needs_baseline=false - if [ "$arch" = "x64" ]; then - if [ "$os" = "linux" ]; then - if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then - needs_baseline=true - fi - fi +target="$os-$arch" +if [ "$needs_baseline" = "true" ]; then + target="$target-baseline" +fi +if [ "$is_musl" = "true" ]; then + target="$target-musl" +fi + +filename="$APP-$target$archive_ext" - if [ "$os" = "darwin" ]; then - avx2=$(sysctl -n hw.optional.avx2_0 2>/dev/null || echo 0) - if [ "$avx2" != "1" ]; then - needs_baseline=true - fi - fi - fi - target="$os-$arch" - if [ "$needs_baseline" = "true" ]; then - target="$target-baseline" +if [ "$os" = "linux" ]; then + if ! command -v tar >/dev/null 2>&1; then + echo -e "${RED}Error: 'tar' is required but not installed.${NC}" + exit 1 fi - if [ "$is_musl" = "true" ]; then - target="$target-musl" +else + if ! command -v unzip >/dev/null 2>&1; then + echo -e "${RED}Error: 'unzip' is required but not installed.${NC}" + exit 1 fi +fi - filename="$APP-$target$archive_ext" +INSTALL_DIR=$HOME/.shuvcode/bin +mkdir -p "$INSTALL_DIR" +if [ -z "$requested_version" ]; then + url="https://github.com/Latitudes-Dev/shuvcode/releases/latest/download/$filename" + specific_version=$(curl -s https://api.github.com/repos/Latitudes-Dev/shuvcode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') - if [ "$os" = "linux" ]; then - if ! command -v tar >/dev/null 2>&1; then - echo -e "${RED}Error: 'tar' is required but not installed.${NC}" - exit 1 - fi - else - if ! command -v unzip >/dev/null 2>&1; then - echo -e "${RED}Error: 'unzip' is required but not installed.${NC}" - exit 1 - fi + if [[ $? -ne 0 || -z "$specific_version" ]]; then + echo -e "${RED}Failed to fetch version information${NC}" + exit 1 fi - - if [ -z "$requested_version" ]; then - url="https://github.com/anomalyco/opencode/releases/latest/download/$filename" - specific_version=$(curl -s https://api.github.com/repos/anomalyco/opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') - - if [[ $? -ne 0 || -z "$specific_version" ]]; then - echo -e "${RED}Failed to fetch version information${NC}" - exit 1 - fi - else - # Strip leading 'v' if present - requested_version="${requested_version#v}" - url="https://github.com/anomalyco/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/anomalyco/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/anomalyco/opencode/releases${NC}" - exit 1 - fi +else + # Strip leading 'v' if present + requested_version="${requested_version#v}" + url="https://github.com/Latitudes-Dev/shuvcode/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/Latitudes-Dev/shuvcode/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/Latitudes-Dev/shuvcode/releases${NC}" + exit 1 fi fi @@ -205,11 +184,11 @@ print_message() { } check_version() { - if command -v opencode >/dev/null 2>&1; then - opencode_path=$(which opencode) + if command -v shuvcode >/dev/null 2>&1; then + shuvcode_path=$(which shuvcode) ## Check the installed version - installed_version=$(opencode --version 2>/dev/null || echo "") + installed_version=$(shuvcode --version 2>/dev/null || echo "") if [[ "$installed_version" != "$specific_version" ]]; then print_message info "${MUTED}Installed version: ${NC}$installed_version." @@ -261,7 +240,7 @@ download_with_progress() { fi local tmp_dir=${TMPDIR:-/tmp} - local basename="${tmp_dir}/opencode_install_$$" + local basename="${tmp_dir}/shuvcode_install_$$" local tracefile="${basename}.trace" rm -f "$tracefile" @@ -273,7 +252,7 @@ download_with_progress() { trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN ( - curl --trace-ascii "$tracefile" -s -L -o "$output" "$url" + curl --trace-ascii "$tracefile" -s -f -L -o "$output" "$url" ) & local curl_pid=$! @@ -311,13 +290,13 @@ download_with_progress() { } download_and_install() { - print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version" - local tmp_dir="${TMPDIR:-/tmp}/opencode_install_$$" + print_message info "\n${MUTED}Installing ${NC}shuvcode ${MUTED}version: ${NC}$specific_version" + local tmp_dir="${TMPDIR:-/tmp}/shuvcode_install_$$" mkdir -p "$tmp_dir" 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" + curl -f -# -L -o "$tmp_dir/$filename" "$url" fi if [ "$os" = "linux" ]; then @@ -326,23 +305,21 @@ download_and_install() { unzip -q "$tmp_dir/$filename" -d "$tmp_dir" fi - mv "$tmp_dir/opencode" "$INSTALL_DIR" - chmod 755 "${INSTALL_DIR}/opencode" + if [ "$os" = "windows" ]; then + mv "$tmp_dir/shuvcode.exe" "$INSTALL_DIR" + chmod 755 "${INSTALL_DIR}/shuvcode.exe" 2>/dev/null || true + # Convenience shim so `shuvcode` works in bash + printf '%s\n' '#!/usr/bin/env bash' 'exec "$(dirname "$0")/shuvcode.exe" "$@"' > "${INSTALL_DIR}/shuvcode" + chmod 755 "${INSTALL_DIR}/shuvcode" 2>/dev/null || true + else + mv "$tmp_dir/shuvcode" "$INSTALL_DIR" + chmod 755 "${INSTALL_DIR}/shuvcode" + fi rm -rf "$tmp_dir" } -install_from_binary() { - print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}from: ${NC}$binary_path" - cp "$binary_path" "${INSTALL_DIR}/opencode" - chmod 755 "${INSTALL_DIR}/opencode" -} - -if [ -n "$binary_path" ]; then - install_from_binary -else - check_version - download_and_install -fi +check_version +download_and_install add_to_path() { @@ -352,9 +329,9 @@ add_to_path() { if grep -Fxq "$command" "$config_file"; then print_message info "Command already exists in $config_file, skipping write." elif [[ -w $config_file ]]; then - echo -e "\n# opencode" >> "$config_file" + echo -e "\n# shuvcode" >> "$config_file" echo "$command" >> "$config_file" - print_message info "${MUTED}Successfully added ${NC}opencode ${MUTED}to \$PATH in ${NC}$config_file" + print_message info "${MUTED}Successfully added ${NC}shuvcode ${MUTED}to \$PATH in ${NC}$config_file" else print_message warning "Manually add the directory to $config_file (or similar):" print_message info " $command" @@ -430,17 +407,14 @@ if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then fi echo -e "" -echo -e "${MUTED}  ${NC} ▄ " -echo -e "${MUTED}█▀▀█ █▀▀█ █▀▀█ █▀▀▄ ${NC}█▀▀▀ █▀▀█ █▀▀█ █▀▀█" -echo -e "${MUTED}█░░█ █░░█ █▀▀▀ █░░█ ${NC}█░░░ █░░█ █░░█ █▀▀▀" -echo -e "${MUTED}▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ${NC}▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀" -echo -e "" +echo -e "${MUTED} ▄ ${NC} ▄ " +echo -e "${MUTED}█▀▀▀ █▀▀█ █ █ █ █ ${NC}█▀▀▀ █▀▀█ █▀▀█ █▀▀█" +echo -e "${MUTED}▀▀▀█ █░░█ █░░█ █░░█ ${NC}█░░░ █░░█ █░░█ █▀▀▀" +echo -e "${MUTED}▀▀▀▀ ▀ ▀ ▀▀▀▀ ▀▀ ${NC}▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀" echo -e "" -echo -e "${MUTED}OpenCode includes free models, to start:${NC}" echo -e "" echo -e "cd ${MUTED}# Open directory${NC}" -echo -e "opencode ${MUTED}# Run command${NC}" -echo -e "" -echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/docs" +echo -e "shuvcode ${MUTED}# Run command${NC}" echo -e "" +echo -e "${MUTED}A fork of opencode. For more info visit ${NC}https://shuv.ai" echo -e "" diff --git a/package.json b/package.json index 4267ef64566..d579ec6c6d9 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,12 @@ "description": "AI-powered development tool", "private": true, "type": "module", - "packageManager": "bun@1.3.5", + "packageManager": "bun@1.3.6", "scripts": { "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", "typecheck": "bun turbo typecheck", "prepare": "husky", + "generate": "bun run --cwd packages/sdk/js build", "random": "echo 'Random script'", "hello": "echo 'Hello World!'", "test": "echo 'do not run tests from root' && exit 1" @@ -99,6 +100,7 @@ "@types/node": "catalog:" }, "patchedDependencies": { + "ghostty-opentui@1.3.7": "patches/ghostty-opentui@1.3.7.patch", "ghostty-web@0.3.0": "patches/ghostty-web@0.3.0.patch" } } diff --git a/packages/app/AGENTS.md b/packages/app/AGENTS.md index 765e960c817..aefbb05d5aa 100644 --- a/packages/app/AGENTS.md +++ b/packages/app/AGENTS.md @@ -14,6 +14,27 @@ - Always prefer `createStore` over multiple `createSignal` calls +## Running Desktop in Development + +To run the desktop app in development mode, you need **two terminals**: + +1. **Terminal 1 - API Server** (from repo root): + + ```bash + bun run dev serve --port 4096 + ``` + +2. **Terminal 2 - Desktop App** (from packages/desktop): + ```bash + bun run dev + ``` + +The desktop dev server runs at http://localhost:3000 and connects to the API at port 4096. + +**Note**: The `--port 4096` flag is required because the server defaults to a random port (for multi-instance support in Tauri). The `.env.development` file sets `VITE_OPENCODE_SERVER_PORT=4096` so the desktop app knows where to connect. + +## Code Style + ## Tool Calling - ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE. diff --git a/packages/app/e2e/context.spec.ts b/packages/app/e2e/context.spec.ts index beabd2eb7dd..3f6684dd091 100644 --- a/packages/app/e2e/context.spec.ts +++ b/packages/app/e2e/context.spec.ts @@ -1,6 +1,4 @@ import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" - test("context panel can be opened from the prompt", async ({ page, sdk, gotoSession }) => { const title = `e2e smoke context ${Date.now()}` const created = await sdk.session.create({ title }).then((r) => r.data) @@ -21,10 +19,13 @@ test("context panel can be opened from the prompt", async ({ page, sdk, gotoSess }) await expect - .poll(async () => { - const messages = await sdk.session.messages({ sessionID, limit: 1 }).then((r) => r.data ?? []) - return messages.length - }) + .poll( + async () => { + const messages = await sdk.session.messages({ sessionID, limit: 1 }).then((r) => r.data ?? []) + return messages.length + }, + { timeout: 30000 }, + ) .toBeGreaterThan(0) await gotoSession(sessionID) diff --git a/packages/app/e2e/fixtures.ts b/packages/app/e2e/fixtures.ts index c5315ff194d..19a887c850c 100644 --- a/packages/app/e2e/fixtures.ts +++ b/packages/app/e2e/fixtures.ts @@ -1,5 +1,5 @@ import { test as base, expect } from "@playwright/test" -import { createSdk, dirSlug, getWorktree, promptSelector, serverUrl, sessionPath } from "./utils" +import { createSdk, dirSlug, getWorktree, promptSelector, serverUrl, sessionPath, waitForAppReady } from "./utils" type TestFixtures = { sdk: ReturnType @@ -80,7 +80,9 @@ export const test = base.extend({ const gotoSession = async (sessionID?: string) => { await page.goto(sessionPath(directory, sessionID)) - await expect(page.locator(promptSelector)).toBeVisible() + // Wait for app to be ready (may show loading states briefly) + await waitForAppReady(page) + await expect(page.locator(promptSelector).first()).toBeVisible({ timeout: 15000 }) } await use(gotoSession) }, diff --git a/packages/app/e2e/home.spec.ts b/packages/app/e2e/home.spec.ts index c6fb0e3b074..31557f195d7 100644 --- a/packages/app/e2e/home.spec.ts +++ b/packages/app/e2e/home.spec.ts @@ -1,18 +1,25 @@ import { test, expect } from "./fixtures" -import { serverName } from "./utils" +import { serverName, waitForAppReady } from "./utils" test("home renders and shows core entrypoints", async ({ page }) => { await page.goto("/") - await expect(page.getByRole("button", { name: "Open project" }).first()).toBeVisible() - await expect(page.getByRole("button", { name: serverName })).toBeVisible() + // Wait for app to be ready + await waitForAppReady(page) + + // Fork uses "Add project" instead of upstream's "Open project" (DialogCreateProject customization) + await expect(page.getByRole("button", { name: "Add project" }).first()).toBeVisible({ timeout: 15000 }) + await expect(page.getByRole("button", { name: serverName })).toBeVisible({ timeout: 15000 }) }) test("server picker dialog opens from home", async ({ page }) => { await page.goto("/") + // Wait for app to be ready + await waitForAppReady(page) + const trigger = page.getByRole("button", { name: serverName }) - await expect(trigger).toBeVisible() + await expect(trigger).toBeVisible({ timeout: 15000 }) await trigger.click() const dialog = page.getByRole("dialog") diff --git a/packages/app/e2e/navigation.spec.ts b/packages/app/e2e/navigation.spec.ts index 76923af6ede..ebc4b947fdb 100644 --- a/packages/app/e2e/navigation.spec.ts +++ b/packages/app/e2e/navigation.spec.ts @@ -1,9 +1,12 @@ import { test, expect } from "./fixtures" -import { dirPath, promptSelector } from "./utils" +import { dirPath, promptSelector, waitForAppReady } from "./utils" test("project route redirects to /session", async ({ page, directory, slug }) => { await page.goto(dirPath(directory)) - await expect(page).toHaveURL(new RegExp(`/${slug}/session`)) - await expect(page.locator(promptSelector)).toBeVisible() + // Wait for app to be ready + await waitForAppReady(page) + + await expect(page).toHaveURL(new RegExp(`/${slug}/session`), { timeout: 15000 }) + await expect(page.locator(promptSelector).first()).toBeVisible({ timeout: 15000 }) }) diff --git a/packages/app/e2e/prompt.spec.ts b/packages/app/e2e/prompt.spec.ts index 3e5892ce8d5..71a38166508 100644 --- a/packages/app/e2e/prompt.spec.ts +++ b/packages/app/e2e/prompt.spec.ts @@ -19,7 +19,7 @@ test("can send a prompt and receive a reply", async ({ page, sdk, gotoSession }) const token = `E2E_OK_${Date.now()}` - const prompt = page.locator(promptSelector) + const prompt = page.locator(promptSelector).first() await prompt.click() await page.keyboard.type(`Reply with exactly: ${token}`) await page.keyboard.press("Enter") diff --git a/packages/app/e2e/session.spec.ts b/packages/app/e2e/session.spec.ts index 19e25a42131..6acca61d2a0 100644 --- a/packages/app/e2e/session.spec.ts +++ b/packages/app/e2e/session.spec.ts @@ -11,7 +11,7 @@ test("can open an existing session and type into the prompt", async ({ page, sdk try { await gotoSession(sessionID) - const prompt = page.locator(promptSelector) + const prompt = page.locator(promptSelector).first() await prompt.click() await page.keyboard.type("hello from e2e") await expect(prompt).toContainText("hello from e2e") diff --git a/packages/app/e2e/terminal.spec.ts b/packages/app/e2e/terminal.spec.ts index fc558b63259..27d4cc71fb9 100644 --- a/packages/app/e2e/terminal.spec.ts +++ b/packages/app/e2e/terminal.spec.ts @@ -4,7 +4,7 @@ import { terminalSelector, terminalToggleKey } from "./utils" test("terminal panel can be toggled", async ({ page, gotoSession }) => { await gotoSession() - const terminal = page.locator(terminalSelector) + const terminal = page.locator(terminalSelector).first() const initiallyOpen = await terminal.isVisible() if (initiallyOpen) { await page.keyboard.press(terminalToggleKey) diff --git a/packages/app/e2e/utils.ts b/packages/app/e2e/utils.ts index eb0395950ae..d1aae6d361c 100644 --- a/packages/app/e2e/utils.ts +++ b/packages/app/e2e/utils.ts @@ -36,3 +36,32 @@ export function dirPath(directory: string) { export function sessionPath(directory: string, sessionID?: string) { return `${dirPath(directory)}/session${sessionID ? `/${sessionID}` : ""}` } + +/** + * Selector for app loading states that should disappear before tests run. + * Matches: "Connecting to server...", any error messages, or loading spinners. + */ +export const loadingSelector = 'text="Connecting to server...", text="Could not connect"' + +/** + * Wait for the app to be ready - checks that loading states are gone + * and basic app structure is visible. + */ +export async function waitForAppReady(page: import("@playwright/test").Page, timeout = 30000) { + // First, wait for the page to have some content + await page.waitForLoadState("domcontentloaded") + + // Then wait for either the app to be ready (buttons visible) or stay in a loading/error state + // If we timeout waiting for buttons, the test will fail with a clear error + const readyChecks = [ + page.locator('[data-component="prompt-input"]').first(), + page.getByRole("button", { name: "Add project" }).first(), + page.getByRole("button", { name: serverName }).first(), + ] + + try { + await Promise.any(readyChecks.map((locator) => locator.waitFor({ state: "visible", timeout }))) + } catch (error) { + throw new Error("Timed out waiting for app to be ready") + } +} diff --git a/packages/app/index.html b/packages/app/index.html index 6fa34553510..db94e4ce85d 100644 --- a/packages/app/index.html +++ b/packages/app/index.html @@ -1,23 +1,47 @@ - + - - OpenCode - - - - + + shuvcode + + + + - - + + + + + + + + + - + + - + -
+
diff --git a/packages/app/package.json b/packages/app/package.json index 96d600541a6..a7bd2b749fc 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.1.39", + "version": "1.1.42", "description": "", "type": "module", "exports": { @@ -32,6 +32,7 @@ "typescript": "catalog:", "vite": "catalog:", "vite-plugin-icons-spritesheet": "3.0.1", + "vite-plugin-pwa": "1.2.0", "vite-plugin-solid": "catalog:" }, "dependencies": { diff --git a/packages/app/script/e2e-local.ts b/packages/app/script/e2e-local.ts index 2c7be2ad952..b30fd288201 100644 --- a/packages/app/script/e2e-local.ts +++ b/packages/app/script/e2e-local.ts @@ -3,6 +3,8 @@ import net from "node:net" import os from "node:os" import path from "node:path" +const isWindows = process.platform === "win32" + async function freePort() { return await new Promise((resolve, reject) => { const server = net.createServer() @@ -24,22 +26,44 @@ async function freePort() { }) } -async function waitForHealth(url: string) { - const timeout = Date.now() + 120_000 - const errors: string[] = [] +async function waitForHealth(url: string, server: Bun.Subprocess) { + const timeoutMs = process.env.CI ? 180_000 : 60_000 + const timeout = Date.now() + timeoutMs + let attempts = 0 + + // Give the server a moment to start on Windows + if (isWindows) { + await new Promise((r) => setTimeout(r, 2000)) + } + while (Date.now() < timeout) { - const result = await fetch(url) - .then((r) => ({ ok: r.ok, error: undefined })) - .catch((error) => ({ - ok: false, - error: error instanceof Error ? error.message : String(error), - })) - if (result.ok) return - if (result.error) errors.push(result.error) + attempts++ + if (attempts % 20 === 0) { + console.log( + `[e2e] Health check attempt ${attempts}, elapsed: ${Math.round((Date.now() - (timeout - timeoutMs)) / 1000)}s`, + ) + } + + const ok = await fetch(url) + .then((r) => r.ok) + .catch(() => false) + if (ok) { + console.log(`[e2e] Server healthy after ${attempts} attempts`) + return + } + + const exited = await Promise.race([ + server.exited.then(() => true).catch(() => true), + new Promise((resolve) => setTimeout(() => resolve(false), 0)), + ]) + + if (exited) { + throw new Error(`Server exited before health check: ${url}`) + } + await new Promise((r) => setTimeout(r, 250)) } - const last = errors.length ? ` (last error: ${errors[errors.length - 1]})` : "" - throw new Error(`Timed out waiting for server health: ${url}${last}`) + throw new Error(`Timed out waiting for server health after ${timeoutMs / 1000}s: ${url}`) } const appDir = process.cwd() @@ -52,7 +76,11 @@ const extraArgs = (() => { return args })() -const [serverPort, webPort] = await Promise.all([freePort(), freePort()]) +const serverHost = process.env.OPENCODE_E2E_SERVER_HOST ?? "127.0.0.1" +// Use fixed ports on Windows to avoid port binding race conditions +const serverPort = isWindows ? 14096 : await freePort() +const webPort = isWindows ? 14097 : await freePort() +console.log(`[e2e] Using server port ${serverPort}, web port ${webPort}`) const sandbox = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-e2e-")) @@ -75,10 +103,10 @@ const serverEnv = { } satisfies Record const runnerEnv = { - ...serverEnv, - PLAYWRIGHT_SERVER_HOST: "127.0.0.1", + ...process.env, + PLAYWRIGHT_SERVER_HOST: serverHost, PLAYWRIGHT_SERVER_PORT: String(serverPort), - VITE_OPENCODE_SERVER_HOST: "127.0.0.1", + VITE_OPENCODE_SERVER_HOST: serverHost, VITE_OPENCODE_SERVER_PORT: String(serverPort), PLAYWRIGHT_PORT: String(webPort), } satisfies Record @@ -92,49 +120,68 @@ const seed = Bun.spawn(["bun", "script/seed-e2e.ts"], { const seedExit = await seed.exited if (seedExit !== 0) { + console.error(`[e2e] Seed process failed with exit code ${seedExit}`) process.exit(seedExit) } +console.log(`[e2e] Seed completed successfully`) -Object.assign(process.env, serverEnv) -process.env.AGENT = "1" -process.env.OPENCODE = "1" - -const log = await import("../../opencode/src/util/log") -const install = await import("../../opencode/src/installation") -await log.Log.init({ - print: true, - dev: install.Installation.isLocal(), - level: "WARN", -}) - -const servermod = await import("../../opencode/src/server/server") -const inst = await import("../../opencode/src/project/instance") -const server = servermod.Server.listen({ port: serverPort, hostname: "127.0.0.1" }) -console.log(`opencode server listening on http://127.0.0.1:${serverPort}`) - -const result = await (async () => { - try { - await waitForHealth(`http://127.0.0.1:${serverPort}/global/health`) +// On Windows, add a small delay to ensure file handles are released +if (isWindows) { + console.log(`[e2e] Waiting for Windows file handles to be released...`) + await new Promise((r) => setTimeout(r, 1000)) +} - const runner = Bun.spawn(["bun", "test:e2e", ...extraArgs], { - cwd: appDir, - env: runnerEnv, - stdout: "inherit", - stderr: "inherit", - }) +console.log(`[e2e] Starting server on ${serverHost}:${serverPort}...`) + +// Run the serve command directly instead of through `bun dev` for better Windows compatibility +const server = Bun.spawn( + [ + "bun", + "run", + "--conditions=browser", + "./src/index.ts", + "--print-logs", + "--log-level", + "INFO", // Use INFO level for better debugging + "serve", + "--port", + String(serverPort), + "--hostname", + serverHost, + ], + { + cwd: opencodeDir, + env: serverEnv, + stdout: "inherit", + stderr: "inherit", + }, +) + +console.log(`[e2e] Server process spawned with PID ${server.pid}`) + +try { + const healthUrl = `http://${serverHost}:${serverPort}/global/health` + console.log(`[e2e] Waiting for server health at ${healthUrl}`) + await waitForHealth(healthUrl, server) + + console.log(`[e2e] Server is healthy, starting Playwright tests...`) + const runner = Bun.spawn(["bun", "test:e2e", ...extraArgs], { + cwd: appDir, + env: runnerEnv, + stdout: "inherit", + stderr: "inherit", + }) - return { code: await runner.exited } - } catch (error) { - return { error } - } finally { - await inst.Instance.disposeAll() - await server.stop() + process.exitCode = await runner.exited + console.log(`[e2e] Tests completed with exit code ${process.exitCode}`) +} catch (error) { + console.error(`[e2e] Error: ${error}`) + // Try to get any output from the server process + if (server.exitCode !== null) { + console.error(`[e2e] Server exited with code ${server.exitCode}`) } -})() - -if ("error" in result) { - console.error(result.error) - process.exit(1) + throw error +} finally { + console.log(`[e2e] Stopping server...`) + server.kill() } - -process.exit(result.code) diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index ba0d1e7aa4e..e13d62bfa87 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -27,6 +27,7 @@ import { CommandProvider } from "@/context/command" import { LanguageProvider, useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" import { HighlightsProvider } from "@/context/highlights" +import { iife } from "@opencode-ai/util/iife" import Layout from "@/pages/layout" import DirectoryLayout from "@/pages/directory-layout" import { ErrorPage } from "./pages/error" @@ -43,10 +44,40 @@ function UiI18nBridge(props: ParentProps) { declare global { interface Window { - __OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string } + __SHUVCODE__?: { updaterEnabled?: boolean; port?: number; serverReady?: boolean } + __OPENCODE__?: { + updaterEnabled?: boolean + port?: number + serverReady?: boolean + serverUrl?: string + serverPassword?: string + } } } +const defaultServerUrl = iife(() => { + // NOTE: The ?url= query parameter was intentionally removed due to CVE-2026-22813 (GHSA-c83v-7274-4vgp) + // Allowing arbitrary server URLs via query params enables XSS attacks on localhost:4096 + + // 1. Configured server URL (from desktop settings) + if (window.__OPENCODE__?.serverUrl) return window.__OPENCODE__.serverUrl + + // 2. Known production hosts -> localhost (same as upstream + shuv.ai) + if (location.hostname.includes("opencode.ai") || location.hostname.includes("shuv.ai")) return "http://localhost:4096" + + // 3. Desktop app (Tauri) with injected port + if (window.__SHUVCODE__?.port) return `http://127.0.0.1:${window.__SHUVCODE__.port}` + if (window.__OPENCODE__?.port) return `http://127.0.0.1:${window.__OPENCODE__.port}` + + // 4. Dev mode -> same-origin so Vite proxy handles LAN access + CORS + if (import.meta.env.DEV) { + return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}` + } + + // 5. Default -> same origin (production web command) + return window.location.origin +}) + function MarkedProviderWithNativeParser(props: ParentProps) { const platform = usePlatform() return {props.children} @@ -56,7 +87,7 @@ export function AppBaseProviders(props: ParentProps) { return ( - + }> @@ -85,28 +116,14 @@ function ServerKey(props: ParentProps) { } export function AppInterface(props: { defaultUrl?: string }) { - const platform = usePlatform() - - const stored = (() => { - if (platform.platform !== "web") return - const result = platform.getDefaultServerUrl?.() - if (result instanceof Promise) return - if (!result) return - return normalizeServerUrl(result) - })() - - const defaultServerUrl = () => { + const getDefaultServerUrl = () => { + // Use props if provided, otherwise use module-level defaultServerUrl if (props.defaultUrl) return props.defaultUrl - if (stored) return stored - if (location.hostname.includes("opencode.ai")) return "http://localhost:4096" - if (import.meta.env.DEV) - return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}` - - return window.location.origin + return defaultServerUrl } return ( - + diff --git a/packages/app/src/components/dialog-create-project.tsx b/packages/app/src/components/dialog-create-project.tsx new file mode 100644 index 00000000000..66c5500d2d0 --- /dev/null +++ b/packages/app/src/components/dialog-create-project.tsx @@ -0,0 +1,673 @@ +import { Component, createSignal, createMemo, Show } from "solid-js" +import { createStore } from "solid-js/store" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { useGlobalSDK } from "@/context/global-sdk" +import { useLayout } from "@/context/layout" +import { Dialog } from "@opencode-ai/ui/dialog" +import { TextField } from "@opencode-ai/ui/text-field" +import { Switch } from "@opencode-ai/ui/switch" +import { Button } from "@opencode-ai/ui/button" +import { Spinner } from "@opencode-ai/ui/spinner" +import { showToast } from "@opencode-ai/ui/toast" +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 { useGlobalSync } from "@/context/global-sync" +import { List } from "@opencode-ai/ui/list" + +interface DirectoryInfo { + path: string + name: string + isGitRepo: boolean + isExistingProject: boolean +} + +// Helper to validate project name (no path separators, no traversal) +function validateProjectName(name: string): string | undefined { + if (!name.trim()) return "Project name is required" + if (name.includes("/") || name.includes("\\")) return "Project name cannot contain path separators" + if (name === "." || name === "..") return "Invalid project name" + if (name.includes("..")) return "Project name cannot contain path traversal" + return undefined +} + +// Helper to derive folder name from repo URL +function deriveFolderNameFromRepo(repoUrl: string): string { + if (!repoUrl.trim()) return "" + // Remove trailing slashes, .git suffix, and get the last path segment + const cleaned = repoUrl.trim().replace(/\/+$/, "").replace(/\.git$/, "") + const parts = cleaned.split("/") + const lastPart = parts[parts.length - 1] || "" + // Remove any remaining path separators from the derived name + return lastPart.replace(/[/\\]/g, "") +} + +// Helper to compute resolved path +function computeResolvedPath(parentDir: string, projectName: string): string { + if (!parentDir || !projectName.trim()) return "" + // Normalize parent dir (remove trailing slash) + const normalizedParent = parentDir.replace(/\/+$/, "") + return `${normalizedParent}/${projectName.trim()}` +} + +export const DialogCreateProject: Component = () => { + const dialog = useDialog() + const globalSDK = useGlobalSDK() + const layout = useLayout() + const navigate = useNavigate() + const platform = usePlatform() + const sync = useGlobalSync() + + const [activeTab, setActiveTab] = createSignal<"existing" | "create" | "clone">("existing") + const [selectedDir, setSelectedDir] = createSignal(null) + + const [store, setStore] = createStore({ + // Common + error: undefined as string | undefined, + loading: false, + // Create New flow + createParentDir: "", + createProjectName: "", + // Clone flow + cloneRepoUrl: "", + cloneParentDir: "", + cloneProjectName: "", // Optional, derived from repo if empty + cloneDegit: false, + }) + + const homedir = createMemo(() => sync.data.path.home || "~") + + // Computed resolved paths + const createResolvedPath = createMemo(() => computeResolvedPath(store.createParentDir, store.createProjectName)) + + const cloneDerivedName = createMemo(() => { + if (store.cloneProjectName.trim()) return store.cloneProjectName.trim() + return deriveFolderNameFromRepo(store.cloneRepoUrl) + }) + + const cloneResolvedPath = createMemo(() => computeResolvedPath(store.cloneParentDir, cloneDerivedName())) + + // Validation for Create New + const createNameError = createMemo(() => validateProjectName(store.createProjectName)) + const createPathError = createMemo(() => { + const path = createResolvedPath() + if (!path) return undefined + if (!path.startsWith("/") && !path.startsWith("~")) return "Path must be absolute" + return undefined + }) + + // Validation for Clone + const cloneRepoError = createMemo(() => { + if (!store.cloneRepoUrl.trim()) return "Repository URL is required" + return undefined + }) + const cloneNameError = createMemo(() => { + const name = cloneDerivedName() + if (!name) return "Project name is required (enter manually or provide valid repo URL)" + return validateProjectName(name) + }) + const clonePathError = createMemo(() => { + const path = cloneResolvedPath() + if (!path) return undefined + if (!path.startsWith("/") && !path.startsWith("~")) return "Path must be absolute" + return undefined + }) + + // Fetch directories for browsing - returns a function for the List component + async function fetchDirectories(query: string): Promise { + const result = await globalSDK.client.project.browse({ + query: query || undefined, + limit: 50, + }) + if (result.error) return [] + return (result.data as DirectoryInfo[]) || [] + } + + function openProject(directory: string) { + layout.projects.open(directory) + navigate(`/${base64Encode(directory)}`) + } + + async function handleCreateSubmit(e: SubmitEvent) { + e.preventDefault() + + // Validate + const nameError = createNameError() + if (nameError) { + setStore("error", nameError) + return + } + if (!store.createParentDir) { + setStore("error", "Parent directory is required") + return + } + const pathError = createPathError() + if (pathError) { + setStore("error", pathError) + return + } + + const resolvedPath = createResolvedPath() + if (!resolvedPath) { + setStore("error", "Could not resolve project path") + return + } + + setStore("error", undefined) + setStore("loading", true) + + try { + const result = await globalSDK.client.project.create({ + path: resolvedPath, + name: store.createProjectName.trim(), + }) + + if (result.error) { + const errorMessage = (result.error as { message?: string }).message || "Failed to create project" + setStore("error", errorMessage) + setStore("loading", false) + return + } + + const { project, created } = result.data! + dialog.close() + openProject(project.worktree) + + showToast({ + variant: "success", + icon: "circle-check", + title: created ? "Project created" : "Project added", + description: created + ? `Created project at ${project.worktree.replace(homedir(), "~")}` + : `Added ${project.worktree.replace(homedir(), "~")}`, + }) + } catch (e: unknown) { + const errorMessage = e instanceof Error ? e.message : "Failed to create project" + setStore("error", errorMessage) + setStore("loading", false) + } + } + + async function handleCloneSubmit(e: SubmitEvent) { + e.preventDefault() + + // Validate + const repoError = cloneRepoError() + if (repoError) { + setStore("error", repoError) + return + } + if (!store.cloneParentDir) { + setStore("error", "Parent directory is required") + return + } + const nameError = cloneNameError() + if (nameError) { + setStore("error", nameError) + return + } + const pathError = clonePathError() + if (pathError) { + setStore("error", pathError) + return + } + + const resolvedPath = cloneResolvedPath() + if (!resolvedPath) { + setStore("error", "Could not resolve project path") + return + } + + setStore("error", undefined) + setStore("loading", true) + + try { + const result = await globalSDK.client.project.create({ + path: resolvedPath, + repo: store.cloneRepoUrl.trim(), + degit: store.cloneDegit, + name: cloneDerivedName(), + }) + + if (result.error) { + const errorMessage = (result.error as { message?: string }).message || "Failed to clone repository" + setStore("error", errorMessage) + setStore("loading", false) + return + } + + const { project, created } = result.data! + dialog.close() + openProject(project.worktree) + + showToast({ + variant: "success", + icon: "circle-check", + title: created ? "Repository cloned" : "Project added", + description: created + ? `Cloned to ${project.worktree.replace(homedir(), "~")}` + : `Added ${project.worktree.replace(homedir(), "~")}`, + }) + } catch (e: unknown) { + const errorMessage = e instanceof Error ? e.message : "Failed to clone repository" + setStore("error", errorMessage) + setStore("loading", false) + } + } + + async function handleAddExisting(dir?: DirectoryInfo | null) { + const directory = dir ?? selectedDir() + if (!directory) return + + setStore("loading", true) + setStore("error", undefined) + + try { + // Use create endpoint - it handles existing directories gracefully + const result = await globalSDK.client.project.create({ path: directory.path }) + + if (result.error) { + const errorMessage = (result.error as { message?: string }).message || "Failed to add project" + setStore("error", errorMessage) + setStore("loading", false) + return + } + + const { project } = result.data! + dialog.close() + openProject(project.worktree) + + showToast({ + variant: "success", + icon: "circle-check", + title: "Project added", + description: `Added ${project.worktree.replace(homedir(), "~")}`, + }) + } catch (e: unknown) { + const errorMessage = e instanceof Error ? e.message : "Failed to add project" + setStore("error", errorMessage) + setStore("loading", false) + } + } + + async function handleBrowseExisting() { + const result = await platform.openDirectoryPickerDialog?.({ + title: "Select folder to add as project", + multiple: false, + }) + if (result && typeof result === "string") { + // Directly add the selected directory + setStore("loading", true) + try { + const createResult = await globalSDK.client.project.create({ path: result }) + if (createResult.error) { + const errorMessage = (createResult.error as { message?: string }).message || "Failed to add project" + setStore("error", errorMessage) + setStore("loading", false) + return + } + const { project } = createResult.data! + dialog.close() + openProject(project.worktree) + showToast({ + variant: "success", + icon: "circle-check", + title: "Project added", + description: `Added ${project.worktree.replace(homedir(), "~")}`, + }) + } catch (e: unknown) { + const errorMessage = e instanceof Error ? e.message : "Failed to add project" + setStore("error", errorMessage) + setStore("loading", false) + } + } + } + + async function handleBrowseCreateParent() { + const result = await platform.openDirectoryPickerDialog?.({ + title: "Select parent directory for new project", + multiple: false, + }) + if (result && typeof result === "string") { + setStore("createParentDir", result) + } + } + + async function handleBrowseCloneParent() { + const result = await platform.openDirectoryPickerDialog?.({ + title: "Select parent directory for cloned repository", + multiple: false, + }) + if (result && typeof result === "string") { + setStore("cloneParentDir", result) + } + } + + function handleSelect(dir: DirectoryInfo | undefined) { + if (!dir) return + if (dir.isExistingProject) { + dialog.close() + openProject(dir.path) + return + } + setSelectedDir(dir) + // Immediately add the project on selection + handleAddExisting(dir) + } + + return ( + +
+ {/* Tab switcher */} +
+ + + +
+ + {/* Add Existing tab content */} + +
+
+ Search for an existing folder to add as a project, or browse your filesystem. +
+ + {/* Directory list with search */} + + class="flex-1 min-h-0 [&_[data-slot=list-item]]:h-auto [&_[data-slot=list-item]]:py-1" + items={fetchDirectories} + key={(dir) => dir.path} + filterKeys={["name", "path"]} + current={selectedDir() ?? undefined} + onSelect={handleSelect} + search={{ placeholder: "Search folders...", autofocus: true }} + emptyMessage="No folders found" + > + {(dir) => ( +
+ + {dir.path.replace(homedir(), "~")} + + git + + + open + +
+ )} + + + {/* Browse button */} + + + + + +
{store.error}
+
+ + +
+ +
+
+
+
+ + {/* Create New tab content */} + +
+
+ Select a parent directory and enter a name for your new project. A new folder will be created and + initialized as a git repository. +
+ + {/* Parent directory browser */} +
+ + +
+ + + {store.createParentDir.replace(homedir(), "~")} + + +
+
+ + + class="flex-1 min-h-0 max-h-40 [&_[data-slot=list-item]]:h-auto [&_[data-slot=list-item]]:py-1" + items={fetchDirectories} + key={(dir) => dir.path} + filterKeys={["name", "path"]} + onSelect={(dir) => dir && setStore("createParentDir", dir.path)} + search={{ placeholder: "Search folders...", autofocus: activeTab() === "create" }} + emptyMessage="No folders found" + > + {(dir) => ( +
+ + {dir.path.replace(homedir(), "~")} +
+ )} + + + + +
+
+ + {/* Project name */} + setStore("createProjectName", value)} + validationState={createNameError() ? "invalid" : undefined} + error={createNameError()} + /> + + {/* Resolved path preview */} + +
+ +
+ {createResolvedPath().replace(homedir(), "~")} +
+
+
+ + +
{store.error}
+
+ +
+ + +
+ +
+ + {/* Git Clone tab content */} + +
+
+ Clone a git repository into a new project folder. +
+ + {/* Repository URL */} + setStore("cloneRepoUrl", value)} + /> + + {/* Parent directory browser */} +
+ + +
+ + + {store.cloneParentDir.replace(homedir(), "~")} + + +
+
+ + + class="flex-1 min-h-0 max-h-40 [&_[data-slot=list-item]]:h-auto [&_[data-slot=list-item]]:py-1" + items={fetchDirectories} + key={(dir) => dir.path} + filterKeys={["name", "path"]} + onSelect={(dir) => dir && setStore("cloneParentDir", dir.path)} + search={{ placeholder: "Search folders..." }} + emptyMessage="No folders found" + > + {(dir) => ( +
+ + {dir.path.replace(homedir(), "~")} +
+ )} + + + + +
+
+ + {/* Project name (optional, derived from repo) */} + setStore("cloneProjectName", value)} + validationState={store.cloneProjectName.trim() && cloneNameError() ? "invalid" : undefined} + error={store.cloneProjectName.trim() ? cloneNameError() : undefined} + /> + + {/* Resolved path preview */} + +
+ +
+ {cloneResolvedPath().replace(homedir(), "~")} +
+
+
+ + {/* Degit toggle */} +
+ setStore("cloneDegit", checked)}> + Degit (remove .git history after cloning) + +
+ + +
{store.error}
+
+ +
+ + +
+ +
+
+
+ ) +} diff --git a/packages/app/src/components/dialog-custom-provider.tsx b/packages/app/src/components/dialog-custom-provider.tsx new file mode 100644 index 00000000000..28a947f3b30 --- /dev/null +++ b/packages/app/src/components/dialog-custom-provider.tsx @@ -0,0 +1,424 @@ +import { Button } from "@opencode-ai/ui/button" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { Dialog } from "@opencode-ai/ui/dialog" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { ProviderIcon } from "@opencode-ai/ui/provider-icon" +import { TextField } from "@opencode-ai/ui/text-field" +import { showToast } from "@opencode-ai/ui/toast" +import { For } 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 { useLanguage } from "@/context/language" +import { DialogSelectProvider } from "./dialog-select-provider" + +const PROVIDER_ID = /^[a-z0-9][a-z0-9-_]*$/ +const OPENAI_COMPATIBLE = "@ai-sdk/openai-compatible" + +type Props = { + back?: "providers" | "close" +} + +export function DialogCustomProvider(props: Props) { + const dialog = useDialog() + const globalSync = useGlobalSync() + const globalSDK = useGlobalSDK() + const language = useLanguage() + + const [form, setForm] = createStore({ + providerID: "", + name: "", + baseURL: "", + apiKey: "", + models: [{ id: "", name: "" }], + headers: [{ key: "", value: "" }], + saving: false, + }) + + const [errors, setErrors] = createStore({ + providerID: undefined as string | undefined, + name: undefined as string | undefined, + baseURL: undefined as string | undefined, + models: [{} as { id?: string; name?: string }], + headers: [{} as { key?: string; value?: string }], + }) + + const goBack = () => { + if (props.back === "close") { + dialog.close() + return + } + dialog.show(() => ) + } + + const addModel = () => { + setForm( + "models", + produce((draft) => { + draft.push({ id: "", name: "" }) + }), + ) + setErrors( + "models", + produce((draft) => { + draft.push({}) + }), + ) + } + + const removeModel = (index: number) => { + if (form.models.length <= 1) return + setForm( + "models", + produce((draft) => { + draft.splice(index, 1) + }), + ) + setErrors( + "models", + produce((draft) => { + draft.splice(index, 1) + }), + ) + } + + const addHeader = () => { + setForm( + "headers", + produce((draft) => { + draft.push({ key: "", value: "" }) + }), + ) + setErrors( + "headers", + produce((draft) => { + draft.push({}) + }), + ) + } + + const removeHeader = (index: number) => { + if (form.headers.length <= 1) return + setForm( + "headers", + produce((draft) => { + draft.splice(index, 1) + }), + ) + setErrors( + "headers", + produce((draft) => { + draft.splice(index, 1) + }), + ) + } + + const validate = () => { + const providerID = form.providerID.trim() + const name = form.name.trim() + const baseURL = form.baseURL.trim() + const apiKey = form.apiKey.trim() + + const env = apiKey.match(/^\{env:([^}]+)\}$/)?.[1]?.trim() + const key = apiKey && !env ? apiKey : undefined + + const idError = !providerID + ? "Provider ID is required" + : !PROVIDER_ID.test(providerID) + ? "Use lowercase letters, numbers, hyphens, or underscores" + : undefined + + const nameError = !name ? "Display name is required" : undefined + const urlError = !baseURL + ? "Base URL is required" + : !/^https?:\/\//.test(baseURL) + ? "Must start with http:// or https://" + : undefined + + const disabled = (globalSync.data.config.disabled_providers ?? []).includes(providerID) + const existingProvider = globalSync.data.provider.all.find((p) => p.id === providerID) + const existsError = idError + ? undefined + : existingProvider && !disabled + ? "That provider ID already exists" + : undefined + + const seenModels = new Set() + const modelErrors = form.models.map((m) => { + const id = m.id.trim() + const modelIdError = !id + ? "Required" + : seenModels.has(id) + ? "Duplicate" + : (() => { + seenModels.add(id) + return undefined + })() + const modelNameError = !m.name.trim() ? "Required" : undefined + return { id: modelIdError, name: modelNameError } + }) + const modelsValid = modelErrors.every((m) => !m.id && !m.name) + const models = Object.fromEntries(form.models.map((m) => [m.id.trim(), { name: m.name.trim() }])) + + const seenHeaders = new Set() + const headerErrors = form.headers.map((h) => { + const key = h.key.trim() + const value = h.value.trim() + + if (!key && !value) return {} + const keyError = !key + ? "Required" + : seenHeaders.has(key.toLowerCase()) + ? "Duplicate" + : (() => { + seenHeaders.add(key.toLowerCase()) + return undefined + })() + const valueError = !value ? "Required" : undefined + return { key: keyError, value: valueError } + }) + const headersValid = headerErrors.every((h) => !h.key && !h.value) + const headers = Object.fromEntries( + form.headers + .map((h) => ({ key: h.key.trim(), value: h.value.trim() })) + .filter((h) => !!h.key && !!h.value) + .map((h) => [h.key, h.value]), + ) + + setErrors( + produce((draft) => { + draft.providerID = idError ?? existsError + draft.name = nameError + draft.baseURL = urlError + draft.models = modelErrors + draft.headers = headerErrors + }), + ) + + const ok = !idError && !existsError && !nameError && !urlError && modelsValid && headersValid + if (!ok) return + + const options = { + baseURL, + ...(Object.keys(headers).length ? { headers } : {}), + } + + return { + providerID, + name, + key, + config: { + npm: OPENAI_COMPATIBLE, + name, + ...(env ? { env: [env] } : {}), + options, + models, + }, + } + } + + const save = async (e: SubmitEvent) => { + e.preventDefault() + if (form.saving) return + + const result = validate() + if (!result) return + + setForm("saving", true) + + const disabledProviders = globalSync.data.config.disabled_providers ?? [] + const nextDisabled = disabledProviders.filter((id) => id !== result.providerID) + + const auth = result.key + ? globalSDK.client.auth.set({ + providerID: result.providerID, + auth: { + type: "api", + key: result.key, + }, + }) + : Promise.resolve() + + auth + .then(() => + globalSync.updateConfig({ provider: { [result.providerID]: result.config }, disabled_providers: nextDisabled }), + ) + .then(() => { + dialog.close() + showToast({ + variant: "success", + icon: "circle-check", + title: language.t("provider.connect.toast.connected.title", { provider: result.name }), + description: language.t("provider.connect.toast.connected.description", { provider: result.name }), + }) + }) + .catch((err: unknown) => { + const message = err instanceof Error ? err.message : String(err) + showToast({ title: language.t("common.requestFailed"), description: message }) + }) + .finally(() => { + setForm("saving", false) + }) + } + + return ( + + } + transition + > +
+
+ +
Custom provider
+
+ +
+

+ Configure an OpenAI-compatible provider. See the{" "} + + provider config docs + + . +

+ +
+ + + + +
+ +
+ + + {(m, i) => ( +
+
+ setForm("models", i(), "id", v)} + validationState={errors.models[i()]?.id ? "invalid" : undefined} + error={errors.models[i()]?.id} + /> +
+
+ setForm("models", i(), "name", v)} + validationState={errors.models[i()]?.name ? "invalid" : undefined} + error={errors.models[i()]?.name} + /> +
+ removeModel(i())} + disabled={form.models.length <= 1} + aria-label="Remove model" + /> +
+ )} +
+ +
+ +
+ + + {(h, i) => ( +
+
+ setForm("headers", i(), "key", v)} + validationState={errors.headers[i()]?.key ? "invalid" : undefined} + error={errors.headers[i()]?.key} + /> +
+
+ setForm("headers", i(), "value", v)} + validationState={errors.headers[i()]?.value ? "invalid" : undefined} + error={errors.headers[i()]?.value} + /> +
+ removeHeader(i())} + disabled={form.headers.length <= 1} + aria-label="Remove header" + /> +
+ )} +
+ +
+ + +
+
+
+ ) +} diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index 5c58725c75b..64b83d31bf0 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -44,7 +44,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil "session.previous", "session.next", "terminal.toggle", - "fileTree.toggle", + "review.toggle", ] const limit = 5 @@ -162,6 +162,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil const value = file.tab(path) tabs().open(value) file.load(path) + layout.fileTree.open() layout.fileTree.setTab("all") props.onOpenFile?.(path) } @@ -195,7 +196,6 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil : language.t("palette.search.placeholder"), autofocus: true, hideIcon: true, - class: "pl-3 pr-2 !mb-0", }} emptyMessage={language.t("palette.empty")} loadingMessage={language.t("common.loading")} @@ -223,7 +223,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil } > -
+
{item.title} diff --git a/packages/app/src/components/dialog-select-model.tsx b/packages/app/src/components/dialog-select-model.tsx index e927ae4fd15..4f0dcc3ee65 100644 --- a/packages/app/src/components/dialog-select-model.tsx +++ b/packages/app/src/components/dialog-select-model.tsx @@ -187,7 +187,7 @@ export function ModelSelectorPopover(props: { setStore("content", el)} - class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden" + class="w-72 h-80 flex flex-col p-2 rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden" onEscapeKeyDown={(event) => { setStore("dismiss", "escape") setStore("open", false) diff --git a/packages/app/src/components/dialog-select-provider.tsx b/packages/app/src/components/dialog-select-provider.tsx index 5933bff1973..20793042d3b 100644 --- a/packages/app/src/components/dialog-select-provider.tsx +++ b/packages/app/src/components/dialog-select-provider.tsx @@ -5,9 +5,17 @@ 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 { iconNames, type IconName } from "@opencode-ai/ui/icons/provider" import { DialogConnectProvider } from "./dialog-connect-provider" import { useLanguage } from "@/context/language" +import { DialogCustomProvider } from "./dialog-custom-provider" + +const CUSTOM_ID = "_custom" + +function icon(id: string): IconName { + if (iconNames.includes(id as IconName)) return id as IconName + return "synthetic" +} export const DialogSelectProvider: Component = () => { const dialog = useDialog() @@ -26,11 +34,13 @@ export const DialogSelectProvider: Component = () => { key={(x) => x?.id} items={() => { language.locale() - return providers.all() + return [{ id: CUSTOM_ID, name: "Custom provider" }, ...providers.all()] }} filterKeys={["id", "name"]} groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())} sortBy={(a, b) => { + if (a.id === CUSTOM_ID) return -1 + if (b.id === CUSTOM_ID) return 1 if (popularProviders.includes(a.id) && popularProviders.includes(b.id)) return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id) return a.name.localeCompare(b.name) @@ -43,13 +53,20 @@ export const DialogSelectProvider: Component = () => { }} onSelect={(x) => { if (!x) return + if (x.id === CUSTOM_ID) { + dialog.show(() => ) + return + } dialog.show(() => ) }} > {(i) => ( -
- - {i.name} +
+ + {i.name} + + {language.t("settings.providers.tag.custom")} + {language.t("dialog.provider.tag.recommended")} diff --git a/packages/app/src/components/dialog-session-rename-global.tsx b/packages/app/src/components/dialog-session-rename-global.tsx new file mode 100644 index 00000000000..7df5a9f6b29 --- /dev/null +++ b/packages/app/src/components/dialog-session-rename-global.tsx @@ -0,0 +1,67 @@ +import { Component, createEffect } from "solid-js" +import { createStore } from "solid-js/store" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { Dialog } from "@opencode-ai/ui/dialog" +import { TextField } from "@opencode-ai/ui/text-field" +import { Button } from "@opencode-ai/ui/button" +import { useGlobalSDK } from "@/context/global-sdk" +import type { Session } from "@opencode-ai/sdk/v2/client" + +interface DialogSessionRenameGlobalProps { + session: Session +} + +export const DialogSessionRenameGlobal: Component = (props) => { + const dialog = useDialog() + const globalSDK = useGlobalSDK() + + const [store, setStore] = createStore({ + value: props.session.title ?? "", + error: undefined as string | undefined, + }) + + // Prefill with session title on mount + createEffect(() => { + if (props.session.title && !store.value) { + setStore("value", props.session.title) + } + }) + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + const trimmed = store.value.trim() + + if (!trimmed) { + setStore("error", "Session name is required") + return + } + + await globalSDK.client.session.update({ + directory: props.session.directory, + sessionID: props.session.id, + title: trimmed, + }) + dialog.close() + } + + return ( + +
+ setStore({ value, error: undefined })} + validationState={store.error ? "invalid" : undefined} + error={store.error} + /> +
+ + +
+ +
+ ) +} diff --git a/packages/app/src/components/dialog-session-rename.tsx b/packages/app/src/components/dialog-session-rename.tsx new file mode 100644 index 00000000000..0cdeea18bbb --- /dev/null +++ b/packages/app/src/components/dialog-session-rename.tsx @@ -0,0 +1,69 @@ +import { Component, createMemo, createEffect } from "solid-js" +import { createStore } from "solid-js/store" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { Dialog } from "@opencode-ai/ui/dialog" +import { TextField } from "@opencode-ai/ui/text-field" +import { Button } from "@opencode-ai/ui/button" +import { useSDK } from "@/context/sdk" +import { useSync } from "@/context/sync" + +interface DialogSessionRenameProps { + sessionID: string +} + +export const DialogSessionRename: Component = (props) => { + const dialog = useDialog() + const sdk = useSDK() + const sync = useSync() + + const session = createMemo(() => sync.session.get(props.sessionID)) + const [store, setStore] = createStore({ + value: "", + error: undefined as string | undefined, + }) + + // Prefill the input when session data becomes available + createEffect(() => { + const s = session() + if (s?.title && !store.value) { + setStore("value", s.title) + } + }) + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + const trimmed = store.value.trim() + + if (!trimmed) { + setStore("error", "Session name is required") + return + } + + await sdk.client.session.update({ + sessionID: props.sessionID, + title: trimmed, + }) + dialog.close() + } + + return ( + +
+ setStore({ value, error: undefined })} + validationState={store.error ? "invalid" : undefined} + error={store.error} + /> +
+ + +
+ +
+ ) +} diff --git a/packages/app/src/components/header.tsx b/packages/app/src/components/header.tsx new file mode 100644 index 00000000000..9e85ec01a40 --- /dev/null +++ b/packages/app/src/components/header.tsx @@ -0,0 +1,223 @@ +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 { AsciiLogo, AsciiMark } 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} + fallback={ +
+ + v{platform.version} + + + + + + +
+
{props.children}
+
+ ) +} diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index e3831c70fe5..fe744ddc242 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -6,6 +6,7 @@ import { Icon } from "@opencode-ai/ui/icon" import { Button } from "@opencode-ai/ui/button" import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" import { useTheme } from "@opencode-ai/ui/theme" +import { AsciiMark } from "@opencode-ai/ui/logo" import { useLayout } from "@/context/layout" import { usePlatform } from "@/context/platform" @@ -144,6 +145,11 @@ export function Titlebar() { >
+ +
+ +
+
{ + 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 interface WelcomeScreenProps { + attemptedUrl?: string + onRetry?: () => void +} + +export function WelcomeScreen(props: WelcomeScreenProps) { + const server = useServer() + const platform = usePlatform() + const [store, setStore] = createStore({ + url: "", + connecting: false, + error: "", + status: {} as Record, + }) + + const urlOverride = getUrlQueryParam() + const isLocalhost = () => { + const url = props.attemptedUrl || "" + return url.includes("localhost") || url.includes("127.0.0.1") + } + + const items = createMemo(() => { + const list = server.list + return list.filter((x) => x !== props.attemptedUrl) + }) + + 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(() => { + if (items().length === 0) return + refreshHealth() + const interval = setInterval(refreshHealth, 10_000) + onCleanup(() => clearInterval(interval)) + }) + + async function handleConnect(url: string, persist = false) { + const normalized = normalizeServerUrl(url) + if (!normalized) return + + setStore("connecting", true) + setStore("error", "") + + const result = await checkHealth(normalized, platform.fetch) + setStore("connecting", false) + + if (!result.healthy) { + setStore("error", "Could not connect to server") + return + } + + if (persist) { + server.add(normalized) + } else { + server.setActive(normalized) + } + props.onRetry?.() + } + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + const value = normalizeServerUrl(store.url) + if (!value) return + await handleConnect(value, true) + } + + return ( +
+
+ + +
+

Welcome to Shuvcode

+

+ {urlOverride + ? `Could not connect to the server at ${urlOverride}` + : "Connect to a Shuvcode server to get started"} +

+
+ + {/* Local Server Section */} +
+
+ +

Local Server

+
+ + +
+

Start a local server by running:

+ shuvcode +

or

+ npx shuvcode +
+
+ + +
+ + {/* Remote Server Section */} +
+
+ +

Remote Server

+
+ +
+
+
+ { + setStore("url", v) + setStore("error", "") + }} + validationState={store.error ? "invalid" : "valid"} + error={store.error} + /> +
+ +
+
+ +

+ Note: Connecting to a remote server means trusting that server with your data. +

+
+ + {/* Saved Servers Section */} + 0}> +
+

Saved Servers

+
+ + {(url) => ( + + )} + +
+
+
+ + {/* Troubleshooting Section */} + +
+ Troubleshooting +
+

+ Server not running: Make sure you have a Shuvcode server running locally or accessible + remotely. +

+

+ CORS blocked: The server must allow requests from{" "} + {location.origin}. Local servers automatically allow + this domain. +

+

+ Mixed content: If connecting to an http:// server from this{" "} + https:// page, your browser may block the connection. Use https:// for remote + servers. +

+
+
+
+ + +

Version: {platform.version}

+
+
+
+ ) +} diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index fb67193ab66..fe826e63f2d 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -7,6 +7,8 @@ import { type Path, type Project, type FileDiff, + type File, + type FileNode, type Todo, type SessionStatus, type ProviderListResponse, @@ -23,7 +25,8 @@ import { createStore, produce, reconcile, type SetStoreFunction, type Store } fr import { Binary } from "@opencode-ai/util/binary" import { retry } from "@opencode-ai/util/retry" import { useGlobalSDK } from "./global-sdk" -import type { InitError } from "../pages/error" +import { ErrorPage, type InitError } from "../pages/error" +import { WelcomeScreen } from "../components/welcome-screen" import { batch, createContext, @@ -41,6 +44,7 @@ import { } from "solid-js" import { showToast } from "@opencode-ai/ui/toast" import { getFilename } from "@opencode-ai/util/path" +import { isHostedEnvironment } from "@/utils/hosted" import { usePlatform } from "./platform" import { useLanguage } from "@/context/language" import { Persist, persisted } from "@/utils/persist" @@ -77,6 +81,8 @@ type State = { todo: { [sessionID: string]: Todo[] } + changes: File[] + node: FileNode[] permission: { [sessionID: string]: PermissionRequest[] } @@ -97,6 +103,8 @@ type State = { } } +type ConnectionState = "connecting" | "ready" | "needs_config" | "error" + type VcsCache = { store: Store<{ value: VcsInfo | undefined }> setStore: SetStoreFunction<{ value: VcsInfo | undefined }> @@ -171,8 +179,10 @@ function createGlobalSync() { } } const [globalStore, setGlobalStore] = createStore<{ + connectionState: ConnectionState ready: boolean error?: InitError + attemptedUrl?: string path: Path project: Project[] provider: ProviderListResponse @@ -180,7 +190,9 @@ function createGlobalSync() { config: Config reload: undefined | "pending" | "complete" }>({ + connectionState: "connecting", ready: false, + attemptedUrl: undefined, path: { state: "", config: "", worktree: "", directory: "", home: "" }, project: projectCache.value, provider: { all: [], connected: [], default: {} }, @@ -188,7 +200,74 @@ function createGlobalSync() { config: {}, reload: undefined, }) - let bootstrapQueue: string[] = [] + + const queued = new Set() + let root = false + let running = false + let timer: ReturnType | undefined + + const paused = () => untrack(() => globalStore.reload) !== undefined + + const tick = () => new Promise((resolve) => setTimeout(resolve, 0)) + + const take = (count: number) => { + if (queued.size === 0) return [] as string[] + const items: string[] = [] + for (const item of queued) { + queued.delete(item) + items.push(item) + if (items.length >= count) break + } + return items + } + + const schedule = () => { + if (timer) return + timer = setTimeout(() => { + timer = undefined + void drain() + }, 0) + } + + const push = (directory: string) => { + if (!directory) return + queued.add(directory) + if (paused()) return + schedule() + } + + const refresh = () => { + root = true + if (paused()) return + schedule() + } + + async function drain() { + if (running) return + running = true + try { + while (true) { + if (paused()) return + + if (root) { + root = false + await bootstrap() + await tick() + continue + } + + const dirs = take(2) + if (dirs.length === 0) return + + await Promise.all(dirs.map((dir) => bootstrapInstance(dir))) + await tick() + } + } finally { + running = false + if (paused()) return + if (root || queued.size) schedule() + } + } createEffect(() => { if (!projectCacheReady()) return @@ -210,14 +289,8 @@ function createGlobalSync() { createEffect(() => { if (globalStore.reload !== "complete") return - if (bootstrapQueue.length) { - for (const directory of bootstrapQueue) { - bootstrapInstance(directory) - } - bootstrap() - } - bootstrapQueue = [] setGlobalStore("reload", undefined) + refresh() }) const children: Record, SetStoreFunction]> = {} @@ -332,6 +405,8 @@ function createGlobalSync() { session_status: {}, session_diff: {}, todo: {}, + changes: [], + node: [], permission: {}, question: {}, mcp: {}, @@ -352,11 +427,17 @@ function createGlobalSync() { }) createEffect(() => { - child[1]("projectMeta", meta[0].value) + const newValue = meta[0].value + // Avoid reactive loops by skipping write if value unchanged + if (JSON.stringify(child[0].projectMeta) === JSON.stringify(newValue)) return + child[1]("projectMeta", newValue) }) createEffect(() => { - child[1]("icon", icon[0].value) + const newValue = icon[0].value + // Avoid reactive loops by skipping write if value unchanged + if (child[0].icon === newValue) return + child[1]("icon", newValue) }) } @@ -393,7 +474,8 @@ function createGlobalSync() { const promise = globalSDK.client.session .list({ directory, roots: true }) .then((x) => { - const nonArchived = (x.data ?? []) + const data = Array.isArray(x.data) ? x.data : [] + const nonArchived = data .filter((s) => !!s?.id) .filter((s) => !s.time?.archived) .sort((a, b) => a.id.localeCompare(b.id)) @@ -584,9 +666,8 @@ function createGlobalSync() { if (directory === "global") { switch (event?.type) { case "global.disposed": { - if (globalStore.reload) return - bootstrap() - break + refresh() + return } case "project.updated": { const result = Binary.search(globalStore.project, event.properties.id, (s) => s.id) @@ -647,12 +728,8 @@ function createGlobalSync() { switch (event.type) { case "server.instance.disposed": { - if (globalStore.reload) { - bootstrapQueue.push(directory) - return - } - bootstrapInstance(directory) - break + push(directory) + return } case "session.created": { const info = event.properties.info @@ -893,36 +970,66 @@ function createGlobalSync() { } }) onCleanup(unsub) + onCleanup(() => { + if (!timer) return + clearTimeout(timer) + }) + + /** + * Probes the server health with a short timeout (2 seconds). + * Used for initial connection to provide quick feedback. + */ + async function probeHealth( + url: string, + healthFn: () => Promise<{ data?: { healthy?: boolean } }>, + ): Promise<{ healthy: boolean }> { + try { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 2000) + + const result = await healthFn() + clearTimeout(timeoutId) + + return { healthy: result.data?.healthy === true } + } catch { + return { healthy: false } + } + } async function bootstrap() { - const health = await globalSDK.client.global - .health() - .then((x) => x.data) - .catch(() => undefined) - if (!health?.healthy) { - showToast({ - variant: "error", - title: language.t("dialog.server.add.error"), - description: language.t("error.globalSync.connectFailed", { url: globalSDK.url }), - }) - setGlobalStore("ready", true) + setGlobalStore("connectionState", "connecting") + setGlobalStore("attemptedUrl", globalSDK.url) + + // Use a short timeout for the health probe (2 seconds) + const probeResult = await probeHealth(globalSDK.url, () => globalSDK.client.global.health()) + + if (!probeResult.healthy) { + // For hosted environments, show the welcome/configuration screen + if (isHostedEnvironment()) { + setGlobalStore("connectionState", "needs_config") + return + } + // For non-hosted environments, show the error page + setGlobalStore("error", new Error(language.t("error.globalSync.connectFailed", { url: globalSDK.url }))) + setGlobalStore("connectionState", "error") return } - const tasks = [ + return Promise.all([ retry(() => globalSDK.client.path.get().then((x) => { setGlobalStore("path", x.data!) }), ), retry(() => - globalSDK.client.config.get().then((x) => { + globalSDK.client.global.config.get().then((x) => { setGlobalStore("config", x.data!) }), ), retry(() => globalSDK.client.project.list().then(async (x) => { - const projects = (x.data ?? []) + const data = Array.isArray(x.data) ? x.data : [] + const projects = data .filter((p) => !!p?.id) .filter((p) => !!p.worktree && !p.worktree.includes("opencode-test")) .slice() @@ -940,22 +1047,15 @@ function createGlobalSync() { setGlobalStore("provider_auth", x.data ?? {}) }), ), - ] - - const results = await Promise.allSettled(tasks) - const errors = results.filter((r): r is PromiseRejectedResult => r.status === "rejected").map((r) => r.reason) - - if (errors.length) { - const message = errors[0] instanceof Error ? errors[0].message : String(errors[0]) - const more = errors.length > 1 ? ` (+${errors.length - 1} more)` : "" - showToast({ - variant: "error", - title: language.t("common.requestFailed"), - description: message + more, + ]) + .then(() => { + setGlobalStore("ready", true) + setGlobalStore("connectionState", "ready") + }) + .catch((e) => { + setGlobalStore("error", e) + setGlobalStore("connectionState", "error") }) - } - - setGlobalStore("ready", true) } onMount(() => { @@ -975,6 +1075,10 @@ function createGlobalSync() { icon, commands, } + // Avoid reactive loops by skipping write if value unchanged + const prevStr = JSON.stringify(store.projectMeta) + const nextStr = JSON.stringify(next) + if (prevStr === nextStr) return cached.setStore("value", next) setStore("projectMeta", next) } @@ -997,15 +1101,21 @@ function createGlobalSync() { get error() { return globalStore.error }, + get connectionState() { + return globalStore.connectionState + }, + get attemptedUrl() { + return globalStore.attemptedUrl + }, child, bootstrap, - updateConfig: async (config: Config) => { + updateConfig: (config: Config) => { setGlobalStore("reload", "pending") - const response = await globalSDK.client.config.update({ config }) - setTimeout(() => { - setGlobalStore("reload", "complete") - }, 1000) - return response + return globalSDK.client.global.config.update({ config }).finally(() => { + setTimeout(() => { + setGlobalStore("reload", "complete") + }, 1000) + }) }, project: { loadSessions, @@ -1021,7 +1131,18 @@ export function GlobalSyncProvider(props: ParentProps) { const value = createGlobalSync() return ( - + +
+
Connecting to server...
+
+
+ + value.bootstrap()} /> + + + + + {props.children}
diff --git a/packages/app/src/context/language.tsx b/packages/app/src/context/language.tsx index da10e4773ae..1b93c9b0515 100644 --- a/packages/app/src/context/language.tsx +++ b/packages/app/src/context/language.tsx @@ -17,6 +17,7 @@ import { dict as ru } from "@/i18n/ru" import { dict as ar } from "@/i18n/ar" import { dict as no } from "@/i18n/no" import { dict as br } from "@/i18n/br" +import { dict as th } from "@/i18n/th" import { dict as uiEn } from "@opencode-ai/ui/i18n/en" import { dict as uiZh } from "@opencode-ai/ui/i18n/zh" import { dict as uiZht } from "@opencode-ai/ui/i18n/zht" @@ -31,13 +32,45 @@ import { dict as uiRu } from "@opencode-ai/ui/i18n/ru" import { dict as uiAr } from "@opencode-ai/ui/i18n/ar" import { dict as uiNo } from "@opencode-ai/ui/i18n/no" import { dict as uiBr } from "@opencode-ai/ui/i18n/br" +import { dict as uiTh } from "@opencode-ai/ui/i18n/th" -export type Locale = "en" | "zh" | "zht" | "ko" | "de" | "es" | "fr" | "da" | "ja" | "pl" | "ru" | "ar" | "no" | "br" +export type Locale = + | "en" + | "zh" + | "zht" + | "ko" + | "de" + | "es" + | "fr" + | "da" + | "ja" + | "pl" + | "ru" + | "ar" + | "no" + | "br" + | "th" type RawDictionary = typeof en & typeof uiEn type Dictionary = i18n.Flatten -const LOCALES: readonly Locale[] = ["en", "zh", "zht", "ko", "de", "es", "fr", "da", "ja", "pl", "ru", "ar", "no", "br"] +const LOCALES: readonly Locale[] = [ + "en", + "zh", + "zht", + "ko", + "de", + "es", + "fr", + "da", + "ja", + "pl", + "ru", + "ar", + "no", + "br", + "th", +] function detectLocale(): Locale { if (typeof navigator !== "object") return "en" @@ -65,6 +98,7 @@ function detectLocale(): Locale { ) return "no" if (language.toLowerCase().startsWith("pt")) return "br" + if (language.toLowerCase().startsWith("th")) return "th" } return "en" @@ -94,6 +128,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont if (store.locale === "ar") return "ar" if (store.locale === "no") return "no" if (store.locale === "br") return "br" + if (store.locale === "th") return "th" return "en" }) @@ -118,6 +153,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont if (locale() === "ar") return { ...base, ...i18n.flatten({ ...ar, ...uiAr }) } if (locale() === "no") return { ...base, ...i18n.flatten({ ...no, ...uiNo }) } if (locale() === "br") return { ...base, ...i18n.flatten({ ...br, ...uiBr }) } + if (locale() === "th") return { ...base, ...i18n.flatten({ ...th, ...uiTh }) } return { ...base, ...i18n.flatten({ ...ko, ...uiKo }) } }) @@ -138,6 +174,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont ar: "language.ar", no: "language.no", br: "language.br", + th: "language.th", } const label = (value: Locale) => t(labelKey[value]) diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index 2ea5f043570..7120fdcef03 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -6,12 +6,25 @@ import { useGlobalSDK } from "./global-sdk" import { useServer } from "./server" import { Project } from "@opencode-ai/sdk/v2" import { Persist, persisted, removePersisted } from "@/utils/persist" +import { applyFontWithLoad } from "@/fonts/apply-font" +import { getFontById, FONTS } from "@/fonts/font-definitions" import { same } from "@/utils/same" import { createScrollPersistence, type SessionScroll } from "./layout-scroll" +export const REVIEW_PANE = { + DEFAULT_WIDTH: 450, + MIN_WIDTH: 200, + MAX_WIDTH_RATIO: 0.5, +} as const + const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const export type AvatarColorKey = (typeof AVATAR_COLOR_KEYS)[number] +type SessionTabs = { + active?: string + all: string[] +} + export function getAvatarColors(key?: string) { if (key && AVATAR_COLOR_KEYS.includes(key as AvatarColorKey)) { return { @@ -25,10 +38,7 @@ export function getAvatarColors(key?: string) { } } -type SessionTabs = { - active?: string - all: string[] -} +type Dialog = "provider" | "model" | "connect" type SessionView = { scroll: Record @@ -37,6 +47,7 @@ type SessionView = { export type LocalProject = Partial & { worktree: string; expanded: boolean } +export type ExpandedSessions = Record export type ReviewDiffStyle = "unified" | "split" export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({ @@ -96,10 +107,13 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( workspacesDefault: false, }, terminal: { - height: 280, opened: false, + height: 280, }, review: { + opened: false, + state: "pane" as "pane" | "tab", + width: REVIEW_PANE.DEFAULT_WIDTH as number, diffStyle: "split" as ReviewDiffStyle, }, fileTree: { @@ -110,11 +124,13 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( session: { width: 600, }, + font: FONTS[0].id, mobileSidebar: { opened: false, }, sessionTabs: {} as Record, sessionView: {} as Record, + expandedSessions: {} as ExpandedSessions, }), ) @@ -402,11 +418,22 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( }) onMount(() => { + // Load project sessions Promise.all( server.projects.list().map((project) => { return globalSync.project.loadSessions(project.worktree) }), ) + + // Normalize persisted review state (ensure opened defaults to false for old/missing state) + if (store.review === undefined || store.review.opened === undefined) { + setStore("review", "opened", false) + } + }) + + createEffect(() => { + const font = getFontById(store.font) ?? FONTS[0] + applyFontWithLoad(font) }) return { @@ -459,20 +486,51 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( }, }, 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), + state: createMemo(() => store.review?.state ?? "pane"), + width: createMemo(() => store.review?.width ?? 450), diffStyle: createMemo(() => store.review?.diffStyle ?? "split"), setDiffStyle(diffStyle: ReviewDiffStyle) { if (!store.review) { - setStore("review", { diffStyle }) + setStore("review", { opened: true, diffStyle }) return } setStore("review", "diffStyle", diffStyle) }, + open() { + setStore("review", "opened", true) + }, + close() { + setStore("review", "opened", false) + }, + toggle() { + setStore("review", "opened", (x) => !x) + }, + pane() { + setStore("review", "state", "pane") + }, + tab() { + setStore("review", "state", "tab") + }, + resize(width: number) { + setStore("review", "width", width) + }, }, fileTree: { opened: createMemo(() => store.fileTree?.opened ?? true), @@ -517,6 +575,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( session: { width: createMemo(() => store.session?.width ?? 600), resize(width: number) { + // ResizeHandle already enforces min/max constraints if (!store.session) { setStore("session", { width }) return @@ -524,6 +583,12 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( setStore("session", "width", width) }, }, + font: { + current: createMemo(() => store.font), + set(font: string) { + setStore("font", font) + }, + }, mobileSidebar: { opened: createMemo(() => store.mobileSidebar?.opened ?? false), show() { @@ -606,6 +671,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( }, } }, + tabs(sessionKey: string | Accessor) { const key = typeof sessionKey === "function" ? sessionKey : () => sessionKey @@ -624,10 +690,11 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] }) return { tabs, - active: createMemo(() => tabs().active), + active: createMemo(() => (tabs().active === "review" ? undefined : tabs().active)), all: createMemo(() => tabs().all.filter((tab) => tab !== "review")), setActive(tab: string | undefined) { const session = key() + if (tab === "review") return if (!store.sessionTabs[session]) { setStore("sessionTabs", session, { all: [], active: tab }) } else { @@ -644,18 +711,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( } }, async open(tab: string) { + if (tab === "review") return const session = key() const current = store.sessionTabs[session] ?? { all: [] } - if (tab === "review") { - if (!store.sessionTabs[session]) { - setStore("sessionTabs", session, { all: current.all, active: tab }) - return - } - setStore("sessionTabs", session, "active", tab) - return - } - if (tab === "context") { const all = [tab, ...current.all.filter((x) => x !== tab)] if (!store.sessionTabs[session]) { diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx index f6fb157f068..d8e2dfce6f3 100644 --- a/packages/app/src/context/platform.tsx +++ b/packages/app/src/context/platform.tsx @@ -1,6 +1,16 @@ import { createSimpleContext } from "@opencode-ai/ui/context" import { AsyncStorage, SyncStorage } from "@solid-primitives/storage" +/** Check if running as installed PWA (standalone mode) */ +export function isPWA(): boolean { + if (typeof window === "undefined") return false + return ( + window.matchMedia("(display-mode: standalone)").matches || + // @ts-ignore - iOS Safari specific + window.navigator.standalone === true + ) +} + export type Platform = { /** Platform discriminator */ platform: "web" | "desktop" diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx index 5c8e140c396..208d69663e1 100644 --- a/packages/app/src/context/sync.tsx +++ b/packages/app/src/context/sync.tsx @@ -271,6 +271,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ await client.session.list().then((x) => { const sessions = (x.data ?? []) .filter((s) => !!s?.id) + .filter((s) => !s.time.archived) .sort((a, b) => a.id.localeCompare(b.id)) .slice(0, store.limit) setStore("session", reconcile(sessions, { key: "id" })) diff --git a/packages/app/src/custom-elements.d.ts b/packages/app/src/custom-elements.d.ts index e4ea0d6cebd..336ce12bb91 120000 --- a/packages/app/src/custom-elements.d.ts +++ b/packages/app/src/custom-elements.d.ts @@ -1 +1 @@ -../../ui/src/custom-elements.d.ts \ No newline at end of file +export {} diff --git a/packages/app/src/env.d.ts b/packages/app/src/env.d.ts index ad575e93b4a..bc8342b1b2f 100644 --- a/packages/app/src/env.d.ts +++ b/packages/app/src/env.d.ts @@ -6,3 +6,6 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv } + +declare const __APP_VERSION__: string +declare const __COMMIT_HASH__: string diff --git a/packages/app/src/fonts/apply-font.ts b/packages/app/src/fonts/apply-font.ts new file mode 100644 index 00000000000..cf3de1f2517 --- /dev/null +++ b/packages/app/src/fonts/apply-font.ts @@ -0,0 +1,31 @@ +import { FONTS, getFontById, type FontDefinition } from "@/fonts/font-definitions" +import { loadFont } from "@/fonts/font-loader" + +function applyFontDefinition(font: FontDefinition): void { + const fontFamily = `"${font.family}", ${font.fallback}` + document.documentElement.style.setProperty("--font-family-sans", fontFamily) +} + +export function applyFont(fontId: string): void { + const font = getFontById(fontId) ?? FONTS[0] + applyFontDefinition(font) +} + +export async function ensureFontLoaded(font: FontDefinition): Promise { + if (!font.googleFontsUrl) return true + try { + await loadFont(font) + return true + } catch { + return false + } +} + +export async function applyFontWithLoad(font: FontDefinition): Promise { + const loaded = await ensureFontLoaded(font) + if (loaded) { + applyFontDefinition(font) + } else { + applyFontDefinition(FONTS[0]) + } +} diff --git a/packages/app/src/fonts/font-definitions.ts b/packages/app/src/fonts/font-definitions.ts new file mode 100644 index 00000000000..d7788e4eb8d --- /dev/null +++ b/packages/app/src/fonts/font-definitions.ts @@ -0,0 +1,93 @@ +export type FontDefinition = { + id: string + name: string + family: string + googleFontsUrl: string // Empty string for fonts already loaded + fallback: string +} + +export const DEFAULT_FONT_ID = "meslo" + +export const FONTS: FontDefinition[] = [ + { + id: "meslo", + name: "Meslo", + family: "meslo", + googleFontsUrl: "", // Already loaded via jsDelivr in index.html + fallback: '"Menlo", "Monaco", "Courier New", monospace', + }, + { + id: "geist", + name: "Geist", + family: "Geist", + googleFontsUrl: "", // Already bundled in UI package + fallback: '"Geist Fallback", sans-serif', + }, + { + id: "inter", + name: "Inter", + family: "Inter", + googleFontsUrl: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap", + fallback: "sans-serif", + }, + { + id: "nunito", + name: "Nunito", + family: "Nunito", + googleFontsUrl: "https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;700&display=swap", + fallback: "sans-serif", + }, + { + id: "roboto-condensed", + name: "Roboto Condensed", + family: "Roboto Condensed", + googleFontsUrl: "https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@400;500;700&display=swap", + fallback: "sans-serif", + }, + { + id: "oswald", + name: "Oswald", + family: "Oswald", + googleFontsUrl: "https://fonts.googleapis.com/css2?family=Oswald:wght@400;500;700&display=swap", + fallback: "sans-serif", + }, + { + id: "forum", + name: "Forum", + family: "Forum", + googleFontsUrl: "https://fonts.googleapis.com/css2?family=Forum&display=swap", + fallback: "serif", + }, + { + id: "rubik", + name: "Rubik", + family: "Rubik", + googleFontsUrl: "https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;700&display=swap", + fallback: "sans-serif", + }, + { + id: "ubuntu-mono", + name: "Ubuntu Mono", + family: "Ubuntu Mono", + googleFontsUrl: "https://fonts.googleapis.com/css2?family=Ubuntu+Mono:wght@400;700&display=swap", + fallback: "monospace", + }, + { + id: "jetbrains-mono", + name: "JetBrains Mono", + family: "JetBrains Mono", + googleFontsUrl: "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap", + fallback: "monospace", + }, + { + id: "inconsolata", + name: "Inconsolata", + family: "Inconsolata", + googleFontsUrl: "https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;500;700&display=swap", + fallback: "monospace", + }, +] + +export function getFontById(id: string): FontDefinition | undefined { + return FONTS.find((f) => f.id === id) +} diff --git a/packages/app/src/fonts/font-loader.ts b/packages/app/src/fonts/font-loader.ts new file mode 100644 index 00000000000..366d5ac2b00 --- /dev/null +++ b/packages/app/src/fonts/font-loader.ts @@ -0,0 +1,52 @@ +import type { FontDefinition } from "./font-definitions" + +const loadedFonts = new Set() +const loadingFonts = new Map>() + +export async function loadFont(font: FontDefinition): Promise { + // Skip if already loaded or if it's a bundled font (no Google Fonts URL) + if (loadedFonts.has(font.id) || !font.googleFontsUrl) { + return + } + + // Return existing promise if already loading + const existingPromise = loadingFonts.get(font.id) + if (existingPromise) { + return existingPromise + } + + const loadPromise = new Promise((resolve, reject) => { + // Check if link already exists in the document + const existingLink = document.querySelector(`link[data-font-id="${font.id}"]`) + if (existingLink) { + loadedFonts.add(font.id) + resolve() + return + } + + const link = document.createElement("link") + link.rel = "stylesheet" + link.href = font.googleFontsUrl + link.setAttribute("data-font-id", font.id) + + link.onload = () => { + loadedFonts.add(font.id) + loadingFonts.delete(font.id) + resolve() + } + + link.onerror = () => { + loadingFonts.delete(font.id) + reject(new Error(`Failed to load font: ${font.name}`)) + } + + document.head.appendChild(link) + }) + + loadingFonts.set(font.id, loadPromise) + return loadPromise +} + +export function isFontLoaded(fontId: string): boolean { + return loadedFonts.has(fontId) +} diff --git a/packages/app/src/hooks/use-keyboard-visibility.tsx b/packages/app/src/hooks/use-keyboard-visibility.tsx new file mode 100644 index 00000000000..57e9d3ae1ec --- /dev/null +++ b/packages/app/src/hooks/use-keyboard-visibility.tsx @@ -0,0 +1,44 @@ +import { createSignal, onMount, onCleanup } from "solid-js" + +export function useKeyboardVisibility() { + const [keyboardHeight, setKeyboardHeight] = createSignal(0) + const [isKeyboardVisible, setIsKeyboardVisible] = createSignal(false) + + onMount(() => { + if (!window.visualViewport) return + + const handleResize = () => { + const height = window.innerHeight - window.visualViewport!.height + const clamped = Math.max(0, height) + setKeyboardHeight(clamped) + const visible = clamped > 100 + setIsKeyboardVisible(visible) + document.documentElement.style.setProperty("--keyboard-offset", `${clamped}px`) + + // Set data attribute on document element for CSS targeting + if (visible) { + document.documentElement.setAttribute("data-keyboard-visible", "true") + } else { + document.documentElement.removeAttribute("data-keyboard-visible") + } + } + + const handleScroll = () => { + // Recalculate on scroll to handle iOS PWA viewport changes + const height = window.innerHeight - window.visualViewport!.height + const clamped = Math.max(0, height) + document.documentElement.style.setProperty("--keyboard-offset", `${clamped}px`) + } + + window.visualViewport.addEventListener("resize", handleResize) + window.visualViewport.addEventListener("scroll", handleScroll) + onCleanup(() => { + window.visualViewport?.removeEventListener("resize", handleResize) + window.visualViewport?.removeEventListener("scroll", handleScroll) + // Clean up the data attribute + document.documentElement.removeAttribute("data-keyboard-visible") + }) + }) + + return { keyboardHeight, isKeyboardVisible } +} diff --git a/packages/app/src/hooks/use-providers.ts b/packages/app/src/hooks/use-providers.ts index 55184aa1b42..4ff956e4611 100644 --- a/packages/app/src/hooks/use-providers.ts +++ b/packages/app/src/hooks/use-providers.ts @@ -16,14 +16,14 @@ export function useProviders() { } return globalSync.data.provider }) - const connected = createMemo(() => providers().all.filter((p) => providers().connected.includes(p.id))) + 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))) + const popular = createMemo(() => (providers().all ?? []).filter((p) => popularProviders.includes(p.id))) return { - all: createMemo(() => providers().all), - default: createMemo(() => providers().default), + all: createMemo(() => providers().all ?? []), + default: createMemo(() => providers().default ?? {}), popular, connected, paid, diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 55d3d9fa71c..8ca05cdfeb3 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -331,6 +331,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "لغة", "toast.language.description": "تم التبديل إلى {{language}}", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index bcbf6316166..ad0772cd8b2 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -330,6 +330,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "Idioma", "toast.language.description": "Alterado para {{language}}", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 9b641f48bed..031d92d4b96 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -332,6 +332,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "Sprog", "toast.language.description": "Skiftede til {{language}}", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index 5fedbaf0a30..9febfcff1e6 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -338,6 +338,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "Sprache", "toast.language.description": "Zu {{language}} gewechselt", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 173976db744..a6a50506a09 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -337,6 +337,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "Language", "toast.language.description": "Switched to {{language}}", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 6e06791dd91..ee75a143df6 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -333,6 +333,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "Idioma", "toast.language.description": "Cambiado a {{language}}", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index ba3ea88616e..f0652a9814e 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -333,6 +333,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "Langue", "toast.language.description": "Passé à {{language}}", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 32c5db3ffdd..ffe5368142c 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -331,6 +331,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "言語", "toast.language.description": "{{language}}に切り替えました", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 2c072b4b8f5..6c30e0123d8 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -334,6 +334,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "언어", "toast.language.description": "{{language}}(으)로 전환됨", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 076ee042860..132c0b6c1fa 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -334,6 +334,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "Språk", "toast.language.description": "Byttet til {{language}}", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 689d291616c..efed3eeb15d 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -332,6 +332,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "Język", "toast.language.description": "Przełączono na {{language}}", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index f2a499bef36..0728c4a3424 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -333,6 +333,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "Язык", "toast.language.description": "Переключено на {{language}}", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts new file mode 100644 index 00000000000..9ccb61ac76b --- /dev/null +++ b/packages/app/src/i18n/th.ts @@ -0,0 +1,718 @@ +export const dict = { + "command.category.suggested": "แนะนำ", + "command.category.view": "มุมมอง", + "command.category.project": "โปรเจกต์", + "command.category.provider": "ผู้ให้บริการ", + "command.category.server": "เซิร์ฟเวอร์", + "command.category.session": "เซสชัน", + "command.category.theme": "ธีม", + "command.category.language": "ภาษา", + "command.category.file": "ไฟล์", + "command.category.context": "บริบท", + "command.category.terminal": "เทอร์มินัล", + "command.category.model": "โมเดล", + "command.category.mcp": "MCP", + "command.category.agent": "เอเจนต์", + "command.category.permissions": "สิทธิ์", + "command.category.workspace": "พื้นที่ทำงาน", + "command.category.settings": "การตั้งค่า", + + "theme.scheme.system": "ระบบ", + "theme.scheme.light": "สว่าง", + "theme.scheme.dark": "มืด", + + "command.sidebar.toggle": "สลับแถบข้าง", + "command.project.open": "เปิดโปรเจกต์", + "command.provider.connect": "เชื่อมต่อผู้ให้บริการ", + "command.server.switch": "สลับเซิร์ฟเวอร์", + "command.settings.open": "เปิดการตั้งค่า", + "command.session.previous": "เซสชันก่อนหน้า", + "command.session.next": "เซสชันถัดไป", + "command.session.archive": "จัดเก็บเซสชัน", + + "command.palette": "คำสั่งค้นหา", + + "command.theme.cycle": "เปลี่ยนธีม", + "command.theme.set": "ใช้ธีม: {{theme}}", + "command.theme.scheme.cycle": "เปลี่ยนโทนสี", + "command.theme.scheme.set": "ใช้โทนสี: {{scheme}}", + + "command.language.cycle": "เปลี่ยนภาษา", + "command.language.set": "ใช้ภาษา: {{language}}", + + "command.session.new": "เซสชันใหม่", + "command.file.open": "เปิดไฟล์", + "command.file.open.description": "ค้นหาไฟล์และคำสั่ง", + "command.context.addSelection": "เพิ่มส่วนที่เลือกไปยังบริบท", + "command.context.addSelection.description": "เพิ่มบรรทัดที่เลือกจากไฟล์ปัจจุบัน", + "command.terminal.toggle": "สลับเทอร์มินัล", + "command.fileTree.toggle": "สลับต้นไม้ไฟล์", + "command.review.toggle": "สลับการตรวจสอบ", + "command.terminal.new": "เทอร์มินัลใหม่", + "command.terminal.new.description": "สร้างแท็บเทอร์มินัลใหม่", + "command.steps.toggle": "สลับขั้นตอน", + "command.steps.toggle.description": "แสดงหรือซ่อนขั้นตอนสำหรับข้อความปัจจุบัน", + "command.message.previous": "ข้อความก่อนหน้า", + "command.message.previous.description": "ไปที่ข้อความผู้ใช้ก่อนหน้า", + "command.message.next": "ข้อความถัดไป", + "command.message.next.description": "ไปที่ข้อความผู้ใช้ถัดไป", + "command.model.choose": "เลือกโมเดล", + "command.model.choose.description": "เลือกโมเดลอื่น", + "command.mcp.toggle": "สลับ MCPs", + "command.mcp.toggle.description": "สลับ MCPs", + "command.agent.cycle": "เปลี่ยนเอเจนต์", + "command.agent.cycle.description": "สลับไปยังเอเจนต์ถัดไป", + "command.agent.cycle.reverse": "เปลี่ยนเอเจนต์ย้อนกลับ", + "command.agent.cycle.reverse.description": "สลับไปยังเอเจนต์ก่อนหน้า", + "command.model.variant.cycle": "เปลี่ยนความพยายามในการคิด", + "command.model.variant.cycle.description": "สลับไปยังระดับความพยายามถัดไป", + "command.permissions.autoaccept.enable": "ยอมรับการแก้ไขโดยอัตโนมัติ", + "command.permissions.autoaccept.disable": "หยุดยอมรับการแก้ไขโดยอัตโนมัติ", + "command.session.undo": "ยกเลิก", + "command.session.undo.description": "ยกเลิกข้อความล่าสุด", + "command.session.redo": "ทำซ้ำ", + "command.session.redo.description": "ทำซ้ำข้อความที่ถูกยกเลิกล่าสุด", + "command.session.compact": "บีบอัดเซสชัน", + "command.session.compact.description": "สรุปเซสชันเพื่อลดขนาดบริบท", + "command.session.fork": "แตกแขนงจากข้อความ", + "command.session.fork.description": "สร้างเซสชันใหม่จากข้อความก่อนหน้า", + "command.session.share": "แชร์เซสชัน", + "command.session.share.description": "แชร์เซสชันนี้และคัดลอก URL ไปยังคลิปบอร์ด", + "command.session.unshare": "ยกเลิกการแชร์เซสชัน", + "command.session.unshare.description": "หยุดการแชร์เซสชันนี้", + + "palette.search.placeholder": "ค้นหาไฟล์และคำสั่ง", + "palette.empty": "ไม่พบผลลัพธ์", + "palette.group.commands": "คำสั่ง", + "palette.group.files": "ไฟล์", + + "dialog.provider.search.placeholder": "ค้นหาผู้ให้บริการ", + "dialog.provider.empty": "ไม่พบผู้ให้บริการ", + "dialog.provider.group.popular": "ยอดนิยม", + "dialog.provider.group.other": "อื่น ๆ", + "dialog.provider.tag.recommended": "แนะนำ", + "dialog.provider.opencode.note": "โมเดลที่คัดสรร รวมถึง Claude, GPT, Gemini และอื่น ๆ", + "dialog.provider.anthropic.note": "เข้าถึงโมเดล Claude โดยตรง รวมถึง Pro และ Max", + "dialog.provider.copilot.note": "โมเดล Claude สำหรับการช่วยเหลือในการเขียนโค้ด", + "dialog.provider.openai.note": "โมเดล GPT สำหรับงาน AI ทั่วไปที่รวดเร็วและมีความสามารถ", + "dialog.provider.google.note": "โมเดล Gemini สำหรับการตอบสนองที่รวดเร็วและมีโครงสร้าง", + "dialog.provider.openrouter.note": "เข้าถึงโมเดลที่รองรับทั้งหมดจากผู้ให้บริการเดียว", + "dialog.provider.vercel.note": "การเข้าถึงโมเดล AI แบบรวมด้วยการกำหนดเส้นทางอัจฉริยะ", + + "dialog.model.select.title": "เลือกโมเดล", + "dialog.model.search.placeholder": "ค้นหาโมเดล", + "dialog.model.empty": "ไม่พบผลลัพธ์โมเดล", + "dialog.model.manage": "จัดการโมเดล", + "dialog.model.manage.description": "ปรับแต่งโมเดลที่จะปรากฏในตัวเลือกโมเดล", + + "dialog.model.unpaid.freeModels.title": "โมเดลฟรีที่จัดหาให้โดย OpenCode", + "dialog.model.unpaid.addMore.title": "เพิ่มโมเดลเพิ่มเติมจากผู้ให้บริการยอดนิยม", + + "dialog.provider.viewAll": "แสดงผู้ให้บริการเพิ่มเติม", + + "provider.connect.title": "เชื่อมต่อ {{provider}}", + "provider.connect.title.anthropicProMax": "เข้าสู่ระบบด้วย Claude Pro/Max", + "provider.connect.selectMethod": "เลือกวิธีการเข้าสู่ระบบสำหรับ {{provider}}", + "provider.connect.method.apiKey": "คีย์ API", + "provider.connect.status.inProgress": "กำลังอนุญาต...", + "provider.connect.status.waiting": "รอการอนุญาต...", + "provider.connect.status.failed": "การอนุญาตล้มเหลว: {{error}}", + "provider.connect.apiKey.description": + "ป้อนคีย์ API ของ {{provider}} เพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน OpenCode", + "provider.connect.apiKey.label": "คีย์ API ของ {{provider}}", + "provider.connect.apiKey.placeholder": "คีย์ API", + "provider.connect.apiKey.required": "ต้องใช้คีย์ API", + "provider.connect.opencodeZen.line1": + "OpenCode Zen ให้คุณเข้าถึงชุดโมเดลที่เชื่อถือได้และปรับแต่งแล้วสำหรับเอเจนต์การเขียนโค้ด", + "provider.connect.opencodeZen.line2": + "ด้วยคีย์ API เดียวคุณจะได้รับการเข้าถึงโมเดล เช่น Claude, GPT, Gemini, GLM และอื่น ๆ", + "provider.connect.opencodeZen.visit.prefix": "เยี่ยมชม ", + "provider.connect.opencodeZen.visit.link": "opencode.ai/zen", + "provider.connect.opencodeZen.visit.suffix": " เพื่อรวบรวมคีย์ API ของคุณ", + "provider.connect.oauth.code.visit.prefix": "เยี่ยมชม ", + "provider.connect.oauth.code.visit.link": "ลิงก์นี้", + "provider.connect.oauth.code.visit.suffix": + " เพื่อรวบรวมรหัสการอนุญาตของคุณเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน OpenCode", + "provider.connect.oauth.code.label": "รหัสการอนุญาต {{method}}", + "provider.connect.oauth.code.placeholder": "รหัสการอนุญาต", + "provider.connect.oauth.code.required": "ต้องใช้รหัสการอนุญาต", + "provider.connect.oauth.code.invalid": "รหัสการอนุญาตไม่ถูกต้อง", + "provider.connect.oauth.auto.visit.prefix": "เยี่ยมชม ", + "provider.connect.oauth.auto.visit.link": "ลิงก์นี้", + "provider.connect.oauth.auto.visit.suffix": + " และป้อนรหัสด้านล่างเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน OpenCode", + "provider.connect.oauth.auto.confirmationCode": "รหัสยืนยัน", + "provider.connect.toast.connected.title": "{{provider}} ที่เชื่อมต่อแล้ว", + "provider.connect.toast.connected.description": "โมเดล {{provider}} พร้อมใช้งานแล้ว", + + "provider.disconnect.toast.disconnected.title": "{{provider}} ที่ยกเลิกการเชื่อมต่อแล้ว", + "provider.disconnect.toast.disconnected.description": "โมเดล {{provider}} ไม่พร้อมใช้งานอีกต่อไป", + + "model.tag.free": "ฟรี", + "model.tag.latest": "ล่าสุด", + "model.provider.anthropic": "Anthropic", + "model.provider.openai": "OpenAI", + "model.provider.google": "Google", + "model.provider.xai": "xAI", + "model.provider.meta": "Meta", + "model.input.text": "ข้อความ", + "model.input.image": "รูปภาพ", + "model.input.audio": "เสียง", + "model.input.video": "วิดีโอ", + "model.input.pdf": "pdf", + "model.tooltip.allows": "อนุญาต: {{inputs}}", + "model.tooltip.reasoning.allowed": "อนุญาตการใช้เหตุผล", + "model.tooltip.reasoning.none": "ไม่มีการใช้เหตุผล", + "model.tooltip.context": "ขีดจำกัดบริบท {{limit}}", + + "common.search.placeholder": "ค้นหา", + "common.goBack": "ย้อนกลับ", + "common.loading": "กำลังโหลด", + "common.loading.ellipsis": "...", + "common.cancel": "ยกเลิก", + "common.connect": "เชื่อมต่อ", + "common.disconnect": "ยกเลิกการเชื่อมต่อ", + "common.submit": "ส่ง", + "common.save": "บันทึก", + "common.saving": "กำลังบันทึก...", + "common.default": "ค่าเริ่มต้น", + "common.attachment": "ไฟล์แนบ", + + "prompt.placeholder.shell": "ป้อนคำสั่งเชลล์...", + "prompt.placeholder.normal": 'ถามอะไรก็ได้... "{{example}}"', + "prompt.placeholder.summarizeComments": "สรุปความคิดเห็น…", + "prompt.placeholder.summarizeComment": "สรุปความคิดเห็น…", + "prompt.mode.shell": "เชลล์", + "prompt.mode.shell.exit": "กด esc เพื่อออก", + + "prompt.example.1": "แก้ไข TODO ในโค้ดเบส", + "prompt.example.2": "เทคโนโลยีของโปรเจกต์นี้คืออะไร?", + "prompt.example.3": "แก้ไขการทดสอบที่เสีย", + "prompt.example.4": "อธิบายวิธีการทำงานของการตรวจสอบสิทธิ์", + "prompt.example.5": "ค้นหาและแก้ไขช่องโหว่ความปลอดภัย", + "prompt.example.6": "เพิ่มการทดสอบหน่วยสำหรับบริการผู้ใช้", + "prompt.example.7": "ปรับโครงสร้างฟังก์ชันนี้ให้อ่านง่ายขึ้น", + "prompt.example.8": "ข้อผิดพลาดนี้หมายความว่าอะไร?", + "prompt.example.9": "ช่วยฉันดีบักปัญหานี้", + "prompt.example.10": "สร้างเอกสาร API", + "prompt.example.11": "ปรับปรุงการสืบค้นฐานข้อมูล", + "prompt.example.12": "เพิ่มการตรวจสอบข้อมูลนำเข้า", + "prompt.example.13": "สร้างคอมโพเนนต์ใหม่สำหรับ...", + "prompt.example.14": "ฉันจะทำให้โปรเจกต์นี้ทำงานได้อย่างไร?", + "prompt.example.15": "ตรวจสอบโค้ดของฉันเพื่อแนวทางปฏิบัติที่ดีที่สุด", + "prompt.example.16": "เพิ่มการจัดการข้อผิดพลาดในฟังก์ชันนี้", + "prompt.example.17": "อธิบายรูปแบบ regex นี้", + "prompt.example.18": "แปลงสิ่งนี้เป็น TypeScript", + "prompt.example.19": "เพิ่มการบันทึกทั่วทั้งโค้ดเบส", + "prompt.example.20": "มีการพึ่งพาอะไรที่ล้าสมัยอยู่?", + "prompt.example.21": "ช่วยฉันเขียนสคริปต์การย้ายข้อมูล", + "prompt.example.22": "ใช้งานแคชสำหรับจุดสิ้นสุดนี้", + "prompt.example.23": "เพิ่มการแบ่งหน้าในรายการนี้", + "prompt.example.24": "สร้างคำสั่ง CLI สำหรับ...", + "prompt.example.25": "ตัวแปรสภาพแวดล้อมทำงานอย่างไรที่นี่?", + + "prompt.popover.emptyResults": "ไม่พบผลลัพธ์ที่ตรงกัน", + "prompt.popover.emptyCommands": "ไม่พบคำสั่งที่ตรงกัน", + "prompt.dropzone.label": "วางรูปภาพหรือ PDF ที่นี่", + "prompt.slash.badge.custom": "กำหนดเอง", + "prompt.context.active": "ใช้งานอยู่", + "prompt.context.includeActiveFile": "รวมไฟล์ที่ใช้งานอยู่", + "prompt.context.removeActiveFile": "เอาไฟล์ที่ใช้งานอยู่ออกจากบริบท", + "prompt.context.removeFile": "เอาไฟล์ออกจากบริบท", + "prompt.action.attachFile": "แนบไฟล์", + "prompt.attachment.remove": "เอาไฟล์แนบออก", + "prompt.action.send": "ส่ง", + "prompt.action.stop": "หยุด", + + "prompt.toast.pasteUnsupported.title": "การวางไม่รองรับ", + "prompt.toast.pasteUnsupported.description": "สามารถวางรูปภาพหรือ PDF เท่านั้น", + "prompt.toast.modelAgentRequired.title": "เลือกเอเจนต์และโมเดล", + "prompt.toast.modelAgentRequired.description": "เลือกเอเจนต์และโมเดลก่อนส่งพร้อมท์", + "prompt.toast.worktreeCreateFailed.title": "ไม่สามารถสร้าง worktree", + "prompt.toast.sessionCreateFailed.title": "ไม่สามารถสร้างเซสชัน", + "prompt.toast.shellSendFailed.title": "ไม่สามารถส่งคำสั่งเชลล์", + "prompt.toast.commandSendFailed.title": "ไม่สามารถส่งคำสั่ง", + "prompt.toast.promptSendFailed.title": "ไม่สามารถส่งพร้อมท์", + + "dialog.mcp.title": "MCPs", + "dialog.mcp.description": "{{enabled}} จาก {{total}} ที่เปิดใช้งาน", + "dialog.mcp.empty": "ไม่มี MCP ที่กำหนดค่า", + + "dialog.lsp.empty": "LSPs ตรวจจับอัตโนมัติจากประเภทไฟล์", + "dialog.plugins.empty": "ปลั๊กอินที่กำหนดค่าใน opencode.json", + + "mcp.status.connected": "เชื่อมต่อแล้ว", + "mcp.status.failed": "ล้มเหลว", + "mcp.status.needs_auth": "ต้องการการตรวจสอบสิทธิ์", + "mcp.status.disabled": "ปิดใช้งาน", + + "dialog.fork.empty": "ไม่มีข้อความให้แตกแขนง", + + "dialog.directory.search.placeholder": "ค้นหาโฟลเดอร์", + "dialog.directory.empty": "ไม่พบโฟลเดอร์", + + "dialog.server.title": "เซิร์ฟเวอร์", + "dialog.server.description": "สลับเซิร์ฟเวอร์ OpenCode ที่แอปนี้เชื่อมต่อด้วย", + "dialog.server.search.placeholder": "ค้นหาเซิร์ฟเวอร์", + "dialog.server.empty": "ยังไม่มีเซิร์ฟเวอร์", + "dialog.server.add.title": "เพิ่มเซิร์ฟเวอร์", + "dialog.server.add.url": "URL เซิร์ฟเวอร์", + "dialog.server.add.placeholder": "http://localhost:4096", + "dialog.server.add.error": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์", + "dialog.server.add.checking": "กำลังตรวจสอบ...", + "dialog.server.add.button": "เพิ่มเซิร์ฟเวอร์", + "dialog.server.default.title": "เซิร์ฟเวอร์เริ่มต้น", + "dialog.server.default.description": + "เชื่อมต่อกับเซิร์ฟเวอร์นี้เมื่อเปิดแอปแทนการเริ่มเซิร์ฟเวอร์ในเครื่อง ต้องรีสตาร์ท", + "dialog.server.default.none": "ไม่ได้เลือกเซิร์ฟเวอร์", + "dialog.server.default.set": "ตั้งเซิร์ฟเวอร์ปัจจุบันเป็นค่าเริ่มต้น", + "dialog.server.default.clear": "ล้าง", + "dialog.server.action.remove": "เอาเซิร์ฟเวอร์ออก", + + "dialog.server.menu.edit": "แก้ไข", + "dialog.server.menu.default": "ตั้งเป็นค่าเริ่มต้น", + "dialog.server.menu.defaultRemove": "เอาค่าเริ่มต้นออก", + "dialog.server.menu.delete": "ลบ", + "dialog.server.current": "เซิร์ฟเวอร์ปัจจุบัน", + "dialog.server.status.default": "ค่าเริ่มต้น", + + "dialog.project.edit.title": "แก้ไขโปรเจกต์", + "dialog.project.edit.name": "ชื่อ", + "dialog.project.edit.icon": "ไอคอน", + "dialog.project.edit.icon.alt": "ไอคอนโปรเจกต์", + "dialog.project.edit.icon.hint": "คลิกหรือลากรูปภาพ", + "dialog.project.edit.icon.recommended": "แนะนำ: 128x128px", + "dialog.project.edit.color": "สี", + "dialog.project.edit.color.select": "เลือกสี {{color}}", + "dialog.project.edit.worktree.startup": "สคริปต์เริ่มต้นพื้นที่ทำงาน", + "dialog.project.edit.worktree.startup.description": "ทำงานหลังจากสร้างพื้นที่ทำงานใหม่ (worktree)", + "dialog.project.edit.worktree.startup.placeholder": "เช่น bun install", + + "context.breakdown.title": "การแบ่งบริบท", + "context.breakdown.note": 'การแบ่งโดยประมาณของโทเค็นนำเข้า "อื่น ๆ" รวมถึงคำนิยามเครื่องมือและโอเวอร์เฮด', + "context.breakdown.system": "ระบบ", + "context.breakdown.user": "ผู้ใช้", + "context.breakdown.assistant": "ผู้ช่วย", + "context.breakdown.tool": "การเรียกเครื่องมือ", + "context.breakdown.other": "อื่น ๆ", + + "context.systemPrompt.title": "พร้อมท์ระบบ", + "context.rawMessages.title": "ข้อความดิบ", + + "context.stats.session": "เซสชัน", + "context.stats.messages": "ข้อความ", + "context.stats.provider": "ผู้ให้บริการ", + "context.stats.model": "โมเดล", + "context.stats.limit": "ขีดจำกัดบริบท", + "context.stats.totalTokens": "โทเค็นทั้งหมด", + "context.stats.usage": "การใช้งาน", + "context.stats.inputTokens": "โทเค็นนำเข้า", + "context.stats.outputTokens": "โทเค็นส่งออก", + "context.stats.reasoningTokens": "โทเค็นการใช้เหตุผล", + "context.stats.cacheTokens": "โทเค็นแคช (อ่าน/เขียน)", + "context.stats.userMessages": "ข้อความผู้ใช้", + "context.stats.assistantMessages": "ข้อความผู้ช่วย", + "context.stats.totalCost": "ต้นทุนทั้งหมด", + "context.stats.sessionCreated": "สร้างเซสชันเมื่อ", + "context.stats.lastActivity": "กิจกรรมล่าสุด", + + "context.usage.tokens": "โทเค็น", + "context.usage.usage": "การใช้งาน", + "context.usage.cost": "ต้นทุน", + "context.usage.clickToView": "คลิกเพื่อดูบริบท", + "context.usage.view": "ดูการใช้บริบท", + + "language.en": "อังกฤษ", + "language.zh": "จีนตัวย่อ", + "language.zht": "จีนตัวเต็ม", + "language.ko": "เกาหลี", + "language.de": "เยอรมัน", + "language.es": "สเปน", + "language.fr": "ฝรั่งเศส", + "language.da": "เดนมาร์ก", + "language.ja": "ญี่ปุ่น", + "language.pl": "โปแลนด์", + "language.ru": "รัสเซีย", + "language.ar": "อาหรับ", + "language.no": "นอร์เวย์", + "language.br": "โปรตุเกส (บราซิล)", + "language.th": "ไทย", + + "toast.language.title": "ภาษา", + "toast.language.description": "สลับไปที่ {{language}}", + + "toast.theme.title": "สลับธีมแล้ว", + "toast.scheme.title": "โทนสี", + + "toast.permissions.autoaccept.on.title": "กำลังยอมรับการแก้ไขโดยอัตโนมัติ", + "toast.permissions.autoaccept.on.description": "สิทธิ์การแก้ไขและเขียนจะได้รับการอนุมัติโดยอัตโนมัติ", + "toast.permissions.autoaccept.off.title": "หยุดยอมรับการแก้ไขโดยอัตโนมัติ", + "toast.permissions.autoaccept.off.description": "สิทธิ์การแก้ไขและเขียนจะต้องได้รับการอนุมัติ", + + "toast.model.none.title": "ไม่ได้เลือกโมเดล", + "toast.model.none.description": "เชื่อมต่อผู้ให้บริการเพื่อสรุปเซสชันนี้", + + "toast.file.loadFailed.title": "ไม่สามารถโหลดไฟล์", + "toast.file.listFailed.title": "ไม่สามารถแสดงรายการไฟล์", + + "toast.context.noLineSelection.title": "ไม่มีการเลือกบรรทัด", + "toast.context.noLineSelection.description": "เลือกช่วงบรรทัดในแท็บไฟล์ก่อน", + + "toast.session.share.copyFailed.title": "ไม่สามารถคัดลอก URL ไปยังคลิปบอร์ด", + "toast.session.share.success.title": "แชร์เซสชันแล้ว", + "toast.session.share.success.description": "คัดลอก URL แชร์ไปยังคลิปบอร์ดแล้ว!", + "toast.session.share.failed.title": "ไม่สามารถแชร์เซสชัน", + "toast.session.share.failed.description": "เกิดข้อผิดพลาดระหว่างการแชร์เซสชัน", + + "toast.session.unshare.success.title": "ยกเลิกการแชร์เซสชันแล้ว", + "toast.session.unshare.success.description": "ยกเลิกการแชร์เซสชันสำเร็จ!", + "toast.session.unshare.failed.title": "ไม่สามารถยกเลิกการแชร์เซสชัน", + "toast.session.unshare.failed.description": "เกิดข้อผิดพลาดระหว่างการยกเลิกการแชร์เซสชัน", + + "toast.session.listFailed.title": "ไม่สามารถโหลดเซสชันสำหรับ {{project}}", + + "toast.update.title": "มีการอัปเดต", + "toast.update.description": "เวอร์ชันใหม่ของ OpenCode ({{version}}) พร้อมใช้งานสำหรับติดตั้ง", + "toast.update.action.installRestart": "ติดตั้งและรีสตาร์ท", + "toast.update.action.notYet": "ยังไม่", + + "error.page.title": "เกิดข้อผิดพลาด", + "error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน", + "error.page.details.label": "รายละเอียดข้อผิดพลาด", + "error.page.action.restart": "รีสตาร์ท", + "error.page.action.checking": "กำลังตรวจสอบ...", + "error.page.action.checkUpdates": "ตรวจสอบการอัปเดต", + "error.page.action.updateTo": "อัปเดตเป็น {{version}}", + "error.page.report.prefix": "โปรดรายงานข้อผิดพลาดนี้ให้ทีม OpenCode", + "error.page.report.discord": "บน Discord", + "error.page.version": "เวอร์ชัน: {{version}}", + + "error.dev.rootNotFound": "ไม่พบองค์ประกอบรูท คุณลืมเพิ่มใน index.html หรือบางทีแอตทริบิวต์ id อาจสะกดผิด?", + + "error.globalSync.connectFailed": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ มีเซิร์ฟเวอร์ทำงานอยู่ที่ `{{url}}` หรือไม่?", + + "error.chain.unknown": "ข้อผิดพลาดที่ไม่รู้จัก", + "error.chain.causedBy": "สาเหตุ:", + "error.chain.apiError": "ข้อผิดพลาด API", + "error.chain.status": "สถานะ: {{status}}", + "error.chain.retryable": "สามารถลองใหม่: {{retryable}}", + "error.chain.responseBody": "เนื้อหาการตอบสนอง:\n{{body}}", + "error.chain.didYouMean": "คุณหมายถึง: {{suggestions}}", + "error.chain.modelNotFound": "ไม่พบโมเดล: {{provider}}/{{model}}", + "error.chain.checkConfig": "ตรวจสอบการกำหนดค่าของคุณ (opencode.json) ชื่อผู้ให้บริการ/โมเดล", + "error.chain.mcpFailed": 'เซิร์ฟเวอร์ MCP "{{name}}" ล้มเหลว โปรดทราบว่า OpenCode ยังไม่รองรับการตรวจสอบสิทธิ์ MCP', + "error.chain.providerAuthFailed": "การตรวจสอบสิทธิ์ผู้ให้บริการล้มเหลว ({{provider}}): {{message}}", + "error.chain.providerInitFailed": 'ไม่สามารถเริ่มต้นผู้ให้บริการ "{{provider}}" ตรวจสอบข้อมูลรับรองและการกำหนดค่า', + "error.chain.configJsonInvalid": "ไฟล์กำหนดค่าที่ {{path}} ไม่ใช่ JSON(C) ที่ถูกต้อง", + "error.chain.configJsonInvalidWithMessage": "ไฟล์กำหนดค่าที่ {{path}} ไม่ใช่ JSON(C) ที่ถูกต้อง: {{message}}", + "error.chain.configDirectoryTypo": + 'ไดเรกทอรี "{{dir}}" ใน {{path}} ไม่ถูกต้อง เปลี่ยนชื่อไดเรกทอรีเป็น "{{suggestion}}" หรือเอาออก นี่เป็นการสะกดผิดทั่วไป', + "error.chain.configFrontmatterError": "ไม่สามารถแยกวิเคราะห์ frontmatter ใน {{path}}:\n{{message}}", + "error.chain.configInvalid": "ไฟล์กำหนดค่าที่ {{path}} ไม่ถูกต้อง", + "error.chain.configInvalidWithMessage": "ไฟล์กำหนดค่าที่ {{path}} ไม่ถูกต้อง: {{message}}", + + "notification.permission.title": "ต้องการสิทธิ์", + "notification.permission.description": "{{sessionTitle}} ใน {{projectName}} ต้องการสิทธิ์", + "notification.question.title": "คำถาม", + "notification.question.description": "{{sessionTitle}} ใน {{projectName}} มีคำถาม", + "notification.action.goToSession": "ไปที่เซสชัน", + + "notification.session.responseReady.title": "การตอบสนองพร้อม", + "notification.session.error.title": "ข้อผิดพลาดเซสชัน", + "notification.session.error.fallbackDescription": "เกิดข้อผิดพลาด", + + "home.recentProjects": "โปรเจกต์ล่าสุด", + "home.empty.title": "ไม่มีโปรเจกต์ล่าสุด", + "home.empty.description": "เริ่มต้นโดยเปิดโปรเจกต์ในเครื่อง", + + "session.tab.session": "เซสชัน", + "session.tab.review": "ตรวจสอบ", + "session.tab.context": "บริบท", + "session.panel.reviewAndFiles": "ตรวจสอบและไฟล์", + "session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง", + "session.review.change.one": "การเปลี่ยนแปลง", + "session.review.change.other": "การเปลี่ยนแปลง", + "session.review.loadingChanges": "กำลังโหลดการเปลี่ยนแปลง...", + "session.review.empty": "ยังไม่มีการเปลี่ยนแปลงในเซสชันนี้", + "session.review.noChanges": "ไม่มีการเปลี่ยนแปลง", + + "session.files.selectToOpen": "เลือกไฟล์เพื่อเปิด", + "session.files.all": "ไฟล์ทั้งหมด", + + "session.messages.renderEarlier": "แสดงข้อความก่อนหน้า", + "session.messages.loadingEarlier": "กำลังโหลดข้อความก่อนหน้า...", + "session.messages.loadEarlier": "โหลดข้อความก่อนหน้า", + "session.messages.loading": "กำลังโหลดข้อความ...", + "session.messages.jumpToLatest": "ไปที่ล่าสุด", + + "session.context.addToContext": "เพิ่ม {{selection}} ไปยังบริบท", + + "session.new.worktree.main": "สาขาหลัก", + "session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})", + "session.new.worktree.create": "สร้าง worktree ใหม่", + "session.new.lastModified": "แก้ไขล่าสุด", + + "session.header.search.placeholder": "ค้นหา {{project}}", + "session.header.searchFiles": "ค้นหาไฟล์", + + "status.popover.trigger": "สถานะ", + "status.popover.ariaLabel": "การกำหนดค่าเซิร์ฟเวอร์", + "status.popover.tab.servers": "เซิร์ฟเวอร์", + "status.popover.tab.mcp": "MCP", + "status.popover.tab.lsp": "LSP", + "status.popover.tab.plugins": "ปลั๊กอิน", + "status.popover.action.manageServers": "จัดการเซิร์ฟเวอร์", + + "session.share.popover.title": "เผยแพร่บนเว็บ", + "session.share.popover.description.shared": "เซสชันนี้เป็นสาธารณะบนเว็บ สามารถเข้าถึงได้โดยผู้ที่มีลิงก์", + "session.share.popover.description.unshared": "แชร์เซสชันสาธารณะบนเว็บ จะเข้าถึงได้โดยผู้ที่มีลิงก์", + "session.share.action.share": "แชร์", + "session.share.action.publish": "เผยแพร่", + "session.share.action.publishing": "กำลังเผยแพร่...", + "session.share.action.unpublish": "ยกเลิกการเผยแพร่", + "session.share.action.unpublishing": "กำลังยกเลิกการเผยแพร่...", + "session.share.action.view": "ดู", + "session.share.copy.copied": "คัดลอกแล้ว", + "session.share.copy.copyLink": "คัดลอกลิงก์", + + "lsp.tooltip.none": "ไม่มีเซิร์ฟเวอร์ LSP", + "lsp.label.connected": "{{count}} LSP", + + "prompt.loading": "กำลังโหลดพร้อมท์...", + "terminal.loading": "กำลังโหลดเทอร์มินัล...", + "terminal.title": "เทอร์มินัล", + "terminal.title.numbered": "เทอร์มินัล {{number}}", + "terminal.close": "ปิดเทอร์มินัล", + "terminal.connectionLost.title": "การเชื่อมต่อขาดหาย", + "terminal.connectionLost.description": "การเชื่อมต่อเทอร์มินัลถูกขัดจังหวะ อาจเกิดขึ้นเมื่อเซิร์ฟเวอร์รีสตาร์ท", + + "common.closeTab": "ปิดแท็บ", + "common.dismiss": "ปิด", + "common.requestFailed": "คำขอล้มเหลว", + "common.moreOptions": "ตัวเลือกเพิ่มเติม", + "common.learnMore": "เรียนรู้เพิ่มเติม", + "common.rename": "เปลี่ยนชื่อ", + "common.reset": "รีเซ็ต", + "common.archive": "จัดเก็บ", + "common.delete": "ลบ", + "common.close": "ปิด", + "common.edit": "แก้ไข", + "common.loadMore": "โหลดเพิ่มเติม", + "common.key.esc": "ESC", + + "sidebar.menu.toggle": "สลับเมนู", + "sidebar.nav.projectsAndSessions": "โปรเจกต์และเซสชัน", + "sidebar.settings": "การตั้งค่า", + "sidebar.help": "ช่วยเหลือ", + "sidebar.workspaces.enable": "เปิดใช้งานพื้นที่ทำงาน", + "sidebar.workspaces.disable": "ปิดใช้งานพื้นที่ทำงาน", + "sidebar.gettingStarted.title": "เริ่มต้นใช้งาน", + "sidebar.gettingStarted.line1": "OpenCode รวมถึงโมเดลฟรีเพื่อให้คุณเริ่มต้นได้ทันที", + "sidebar.gettingStarted.line2": "เชื่อมต่อผู้ให้บริการใด ๆ เพื่อใช้โมเดล รวมถึง Claude, GPT, Gemini ฯลฯ", + "sidebar.project.recentSessions": "เซสชันล่าสุด", + "sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด", + + "app.name.desktop": "OpenCode Desktop", + + "settings.section.desktop": "เดสก์ท็อป", + "settings.section.server": "เซิร์ฟเวอร์", + "settings.tab.general": "ทั่วไป", + "settings.tab.shortcuts": "ทางลัด", + + "settings.general.section.appearance": "รูปลักษณ์", + "settings.general.section.notifications": "การแจ้งเตือนระบบ", + "settings.general.section.updates": "การอัปเดต", + "settings.general.section.sounds": "เสียงเอฟเฟกต์", + + "settings.general.row.language.title": "ภาษา", + "settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode", + "settings.general.row.appearance.title": "รูปลักษณ์", + "settings.general.row.appearance.description": "ปรับแต่งวิธีการที่ OpenCode มีลักษณะบนอุปกรณ์ของคุณ", + "settings.general.row.theme.title": "ธีม", + "settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม", + "settings.general.row.font.title": "ฟอนต์", + "settings.general.row.font.description": "ปรับแต่งฟอนต์โมโนที่ใช้ในบล็อกโค้ด", + + "settings.general.row.releaseNotes.title": "บันทึกการอัปเดต", + "settings.general.row.releaseNotes.description": "แสดงป๊อปอัพ What's New หลังจากอัปเดต", + "font.option.ibmPlexMono": "IBM Plex Mono", + "font.option.cascadiaCode": "Cascadia Code", + "font.option.firaCode": "Fira Code", + "font.option.hack": "Hack", + "font.option.inconsolata": "Inconsolata", + "font.option.intelOneMono": "Intel One Mono", + "font.option.iosevka": "Iosevka", + "font.option.jetbrainsMono": "JetBrains Mono", + "font.option.mesloLgs": "Meslo LGS", + "font.option.robotoMono": "Roboto Mono", + "font.option.sourceCodePro": "Source Code Pro", + "font.option.ubuntuMono": "Ubuntu Mono", + "sound.option.alert01": "เสียงเตือน 01", + "sound.option.alert02": "เสียงเตือน 02", + "sound.option.alert03": "เสียงเตือน 03", + "sound.option.alert04": "เสียงเตือน 04", + "sound.option.alert05": "เสียงเตือน 05", + "sound.option.alert06": "เสียงเตือน 06", + "sound.option.alert07": "เสียงเตือน 07", + "sound.option.alert08": "เสียงเตือน 08", + "sound.option.alert09": "เสียงเตือน 09", + "sound.option.alert10": "เสียงเตือน 10", + "sound.option.bipbop01": "Bip-bop 01", + "sound.option.bipbop02": "Bip-bop 02", + "sound.option.bipbop03": "Bip-bop 03", + "sound.option.bipbop04": "Bip-bop 04", + "sound.option.bipbop05": "Bip-bop 05", + "sound.option.bipbop06": "Bip-bop 06", + "sound.option.bipbop07": "Bip-bop 07", + "sound.option.bipbop08": "Bip-bop 08", + "sound.option.bipbop09": "Bip-bop 09", + "sound.option.bipbop10": "Bip-bop 10", + "sound.option.staplebops01": "Staplebops 01", + "sound.option.staplebops02": "Staplebops 02", + "sound.option.staplebops03": "Staplebops 03", + "sound.option.staplebops04": "Staplebops 04", + "sound.option.staplebops05": "Staplebops 05", + "sound.option.staplebops06": "Staplebops 06", + "sound.option.staplebops07": "Staplebops 07", + "sound.option.nope01": "Nope 01", + "sound.option.nope02": "Nope 02", + "sound.option.nope03": "Nope 03", + "sound.option.nope04": "Nope 04", + "sound.option.nope05": "Nope 05", + "sound.option.nope06": "Nope 06", + "sound.option.nope07": "Nope 07", + "sound.option.nope08": "Nope 08", + "sound.option.nope09": "Nope 09", + "sound.option.nope10": "Nope 10", + "sound.option.nope11": "Nope 11", + "sound.option.nope12": "Nope 12", + "sound.option.yup01": "Yup 01", + "sound.option.yup02": "Yup 02", + "sound.option.yup03": "Yup 03", + "sound.option.yup04": "Yup 04", + "sound.option.yup05": "Yup 05", + "sound.option.yup06": "Yup 06", + + "settings.general.notifications.agent.title": "เอเจนต์", + "settings.general.notifications.agent.description": "แสดงการแจ้งเตือนระบบเมื่อเอเจนต์เสร็จสิ้นหรือต้องการความสนใจ", + "settings.general.notifications.permissions.title": "สิทธิ์", + "settings.general.notifications.permissions.description": "แสดงการแจ้งเตือนระบบเมื่อต้องการสิทธิ์", + "settings.general.notifications.errors.title": "ข้อผิดพลาด", + "settings.general.notifications.errors.description": "แสดงการแจ้งเตือนระบบเมื่อเกิดข้อผิดพลาด", + + "settings.general.sounds.agent.title": "เอเจนต์", + "settings.general.sounds.agent.description": "เล่นเสียงเมื่อเอเจนต์เสร็จสิ้นหรือต้องการความสนใจ", + "settings.general.sounds.permissions.title": "สิทธิ์", + "settings.general.sounds.permissions.description": "เล่นเสียงเมื่อต้องการสิทธิ์", + "settings.general.sounds.errors.title": "ข้อผิดพลาด", + "settings.general.sounds.errors.description": "เล่นเสียงเมื่อเกิดข้อผิดพลาด", + + "settings.shortcuts.title": "ทางลัดแป้นพิมพ์", + "settings.shortcuts.reset.button": "รีเซ็ตเป็นค่าเริ่มต้น", + "settings.shortcuts.reset.toast.title": "รีเซ็ตทางลัดแล้ว", + "settings.shortcuts.reset.toast.description": "รีเซ็ตทางลัดแป้นพิมพ์เป็นค่าเริ่มต้นแล้ว", + "settings.shortcuts.conflict.title": "ทางลัดใช้งานอยู่แล้ว", + "settings.shortcuts.conflict.description": "{{keybind}} ถูกกำหนดให้กับ {{titles}} แล้ว", + "settings.shortcuts.unassigned": "ไม่ได้กำหนด", + "settings.shortcuts.pressKeys": "กดปุ่ม", + "settings.shortcuts.search.placeholder": "ค้นหาทางลัด", + "settings.shortcuts.search.empty": "ไม่พบทางลัด", + + "settings.shortcuts.group.general": "ทั่วไป", + "settings.shortcuts.group.session": "เซสชัน", + "settings.shortcuts.group.navigation": "การนำทาง", + "settings.shortcuts.group.modelAndAgent": "โมเดลและเอเจนต์", + "settings.shortcuts.group.terminal": "เทอร์มินัล", + "settings.shortcuts.group.prompt": "พร้อมท์", + + "settings.providers.title": "ผู้ให้บริการ", + "settings.providers.description": "การตั้งค่าผู้ให้บริการจะสามารถกำหนดค่าได้ที่นี่", + "settings.providers.section.connected": "ผู้ให้บริการที่เชื่อมต่อ", + "settings.providers.connected.empty": "ไม่มีผู้ให้บริการที่เชื่อมต่อ", + "settings.providers.section.popular": "ผู้ให้บริการยอดนิยม", + "settings.providers.tag.environment": "สภาพแวดล้อม", + "settings.providers.tag.config": "กำหนดค่า", + "settings.providers.tag.custom": "กำหนดเอง", + "settings.providers.tag.other": "อื่น ๆ", + "settings.models.title": "โมเดล", + "settings.models.description": "การตั้งค่าโมเดลจะสามารถกำหนดค่าได้ที่นี่", + "settings.agents.title": "เอเจนต์", + "settings.agents.description": "การตั้งค่าเอเจนต์จะสามารถกำหนดค่าได้ที่นี่", + "settings.commands.title": "คำสั่ง", + "settings.commands.description": "การตั้งค่าคำสั่งจะสามารถกำหนดค่าได้ที่นี่", + "settings.mcp.title": "MCP", + "settings.mcp.description": "การตั้งค่า MCP จะสามารถกำหนดค่าได้ที่นี่", + + "settings.permissions.title": "สิทธิ์", + "settings.permissions.description": "ควบคุมเครื่องมือที่เซิร์ฟเวอร์สามารถใช้โดยค่าเริ่มต้น", + "settings.permissions.section.tools": "เครื่องมือ", + "settings.permissions.toast.updateFailed.title": "ไม่สามารถอัปเดตสิทธิ์", + + "settings.permissions.action.allow": "อนุญาต", + "settings.permissions.action.ask": "ถาม", + "settings.permissions.action.deny": "ปฏิเสธ", + + "settings.permissions.tool.read.title": "อ่าน", + "settings.permissions.tool.read.description": "อ่านไฟล์ (ตรงกับเส้นทางไฟล์)", + "settings.permissions.tool.edit.title": "แก้ไข", + "settings.permissions.tool.edit.description": "แก้ไขไฟล์ รวมถึงการแก้ไข เขียน แพตช์ และแก้ไขหลายรายการ", + "settings.permissions.tool.glob.title": "Glob", + "settings.permissions.tool.glob.description": "จับคู่ไฟล์โดยใช้รูปแบบ glob", + "settings.permissions.tool.grep.title": "Grep", + "settings.permissions.tool.grep.description": "ค้นหาเนื้อหาไฟล์โดยใช้นิพจน์ทั่วไป", + "settings.permissions.tool.list.title": "รายการ", + "settings.permissions.tool.list.description": "แสดงรายการไฟล์ภายในไดเรกทอรี", + "settings.permissions.tool.bash.title": "Bash", + "settings.permissions.tool.bash.description": "เรียกใช้คำสั่งเชลล์", + "settings.permissions.tool.task.title": "งาน", + "settings.permissions.tool.task.description": "เปิดเอเจนต์ย่อย", + "settings.permissions.tool.skill.title": "ทักษะ", + "settings.permissions.tool.skill.description": "โหลดทักษะตามชื่อ", + "settings.permissions.tool.lsp.title": "LSP", + "settings.permissions.tool.lsp.description": "เรียกใช้การสืบค้นเซิร์ฟเวอร์ภาษา", + "settings.permissions.tool.todoread.title": "อ่านรายการงาน", + "settings.permissions.tool.todoread.description": "อ่านรายการงาน", + "settings.permissions.tool.todowrite.title": "เขียนรายการงาน", + "settings.permissions.tool.todowrite.description": "อัปเดตรายการงาน", + "settings.permissions.tool.webfetch.title": "ดึงข้อมูลจากเว็บ", + "settings.permissions.tool.webfetch.description": "ดึงเนื้อหาจาก URL", + "settings.permissions.tool.websearch.title": "ค้นหาเว็บ", + "settings.permissions.tool.websearch.description": "ค้นหาบนเว็บ", + "settings.permissions.tool.codesearch.title": "ค้นหาโค้ด", + "settings.permissions.tool.codesearch.description": "ค้นหาโค้ดบนเว็บ", + "settings.permissions.tool.external_directory.title": "ไดเรกทอรีภายนอก", + "settings.permissions.tool.external_directory.description": "เข้าถึงไฟล์นอกไดเรกทอรีโปรเจกต์", + "settings.permissions.tool.doom_loop.title": "Doom Loop", + "settings.permissions.tool.doom_loop.description": "ตรวจจับการเรียกเครื่องมือซ้ำด้วยข้อมูลนำเข้าเหมือนกัน", + + "session.delete.failed.title": "ไม่สามารถลบเซสชัน", + "session.delete.title": "ลบเซสชัน", + "session.delete.confirm": 'ลบเซสชัน "{{name}}" หรือไม่?', + "session.delete.button": "ลบเซสชัน", + + "workspace.new": "พื้นที่ทำงานใหม่", + "workspace.type.local": "ในเครื่อง", + "workspace.type.sandbox": "แซนด์บ็อกซ์", + "workspace.create.failed.title": "ไม่สามารถสร้างพื้นที่ทำงาน", + "workspace.delete.failed.title": "ไม่สามารถลบพื้นที่ทำงาน", + "workspace.resetting.title": "กำลังรีเซ็ตพื้นที่ทำงาน", + "workspace.resetting.description": "อาจใช้เวลาประมาณหนึ่งนาที", + "workspace.reset.failed.title": "ไม่สามารถรีเซ็ตพื้นที่ทำงาน", + "workspace.reset.success.title": "รีเซ็ตพื้นที่ทำงานแล้ว", + "workspace.reset.success.description": "พื้นที่ทำงานตรงกับสาขาเริ่มต้นแล้ว", + "workspace.error.stillPreparing": "พื้นที่ทำงานกำลังเตรียมอยู่", + "workspace.status.checking": "กำลังตรวจสอบการเปลี่ยนแปลงที่ไม่ได้ผสาน...", + "workspace.status.error": "ไม่สามารถตรวจสอบสถานะ git", + "workspace.status.clean": "ไม่ตรวจพบการเปลี่ยนแปลงที่ไม่ได้ผสาน", + "workspace.status.dirty": "ตรวจพบการเปลี่ยนแปลงที่ไม่ได้ผสานในพื้นที่ทำงานนี้", + "workspace.delete.title": "ลบพื้นที่ทำงาน", + "workspace.delete.confirm": 'ลบพื้นที่ทำงาน "{{name}}" หรือไม่?', + "workspace.delete.button": "ลบพื้นที่ทำงาน", + "workspace.reset.title": "รีเซ็ตพื้นที่ทำงาน", + "workspace.reset.confirm": 'รีเซ็ตพื้นที่ทำงาน "{{name}}" หรือไม่?', + "workspace.reset.button": "รีเซ็ตพื้นที่ทำงาน", + "workspace.reset.archived.none": "ไม่มีเซสชันที่ใช้งานอยู่จะถูกจัดเก็บ", + "workspace.reset.archived.one": "1 เซสชันจะถูกจัดเก็บ", + "workspace.reset.archived.many": "{{count}} เซสชันจะถูกจัดเก็บ", + "workspace.reset.note": "สิ่งนี้จะรีเซ็ตพื้นที่ทำงานให้ตรงกับสาขาเริ่มต้น", +} diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index c88188b0993..2266c109b08 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -37,12 +37,12 @@ export const dict = { "command.palette": "命令面板", "command.theme.cycle": "切换主题", - "command.theme.set": "使用主题: {{theme}}", + "command.theme.set": "使用主题:{{theme}}", "command.theme.scheme.cycle": "切换配色方案", - "command.theme.scheme.set": "使用配色方案: {{scheme}}", + "command.theme.scheme.set": "使用配色方案:{{scheme}}", "command.language.cycle": "切换语言", - "command.language.set": "使用语言: {{language}}", + "command.language.set": "使用语言:{{language}}", "command.session.new": "新建会话", "command.file.open": "打开文件", @@ -98,6 +98,10 @@ export const dict = { "dialog.provider.anthropic.note": "使用 Claude Pro/Max 或 API 密钥连接", "dialog.provider.openai.note": "使用 ChatGPT Pro/Plus 或 API 密钥连接", "dialog.provider.copilot.note": "使用 Copilot 或 API 密钥连接", + "dialog.provider.opencode.note": "使用 OpenCode Zen 或 API 密钥连接", + "dialog.provider.google.note": "使用 Google 账号或 API 密钥连接", + "dialog.provider.openrouter.note": "使用 OpenRouter 账号或 API 密钥连接", + "dialog.provider.vercel.note": "使用 Vercel 账号或 API 密钥连接", "dialog.model.select.title": "选择模型", "dialog.model.search.placeholder": "搜索模型", @@ -116,7 +120,7 @@ export const dict = { "provider.connect.method.apiKey": "API 密钥", "provider.connect.status.inProgress": "正在授权...", "provider.connect.status.waiting": "等待授权...", - "provider.connect.status.failed": "授权失败: {{error}}", + "provider.connect.status.failed": "授权失败:{{error}}", "provider.connect.apiKey.description": "输入你的 {{provider}} API 密钥以连接帐户,并在 OpenCode 中使用 {{provider}} 模型。", "provider.connect.apiKey.label": "{{provider}} API 密钥", @@ -156,7 +160,7 @@ export const dict = { "model.input.audio": "音频", "model.input.video": "视频", "model.input.pdf": "pdf", - "model.tooltip.allows": "支持: {{inputs}}", + "model.tooltip.allows": "支持:{{inputs}}", "model.tooltip.reasoning.allowed": "支持推理", "model.tooltip.reasoning.none": "不支持推理", "model.tooltip.context": "上下文上限 {{limit}}", @@ -181,30 +185,30 @@ export const dict = { "prompt.mode.shell.exit": "按 esc 退出", "prompt.example.1": "修复代码库中的一个 TODO", - "prompt.example.2": "这个项目的技术栈是什么?", + "prompt.example.2": "这个项目的技术栈是什么?", "prompt.example.3": "修复失败的测试", "prompt.example.4": "解释认证是如何工作的", "prompt.example.5": "查找并修复安全漏洞", "prompt.example.6": "为用户服务添加单元测试", "prompt.example.7": "重构这个函数,让它更易读", - "prompt.example.8": "这个错误是什么意思?", + "prompt.example.8": "这个错误是什么意思?", "prompt.example.9": "帮我调试这个问题", "prompt.example.10": "生成 API 文档", "prompt.example.11": "优化数据库查询", "prompt.example.12": "添加输入校验", "prompt.example.13": "创建一个新的组件用于...", - "prompt.example.14": "我该如何部署这个项目?", + "prompt.example.14": "我该如何部署这个项目?", "prompt.example.15": "审查我的代码并给出最佳实践建议", "prompt.example.16": "为这个函数添加错误处理", "prompt.example.17": "解释这个正则表达式", "prompt.example.18": "把它转换成 TypeScript", "prompt.example.19": "在整个代码库中添加日志", - "prompt.example.20": "哪些依赖已经过期?", + "prompt.example.20": "哪些依赖已经过期?", "prompt.example.21": "帮我写一个迁移脚本", "prompt.example.22": "为这个接口实现缓存", "prompt.example.23": "给这个列表添加分页", "prompt.example.24": "创建一个 CLI 命令用于...", - "prompt.example.25": "这里的环境变量是怎么工作的?", + "prompt.example.25": "这里的环境变量是怎么工作的?", "prompt.popover.emptyResults": "没有匹配的结果", "prompt.popover.emptyCommands": "没有匹配的命令", @@ -330,6 +334,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "语言", "toast.language.description": "已切换到{{language}}", @@ -377,31 +382,31 @@ export const dict = { "error.page.action.updateTo": "更新到 {{version}}", "error.page.report.prefix": "请将此错误报告给 OpenCode 团队", "error.page.report.discord": "在 Discord 上", - "error.page.version": "版本: {{version}}", + "error.page.version": "版本:{{version}}", - "error.dev.rootNotFound": "未找到根元素。你是不是忘了把它添加到 index.html? 或者 id 属性拼写错了?", + "error.dev.rootNotFound": "未找到根元素。你是不是忘了把它添加到 index.html?或者 id 属性拼写错了?", - "error.globalSync.connectFailed": "无法连接到服务器。是否有服务器正在 `{{url}}` 运行?", + "error.globalSync.connectFailed": "无法连接到服务器。是否有服务器正在 `{{url}}` 运行?", "error.chain.unknown": "未知错误", - "error.chain.causedBy": "原因:", + "error.chain.causedBy": "原因:", "error.chain.apiError": "API 错误", - "error.chain.status": "状态: {{status}}", - "error.chain.retryable": "可重试: {{retryable}}", - "error.chain.responseBody": "响应内容:\n{{body}}", - "error.chain.didYouMean": "你是不是想输入: {{suggestions}}", - "error.chain.modelNotFound": "未找到模型: {{provider}}/{{model}}", + "error.chain.status": "状态:{{status}}", + "error.chain.retryable": "可重试:{{retryable}}", + "error.chain.responseBody": "响应内容:\n{{body}}", + "error.chain.didYouMean": "你是不是想输入:{{suggestions}}", + "error.chain.modelNotFound": "未找到模型:{{provider}}/{{model}}", "error.chain.checkConfig": "请检查你的配置 (opencode.json) 中的 provider/model 名称", "error.chain.mcpFailed": 'MCP 服务器 "{{name}}" 启动失败。注意: OpenCode 暂不支持 MCP 认证。', - "error.chain.providerAuthFailed": "提供商认证失败 ({{provider}}): {{message}}", + "error.chain.providerAuthFailed": "提供商认证失败({{provider}}):{{message}}", "error.chain.providerInitFailed": '无法初始化提供商 "{{provider}}"。请检查凭据和配置。', "error.chain.configJsonInvalid": "配置文件 {{path}} 不是有效的 JSON(C)", - "error.chain.configJsonInvalidWithMessage": "配置文件 {{path}} 不是有效的 JSON(C): {{message}}", + "error.chain.configJsonInvalidWithMessage": "配置文件 {{path}} 不是有效的 JSON(C):{{message}}", "error.chain.configDirectoryTypo": '{{path}} 中的目录 "{{dir}}" 无效。请将目录重命名为 "{{suggestion}}" 或移除它。这是一个常见拼写错误。', - "error.chain.configFrontmatterError": "无法解析 {{path}} 中的 frontmatter:\n{{message}}", + "error.chain.configFrontmatterError": "无法解析 {{path}} 中的 frontmatter:\n{{message}}", "error.chain.configInvalid": "配置文件 {{path}} 无效", - "error.chain.configInvalidWithMessage": "配置文件 {{path}} 无效: {{message}}", + "error.chain.configInvalidWithMessage": "配置文件 {{path}} 无效:{{message}}", "notification.permission.title": "需要权限", "notification.permission.description": "{{sessionTitle}}({{projectName}})需要权限", @@ -438,7 +443,7 @@ export const dict = { "session.context.addToContext": "将 {{selection}} 添加到上下文", "session.new.worktree.main": "主分支", - "session.new.worktree.mainWithBranch": "主分支 ({{branch}})", + "session.new.worktree.mainWithBranch": "主分支({{branch}})", "session.new.worktree.create": "创建新的 worktree", "session.new.lastModified": "最后修改", @@ -521,7 +526,6 @@ export const dict = { "settings.general.row.theme.description": "自定义 OpenCode 的主题。", "settings.general.row.font.title": "字体", "settings.general.row.font.description": "自定义代码块使用的等宽字体", - "settings.general.row.releaseNotes.title": "发行说明", "settings.general.row.releaseNotes.description": "更新后显示“新功能”弹窗", @@ -685,7 +689,7 @@ export const dict = { "session.delete.failed.title": "删除会话失败", "session.delete.title": "删除会话", - "session.delete.confirm": '删除会话 "{{name}}"?', + "session.delete.confirm": '删除会话 "{{name}}"?', "session.delete.button": "删除会话", "workspace.new": "新建工作区", @@ -704,10 +708,10 @@ export const dict = { "workspace.status.clean": "未检测到未合并的更改。", "workspace.status.dirty": "检测到未合并的更改。", "workspace.delete.title": "删除工作区", - "workspace.delete.confirm": '删除工作区 "{{name}}"?', + "workspace.delete.confirm": '删除工作区 "{{name}}"?', "workspace.delete.button": "删除工作区", "workspace.reset.title": "重置工作区", - "workspace.reset.confirm": '重置工作区 "{{name}}"?', + "workspace.reset.confirm": '重置工作区 "{{name}}"?', "workspace.reset.button": "重置工作区", "workspace.reset.archived.none": "不会归档任何活跃会话。", "workspace.reset.archived.one": "将归档 1 个会话。", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index dcc3ad44ded..30837e56fb5 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -331,6 +331,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.th": "ไทย", "toast.language.title": "語言", "toast.language.description": "已切換到 {{language}}", diff --git a/packages/app/src/index.css b/packages/app/src/index.css index 4af87bca632..edfe0357e60 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -1 +1,218 @@ @import "@opencode-ai/ui/styles/tailwind"; + +:root { + /* Font is set via JavaScript in index.html */ + /* Default fallback handled in inline script */ + + /* Safe area insets for mobile devices (notch, home indicator, etc.) */ + --safe-area-inset-top: env(safe-area-inset-top, 0px); + --safe-area-inset-bottom: env(safe-area-inset-bottom, 0px); + --safe-area-inset-left: env(safe-area-inset-left, 0px); + --safe-area-inset-right: env(safe-area-inset-right, 0px); + + /* Keyboard offset for mobile terminal */ + --keyboard-offset: 0px; +} + +/* When keyboard is visible, adjust the main app container to shrink and stay above keyboard */ +[data-keyboard-visible="true"] #root { + height: calc(100% - var(--keyboard-offset)); + overflow: hidden; +} + +/* For fullscreen mobile terminal overlay, adjust bottom position when keyboard is visible */ +[data-component="mobile-terminal-fullscreen"][data-keyboard-visible="true"] { + bottom: var(--keyboard-offset); +} + +a { + cursor: default; +} + +/* Mobile breakpoint: 40rem = 640px */ +@media (max-width: 40rem) { + /* Prevent zoom on input focus */ + input, + textarea, + select, + button, + [contenteditable] { + font-size: 16px; + } + + /* Full-screen dialogs on mobile */ + [data-component="dialog"] { + margin-left: 0 !important; + padding: var(--safe-area-inset-top) var(--safe-area-inset-right) var(--safe-area-inset-bottom) + var(--safe-area-inset-left); + + [data-slot="dialog-container"] { + width: 100% !important; + height: 100% !important; + max-width: 100% !important; + max-height: 100% !important; + + &[data-size="sm"], + &[data-size="md"], + &[data-size="lg"] { + width: 100% !important; + height: 100% !important; + max-width: 100% !important; + max-height: 100% !important; + } + + [data-slot="dialog-content"] { + border-radius: 0; + border: none; + min-height: 100%; + height: 100%; + } + } + } + + /* Mobile header adjustments */ + header[data-tauri-drag-region] { + /* Padding handled by layout wrapper */ + } +} + +/* Ensure proper background and sizing */ +html, +body { + background-color: var(--background-base); + height: 100%; +} + +#root { + height: 100%; +} + +/* PWA standalone mode specific styles */ +@media (display-mode: standalone) { + :root { + /* When running as PWA, ensure we account for device UI */ + --pwa-top-offset: var(--safe-area-inset-top); + --pwa-bottom-offset: var(--safe-area-inset-bottom); + } + + /* Critical iOS viewport locking - prevents rubber-banding on html/body */ + html { + position: fixed; + width: 100%; + height: 100%; + overflow: hidden; + overscroll-behavior: none; + } + + body { + position: fixed; + width: 100%; + height: 100%; + overflow: hidden; + overscroll-behavior: none; + } + + /* Root container must also be fixed to prevent viewport bounce */ + #root { + position: fixed; + inset: 0; + overflow: hidden; + overscroll-behavior: none; + /* Safe areas handled by child components (header/prompt-dock) */ + } + + .home-menu-button { + top: var(--safe-area-inset-top); + } + + /* Scroll containers should contain overscroll within their bounds */ + .session-scroll-container { + overscroll-behavior: contain; + -webkit-overflow-scrolling: touch; + } +} + +/* PWA standalone mode - safe area handling for iOS */ +@media (display-mode: standalone) { + /* Header should clear the Dynamic Island */ + header[data-tauri-drag-region] { + padding-top: var(--safe-area-inset-top); + min-height: calc(3rem + var(--safe-area-inset-top)); + } +} + +/* Mobile PWA prompt container bottom padding */ +@media (display-mode: standalone) and (max-width: 40rem) { + /* Adjust bottom positioning for mobile prompt input */ + [data-component="prompt-dock"] { + padding-bottom: max(1.5rem, var(--safe-area-inset-bottom)); + } +} + +/* Touch-friendly improvements */ +@media (pointer: coarse) { + /* Larger touch targets */ + button, + [role="button"], + a { + min-height: 44px; + min-width: 44px; + } + + /* Exception for inline/small buttons that are part of larger components */ + [data-slot="icon-button"], + [data-component="icon-button"] { + min-height: 32px; + min-width: 32px; + } +} + +[data-component="markdown"] ul { + list-style: disc outside; + padding-left: 1.5rem; +} + +[data-component="markdown"] ol { + list-style: decimal outside; + padding-left: 1.5rem; +} + +[data-component="markdown"] li > p:first-child { + display: inline; + margin: 0; +} + +[data-component="markdown"] li > p + p { + display: block; + margin-top: 0.5rem; +} + +*[data-tauri-drag-region] { + app-region: drag; +} + +.session-scroller::-webkit-scrollbar { + width: 10px !important; + height: 10px !important; +} + +.session-scroller::-webkit-scrollbar-track { + background: transparent !important; + border-radius: 5px !important; +} + +.session-scroller::-webkit-scrollbar-thumb { + background: var(--border-weak-base) !important; + border-radius: 5px !important; + border: 3px solid transparent !important; + background-clip: padding-box !important; +} + +.session-scroller::-webkit-scrollbar-thumb:hover { + background: var(--border-weak-base) !important; +} + +.session-scroller { + scrollbar-width: thin !important; + scrollbar-color: var(--border-weak-base) transparent !important; +} diff --git a/packages/app/src/pages/error.tsx b/packages/app/src/pages/error.tsx index 6d6faf6fa3b..f27ec414c35 100644 --- a/packages/app/src/pages/error.tsx +++ b/packages/app/src/pages/error.tsx @@ -1,5 +1,5 @@ import { TextField } from "@opencode-ai/ui/text-field" -import { Logo } from "@opencode-ai/ui/logo" +import { AsciiLogo } from "@opencode-ai/ui/logo" import { Button } from "@opencode-ai/ui/button" import { Component, Show } from "solid-js" import { createStore } from "solid-js/store" @@ -231,7 +231,7 @@ export const ErrorPage: Component = (props) => { return (
- +

{language.t("error.page.title")}

{language.t("error.page.description")}

@@ -272,7 +272,7 @@ export const ErrorPage: Component = (props) => { 0}> -
-
-
{language.t("home.recentProjects")}
-
    @@ -94,13 +74,13 @@ export default function Home() { )} @@ -111,12 +91,12 @@ export default function Home() {
    -
    {language.t("home.empty.title")}
    -
    {language.t("home.empty.description")}
    +
    No recent projects
    +
    Get started by adding a project
    -
    diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index afef14c84a2..7fdd8e24d80 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -37,7 +37,7 @@ import { Spinner } from "@opencode-ai/ui/spinner" import { Dialog } from "@opencode-ai/ui/dialog" import { getFilename } from "@opencode-ai/util/path" import { Session, type Message, type TextPart } from "@opencode-ai/sdk/v2/client" -import { usePlatform } from "@/context/platform" +import { usePlatform, isPWA } from "@/context/platform" import { useSettings } from "@/context/settings" import { createStore, produce, reconcile } from "solid-js/store" import { @@ -55,6 +55,7 @@ import { useGlobalSDK } from "@/context/global-sdk" import { useNotification } from "@/context/notification" import { usePermission } from "@/context/permission" import { Binary } from "@opencode-ai/util/binary" +import { PullToRefresh } from "@/components/pull-to-refresh" import { retry } from "@opencode-ai/util/retry" import { playSound, soundSrc } from "@/utils/sound" import { Worktree as WorktreeState } from "@/utils/worktree" @@ -69,6 +70,7 @@ import { useCommand, type CommandOption } from "@/context/command" import { ConstrainDragXAxis } from "@/utils/solid-dnd" import { navStart } from "@/utils/perf" import { DialogSelectDirectory } from "@/components/dialog-select-directory" +import { DialogSessionRenameGlobal } from "@/components/dialog-session-rename-global" import { DialogEditProject } from "@/components/dialog-edit-project" import { Titlebar } from "@/components/titlebar" import { useServer } from "@/context/server" @@ -91,6 +93,18 @@ export default function Layout(props: ParentProps) { const pageReady = createMemo(() => ready()) let scrollContainerRef: HTMLDivElement | undefined + const smQuery = window.matchMedia("(min-width: 640px)") + const xlQuery = window.matchMedia("(min-width: 1280px)") + const [isSmallViewport, setIsSmallViewport] = createSignal(smQuery.matches) + const [isLargeViewport, setIsLargeViewport] = createSignal(xlQuery.matches) + const handleSmallViewportChange = (e: MediaQueryListEvent) => setIsSmallViewport(e.matches) + const handleLargeViewportChange = (e: MediaQueryListEvent) => setIsLargeViewport(e.matches) + smQuery.addEventListener("change", handleSmallViewportChange) + xlQuery.addEventListener("change", handleLargeViewportChange) + onCleanup(() => { + smQuery.removeEventListener("change", handleSmallViewportChange) + xlQuery.removeEventListener("change", handleLargeViewportChange) + }) const params = useParams() const globalSDK = useGlobalSDK() @@ -2771,12 +2785,12 @@ export default function Layout(props: ParentProps) { return (
    -
    +
    + } + > +
    {props.children}
    +
    diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 7b4f31c50df..6a8bb9fa4ce 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -23,7 +23,6 @@ import { IconButton } from "@opencode-ai/ui/icon-button" import { Button } from "@opencode-ai/ui/button" import { Icon } from "@opencode-ai/ui/icon" import { Tooltip, TooltipKeybind } 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" @@ -32,7 +31,7 @@ import { SessionTurn } from "@opencode-ai/ui/session-turn" import { BasicTool } from "@opencode-ai/ui/basic-tool" import { createAutoScroll } from "@opencode-ai/ui/hooks" import { SessionReview } from "@opencode-ai/ui/session-review" -import { Mark } from "@opencode-ai/ui/logo" +import { AsciiMark } from "@opencode-ai/ui/logo" import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd" import type { DragEvent } from "@thisbeyond/solid-dnd" @@ -325,6 +324,7 @@ export default function Page() { } const isDesktop = createMediaQuery("(min-width: 768px)") + const centered = createMemo(() => isDesktop() && !layout.fileTree.opened()) function normalizeTab(tab: string) { if (!tab.startsWith("file://")) return tab @@ -730,8 +730,8 @@ export default function Page() { onSelect: () => view().terminal.toggle(), }, { - id: "fileTree.toggle", - title: language.t("command.fileTree.toggle"), + id: "review.toggle", + title: language.t("command.review.toggle"), description: "", category: language.t("command.category.view"), keybind: "mod+shift+r", @@ -1097,12 +1097,11 @@ export default function Page() { }, 0) } - const reviewTab = createMemo(() => isDesktop() && !layout.fileTree.opened()) const contextOpen = createMemo(() => tabs().active() === "context" || tabs().all().includes("context")) const openedTabs = createMemo(() => tabs() .all() - .filter((tab) => tab !== "context" && tab !== "review"), + .filter((tab) => tab !== "context"), ) const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes") @@ -1159,7 +1158,7 @@ export default function Page() {
    - +
    {language.t("session.review.empty")}
    @@ -1271,56 +1270,29 @@ export default function Page() { const activeTab = createMemo(() => { const active = tabs().active() if (active === "context") return "context" - if (active === "review" && reviewTab()) return "review" if (active && file.pathFromTab(active)) return normalizeTab(active) const first = openedTabs()[0] if (first) return first if (contextOpen()) return "context" - if (reviewTab() && hasReview()) return "review" return "empty" }) createEffect(() => { if (!layout.ready()) return if (tabs().active()) return - if (openedTabs().length === 0 && !contextOpen() && !(reviewTab() && hasReview())) return + if (openedTabs().length === 0 && !contextOpen()) return const next = activeTab() if (next === "empty") return tabs().setActive(next) }) - createEffect( - on( - () => layout.fileTree.opened(), - (opened, prev) => { - if (prev === undefined) return - if (!isDesktop()) return - - if (opened) { - const active = tabs().active() - const tab = active === "review" || (!active && hasReview()) ? "changes" : "all" - layout.fileTree.setTab(tab) - return - } - - if (fileTreeTab() !== "changes") return - tabs().setActive("review") - }, - { defer: true }, - ), - ) - createEffect(() => { const id = params.id if (!id) return - const wants = isDesktop() - ? layout.fileTree.opened() - ? fileTreeTab() === "changes" - : activeTab() === "review" - : store.mobileTab === "changes" + const wants = isDesktop() ? layout.fileTree.opened() && fileTreeTab() === "changes" : store.mobileTab === "changes" if (!wants) return if (sync.data.session_diff[id] !== undefined) return if (sync.status === "loading") return @@ -1789,10 +1761,11 @@ export default function Page() {
    @@ -1839,7 +1812,7 @@ export default function Page() {
    - +
    {language.t("session.review.empty")}
    @@ -1967,6 +1940,7 @@ export default function Page() { "sticky top-0 z-30 bg-background-stronger": true, "w-full": true, "px-4 md:px-6": true, + "md:max-w-200 md:mx-auto": centered(), }} >
    @@ -1991,7 +1965,13 @@ export default function Page() {
    0}>
    @@ -2039,7 +2019,10 @@ export default function Page() {
    (promptDock = el)} class="absolute inset-x-0 bottom-0 pt-12 pb-4 flex flex-col justify-center items-center z-50 px-4 md:px-0 bg-gradient-to-t from-background-stronger via-background-stronger to-transparent pointer-events-none" > -
    +
    {(perm) => (
    @@ -2163,7 +2151,7 @@ export default function Page() {
    - + {/* Desktop side panel - hidden on mobile */} - +