diff --git a/.github/workflows/docs-update.yml b/.github/workflows/docs-update.yml index bdb3e43b8..11d6a9c82 100644 --- a/.github/workflows/docs-update.yml +++ b/.github/workflows/docs-update.yml @@ -2,13 +2,13 @@ name: Docs Update on: schedule: - # Run every 4 hours - - cron: "0 */4 * * *" - workflow_dispatch: # Allow manual trigger for testing + - cron: "0 */12 * * *" + workflow_dispatch: jobs: update-docs: - runs-on: ubuntu-latest + if: github.repository == 'sst/opencode' + runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: id-token: write contents: write @@ -19,6 +19,9 @@ jobs: 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: | @@ -62,4 +65,5 @@ jobs: 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. - All doc related commits should start with "docs:" prefix. + 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/publish.yml b/.github/workflows/publish.yml index 9e6b49339..ec98d7061 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -151,12 +151,12 @@ jobs: - uses: Swatinem/rust-cache@v2 with: - workspaces: packages/tauri/src-tauri + workspaces: packages/desktop/src-tauri shared-key: ${{ matrix.settings.target }} - name: Prepare run: | - cd packages/tauri + cd packages/desktop bun ./scripts/prepare.ts env: OPENCODE_VERSION: ${{ needs.publish.outputs.version }} @@ -191,7 +191,7 @@ jobs: APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8 with: - projectPath: packages/tauri + projectPath: packages/desktop uploadWorkflowArtifacts: true tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }} args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose diff --git a/.opencode/skill/test-skill/SKILL.md b/.opencode/skill/test-skill/SKILL.md new file mode 100644 index 000000000..3fef059f2 --- /dev/null +++ b/.opencode/skill/test-skill/SKILL.md @@ -0,0 +1,6 @@ +--- +name: test-skill +description: use this when asked to test skill +--- + +woah this is a test skill diff --git a/AGENTS.md b/AGENTS.md index 5a95fc509..bbb2a96f2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,31 +4,4 @@ ## Tool Calling -- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE. Here is an example illustrating how to execute 3 parallel file reads in this chat environment: - -json -{ -"recipient_name": "multi_tool_use.parallel", -"parameters": { -"tool_uses": [ -{ -"recipient_name": "functions.read", -"parameters": { -"filePath": "path/to/file.tsx" -} -}, -{ -"recipient_name": "functions.read", -"parameters": { -"filePath": "path/to/file.ts" -} -}, -{ -"recipient_name": "functions.read", -"parameters": { -"filePath": "path/to/file.md" -} -} -] -} -} +- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE. diff --git a/bun.lock b/bun.lock index 706347b59..f98fe1951 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,13 @@ "": { "name": "opencode", "dependencies": { + "@ai-sdk/cerebras": "1.0.33", + "@ai-sdk/cohere": "2.0.21", + "@ai-sdk/deepinfra": "1.0.30", + "@ai-sdk/gateway": "2.0.23", + "@ai-sdk/groq": "2.0.33", + "@ai-sdk/perplexity": "2.0.22", + "@ai-sdk/togetherai": "1.0.30", "@aws-sdk/client-s3": "3.933.0", "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", @@ -20,112 +27,9 @@ "turbo": "2.5.6", }, }, - "packages/console/app": { - "name": "@opencode-ai/console-app", - "version": "1.0.186", - "dependencies": { - "@cloudflare/vite-plugin": "1.15.2", - "@ibm/plex": "6.4.1", - "@jsx-email/render": "1.1.1", - "@kobalte/core": "catalog:", - "@openauthjs/openauth": "catalog:", - "@opencode-ai/console-core": "workspace:*", - "@opencode-ai/console-mail": "workspace:*", - "@opencode-ai/console-resource": "workspace:*", - "@opencode-ai/ui": "workspace:*", - "@solidjs/meta": "catalog:", - "@solidjs/router": "catalog:", - "@solidjs/start": "catalog:", - "chart.js": "4.5.1", - "nitro": "3.0.1-alpha.1", - "solid-js": "catalog:", - "vite": "catalog:", - "zod": "catalog:", - }, - "devDependencies": { - "@typescript/native-preview": "catalog:", - "typescript": "catalog:", - "wrangler": "4.50.0", - }, - }, - "packages/console/core": { - "name": "@opencode-ai/console-core", - "version": "1.0.186", - "dependencies": { - "@aws-sdk/client-sts": "3.782.0", - "@jsx-email/render": "1.1.1", - "@opencode-ai/console-mail": "workspace:*", - "@opencode-ai/console-resource": "workspace:*", - "@planetscale/database": "1.19.0", - "aws4fetch": "1.0.20", - "drizzle-orm": "0.41.0", - "postgres": "3.4.7", - "stripe": "18.0.0", - "ulid": "catalog:", - "zod": "catalog:", - }, - "devDependencies": { - "@cloudflare/workers-types": "catalog:", - "@tsconfig/node22": "22.0.2", - "@types/bun": "1.3.0", - "@types/node": "catalog:", - "@typescript/native-preview": "catalog:", - "drizzle-kit": "0.30.5", - "mysql2": "3.14.4", - "typescript": "catalog:", - }, - }, - "packages/console/function": { - "name": "@opencode-ai/console-function", - "version": "1.0.186", - "dependencies": { - "@ai-sdk/anthropic": "2.0.0", - "@ai-sdk/openai": "2.0.2", - "@ai-sdk/openai-compatible": "1.0.1", - "@hono/zod-validator": "catalog:", - "@openauthjs/openauth": "0.0.0-20250322224806", - "@opencode-ai/console-core": "workspace:*", - "@opencode-ai/console-resource": "workspace:*", - "ai": "catalog:", - "hono": "catalog:", - "zod": "catalog:", - }, - "devDependencies": { - "@cloudflare/workers-types": "catalog:", - "@tsconfig/node22": "22.0.2", - "@types/node": "catalog:", - "@typescript/native-preview": "catalog:", - "openai": "5.11.0", - "typescript": "catalog:", - }, - }, - "packages/console/mail": { - "name": "@opencode-ai/console-mail", - "version": "1.0.186", - "dependencies": { - "@jsx-email/all": "2.2.3", - "@jsx-email/cli": "1.4.3", - "@tsconfig/bun": "1.0.9", - "@types/react": "18.0.25", - "react": "18.2.0", - "solid-js": "catalog:", - }, - }, - "packages/console/resource": { - "name": "@opencode-ai/console-resource", - "dependencies": { - "@cloudflare/workers-types": "catalog:", - }, - "devDependencies": { - "@cloudflare/workers-types": "catalog:", - "@tsconfig/node22": "22.0.2", - "@types/node": "catalog:", - "cloudflare": "5.2.0", - }, - }, - "packages/desktop": { - "name": "@opencode-ai/desktop", - "version": "1.0.186", + "packages/app": { + "name": "@opencode-ai/app", + "version": "1.0.191", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -171,9 +75,139 @@ "vite-plugin-solid": "catalog:", }, }, + "packages/console/app": { + "name": "@opencode-ai/console-app", + "version": "1.0.191", + "dependencies": { + "@cloudflare/vite-plugin": "1.15.2", + "@ibm/plex": "6.4.1", + "@jsx-email/render": "1.1.1", + "@kobalte/core": "catalog:", + "@openauthjs/openauth": "catalog:", + "@opencode-ai/console-core": "workspace:*", + "@opencode-ai/console-mail": "workspace:*", + "@opencode-ai/console-resource": "workspace:*", + "@opencode-ai/ui": "workspace:*", + "@solidjs/meta": "catalog:", + "@solidjs/router": "catalog:", + "@solidjs/start": "catalog:", + "chart.js": "4.5.1", + "nitro": "3.0.1-alpha.1", + "solid-js": "catalog:", + "vite": "catalog:", + "zod": "catalog:", + }, + "devDependencies": { + "@typescript/native-preview": "catalog:", + "typescript": "catalog:", + "wrangler": "4.50.0", + }, + }, + "packages/console/core": { + "name": "@opencode-ai/console-core", + "version": "1.0.191", + "dependencies": { + "@aws-sdk/client-sts": "3.782.0", + "@jsx-email/render": "1.1.1", + "@opencode-ai/console-mail": "workspace:*", + "@opencode-ai/console-resource": "workspace:*", + "@planetscale/database": "1.19.0", + "aws4fetch": "1.0.20", + "drizzle-orm": "0.41.0", + "postgres": "3.4.7", + "stripe": "18.0.0", + "ulid": "catalog:", + "zod": "catalog:", + }, + "devDependencies": { + "@cloudflare/workers-types": "catalog:", + "@tsconfig/node22": "22.0.2", + "@types/bun": "1.3.0", + "@types/node": "catalog:", + "@typescript/native-preview": "catalog:", + "drizzle-kit": "0.30.5", + "mysql2": "3.14.4", + "typescript": "catalog:", + }, + }, + "packages/console/function": { + "name": "@opencode-ai/console-function", + "version": "1.0.191", + "dependencies": { + "@ai-sdk/anthropic": "2.0.0", + "@ai-sdk/openai": "2.0.2", + "@ai-sdk/openai-compatible": "1.0.1", + "@hono/zod-validator": "catalog:", + "@openauthjs/openauth": "0.0.0-20250322224806", + "@opencode-ai/console-core": "workspace:*", + "@opencode-ai/console-resource": "workspace:*", + "ai": "catalog:", + "hono": "catalog:", + "zod": "catalog:", + }, + "devDependencies": { + "@cloudflare/workers-types": "catalog:", + "@tsconfig/node22": "22.0.2", + "@types/node": "catalog:", + "@typescript/native-preview": "catalog:", + "openai": "5.11.0", + "typescript": "catalog:", + }, + }, + "packages/console/mail": { + "name": "@opencode-ai/console-mail", + "version": "1.0.191", + "dependencies": { + "@jsx-email/all": "2.2.3", + "@jsx-email/cli": "1.4.3", + "@tsconfig/bun": "1.0.9", + "@types/react": "18.0.25", + "react": "18.2.0", + "solid-js": "catalog:", + }, + }, + "packages/console/resource": { + "name": "@opencode-ai/console-resource", + "dependencies": { + "@cloudflare/workers-types": "catalog:", + }, + "devDependencies": { + "@cloudflare/workers-types": "catalog:", + "@tsconfig/node22": "22.0.2", + "@types/node": "catalog:", + "cloudflare": "5.2.0", + }, + }, + "packages/desktop": { + "name": "@opencode-ai/desktop", + "version": "1.0.191", + "dependencies": { + "@opencode-ai/app": "workspace:*", + "@solid-primitives/storage": "catalog:", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "~2", + "@tauri-apps/plugin-http": "~2", + "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-os": "~2", + "@tauri-apps/plugin-process": "~2", + "@tauri-apps/plugin-shell": "~2", + "@tauri-apps/plugin-store": "~2", + "@tauri-apps/plugin-updater": "~2", + "@tauri-apps/plugin-window-state": "~2", + "solid-js": "catalog:", + }, + "devDependencies": { + "@actions/artifact": "4.0.0", + "@tauri-apps/cli": "^2", + "@types/bun": "catalog:", + "@typescript/native-preview": "catalog:", + "typescript": "~5.6.2", + "vite": "catalog:", + }, + }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.0.186", + "version": "1.0.191", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -202,7 +236,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.0.186", + "version": "1.0.191", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -218,7 +252,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.0.186", + "version": "1.0.191", "bin": { "opencode": "./bin/opencode", }, @@ -232,10 +266,12 @@ "@ai-sdk/google": "2.0.44", "@ai-sdk/google-vertex": "3.0.81", "@ai-sdk/mcp": "0.0.8", + "@ai-sdk/mistral": "2.0.26", "@ai-sdk/openai": "2.0.71", "@ai-sdk/openai-compatible": "1.0.27", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18", + "@ai-sdk/xai": "2.0.42", "@clack/prompts": "1.0.0-alpha.1", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", @@ -248,8 +284,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "1.5.2", - "@opentui/core": "0.1.62", - "@opentui/solid": "0.1.62", + "@opentui/core": "0.1.63", + "@opentui/solid": "0.1.63", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -310,7 +346,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.0.186", + "version": "1.0.191", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -330,7 +366,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.0.186", + "version": "1.0.191", "devDependencies": { "@hey-api/openapi-ts": "0.88.1", "@tsconfig/node22": "catalog:", @@ -341,7 +377,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.0.186", + "version": "1.0.191", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -352,36 +388,9 @@ "typescript": "catalog:", }, }, - "packages/tauri": { - "name": "@opencode-ai/tauri", - "version": "1.0.186", - "dependencies": { - "@opencode-ai/desktop": "workspace:*", - "@solid-primitives/storage": "catalog:", - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-dialog": "~2", - "@tauri-apps/plugin-http": "~2", - "@tauri-apps/plugin-opener": "^2", - "@tauri-apps/plugin-os": "~2", - "@tauri-apps/plugin-process": "~2", - "@tauri-apps/plugin-shell": "~2", - "@tauri-apps/plugin-store": "~2", - "@tauri-apps/plugin-updater": "~2", - "@tauri-apps/plugin-window-state": "~2", - "solid-js": "catalog:", - }, - "devDependencies": { - "@actions/artifact": "4.0.0", - "@tauri-apps/cli": "^2", - "@types/bun": "catalog:", - "@typescript/native-preview": "catalog:", - "typescript": "~5.6.2", - "vite": "catalog:", - }, - }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.0.186", + "version": "1.0.191", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -416,7 +425,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.0.186", + "version": "1.0.191", "dependencies": { "zod": "catalog:", }, @@ -427,7 +436,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.0.186", + "version": "1.0.191", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -446,7 +455,6 @@ "marked-shiki": "1.2.1", "rehype-autolink-headings": "7.1.0", "remeda": "catalog:", - "sharp": "0.32.5", "shiki": "3.4.2", "solid-js": "catalog:", "toolbeam-docs-theme": "0.4.8", @@ -459,7 +467,6 @@ }, }, "trustedDependencies": [ - "sharp", "esbuild", "web-tree-sitter", "tree-sitter-bash", @@ -529,22 +536,38 @@ "@ai-sdk/azure": ["@ai-sdk/azure@2.0.73", "", { "dependencies": { "@ai-sdk/openai": "2.0.71", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LpAg3Ak/V3WOemBu35Qbx9jfQfApsHNXX9p3bXVsnRu3XXi1QQUt5gMOCIb4znPonz+XnHenIDZMBwdsb1TfRQ=="], - "@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.12", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W+cB1sOWvPcz9qiIsNtD+HxUrBUva2vWv2K1EFukuImX+HA0uZx3EyyOjhYQ9gtf/teqEG80M6OvJ7xx/VLV2A=="], + "@ai-sdk/cerebras": ["@ai-sdk/cerebras@1.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2gSSS/7kunIwMdC4td5oWsUAzoLw84ccGpz6wQbxVnrb1iWnrEnKa5tRBduaP6IXpzLWsu8wME3+dQhZy+gT7w=="], + + "@ai-sdk/cohere": ["@ai-sdk/cohere@2.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZjaZFvJlc5XOPi3QwTLEFZbHIgTJc6YGvxz+8zIMGVZi/hdynR8/f/C1A9x6mhzmBtAqi/dZ2h11oouAQH5z4g=="], + + "@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.30", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XK8oRZFApzo6xnS5C+FhWUUkB2itA5Nfon3pU9dJVM0goViq8GwdleZTBRqhu4DE4KJURo5DGWpJr2hfV54cEg=="], + + "@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-qmX7afPRszUqG5hryHF3UN8ITPIRSGmDW6VYCmByzjoUkgm3MekzSx2hMV1wr0P+llDeuXb378SjqUfpvWJulg=="], "@ai-sdk/google": ["@ai-sdk/google@2.0.44", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-c5dck36FjqiVoeeMJQLTEmUheoURcGTU/nBT6iJu8/nZiKFT/y8pD85KMDRB7RerRYaaQOtslR2d6/5PditiRw=="], "@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.81", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.50", "@ai-sdk/google": "2.0.44", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18", "google-auth-library": "^9.15.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yrl5Ug0Mqwo9ya45oxczgy2RWgpEA/XQQCSFYP+3NZMQ4yA3Iim1vkOjVCsGaZZ8rjVk395abi1ZMZV0/6rqVA=="], + "@ai-sdk/groq": ["@ai-sdk/groq@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FWGl7xNr88NBveao3y9EcVWYUt9ABPrwLFY7pIutSNgaTf32vgvyhREobaMrLU4Scr5G/2tlNqOPZ5wkYMaZig=="], + "@ai-sdk/mcp": ["@ai-sdk/mcp@0.0.8", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "pkce-challenge": "^5.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9y9GuGcZ9/+pMIHfpOCJgZVp+AZMv6TkjX2NVT17SQZvTF2N8LXuCXyoUPyi1PxIxzxl0n463LxxaB2O6olC+Q=="], + "@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.26", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jxDB++4WI1wEx5ONNBI+VbkmYJOYIuS8UQY13/83UGRaiW7oB/WHiH4ETe6KzbKpQPB3XruwTJQjUMsMfKyTXA=="], + "@ai-sdk/openai": ["@ai-sdk/openai@2.0.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="], "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-luHVcU+yKzwv3ekKgbP3v+elUVxb2Rt+8c6w9qi7g2NYG2/pEL21oIrnaEnc6UtTZLLZX9EFBcpq2N1FQKDIMw=="], + "@ai-sdk/perplexity": ["@ai-sdk/perplexity@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zwzcnk08R2J3mZcQPn4Ifl4wYGrvANR7jsBB0hCTUSbb+Rx3ybpikSWiGuXQXxdiRc1I5MWXgj70m+bZaLPvHw=="], + "@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="], "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="], + "@ai-sdk/togetherai": ["@ai-sdk/togetherai@1.0.30", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9bxQbIXnWSN4bNismrza3NvIo+ui/Y3pj3UN6e9vCszCWFCN45RgISi4oDe10RqmzaJ/X8cfO/Tem+K8MT3wGQ=="], + + "@ai-sdk/xai": ["@ai-sdk/xai@2.0.42", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wlwO4yRoZ/d+ca29vN8SDzxus7POdnL7GBTyRdSrt6icUF0hooLesauC8qRUC4aLxtqvMEc1YHtJOU7ZnLWbTQ=="], + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], @@ -1125,6 +1148,8 @@ "@openauthjs/openauth": ["@openauthjs/openauth@0.0.0-20250322224806", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-p5IWSRXvABcwocH2dNI0w8c1QJelIOFulwhKk+aLLFfUbs8u1pr7kQbYe8yCSM2+bcLHiwbogpUQc2ovrGwCuw=="], + "@opencode-ai/app": ["@opencode-ai/app@workspace:packages/app"], + "@opencode-ai/console-app": ["@opencode-ai/console-app@workspace:packages/console/app"], "@opencode-ai/console-core": ["@opencode-ai/console-core@workspace:packages/console/core"], @@ -1149,8 +1174,6 @@ "@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"], - "@opencode-ai/tauri": ["@opencode-ai/tauri@workspace:packages/tauri"], - "@opencode-ai/ui": ["@opencode-ai/ui@workspace:packages/ui"], "@opencode-ai/util": ["@opencode-ai/util@workspace:packages/util"], @@ -1163,21 +1186,21 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@opentui/core": ["@opentui/core@0.1.62", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.62", "@opentui/core-darwin-x64": "0.1.62", "@opentui/core-linux-arm64": "0.1.62", "@opentui/core-linux-x64": "0.1.62", "@opentui/core-win32-arm64": "0.1.62", "@opentui/core-win32-x64": "0.1.62", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-T9wsXaS4rFoZF2loaEFqAeuGj5DV3pJzrk18z1um3UfUS2NNH4jyDh5rDdHPb2/YrvO1lU9hd0VoAS/7zUAq/w=="], + "@opentui/core": ["@opentui/core@0.1.63", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.63", "@opentui/core-darwin-x64": "0.1.63", "@opentui/core-linux-arm64": "0.1.63", "@opentui/core-linux-x64": "0.1.63", "@opentui/core-win32-arm64": "0.1.63", "@opentui/core-win32-x64": "0.1.63", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-m4xZQTNCnHXWUWCnGvacJ3Gts1H2aMwP5V/puAG77SDb51jm4W/QOyqAAdgeSakkb9II+8FfUpApX7sfwRXPUg=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.62", "", { "os": "darwin", "cpu": "arm64" }, "sha512-IohPhCkD/DbZEH4M5ft1/o1pI6Vvw2pdxdyoouW/TO1g21W5G8usaWTSRDXO+16BT115Nfb9/DT69H5pzAc2Eg=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.63", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jKCThZGiiublKkP/hMtDtl1MLCw5NU0hMNJdEYvz1WLT9bzliWf6Kb7MIDAmk32XlbQW8/RHdp+hGyGDXK62OQ=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.62", "", { "os": "darwin", "cpu": "x64" }, "sha512-BqbjQl2sLYrJ1Pq1b3H1I2CFedRiMz0QtZX08IMbyZ5kok+J0A8eQS5tmlbfqoS/VH0de9XiEbuHjG09/nSj1A=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.63", "", { "os": "darwin", "cpu": "x64" }, "sha512-rfNxynHzJpxN9i+SAMnn1NToEc8rYj64BsOxY78JNsm4Gg1Js1uyMaawwh2WbdGknFy4cDXS9QwkUMdMcfnjiw=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.62", "", { "os": "linux", "cpu": "arm64" }, "sha512-P5FleF+W8O4uGubqBvV8DB1AK0+fJhJS8HvfmTZQ2DhSSJJH9Af/WXqitD7ILQY9ltlaUP7l38BC5cVdxnWzCQ=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.63", "", { "os": "linux", "cpu": "arm64" }, "sha512-wG9d6mHWWKZGrzxYS4c+BrcEGXBv/MYBUPSyjP/lD0CxT+X3h6CYhI317JkRyMNfh3vI9CpAKGFTOFvrTTHimQ=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.62", "", { "os": "linux", "cpu": "x64" }, "sha512-l9ab5tgOGcdf8k3NU4TzK/3C8UC0+QuMxgLA/j60BhB1e9bwJleFeYJc+wLIktTUu9QwqCsU4YcuGHL+C2lCzA=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.63", "", { "os": "linux", "cpu": "x64" }, "sha512-TKSzFv4BgWW3RB/iZmq5qxTR4/tRaXo8IZNnVR+LFzShbPOqhUi466AByy9SUmCxD8uYjmMDFYfKtkCy0AnAwA=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.62", "", { "os": "win32", "cpu": "arm64" }, "sha512-U1zsOpQl3EGhs8BwoehKAwwVONe+XOXRnXTxMhXw8huF0WWXDWOUL5psjBvfSWPm1rLmagxkQsH84jTSWA/vLA=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.63", "", { "os": "win32", "cpu": "arm64" }, "sha512-CBWPyPognERP0Mq4eC1q01Ado2C2WU+BLTgMdhyt+E2P4w8rPhJ2kCt2MNxO66vQUiynspmZkgjQr0II/VjxWA=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.62", "", { "os": "win32", "cpu": "x64" }, "sha512-JgLZXSaE4q7gUIQb9x6fLWFF3BYlMod2VBhOT1qGBdeveZxsM6ZAno/g+CL9IDUydWfLFadOIBjdYFDVWV2Z2w=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.63", "", { "os": "win32", "cpu": "x64" }, "sha512-qEp6h//FrT+TQiiHm87wZWUwqTPTqIy1ZD+8R+VCUK+usoQiOAD2SqrYnM7W8JkCMGn5/TKm/GaKLyx/qlK4VA=="], - "@opentui/solid": ["@opentui/solid@0.1.62", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.62", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-3th4oZROv3cZvcoL+IwNCEMTKLZaT1BBWKVHxH29wUD0/EPxtowLQCibnjKDqqdTuEUuFA/QtSX52WqQEioR8g=="], + "@opentui/solid": ["@opentui/solid@0.1.63", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.63", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-Gccln4qRucAoaoQEZ4NPAHvGmVYzU/8aKCLG8EPgwCKTcpUzlqYt4357cDHq4cnCNOcXOC06hTz/0pK9r0dqXA=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], @@ -1951,16 +1974,6 @@ "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="], - "bare-fs": ["bare-fs@4.5.1", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-zGUCsm3yv/ePt2PHNbVxjjn0nNB1MkIaR4wOCxJ2ig5pCf5cCVAYJXVhQg/3OhhJV6DB1ts7Hv0oUaElc2TPQg=="], - - "bare-os": ["bare-os@3.6.2", "", {}, "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A=="], - - "bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="], - - "bare-stream": ["bare-stream@2.7.0", "", { "dependencies": { "streamx": "^2.21.0" }, "peerDependencies": { "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-buffer", "bare-events"] }, "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A=="], - - "bare-url": ["bare-url@2.3.2", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw=="], - "base-64": ["base-64@1.0.0", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="], "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], @@ -1979,8 +1992,6 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], - "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], "blob-to-buffer": ["blob-to-buffer@1.2.9", "", {}, "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA=="], @@ -2057,7 +2068,7 @@ "chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="], - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], @@ -2183,10 +2194,6 @@ "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], - "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], - - "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], - "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], "default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="], @@ -2215,7 +2222,7 @@ "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], @@ -2275,8 +2282,6 @@ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], - "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -2357,8 +2362,6 @@ "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], - "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], - "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], "express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="], @@ -2427,8 +2430,6 @@ "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], - "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], - "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], @@ -2479,8 +2480,6 @@ "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], - "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], - "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], @@ -3005,8 +3004,6 @@ "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], - "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], - "miniflare": ["miniflare@4.20251118.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251118.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-uLSAE/DvOm392fiaig4LOaatxLjM7xzIniFRG5Y3yF9IduOYLLK/pkCPQNCgKQH3ou0YJRHnTN+09LPfqYNTQQ=="], "minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], @@ -3019,8 +3016,6 @@ "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], - "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], - "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -3035,8 +3030,6 @@ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], - "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], @@ -3049,9 +3042,7 @@ "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], - "node-abi": ["node-abi@3.85.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg=="], - - "node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="], + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], @@ -3243,8 +3234,6 @@ "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], - "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], - "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], "pretty": ["pretty@2.0.0", "", { "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", "js-beautify": "^1.6.12" } }, "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w=="], @@ -3267,8 +3256,6 @@ "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], - "punycode": ["punycode@1.3.2", "", {}, "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="], "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], @@ -3285,8 +3272,6 @@ "raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="], - "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], - "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], "react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], @@ -3445,7 +3430,7 @@ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], - "sharp": ["sharp@0.32.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.2", "node-addon-api": "^6.1.0", "prebuild-install": "^7.1.1", "semver": "^7.5.4", "simple-get": "^4.0.1", "tar-fs": "^3.0.4", "tunnel-agent": "^0.6.0" } }, "sha512-0dap3iysgDkNaPOaOL4X/0akdu0ma62GcdC2NBQ+93eqpePdDdr2/LM0sFdDSMmN7yS+odyZtPsb7tx/cYBKnQ=="], + "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -3469,10 +3454,6 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], - - "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], - "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], "simple-xml-to-json": ["simple-xml-to-json@1.2.3", "", {}, "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA=="], @@ -3571,8 +3552,6 @@ "strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="], - "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], - "stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="], "strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="], @@ -3599,8 +3578,6 @@ "tar": ["tar@7.5.2", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg=="], - "tar-fs": ["tar-fs@3.1.1", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg=="], - "tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], "terracotta": ["terracotta@1.0.6", "", { "dependencies": { "solid-use": "^0.9.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-yVrmT/Lg6a3tEbeYEJH8ksb1PYkR5FA9k5gr1TchaSNIiA2ZWs5a+koEbePXwlBP0poaV7xViZ/v50bQFcMgqw=="], @@ -3661,8 +3638,6 @@ "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], - "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], - "turbo": ["turbo@2.5.6", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.6", "turbo-darwin-arm64": "2.5.6", "turbo-linux-64": "2.5.6", "turbo-linux-arm64": "2.5.6", "turbo-windows-64": "2.5.6", "turbo-windows-arm64": "2.5.6" }, "bin": { "turbo": "bin/turbo" } }, "sha512-gxToHmi9oTBNB05UjUsrWf0OyN5ZXtD0apOarC1KIx232Vp3WimRNy3810QzeNSgyD5rsaIDXlxlbnOzlouo+w=="], "turbo-darwin-64": ["turbo-darwin-64@2.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-3C1xEdo4aFwMJAPvtlPqz1Sw/+cddWIOmsalHFMrsqqydcptwBfu26WW2cDm3u93bUzMbBJ8k3zNKFqxJ9ei2A=="], @@ -3907,16 +3882,40 @@ "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], - "@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + "@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="], + + "@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], + + "@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], + + "@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="], + + "@ai-sdk/deepinfra/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], + + "@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="], + "@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], + "@ai-sdk/mcp/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + "@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], + "@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="], "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="], + "@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], + + "@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="], + + "@ai-sdk/togetherai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], + + "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="], + + "@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], + "@astrojs/cloudflare/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], "@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="], @@ -3983,6 +3982,8 @@ "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], + "@dot/log/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@expressive-code/plugin-shiki/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="], @@ -4029,6 +4030,8 @@ "@jimp/types/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@jsx-email/cli/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "@jsx-email/cli/esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], "@jsx-email/cli/tailwindcss": ["tailwindcss@3.3.3", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.18.2", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w=="], @@ -4103,9 +4106,9 @@ "@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], - "@opencode-ai/tauri/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="], + "@opencode-ai/desktop/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="], - "@opencode-ai/tauri/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], + "@opencode-ai/desktop/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], "@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.4.2", "", { "dependencies": { "@shikijs/core": "3.4.2", "@shikijs/types": "3.4.2" } }, "sha512-I5baLVi/ynLEOZoWSAMlACHNnG+yw5HDmse0oe+GW6U1u+ULdEB3UHiVWaHoJSSONV7tlcVxuaMy74sREDkSvg=="], @@ -4119,10 +4122,6 @@ "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="], - "@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], - - "@parcel/watcher/node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], - "@pierre/diffs/@shikijs/core": ["@shikijs/core@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA=="], "@pierre/diffs/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-ZfWJNm2VMhKkQIKT9qXbs76RRcT0SF/CAvEz0+RkpUDAoDaCx0uFdCGzSRiD9gSlhm6AHkjdieOBJMaO2eC1rQ=="], @@ -4161,6 +4160,8 @@ "@solidjs/start/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="], + "@tailwindcss/oxide/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], @@ -4175,6 +4176,8 @@ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "ai/@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.12", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W+cB1sOWvPcz9qiIsNtD+HxUrBUva2vWv2K1EFukuImX+HA0uZx3EyyOjhYQ9gtf/teqEG80M6OvJ7xx/VLV2A=="], + "ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -4189,8 +4192,6 @@ "astro/diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="], - "astro/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], - "astro/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="], "astro/unstorage": ["unstorage@1.17.3", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q=="], @@ -4205,18 +4206,12 @@ "babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], - "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - - "bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], "body-parser/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="], - "boxen/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "clean-css/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], @@ -4235,6 +4230,8 @@ "es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "esbuild-plugin-copy/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "esbuild-plugin-copy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], @@ -4271,6 +4268,8 @@ "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="], "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], @@ -4279,8 +4278,6 @@ "miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], - "miniflare/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], - "miniflare/undici": ["undici@7.14.0", "", {}, "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ=="], "miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], @@ -4327,8 +4324,6 @@ "postcss-load-config/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - "prebuild-install/tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], - "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], "raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], @@ -4349,6 +4344,8 @@ "send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + "sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "sitemap/sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="], "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], @@ -4385,8 +4382,6 @@ "utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], - "vite-plugin-icons-spritesheet/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "vitest/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], "vitest/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="], @@ -4693,7 +4688,7 @@ "@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], - "@opencode-ai/tauri/@actions/artifact/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], + "@opencode-ai/desktop/@actions/artifact/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], "@opencode-ai/web/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.4.2", "", { "dependencies": { "@shikijs/types": "3.4.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AG8vnSi1W2pbgR2B911EfGqtLE9c4hQBYkv/x7Z+Kt0VxhgQKcW7UNDVYsu9YxwV6u+OJrvdJrMq6DNWoBjihQ=="], @@ -4923,10 +4918,6 @@ "pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="], - "prebuild-install/tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], - - "prebuild-install/tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], - "readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -5057,7 +5048,7 @@ "@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], - "@opencode-ai/tauri/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + "@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], "@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -5111,8 +5102,6 @@ "pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], - "prebuild-install/tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], diff --git a/flake.lock b/flake.lock index e1c4419dc..6beb162c7 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1766125104, - "narHash": "sha256-l/YGrEpLromL4viUo5GmFH3K5M1j0Mb9O+LiaeCPWEM=", + "lastModified": 1766314097, + "narHash": "sha256-laJftWbghBehazn/zxVJ8NdENVgjccsWAdAqKXhErrM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7d853e518814cca2a657b72eeba67ae20ebf7059", + "rev": "306ea70f9eb0fb4e040f8540e2deab32ed7e2055", "type": "github" }, "original": { diff --git a/infra/app.ts b/infra/app.ts index 7215995ba..da4ac45b8 100644 --- a/infra/app.ts +++ b/infra/app.ts @@ -44,3 +44,12 @@ new sst.cloudflare.x.Astro("Web", { VITE_API_URL: api.url.apply((url) => url!), }, }) + +new sst.cloudflare.StaticSite("App", { + domain: "app." + domain, + path: "packages/app", + build: { + command: "bun turbo build", + output: "./dist", + }, +}) diff --git a/infra/console.ts b/infra/console.ts index 8f54823f8..0cc6a404b 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -118,6 +118,7 @@ const gatewayKv = new sst.cloudflare.Kv("GatewayKv") //////////////// const bucket = new sst.cloudflare.Bucket("ZenData") +const bucketNew = new sst.cloudflare.Bucket("ZenDataNew") const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID") const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY") @@ -136,6 +137,7 @@ new sst.cloudflare.x.SolidStart("Console", { path: "packages/console/app", link: [ bucket, + bucketNew, database, AUTH_API_URL, STRIPE_WEBHOOK_SECRET, diff --git a/infra/desktop.ts b/infra/desktop.ts index d4e32c65d..5c4155cc9 100644 --- a/infra/desktop.ts +++ b/infra/desktop.ts @@ -2,7 +2,7 @@ import { domain } from "./stage" new sst.cloudflare.StaticSite("Desktop", { domain: "desktop." + domain, - path: "packages/desktop", + path: "packages/app", build: { command: "bun turbo build", output: "./dist", diff --git a/nix/hashes.json b/nix/hashes.json index 1bc7f95f1..dbf753171 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,3 +1,3 @@ { - "nodeModules": "sha256-X9r0BsxLlhhCIioG8xuDVp+mDSlr37ZfqlblvEPrOJQ=" + "nodeModules": "sha256-QlQblkUq49DOdvNNMNAzHHAfHxR6cZNmJtyzc4rD168=" } diff --git a/package.json b/package.json index 2ddba2c9a..3134cc976 100644 --- a/package.json +++ b/package.json @@ -64,10 +64,17 @@ "turbo": "2.5.6" }, "dependencies": { + "@ai-sdk/cerebras": "1.0.33", + "@ai-sdk/cohere": "2.0.21", + "@ai-sdk/deepinfra": "1.0.30", + "@ai-sdk/gateway": "2.0.23", + "@ai-sdk/groq": "2.0.33", + "@ai-sdk/perplexity": "2.0.22", + "@ai-sdk/togetherai": "1.0.30", "@aws-sdk/client-s3": "3.933.0", + "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", - "@opencode-ai/plugin": "workspace:*", "typescript": "catalog:" }, "repository": { @@ -82,7 +89,6 @@ "trustedDependencies": [ "esbuild", "protobufjs", - "sharp", "tree-sitter", "tree-sitter-bash", "web-tree-sitter" diff --git a/packages/app/.gitignore b/packages/app/.gitignore new file mode 100644 index 000000000..4a20d55a7 --- /dev/null +++ b/packages/app/.gitignore @@ -0,0 +1 @@ +src/assets/theme.css diff --git a/packages/desktop/AGENTS.md b/packages/app/AGENTS.md similarity index 100% rename from packages/desktop/AGENTS.md rename to packages/app/AGENTS.md diff --git a/packages/app/README.md b/packages/app/README.md new file mode 100644 index 000000000..6a1764536 --- /dev/null +++ b/packages/app/README.md @@ -0,0 +1,34 @@ +## Usage + +Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`. + +This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template. + +```bash +$ npm install # or pnpm install or yarn install +``` + +### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) + +## Available Scripts + +In the project directory, you can run: + +### `npm run dev` or `npm start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+ +### `npm run build` + +Builds the app for production to the `dist` folder.
+It correctly bundles Solid in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +## Deployment + +You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.) diff --git a/packages/desktop/bunfig.toml b/packages/app/bunfig.toml similarity index 100% rename from packages/desktop/bunfig.toml rename to packages/app/bunfig.toml diff --git a/packages/desktop/happydom.ts b/packages/app/happydom.ts similarity index 100% rename from packages/desktop/happydom.ts rename to packages/app/happydom.ts diff --git a/packages/tauri/index.html b/packages/app/index.html similarity index 88% rename from packages/tauri/index.html rename to packages/app/index.html index faeb1a1fd..9803517a0 100644 --- a/packages/tauri/index.html +++ b/packages/app/index.html @@ -14,7 +14,7 @@ - +
- + diff --git a/packages/app/package.json b/packages/app/package.json new file mode 100644 index 000000000..404f8f11c --- /dev/null +++ b/packages/app/package.json @@ -0,0 +1,62 @@ +{ + "name": "@opencode-ai/app", + "version": "1.0.191", + "description": "", + "type": "module", + "exports": { + ".": "./src/index.ts", + "./vite": "./vite.js" + }, + "scripts": { + "typecheck": "tsgo -b", + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "license": "MIT", + "devDependencies": { + "@happy-dom/global-registrator": "20.0.11", + "@tailwindcss/vite": "catalog:", + "@tsconfig/bun": "1.0.9", + "@types/bun": "catalog:", + "@types/luxon": "catalog:", + "@types/node": "catalog:", + "@typescript/native-preview": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plugin-icons-spritesheet": "3.0.1", + "vite-plugin-solid": "catalog:" + }, + "dependencies": { + "@kobalte/core": "catalog:", + "@opencode-ai/sdk": "workspace:*", + "@opencode-ai/ui": "workspace:*", + "@opencode-ai/util": "workspace:*", + "@shikijs/transformers": "3.9.2", + "@solid-primitives/active-element": "2.1.3", + "@solid-primitives/audio": "1.4.2", + "@solid-primitives/event-bus": "1.1.2", + "@solid-primitives/media": "2.3.3", + "@solid-primitives/resize-observer": "2.1.3", + "@solid-primitives/scroll": "2.1.3", + "@solid-primitives/storage": "catalog:", + "@solid-primitives/websocket": "1.3.1", + "@solidjs/meta": "catalog:", + "@solidjs/router": "catalog:", + "@thisbeyond/solid-dnd": "0.7.5", + "diff": "catalog:", + "fuzzysort": "catalog:", + "ghostty-web": "0.3.0", + "luxon": "catalog:", + "marked": "16.2.0", + "marked-shiki": "1.2.1", + "remeda": "catalog:", + "shiki": "3.9.2", + "solid-js": "catalog:", + "solid-list": "catalog:", + "tailwindcss": "catalog:", + "virtua": "catalog:", + "zod": "catalog:" + } +} diff --git a/packages/desktop/public/apple-touch-icon.png b/packages/app/public/apple-touch-icon.png similarity index 100% rename from packages/desktop/public/apple-touch-icon.png rename to packages/app/public/apple-touch-icon.png diff --git a/packages/desktop/public/favicon-96x96.png b/packages/app/public/favicon-96x96.png similarity index 100% rename from packages/desktop/public/favicon-96x96.png rename to packages/app/public/favicon-96x96.png diff --git a/packages/desktop/public/favicon.ico b/packages/app/public/favicon.ico similarity index 100% rename from packages/desktop/public/favicon.ico rename to packages/app/public/favicon.ico diff --git a/packages/desktop/public/favicon.svg b/packages/app/public/favicon.svg similarity index 100% rename from packages/desktop/public/favicon.svg rename to packages/app/public/favicon.svg diff --git a/packages/desktop/public/site.webmanifest b/packages/app/public/site.webmanifest similarity index 100% rename from packages/desktop/public/site.webmanifest rename to packages/app/public/site.webmanifest diff --git a/packages/desktop/public/social-share-zen.png b/packages/app/public/social-share-zen.png similarity index 100% rename from packages/desktop/public/social-share-zen.png rename to packages/app/public/social-share-zen.png diff --git a/packages/desktop/public/social-share.png b/packages/app/public/social-share.png similarity index 100% rename from packages/desktop/public/social-share.png rename to packages/app/public/social-share.png diff --git a/packages/desktop/public/web-app-manifest-192x192.png b/packages/app/public/web-app-manifest-192x192.png similarity index 100% rename from packages/desktop/public/web-app-manifest-192x192.png rename to packages/app/public/web-app-manifest-192x192.png diff --git a/packages/desktop/public/web-app-manifest-512x512.png b/packages/app/public/web-app-manifest-512x512.png similarity index 100% rename from packages/desktop/public/web-app-manifest-512x512.png rename to packages/app/public/web-app-manifest-512x512.png diff --git a/packages/desktop/src/addons/serialize.test.ts b/packages/app/src/addons/serialize.test.ts similarity index 100% rename from packages/desktop/src/addons/serialize.test.ts rename to packages/app/src/addons/serialize.test.ts diff --git a/packages/desktop/src/addons/serialize.ts b/packages/app/src/addons/serialize.ts similarity index 100% rename from packages/desktop/src/addons/serialize.ts rename to packages/app/src/addons/serialize.ts diff --git a/packages/desktop/src/app.tsx b/packages/app/src/app.tsx similarity index 85% rename from packages/desktop/src/app.tsx rename to packages/app/src/app.tsx index 2ed529bbc..11216643e 100644 --- a/packages/desktop/src/app.tsx +++ b/packages/app/src/app.tsx @@ -21,6 +21,7 @@ import Home from "@/pages/home" import DirectoryLayout from "@/pages/directory-layout" import Session from "@/pages/session" import { ErrorPage } from "./pages/error" +import { iife } from "@opencode-ai/util/iife" declare global { interface Window { @@ -28,14 +29,17 @@ declare global { } } -const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1" -const port = window.__OPENCODE__?.port ?? import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096" +const url = iife(() => { + const param = new URLSearchParams(document.location.search).get("url") + if (param) return param -const url = - new URLSearchParams(document.location.search).get("url") || - (location.hostname.includes("opencode.ai") || location.hostname.includes("localhost") - ? `http://${host}:${port}` - : "/") + if (location.hostname.includes("opencode.ai")) return "http://localhost:4096" + if (window.__OPENCODE__) return `http://127.0.0.1:${window.__OPENCODE__.port}` + if (import.meta.env.DEV) + return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}` + + return "http://localhost:4096" +}) export function App() { return ( diff --git a/packages/desktop/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx similarity index 100% rename from packages/desktop/src/components/dialog-connect-provider.tsx rename to packages/app/src/components/dialog-connect-provider.tsx diff --git a/packages/desktop/src/components/dialog-manage-models.tsx b/packages/app/src/components/dialog-manage-models.tsx similarity index 100% rename from packages/desktop/src/components/dialog-manage-models.tsx rename to packages/app/src/components/dialog-manage-models.tsx diff --git a/packages/desktop/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx similarity index 100% rename from packages/desktop/src/components/dialog-select-file.tsx rename to packages/app/src/components/dialog-select-file.tsx diff --git a/packages/desktop/src/components/dialog-select-model-unpaid.tsx b/packages/app/src/components/dialog-select-model-unpaid.tsx similarity index 100% rename from packages/desktop/src/components/dialog-select-model-unpaid.tsx rename to packages/app/src/components/dialog-select-model-unpaid.tsx diff --git a/packages/desktop/src/components/dialog-select-model.tsx b/packages/app/src/components/dialog-select-model.tsx similarity index 100% rename from packages/desktop/src/components/dialog-select-model.tsx rename to packages/app/src/components/dialog-select-model.tsx diff --git a/packages/desktop/src/components/dialog-select-provider.tsx b/packages/app/src/components/dialog-select-provider.tsx similarity index 100% rename from packages/desktop/src/components/dialog-select-provider.tsx rename to packages/app/src/components/dialog-select-provider.tsx diff --git a/packages/desktop/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx similarity index 100% rename from packages/desktop/src/components/file-tree.tsx rename to packages/app/src/components/file-tree.tsx diff --git a/packages/desktop/src/components/header.tsx b/packages/app/src/components/header.tsx similarity index 84% rename from packages/desktop/src/components/header.tsx rename to packages/app/src/components/header.tsx index c5ecd9871..ec7cdfa25 100644 --- a/packages/desktop/src/components/header.tsx +++ b/packages/app/src/components/header.tsx @@ -109,6 +109,35 @@ export function Header(props: {
+
+ } + > + + +
(params.id ? (sync.data.message[params.id] ?? []) : [])) + + const cost = createMemo(() => { + const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0) + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format(total) + }) + + const context = createMemo(() => { + const last = messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage + if (!last) return + const total = + last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write + const model = sync.data.provider.all.find((x) => x.id === last.providerID)?.models[last.modelID] + return { + tokens: total.toLocaleString(), + percentage: model?.limit.context ? Math.round((total / model.limit.context) * 100) : null, + } + }) + + return ( + + {(ctx) => ( + +
+ Tokens + {ctx().tokens} +
+
+ Usage + {ctx().percentage ?? 0}% +
+
+ Cost + {cost()} +
+
+ } + placement="top" + > +
+ {`${ctx().percentage ?? 0}%`} + +
+ + )} + + ) +} diff --git a/packages/desktop/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx similarity index 100% rename from packages/desktop/src/components/terminal.tsx rename to packages/app/src/components/terminal.tsx diff --git a/packages/desktop/src/context/command.tsx b/packages/app/src/context/command.tsx similarity index 100% rename from packages/desktop/src/context/command.tsx rename to packages/app/src/context/command.tsx diff --git a/packages/desktop/src/context/global-sdk.tsx b/packages/app/src/context/global-sdk.tsx similarity index 100% rename from packages/desktop/src/context/global-sdk.tsx rename to packages/app/src/context/global-sdk.tsx diff --git a/packages/desktop/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx similarity index 97% rename from packages/desktop/src/context/global-sync.tsx rename to packages/app/src/context/global-sync.tsx index 27a89e7bc..ae40555d6 100644 --- a/packages/desktop/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -295,6 +295,15 @@ function createGlobalSync() { }) async function bootstrap() { + const health = await globalSDK.client.global.health().then((x) => x.data) + if (!health?.healthy) { + setGlobalStore( + "error", + new Error(`Could not connect to server. Is there a server running at \`${globalSDK.url}\`?`), + ) + return + } + return Promise.all([ retry(() => globalSDK.client.path.get().then((x) => { diff --git a/packages/desktop/src/context/layout.tsx b/packages/app/src/context/layout.tsx similarity index 95% rename from packages/desktop/src/context/layout.tsx rename to packages/app/src/context/layout.tsx index 17cd4785c..c6ba5fef5 100644 --- a/packages/desktop/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -46,6 +46,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( opened: false, height: 280, }, + review: { + opened: true, + }, session: { width: 600, }, @@ -158,6 +161,18 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( setStore("terminal", "height", height) }, }, + review: { + opened: createMemo(() => store.review?.opened ?? true), + open() { + setStore("review", "opened", true) + }, + close() { + setStore("review", "opened", false) + }, + toggle() { + setStore("review", "opened", (x) => !x) + }, + }, session: { width: createMemo(() => store.session?.width ?? 600), resize(width: number) { diff --git a/packages/desktop/src/context/local.tsx b/packages/app/src/context/local.tsx similarity index 100% rename from packages/desktop/src/context/local.tsx rename to packages/app/src/context/local.tsx diff --git a/packages/desktop/src/context/notification.tsx b/packages/app/src/context/notification.tsx similarity index 100% rename from packages/desktop/src/context/notification.tsx rename to packages/app/src/context/notification.tsx diff --git a/packages/desktop/src/context/platform.tsx b/packages/app/src/context/platform.tsx similarity index 100% rename from packages/desktop/src/context/platform.tsx rename to packages/app/src/context/platform.tsx diff --git a/packages/desktop/src/context/prompt.tsx b/packages/app/src/context/prompt.tsx similarity index 100% rename from packages/desktop/src/context/prompt.tsx rename to packages/app/src/context/prompt.tsx diff --git a/packages/desktop/src/context/sdk.tsx b/packages/app/src/context/sdk.tsx similarity index 100% rename from packages/desktop/src/context/sdk.tsx rename to packages/app/src/context/sdk.tsx diff --git a/packages/desktop/src/context/sync.tsx b/packages/app/src/context/sync.tsx similarity index 100% rename from packages/desktop/src/context/sync.tsx rename to packages/app/src/context/sync.tsx diff --git a/packages/desktop/src/context/terminal.tsx b/packages/app/src/context/terminal.tsx similarity index 100% rename from packages/desktop/src/context/terminal.tsx rename to packages/app/src/context/terminal.tsx diff --git a/packages/desktop/src/custom-elements.d.ts b/packages/app/src/custom-elements.d.ts similarity index 100% rename from packages/desktop/src/custom-elements.d.ts rename to packages/app/src/custom-elements.d.ts diff --git a/packages/desktop/src/entry.tsx b/packages/app/src/entry.tsx similarity index 100% rename from packages/desktop/src/entry.tsx rename to packages/app/src/entry.tsx diff --git a/packages/desktop/src/env.d.ts b/packages/app/src/env.d.ts similarity index 100% rename from packages/desktop/src/env.d.ts rename to packages/app/src/env.d.ts diff --git a/packages/desktop/src/hooks/use-providers.ts b/packages/app/src/hooks/use-providers.ts similarity index 100% rename from packages/desktop/src/hooks/use-providers.ts rename to packages/app/src/hooks/use-providers.ts diff --git a/packages/desktop/src/index.css b/packages/app/src/index.css similarity index 100% rename from packages/desktop/src/index.css rename to packages/app/src/index.css diff --git a/packages/desktop/src/index.ts b/packages/app/src/index.ts similarity index 100% rename from packages/desktop/src/index.ts rename to packages/app/src/index.ts diff --git a/packages/desktop/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx similarity index 100% rename from packages/desktop/src/pages/directory-layout.tsx rename to packages/app/src/pages/directory-layout.tsx diff --git a/packages/desktop/src/pages/error.tsx b/packages/app/src/pages/error.tsx similarity index 78% rename from packages/desktop/src/pages/error.tsx rename to packages/app/src/pages/error.tsx index c7330c298..9914279ad 100644 --- a/packages/desktop/src/pages/error.tsx +++ b/packages/app/src/pages/error.tsx @@ -62,27 +62,49 @@ function formatInitError(error: InitError): string { } } -function formatErrorChain(error: unknown, depth = 0): string { +function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): string { if (!error) return "Unknown error" - const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : "" - if (isInitError(error)) { - return indent + formatInitError(error) + const message = formatInitError(error) + if (depth > 0 && parentMessage === message) return "" + const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : "" + return indent + message } if (error instanceof Error) { - const parts = [indent + `${error.name}: ${error.message}`] - if (error.stack) { - parts.push(error.stack) + const isDuplicate = depth > 0 && parentMessage === error.message + const parts: string[] = [] + const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : "" + + if (!isDuplicate) { + // Stack already includes error name and message, so prefer it + parts.push(indent + (error.stack ?? `${error.name}: ${error.message}`)) + } else if (error.stack) { + // Duplicate message - only show the stack trace lines (skip message) + const trace = error.stack.split("\n").slice(1).join("\n").trim() + if (trace) { + parts.push(trace) + } } + if (error.cause) { - parts.push(formatErrorChain(error.cause, depth + 1)) + const causeResult = formatErrorChain(error.cause, depth + 1, error.message) + if (causeResult) { + parts.push(causeResult) + } } + return parts.join("\n\n") } - if (typeof error === "string") return indent + error + if (typeof error === "string") { + if (depth > 0 && parentMessage === error) return "" + const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : "" + return indent + error + } + + const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : "" return indent + JSON.stringify(error, null, 2) } diff --git a/packages/desktop/src/pages/home.tsx b/packages/app/src/pages/home.tsx similarity index 100% rename from packages/desktop/src/pages/home.tsx rename to packages/app/src/pages/home.tsx diff --git a/packages/desktop/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx similarity index 100% rename from packages/desktop/src/pages/layout.tsx rename to packages/app/src/pages/layout.tsx diff --git a/packages/desktop/src/pages/session.tsx b/packages/app/src/pages/session.tsx similarity index 91% rename from packages/desktop/src/pages/session.tsx rename to packages/app/src/pages/session.tsx index 1f38da3c7..42e43232a 100644 --- a/packages/desktop/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -12,7 +12,7 @@ import { createRenderEffect, batch, } from "solid-js" -import { createResizeObserver } from "@solid-primitives/resize-observer" + import { Dynamic } from "solid-js/web" import { useLocal, type LocalFile } from "@/context/local" import { createStore } from "solid-js/store" @@ -27,6 +27,7 @@ import { ResizeHandle } from "@opencode-ai/ui/resize-handle" import { Tabs } from "@opencode-ai/ui/tabs" import { useCodeComponent } from "@opencode-ai/ui/context/code" import { SessionTurn } from "@opencode-ai/ui/session-turn" +import { createAutoScroll } from "@opencode-ai/ui/hooks" import { SessionMessageRail } from "@opencode-ai/ui/session-message-rail" import { SessionReview } from "@opencode-ai/ui/session-review" import { @@ -93,13 +94,6 @@ export default function Page() { userInteracted: false, stepsExpanded: true, mobileStepsExpanded: {} as Record, - mobileLastScrollTop: 0, - mobileLastScrollHeight: 0, - mobileAutoScrolled: false, - mobileUserScrolled: false, - mobileContentRef: undefined as HTMLDivElement | undefined, - mobileLastContentWidth: 0, - mobileReflowing: false, messageId: undefined as string | undefined, }) @@ -227,6 +221,15 @@ export default function Page() { slash: "terminal", onSelect: () => layout.terminal.toggle(), }, + { + id: "review.toggle", + title: "Toggle review", + description: "Show or hide the review panel", + category: "View", + keybind: "mod+b", + slash: "review", + onSelect: () => layout.review.toggle(), + }, { id: "terminal.new", title: "New terminal", @@ -539,92 +542,22 @@ export default function Page() { ) } - const showTabs = createMemo(() => diffs().length > 0 || tabs().all().length > 0) + const showTabs = createMemo(() => layout.review.opened() && (diffs().length > 0 || tabs().all().length > 0)) - let mobileScrollRef: HTMLDivElement | undefined const mobileWorking = createMemo(() => status().type !== "idle") - - function handleMobileScroll() { - if (!mobileScrollRef || store.mobileAutoScrolled) return - - const scrollTop = mobileScrollRef.scrollTop - const scrollHeight = mobileScrollRef.scrollHeight - - if (store.mobileReflowing) { - batch(() => { - setStore("mobileLastScrollTop", scrollTop) - setStore("mobileLastScrollHeight", scrollHeight) - }) - return - } - - const scrolledUp = scrollTop < store.mobileLastScrollTop - 50 - if (scrolledUp && mobileWorking()) { - setStore("mobileUserScrolled", true) - setStore("userInteracted", true) - } - - batch(() => { - setStore("mobileLastScrollTop", scrollTop) - setStore("mobileLastScrollHeight", scrollHeight) - }) - } - - function handleMobileInteraction() { - if (mobileWorking()) { - setStore("mobileUserScrolled", true) - setStore("userInteracted", true) - } - } - - function scrollMobileToBottom() { - if (!mobileScrollRef || store.mobileUserScrolled || !mobileWorking()) return - setStore("mobileAutoScrolled", true) - requestAnimationFrame(() => { - mobileScrollRef?.scrollTo({ top: mobileScrollRef.scrollHeight, behavior: "smooth" }) - requestAnimationFrame(() => { - batch(() => { - setStore("mobileLastScrollTop", mobileScrollRef?.scrollTop ?? 0) - setStore("mobileLastScrollHeight", mobileScrollRef?.scrollHeight ?? 0) - setStore("mobileAutoScrolled", false) - }) - }) - }) - } - - createEffect(() => { - if (!mobileWorking()) setStore("mobileUserScrolled", false) + const mobileAutoScroll = createAutoScroll({ + working: mobileWorking, + onUserInteracted: () => setStore("userInteracted", true), }) - createResizeObserver( - () => store.mobileContentRef, - ({ width }) => { - const widthChanged = Math.abs(width - store.mobileLastContentWidth) > 5 - if (widthChanged && store.mobileLastContentWidth > 0) { - setStore("mobileReflowing", true) - requestAnimationFrame(() => { - requestAnimationFrame(() => { - setStore("mobileReflowing", false) - if (mobileWorking() && !store.mobileUserScrolled) { - scrollMobileToBottom() - } - }) - }) - } else if (!store.mobileReflowing) { - scrollMobileToBottom() - } - setStore("mobileLastContentWidth", width) - }, - ) - const MobileTurns = () => (
-
setStore("mobileContentRef", el)} class="flex flex-col gap-45 items-start justify-start mt-4"> +
{(message) => ( = { "darwin-x64-dmg": "opencode-desktop-darwin-x64.dmg", "windows-x64-nsis": "opencode-desktop-windows-x64.exe", "linux-x64-deb": "opencode-desktop-linux-amd64.deb", + "linux-x64-appimage": "opencode-desktop-linux-amd64.AppImage", "linux-x64-rpm": "opencode-desktop-linux-x86_64.rpm", } satisfies Record diff --git a/packages/console/app/src/routes/download/index.tsx b/packages/console/app/src/routes/download/index.tsx index 4f4258755..d4de97c68 100644 --- a/packages/console/app/src/routes/download/index.tsx +++ b/packages/console/app/src/routes/download/index.tsx @@ -244,6 +244,22 @@ export default function Download() { Download
+
+
+ + + + + + Linux (.AppImage) +
+ + Download + +
diff --git a/packages/console/app/src/routes/download/types.ts b/packages/console/app/src/routes/download/types.ts index adf71880d..916f97022 100644 --- a/packages/console/app/src/routes/download/types.ts +++ b/packages/console/app/src/routes/download/types.ts @@ -1 +1,4 @@ -export type DownloadPlatform = `darwin-${"x64" | "aarch64"}-dmg` | "windows-x64-nsis" | `linux-x64-${"deb" | "rpm"}` +export type DownloadPlatform = + | `darwin-${"x64" | "aarch64"}-dmg` + | "windows-x64-nsis" + | `linux-x64-${"deb" | "rpm" | "appimage"}` diff --git a/packages/console/app/src/routes/zen/util/dataDumper.ts b/packages/console/app/src/routes/zen/util/dataDumper.ts index 155cc6c58..b852ca0b5 100644 --- a/packages/console/app/src/routes/zen/util/dataDumper.ts +++ b/packages/console/app/src/routes/zen/util/dataDumper.ts @@ -19,17 +19,23 @@ export function createDataDumper(sessionId: string, requestId: string, projectId if (!data.modelName) return const timestamp = new Date().toISOString().replace(/[^0-9]/g, "") + const year = timestamp.substring(0, 4) + const month = timestamp.substring(4, 6) + const day = timestamp.substring(6, 8) + const hour = timestamp.substring(8, 10) + const minute = timestamp.substring(10, 12) + const second = timestamp.substring(12, 14) waitUntil( - Resource.ZenData.put( - `data/${data.modelName}/${sessionId}/${requestId}.json`, + Resource.ZenDataNew.put( + `data/${data.modelName}/${year}/${month}/${day}/${hour}/${minute}/${second}/${requestId}.json`, JSON.stringify({ timestamp, ...data }), ), ) waitUntil( - Resource.ZenData.put( - `meta/${data.modelName}/${timestamp}/${requestId}.json`, + Resource.ZenDataNew.put( + `meta/${data.modelName}/${sessionId}/${requestId}.json`, JSON.stringify({ timestamp, ...metadata }), ), ) diff --git a/packages/console/core/package.json b/packages/console/core/package.json index e1971f3ef..6ffedab0a 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.0.186", + "version": "1.0.191", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/core/sst-env.d.ts b/packages/console/core/sst-env.d.ts index 632ea3fbe..ffa17f276 100644 --- a/packages/console/core/sst-env.d.ts +++ b/packages/console/core/sst-env.d.ts @@ -132,6 +132,7 @@ declare module "sst" { "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "ZenData": cloudflare.R2Bucket + "ZenDataNew": cloudflare.R2Bucket } } diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 8c9daf340..95fc0e474 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.0.186", + "version": "1.0.191", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/function/sst-env.d.ts b/packages/console/function/sst-env.d.ts index 632ea3fbe..ffa17f276 100644 --- a/packages/console/function/sst-env.d.ts +++ b/packages/console/function/sst-env.d.ts @@ -132,6 +132,7 @@ declare module "sst" { "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "ZenData": cloudflare.R2Bucket + "ZenDataNew": cloudflare.R2Bucket } } diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 2ce541a3b..ff6b3f102 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.0.186", + "version": "1.0.191", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/console/resource/sst-env.d.ts b/packages/console/resource/sst-env.d.ts index 632ea3fbe..ffa17f276 100644 --- a/packages/console/resource/sst-env.d.ts +++ b/packages/console/resource/sst-env.d.ts @@ -132,6 +132,7 @@ declare module "sst" { "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "ZenData": cloudflare.R2Bucket + "ZenDataNew": cloudflare.R2Bucket } } diff --git a/packages/desktop/.gitignore b/packages/desktop/.gitignore index 4a20d55a7..a547bf36d 100644 --- a/packages/desktop/.gitignore +++ b/packages/desktop/.gitignore @@ -1 +1,24 @@ -src/assets/theme.css +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 6a1764536..b381dcf5b 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -1,34 +1,7 @@ -## Usage +# Tauri + Vanilla TS -Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`. +This template should help get you started developing with Tauri in vanilla HTML, CSS and Typescript. -This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template. +## Recommended IDE Setup -```bash -$ npm install # or pnpm install or yarn install -``` - -### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) - -## Available Scripts - -In the project directory, you can run: - -### `npm run dev` or `npm start` - -Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
- -### `npm run build` - -Builds the app for production to the `dist` folder.
-It correctly bundles Solid in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -## Deployment - -You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.) +- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) diff --git a/packages/desktop/index.html b/packages/desktop/index.html index 9803517a0..faeb1a1fd 100644 --- a/packages/desktop/index.html +++ b/packages/desktop/index.html @@ -14,7 +14,7 @@ - +
- + diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 5f38ac60d..23eab7f4d 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,62 +1,37 @@ { "name": "@opencode-ai/desktop", - "version": "1.0.186", - "description": "", + "private": true, + "version": "1.0.191", "type": "module", - "exports": { - ".": "./src/index.ts", - "./vite": "./vite.js" - }, "scripts": { "typecheck": "tsgo -b", - "start": "vite", + "predev": "bun ./scripts/predev.ts", "dev": "vite", - "build": "vite build", - "serve": "vite preview" - }, - "license": "MIT", - "devDependencies": { - "@happy-dom/global-registrator": "20.0.11", - "@tailwindcss/vite": "catalog:", - "@tsconfig/bun": "1.0.9", - "@types/bun": "catalog:", - "@types/luxon": "catalog:", - "@types/node": "catalog:", - "@typescript/native-preview": "catalog:", - "typescript": "catalog:", - "vite": "catalog:", - "vite-plugin-icons-spritesheet": "3.0.1", - "vite-plugin-solid": "catalog:" + "build": "bun run typecheck && vite build", + "preview": "vite preview", + "tauri": "tauri" }, "dependencies": { - "@kobalte/core": "catalog:", - "@opencode-ai/sdk": "workspace:*", - "@opencode-ai/ui": "workspace:*", - "@opencode-ai/util": "workspace:*", - "@shikijs/transformers": "3.9.2", - "@solid-primitives/active-element": "2.1.3", - "@solid-primitives/audio": "1.4.2", - "@solid-primitives/event-bus": "1.1.2", - "@solid-primitives/media": "2.3.3", - "@solid-primitives/resize-observer": "2.1.3", - "@solid-primitives/scroll": "2.1.3", + "@opencode-ai/app": "workspace:*", "@solid-primitives/storage": "catalog:", - "@solid-primitives/websocket": "1.3.1", - "@solidjs/meta": "catalog:", - "@solidjs/router": "catalog:", - "@thisbeyond/solid-dnd": "0.7.5", - "diff": "catalog:", - "fuzzysort": "catalog:", - "ghostty-web": "0.3.0", - "luxon": "catalog:", - "marked": "16.2.0", - "marked-shiki": "1.2.1", - "remeda": "catalog:", - "shiki": "3.9.2", - "solid-js": "catalog:", - "solid-list": "catalog:", - "tailwindcss": "catalog:", - "virtua": "catalog:", - "zod": "catalog:" + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "~2", + "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-os": "~2", + "@tauri-apps/plugin-process": "~2", + "@tauri-apps/plugin-shell": "~2", + "@tauri-apps/plugin-store": "~2", + "@tauri-apps/plugin-updater": "~2", + "@tauri-apps/plugin-http": "~2", + "@tauri-apps/plugin-window-state": "~2", + "solid-js": "catalog:" + }, + "devDependencies": { + "@actions/artifact": "4.0.0", + "@tauri-apps/cli": "^2", + "@types/bun": "catalog:", + "@typescript/native-preview": "catalog:", + "typescript": "~5.6.2", + "vite": "catalog:" } } diff --git a/packages/tauri/scripts/copy-bundles.ts b/packages/desktop/scripts/copy-bundles.ts similarity index 100% rename from packages/tauri/scripts/copy-bundles.ts rename to packages/desktop/scripts/copy-bundles.ts diff --git a/packages/tauri/scripts/predev.ts b/packages/desktop/scripts/predev.ts similarity index 100% rename from packages/tauri/scripts/predev.ts rename to packages/desktop/scripts/predev.ts diff --git a/packages/tauri/scripts/prepare.ts b/packages/desktop/scripts/prepare.ts similarity index 100% rename from packages/tauri/scripts/prepare.ts rename to packages/desktop/scripts/prepare.ts diff --git a/packages/tauri/scripts/utils.ts b/packages/desktop/scripts/utils.ts similarity index 100% rename from packages/tauri/scripts/utils.ts rename to packages/desktop/scripts/utils.ts diff --git a/packages/tauri/src-tauri/.gitignore b/packages/desktop/src-tauri/.gitignore similarity index 100% rename from packages/tauri/src-tauri/.gitignore rename to packages/desktop/src-tauri/.gitignore diff --git a/packages/tauri/src-tauri/Cargo.lock b/packages/desktop/src-tauri/Cargo.lock similarity index 100% rename from packages/tauri/src-tauri/Cargo.lock rename to packages/desktop/src-tauri/Cargo.lock diff --git a/packages/tauri/src-tauri/Cargo.toml b/packages/desktop/src-tauri/Cargo.toml similarity index 92% rename from packages/tauri/src-tauri/Cargo.toml rename to packages/desktop/src-tauri/Cargo.toml index c6208190b..0463966c0 100644 --- a/packages/tauri/src-tauri/Cargo.toml +++ b/packages/desktop/src-tauri/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "opencode-desktop" version = "0.0.0" -description = "A Tauri App" -authors = ["you"] +description = "The open source AI coding agent" +authors = ["Anomaly Innovations"] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/packages/tauri/src-tauri/assets/nsis-header.bmp b/packages/desktop/src-tauri/assets/nsis-header.bmp similarity index 100% rename from packages/tauri/src-tauri/assets/nsis-header.bmp rename to packages/desktop/src-tauri/assets/nsis-header.bmp diff --git a/packages/tauri/src-tauri/assets/nsis-sidebar.bmp b/packages/desktop/src-tauri/assets/nsis-sidebar.bmp similarity index 100% rename from packages/tauri/src-tauri/assets/nsis-sidebar.bmp rename to packages/desktop/src-tauri/assets/nsis-sidebar.bmp diff --git a/packages/tauri/src-tauri/build.rs b/packages/desktop/src-tauri/build.rs similarity index 100% rename from packages/tauri/src-tauri/build.rs rename to packages/desktop/src-tauri/build.rs diff --git a/packages/tauri/src-tauri/capabilities/default.json b/packages/desktop/src-tauri/capabilities/default.json similarity index 100% rename from packages/tauri/src-tauri/capabilities/default.json rename to packages/desktop/src-tauri/capabilities/default.json diff --git a/packages/tauri/src-tauri/entitlements.plist b/packages/desktop/src-tauri/entitlements.plist similarity index 100% rename from packages/tauri/src-tauri/entitlements.plist rename to packages/desktop/src-tauri/entitlements.plist diff --git a/packages/tauri/src-tauri/icons/README.md b/packages/desktop/src-tauri/icons/README.md similarity index 88% rename from packages/tauri/src-tauri/icons/README.md rename to packages/desktop/src-tauri/icons/README.md index d4a4e687d..db86593cc 100644 --- a/packages/tauri/src-tauri/icons/README.md +++ b/packages/desktop/src-tauri/icons/README.md @@ -2,7 +2,7 @@ Here's the process I've been using to create icons: -- Save source image as `app-icon.png` in `packages/tauri` +- Save source image as `app-icon.png` in `packages/desktop` - `cd` to `src-tauri` - Run `bun tauri icons -o icons/{environment}` - Use [Image2Icon](https://img2icnsapp.com/)'s 'Big Sur Icon' preset to generate an `icon.icns` file and place it in the appropriate icons folder diff --git a/packages/tauri/src-tauri/icons/dev/128x128.png b/packages/desktop/src-tauri/icons/dev/128x128.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/128x128.png rename to packages/desktop/src-tauri/icons/dev/128x128.png diff --git a/packages/tauri/src-tauri/icons/dev/128x128@2x.png b/packages/desktop/src-tauri/icons/dev/128x128@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/128x128@2x.png rename to packages/desktop/src-tauri/icons/dev/128x128@2x.png diff --git a/packages/tauri/src-tauri/icons/dev/32x32.png b/packages/desktop/src-tauri/icons/dev/32x32.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/32x32.png rename to packages/desktop/src-tauri/icons/dev/32x32.png diff --git a/packages/tauri/src-tauri/icons/dev/64x64.png b/packages/desktop/src-tauri/icons/dev/64x64.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/64x64.png rename to packages/desktop/src-tauri/icons/dev/64x64.png diff --git a/packages/tauri/src-tauri/icons/dev/Square107x107Logo.png b/packages/desktop/src-tauri/icons/dev/Square107x107Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/Square107x107Logo.png rename to packages/desktop/src-tauri/icons/dev/Square107x107Logo.png diff --git a/packages/tauri/src-tauri/icons/dev/Square142x142Logo.png b/packages/desktop/src-tauri/icons/dev/Square142x142Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/Square142x142Logo.png rename to packages/desktop/src-tauri/icons/dev/Square142x142Logo.png diff --git a/packages/tauri/src-tauri/icons/dev/Square150x150Logo.png b/packages/desktop/src-tauri/icons/dev/Square150x150Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/Square150x150Logo.png rename to packages/desktop/src-tauri/icons/dev/Square150x150Logo.png diff --git a/packages/tauri/src-tauri/icons/dev/Square284x284Logo.png b/packages/desktop/src-tauri/icons/dev/Square284x284Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/Square284x284Logo.png rename to packages/desktop/src-tauri/icons/dev/Square284x284Logo.png diff --git a/packages/tauri/src-tauri/icons/dev/Square30x30Logo.png b/packages/desktop/src-tauri/icons/dev/Square30x30Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/Square30x30Logo.png rename to packages/desktop/src-tauri/icons/dev/Square30x30Logo.png diff --git a/packages/tauri/src-tauri/icons/dev/Square310x310Logo.png b/packages/desktop/src-tauri/icons/dev/Square310x310Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/Square310x310Logo.png rename to packages/desktop/src-tauri/icons/dev/Square310x310Logo.png diff --git a/packages/tauri/src-tauri/icons/dev/Square44x44Logo.png b/packages/desktop/src-tauri/icons/dev/Square44x44Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/Square44x44Logo.png rename to packages/desktop/src-tauri/icons/dev/Square44x44Logo.png diff --git a/packages/tauri/src-tauri/icons/dev/Square71x71Logo.png b/packages/desktop/src-tauri/icons/dev/Square71x71Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/Square71x71Logo.png rename to packages/desktop/src-tauri/icons/dev/Square71x71Logo.png diff --git a/packages/tauri/src-tauri/icons/dev/Square89x89Logo.png b/packages/desktop/src-tauri/icons/dev/Square89x89Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/Square89x89Logo.png rename to packages/desktop/src-tauri/icons/dev/Square89x89Logo.png diff --git a/packages/tauri/src-tauri/icons/dev/StoreLogo.png b/packages/desktop/src-tauri/icons/dev/StoreLogo.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/StoreLogo.png rename to packages/desktop/src-tauri/icons/dev/StoreLogo.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml b/packages/desktop/src-tauri/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml rename to packages/desktop/src-tauri/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_round.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_round.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_round.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_round.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/packages/tauri/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png rename to packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/packages/tauri/src-tauri/icons/dev/android/values/ic_launcher_background.xml b/packages/desktop/src-tauri/icons/dev/android/values/ic_launcher_background.xml similarity index 100% rename from packages/tauri/src-tauri/icons/dev/android/values/ic_launcher_background.xml rename to packages/desktop/src-tauri/icons/dev/android/values/ic_launcher_background.xml diff --git a/packages/tauri/src-tauri/icons/dev/icon.icns b/packages/desktop/src-tauri/icons/dev/icon.icns similarity index 100% rename from packages/tauri/src-tauri/icons/dev/icon.icns rename to packages/desktop/src-tauri/icons/dev/icon.icns diff --git a/packages/tauri/src-tauri/icons/dev/icon.ico b/packages/desktop/src-tauri/icons/dev/icon.ico similarity index 100% rename from packages/tauri/src-tauri/icons/dev/icon.ico rename to packages/desktop/src-tauri/icons/dev/icon.ico diff --git a/packages/tauri/src-tauri/icons/dev/icon.png b/packages/desktop/src-tauri/icons/dev/icon.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/icon.png rename to packages/desktop/src-tauri/icons/dev/icon.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-20x20@1x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@1x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-20x20@1x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@1x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-20x20@2x-1.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x-1.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-20x20@2x-1.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x-1.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-20x20@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-20x20@2x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-20x20@3x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@3x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-20x20@3x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@3x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-29x29@1x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@1x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-29x29@1x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@1x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-29x29@2x-1.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x-1.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-29x29@2x-1.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x-1.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-29x29@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-29x29@2x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-29x29@3x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@3x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-29x29@3x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@3x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-40x40@1x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@1x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-40x40@1x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@1x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-40x40@2x-1.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x-1.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-40x40@2x-1.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x-1.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-40x40@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-40x40@2x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-40x40@3x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@3x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-40x40@3x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@3x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-512@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-512@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-512@2x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-512@2x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-60x60@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-60x60@2x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@2x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-60x60@3x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@3x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-60x60@3x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@3x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-76x76@1x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@1x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-76x76@1x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@1x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-76x76@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-76x76@2x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@2x.png diff --git a/packages/tauri/src-tauri/icons/dev/ios/AppIcon-83.5x83.5@2x.png b/packages/desktop/src-tauri/icons/dev/ios/AppIcon-83.5x83.5@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/dev/ios/AppIcon-83.5x83.5@2x.png rename to packages/desktop/src-tauri/icons/dev/ios/AppIcon-83.5x83.5@2x.png diff --git a/packages/tauri/src-tauri/icons/prod/128x128.png b/packages/desktop/src-tauri/icons/prod/128x128.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/128x128.png rename to packages/desktop/src-tauri/icons/prod/128x128.png diff --git a/packages/tauri/src-tauri/icons/prod/128x128@2x.png b/packages/desktop/src-tauri/icons/prod/128x128@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/128x128@2x.png rename to packages/desktop/src-tauri/icons/prod/128x128@2x.png diff --git a/packages/tauri/src-tauri/icons/prod/32x32.png b/packages/desktop/src-tauri/icons/prod/32x32.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/32x32.png rename to packages/desktop/src-tauri/icons/prod/32x32.png diff --git a/packages/tauri/src-tauri/icons/prod/64x64.png b/packages/desktop/src-tauri/icons/prod/64x64.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/64x64.png rename to packages/desktop/src-tauri/icons/prod/64x64.png diff --git a/packages/tauri/src-tauri/icons/prod/Square107x107Logo.png b/packages/desktop/src-tauri/icons/prod/Square107x107Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/Square107x107Logo.png rename to packages/desktop/src-tauri/icons/prod/Square107x107Logo.png diff --git a/packages/tauri/src-tauri/icons/prod/Square142x142Logo.png b/packages/desktop/src-tauri/icons/prod/Square142x142Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/Square142x142Logo.png rename to packages/desktop/src-tauri/icons/prod/Square142x142Logo.png diff --git a/packages/tauri/src-tauri/icons/prod/Square150x150Logo.png b/packages/desktop/src-tauri/icons/prod/Square150x150Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/Square150x150Logo.png rename to packages/desktop/src-tauri/icons/prod/Square150x150Logo.png diff --git a/packages/tauri/src-tauri/icons/prod/Square284x284Logo.png b/packages/desktop/src-tauri/icons/prod/Square284x284Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/Square284x284Logo.png rename to packages/desktop/src-tauri/icons/prod/Square284x284Logo.png diff --git a/packages/tauri/src-tauri/icons/prod/Square30x30Logo.png b/packages/desktop/src-tauri/icons/prod/Square30x30Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/Square30x30Logo.png rename to packages/desktop/src-tauri/icons/prod/Square30x30Logo.png diff --git a/packages/tauri/src-tauri/icons/prod/Square310x310Logo.png b/packages/desktop/src-tauri/icons/prod/Square310x310Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/Square310x310Logo.png rename to packages/desktop/src-tauri/icons/prod/Square310x310Logo.png diff --git a/packages/tauri/src-tauri/icons/prod/Square44x44Logo.png b/packages/desktop/src-tauri/icons/prod/Square44x44Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/Square44x44Logo.png rename to packages/desktop/src-tauri/icons/prod/Square44x44Logo.png diff --git a/packages/tauri/src-tauri/icons/prod/Square71x71Logo.png b/packages/desktop/src-tauri/icons/prod/Square71x71Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/Square71x71Logo.png rename to packages/desktop/src-tauri/icons/prod/Square71x71Logo.png diff --git a/packages/tauri/src-tauri/icons/prod/Square89x89Logo.png b/packages/desktop/src-tauri/icons/prod/Square89x89Logo.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/Square89x89Logo.png rename to packages/desktop/src-tauri/icons/prod/Square89x89Logo.png diff --git a/packages/tauri/src-tauri/icons/prod/StoreLogo.png b/packages/desktop/src-tauri/icons/prod/StoreLogo.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/StoreLogo.png rename to packages/desktop/src-tauri/icons/prod/StoreLogo.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml b/packages/desktop/src-tauri/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml rename to packages/desktop/src-tauri/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_round.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_round.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_round.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_round.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/packages/tauri/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png rename to packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/packages/tauri/src-tauri/icons/prod/android/values/ic_launcher_background.xml b/packages/desktop/src-tauri/icons/prod/android/values/ic_launcher_background.xml similarity index 100% rename from packages/tauri/src-tauri/icons/prod/android/values/ic_launcher_background.xml rename to packages/desktop/src-tauri/icons/prod/android/values/ic_launcher_background.xml diff --git a/packages/tauri/src-tauri/icons/prod/icon.icns b/packages/desktop/src-tauri/icons/prod/icon.icns similarity index 100% rename from packages/tauri/src-tauri/icons/prod/icon.icns rename to packages/desktop/src-tauri/icons/prod/icon.icns diff --git a/packages/tauri/src-tauri/icons/prod/icon.ico b/packages/desktop/src-tauri/icons/prod/icon.ico similarity index 100% rename from packages/tauri/src-tauri/icons/prod/icon.ico rename to packages/desktop/src-tauri/icons/prod/icon.ico diff --git a/packages/tauri/src-tauri/icons/prod/icon.png b/packages/desktop/src-tauri/icons/prod/icon.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/icon.png rename to packages/desktop/src-tauri/icons/prod/icon.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-20x20@1x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@1x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-20x20@1x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@1x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-20x20@2x-1.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x-1.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-20x20@2x-1.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x-1.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-20x20@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-20x20@2x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-20x20@3x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@3x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-20x20@3x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@3x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-29x29@1x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@1x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-29x29@1x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@1x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-29x29@2x-1.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x-1.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-29x29@2x-1.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x-1.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-29x29@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-29x29@2x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-29x29@3x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@3x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-29x29@3x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@3x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-40x40@1x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@1x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-40x40@1x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@1x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-40x40@2x-1.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x-1.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-40x40@2x-1.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x-1.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-40x40@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-40x40@2x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-40x40@3x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@3x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-40x40@3x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@3x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-512@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-512@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-512@2x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-512@2x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-60x60@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-60x60@2x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@2x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-60x60@3x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@3x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-60x60@3x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@3x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-76x76@1x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@1x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-76x76@1x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@1x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-76x76@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-76x76@2x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@2x.png diff --git a/packages/tauri/src-tauri/icons/prod/ios/AppIcon-83.5x83.5@2x.png b/packages/desktop/src-tauri/icons/prod/ios/AppIcon-83.5x83.5@2x.png similarity index 100% rename from packages/tauri/src-tauri/icons/prod/ios/AppIcon-83.5x83.5@2x.png rename to packages/desktop/src-tauri/icons/prod/ios/AppIcon-83.5x83.5@2x.png diff --git a/packages/tauri/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs similarity index 100% rename from packages/tauri/src-tauri/src/lib.rs rename to packages/desktop/src-tauri/src/lib.rs diff --git a/packages/tauri/src-tauri/src/main.rs b/packages/desktop/src-tauri/src/main.rs similarity index 100% rename from packages/tauri/src-tauri/src/main.rs rename to packages/desktop/src-tauri/src/main.rs diff --git a/packages/tauri/src-tauri/src/window_customizer.rs b/packages/desktop/src-tauri/src/window_customizer.rs similarity index 100% rename from packages/tauri/src-tauri/src/window_customizer.rs rename to packages/desktop/src-tauri/src/window_customizer.rs diff --git a/packages/tauri/src-tauri/tauri.conf.json b/packages/desktop/src-tauri/tauri.conf.json similarity index 100% rename from packages/tauri/src-tauri/tauri.conf.json rename to packages/desktop/src-tauri/tauri.conf.json diff --git a/packages/tauri/src-tauri/tauri.prod.conf.json b/packages/desktop/src-tauri/tauri.prod.conf.json similarity index 100% rename from packages/tauri/src-tauri/tauri.prod.conf.json rename to packages/desktop/src-tauri/tauri.prod.conf.json diff --git a/packages/tauri/src/index.tsx b/packages/desktop/src/index.tsx similarity index 97% rename from packages/tauri/src/index.tsx rename to packages/desktop/src/index.tsx index dc2c4047d..57c1fbe55 100644 --- a/packages/tauri/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -1,6 +1,6 @@ // @refresh reload import { render } from "solid-js/web" -import { App, PlatformProvider, Platform } from "@opencode-ai/desktop" +import { App, PlatformProvider, Platform } from "@opencode-ai/app" import { open, save } from "@tauri-apps/plugin-dialog" import { open as shellOpen } from "@tauri-apps/plugin-shell" import { type as ostype } from "@tauri-apps/plugin-os" diff --git a/packages/tauri/src/menu.ts b/packages/desktop/src/menu.ts similarity index 100% rename from packages/tauri/src/menu.ts rename to packages/desktop/src/menu.ts diff --git a/packages/tauri/src/updater.ts b/packages/desktop/src/updater.ts similarity index 100% rename from packages/tauri/src/updater.ts rename to packages/desktop/src/updater.ts diff --git a/packages/desktop/tsconfig.json b/packages/desktop/tsconfig.json index db04f79ca..64a6bc357 100644 --- a/packages/desktop/tsconfig.json +++ b/packages/desktop/tsconfig.json @@ -1,7 +1,5 @@ { - "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { - "composite": true, "target": "ESNext", "module": "ESNext", "skipLibCheck": true, @@ -12,13 +10,11 @@ "jsxImportSource": "solid-js", "allowJs": true, "strict": true, - "noEmit": false, - "emitDeclarationOnly": true, - "outDir": "node_modules/.ts-dist", "isolatedModules": true, - "paths": { - "@/*": ["./src/*"] - } + "noEmit": true, + "emitDeclarationOnly": false, + "outDir": "node_modules/.ts-dist" }, - "exclude": ["dist", "ts-dist"] + "references": [{ "path": "../app" }], + "include": ["src"] } diff --git a/packages/desktop/vite.config.ts b/packages/desktop/vite.config.ts index 57071a894..123a2028c 100644 --- a/packages/desktop/vite.config.ts +++ b/packages/desktop/vite.config.ts @@ -1,15 +1,30 @@ import { defineConfig } from "vite" -import desktopPlugin from "./vite" +import appPlugin from "@opencode-ai/app/vite" +const host = process.env.TAURI_DEV_HOST + +// https://vite.dev/config/ export default defineConfig({ - plugins: [desktopPlugin] as any, + plugins: [appPlugin], + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // + // 1. prevent Vite from obscuring rust errors + clearScreen: false, + // 2. tauri expects a fixed port, fail if that port is not available server: { - host: "0.0.0.0", - allowedHosts: true, - port: 3000, - }, - build: { - target: "esnext", - sourcemap: true, + port: 1420, + strictPort: true, + host: host || false, + hmr: host + ? { + protocol: "ws", + host, + port: 1421, + } + : undefined, + watch: { + // 3. tell Vite to ignore watching `src-tauri` + ignored: ["**/src-tauri/**"], + }, }, }) diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 602788abb..3df50aff2 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.0.186", + "version": "1.0.191", "private": true, "type": "module", "scripts": { diff --git a/packages/enterprise/src/routes/share/[shareID].tsx b/packages/enterprise/src/routes/share/[shareID].tsx index a8b2c7f24..471104d79 100644 --- a/packages/enterprise/src/routes/share/[shareID].tsx +++ b/packages/enterprise/src/routes/share/[shareID].tsx @@ -212,6 +212,7 @@ export default function () { {iife(() => { const [store, setStore] = createStore({ messageId: undefined as string | undefined, + expandedSteps: {} as Record, }) const messages = createMemo(() => data().sessionID @@ -253,20 +254,22 @@ export default function () { const title = () => (
-
-
+
+
v{info().version}
-
- -
{model()?.name ?? modelID()}
-
-
- {DateTime.fromMillis(info().time.created).toFormat("dd MMM yyyy, HH:mm")} +
+
+ +
{model()?.name ?? modelID()}
+
+
+ {DateTime.fromMillis(info().time.created).toFormat("dd MMM yyyy, HH:mm")} +
{info().title}
@@ -282,6 +285,8 @@ export default function () { setStore("expandedSteps", message.id, (v) => !v)} classes={{ root: "min-w-0 w-full relative", content: @@ -359,6 +364,13 @@ export default function () { { + const id = store.messageId ?? firstUserMessage()!.id! + setStore("expandedSteps", id, (v) => !v) + }} classes={{ root: "grow", content: "flex flex-col justify-between", diff --git a/packages/enterprise/sst-env.d.ts b/packages/enterprise/sst-env.d.ts index 632ea3fbe..ffa17f276 100644 --- a/packages/enterprise/sst-env.d.ts +++ b/packages/enterprise/sst-env.d.ts @@ -132,6 +132,7 @@ declare module "sst" { "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "ZenData": cloudflare.R2Bucket + "ZenDataNew": cloudflare.R2Bucket } } diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 8d0141d7b..7e415b51f 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.0.186" +version = "1.0.191" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/sst/opencode" @@ -11,26 +11,26 @@ name = "OpenCode" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.186/opencode-darwin-arm64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.191/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.186/opencode-darwin-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.191/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.186/opencode-linux-arm64.tar.gz" +archive = "https://github.com/sst/opencode/releases/download/v1.0.191/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.186/opencode-linux-x64.tar.gz" +archive = "https://github.com/sst/opencode/releases/download/v1.0.191/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.186/opencode-windows-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.191/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 593499d28..83de02f9d 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.0.186", + "version": "1.0.191", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/sst-env.d.ts b/packages/function/sst-env.d.ts index 632ea3fbe..ffa17f276 100644 --- a/packages/function/sst-env.d.ts +++ b/packages/function/sst-env.d.ts @@ -132,6 +132,7 @@ declare module "sst" { "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "ZenData": cloudflare.R2Bucket + "ZenDataNew": cloudflare.R2Bucket } } diff --git a/packages/opencode/package.json b/packages/opencode/package.json index dfba43513..59307256c 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.0.186", + "version": "1.0.191", "name": "opencode", "type": "module", "private": true, @@ -55,10 +55,12 @@ "@ai-sdk/google": "2.0.44", "@ai-sdk/google-vertex": "3.0.81", "@ai-sdk/mcp": "0.0.8", + "@ai-sdk/mistral": "2.0.26", "@ai-sdk/openai": "2.0.71", "@ai-sdk/openai-compatible": "1.0.27", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18", + "@ai-sdk/xai": "2.0.42", "@clack/prompts": "1.0.0-alpha.1", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", @@ -71,8 +73,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "1.5.2", - "@opentui/core": "0.1.62", - "@opentui/solid": "0.1.62", + "@opentui/core": "0.1.63", + "@opentui/solid": "0.1.63", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 90c8594cd..ad665e5d6 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -30,6 +30,7 @@ export namespace Agent { permission: z.object({ edit: Config.Permission, bash: z.record(z.string(), Config.Permission), + skill: z.record(z.string(), Config.Permission), webfetch: Config.Permission.optional(), doom_loop: Config.Permission.optional(), external_directory: Config.Permission.optional(), @@ -58,6 +59,9 @@ export namespace Agent { bash: { "*": "allow", }, + skill: { + "*": "allow", + }, webfetch: "allow", doom_loop: "ask", external_directory: "ask", @@ -337,6 +341,17 @@ function mergeAgentPermissions(basePermission: any, overridePermission: any): Ag "*": overridePermission.bash, } } + + if (typeof basePermission.skill === "string") { + basePermission.skill = { + "*": basePermission.skill, + } + } + if (typeof overridePermission.skill === "string") { + overridePermission.skill = { + "*": overridePermission.skill, + } + } const merged = mergeDeep(basePermission ?? {}, overridePermission ?? {}) as any let mergedBash if (merged.bash) { @@ -354,10 +369,27 @@ function mergeAgentPermissions(basePermission: any, overridePermission: any): Ag } } + let mergedSkill + if (merged.skill) { + if (typeof merged.skill === "string") { + mergedSkill = { + "*": merged.skill, + } + } else if (typeof merged.skill === "object") { + mergedSkill = mergeDeep( + { + "*": "allow", + }, + merged.skill, + ) + } + } + const result: Agent.Info["permission"] = { edit: merged.edit ?? "allow", webfetch: merged.webfetch ?? "allow", bash: mergedBash ?? { "*": "allow" }, + skill: mergedSkill ?? { "*": "allow" }, doom_loop: merged.doom_loop, external_directory: merged.external_directory, } diff --git a/packages/opencode/src/agent/prompt/summary.txt b/packages/opencode/src/agent/prompt/summary.txt index 6c11638db..c9264db18 100644 --- a/packages/opencode/src/agent/prompt/summary.txt +++ b/packages/opencode/src/agent/prompt/summary.txt @@ -1,4 +1,10 @@ -Summarize the following conversation into 2 sentences MAX explaining what the -assistant did and why -Do not explain the user's input. -Do not speak in the third person about the assistant. +Summarize what was done in this conversation. Write like a pull request description. + +Rules: +- 2-3 sentences max +- Describe the changes made, not the process +- Do not mention running tests, builds, or other validation steps +- Do not explain what the user asked for +- Write in first person (I added..., I fixed...) +- Never ask questions or add new questions +- Only exception: if the conversation ends with an unanswered question to the user, preserve that exact question diff --git a/packages/opencode/src/cli/cmd/debug/index.ts b/packages/opencode/src/cli/cmd/debug/index.ts index 172987875..3b0aefa28 100644 --- a/packages/opencode/src/cli/cmd/debug/index.ts +++ b/packages/opencode/src/cli/cmd/debug/index.ts @@ -6,6 +6,7 @@ import { FileCommand } from "./file" import { LSPCommand } from "./lsp" import { RipgrepCommand } from "./ripgrep" import { ScrapCommand } from "./scrap" +import { SkillCommand } from "./skill" import { SnapshotCommand } from "./snapshot" export const DebugCommand = cmd({ @@ -17,6 +18,7 @@ export const DebugCommand = cmd({ .command(RipgrepCommand) .command(FileCommand) .command(ScrapCommand) + .command(SkillCommand) .command(SnapshotCommand) .command(PathsCommand) .command({ diff --git a/packages/opencode/src/cli/cmd/debug/skill.ts b/packages/opencode/src/cli/cmd/debug/skill.ts new file mode 100644 index 000000000..8079b688e --- /dev/null +++ b/packages/opencode/src/cli/cmd/debug/skill.ts @@ -0,0 +1,15 @@ +import { EOL } from "os" +import { Skill } from "../../../skill" +import { bootstrap } from "../../bootstrap" +import { cmd } from "../cmd" + +export const SkillCommand = cmd({ + command: "skill", + builder: (yargs) => yargs, + async handler() { + await bootstrap(process.cwd(), async () => { + const skills = await Skill.all() + process.stdout.write(JSON.stringify(skills, null, 2) + EOL) + }) + }, +}) diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index 7234cb12f..607fc7caf 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -7,7 +7,7 @@ import { graphql } from "@octokit/graphql" import * as core from "@actions/core" import * as github from "@actions/github" import type { Context } from "@actions/github/lib/context" -import type { IssueCommentEvent, PullRequestReviewCommentEvent } from "@octokit/webhooks-types" +import type { IssueCommentEvent, PullRequestReviewCommentEvent, PullRequestEvent } from "@octokit/webhooks-types" import { UI } from "../ui" import { cmd } from "./cmd" import { ModelsDev } from "../../provider/models" @@ -127,7 +127,7 @@ type IssueQueryResponse = { const AGENT_USERNAME = "opencode-agent[bot]" const AGENT_REACTION = "eyes" const WORKFLOW_FILE = ".github/workflows/opencode.yml" -const SUPPORTED_EVENTS = ["issue_comment", "pull_request_review_comment", "schedule"] as const +const SUPPORTED_EVENTS = ["issue_comment", "pull_request_review_comment", "schedule", "pull_request"] as const // Parses GitHub remote URLs in various formats: // - https://github.com/owner/repo.git @@ -392,6 +392,7 @@ export const GithubRunCommand = cmd({ core.setFailed(`Unsupported event type: ${context.eventName}`) process.exit(1) } + const isCommentEvent = ["issue_comment", "pull_request_review_comment"].includes(context.eventName) const isScheduleEvent = context.eventName === "schedule" const { providerID, modelID } = normalizeModel() @@ -400,17 +401,17 @@ export const GithubRunCommand = cmd({ const oidcBaseUrl = normalizeOidcBaseUrl() const { owner, repo } = context.repo // For schedule events, payload has no issue/comment data - const payload = isScheduleEvent - ? undefined - : (context.payload as IssueCommentEvent | PullRequestReviewCommentEvent) + const payload = isCommentEvent + ? (context.payload as IssueCommentEvent | PullRequestReviewCommentEvent) + : undefined const issueEvent = payload && isIssueCommentEvent(payload) ? payload : undefined const actor = isScheduleEvent ? undefined : context.actor const issueId = isScheduleEvent ? undefined - : context.eventName === "pull_request_review_comment" - ? (payload as PullRequestReviewCommentEvent).pull_request.number - : (payload as IssueCommentEvent).issue.number + : context.eventName === "issue_comment" + ? (payload as IssueCommentEvent).issue.number + : (payload as PullRequestEvent | PullRequestReviewCommentEvent).pull_request.number const runUrl = `/${owner}/${repo}/actions/runs/${runId}` const shareBaseUrl = isMock ? "https://dev.opencode.ai" : "https://opencode.ai" @@ -424,11 +425,11 @@ export const GithubRunCommand = cmd({ type PromptFiles = Awaited>["promptFiles"] const triggerCommentId = payload?.comment.id const useGithubToken = normalizeUseGithubToken() - const commentType = isScheduleEvent - ? undefined - : context.eventName === "pull_request_review_comment" + const commentType = isCommentEvent + ? context.eventName === "pull_request_review_comment" ? "pr_review" : "issue" + : undefined try { if (useGithubToken) { @@ -455,7 +456,7 @@ export const GithubRunCommand = cmd({ // Skip permission check for schedule events (no actor to check) if (!isScheduleEvent) { await assertPermissions() - await addReaction(commentType!) + await addReaction(commentType) } // Setup opencode session @@ -494,7 +495,10 @@ export const GithubRunCommand = cmd({ } else { console.log("Response:", response) } - } else if (context.eventName === "pull_request_review_comment" || issueEvent?.issue.pull_request) { + } else if ( + ["pull_request", "pull_request_review_comment"].includes(context.eventName) || + issueEvent?.issue.pull_request + ) { const prData = await fetchPR() // Local PR if (prData.headRepository.nameWithOwner === prData.baseRepository.nameWithOwner) { @@ -509,7 +513,7 @@ export const GithubRunCommand = cmd({ } const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${shareBaseUrl}/s/${shareId}`)) await createComment(`${response}${footer({ image: !hasShared })}`) - await removeReaction(commentType!) + await removeReaction(commentType) } // Fork PR else { @@ -524,7 +528,7 @@ export const GithubRunCommand = cmd({ } const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${shareBaseUrl}/s/${shareId}`)) await createComment(`${response}${footer({ image: !hasShared })}`) - await removeReaction(commentType!) + await removeReaction(commentType) } } // Issue @@ -545,10 +549,10 @@ export const GithubRunCommand = cmd({ `${response}\n\nCloses #${issueId}${footer({ image: true })}`, ) await createComment(`Created PR #${pr}${footer({ image: true })}`) - await removeReaction(commentType!) + await removeReaction(commentType) } else { await createComment(`${response}${footer({ image: true })}`) - await removeReaction(commentType!) + await removeReaction(commentType) } } } catch (e: any) { @@ -562,7 +566,7 @@ export const GithubRunCommand = cmd({ } if (!isScheduleEvent) { await createComment(`${msg}${footer()}`) - await removeReaction(commentType!) + await removeReaction(commentType) } core.setFailed(msg) // Also output the clean error message for the action to capture @@ -657,6 +661,9 @@ export const GithubRunCommand = cmd({ .map((m) => m.trim().toLowerCase()) .filter(Boolean) let prompt = (() => { + if (!isCommentEvent) { + return "Review this pull request" + } const body = payload!.comment.body.trim() const bodyLower = body.toLowerCase() if (mentions.some((m) => bodyLower === m)) { @@ -1030,30 +1037,57 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"` if (!["admin", "write"].includes(permission)) throw new Error(`User ${actor} does not have write permissions`) } - async function addReaction(commentType: "issue" | "pr_review") { + async function addReaction(commentType?: "issue" | "pr_review") { // Only called for non-schedule events, so triggerCommentId is defined console.log("Adding reaction...") - if (commentType === "pr_review") { - return await octoRest.rest.reactions.createForPullRequestReviewComment({ + if (triggerCommentId) { + if (commentType === "pr_review") { + return await octoRest.rest.reactions.createForPullRequestReviewComment({ + owner, + repo, + comment_id: triggerCommentId!, + content: AGENT_REACTION, + }) + } + return await octoRest.rest.reactions.createForIssueComment({ owner, repo, comment_id: triggerCommentId!, content: AGENT_REACTION, }) } - return await octoRest.rest.reactions.createForIssueComment({ + return await octoRest.rest.reactions.createForIssue({ owner, repo, - comment_id: triggerCommentId!, + issue_number: issueId!, content: AGENT_REACTION, }) } - async function removeReaction(commentType: "issue" | "pr_review") { + async function removeReaction(commentType?: "issue" | "pr_review") { // Only called for non-schedule events, so triggerCommentId is defined console.log("Removing reaction...") - if (commentType === "pr_review") { - const reactions = await octoRest.rest.reactions.listForPullRequestReviewComment({ + if (triggerCommentId) { + if (commentType === "pr_review") { + const reactions = await octoRest.rest.reactions.listForPullRequestReviewComment({ + owner, + repo, + comment_id: triggerCommentId!, + content: AGENT_REACTION, + }) + + const eyesReaction = reactions.data.find((r) => r.user?.login === AGENT_USERNAME) + if (!eyesReaction) return + + return await octoRest.rest.reactions.deleteForPullRequestComment({ + owner, + repo, + comment_id: triggerCommentId!, + reaction_id: eyesReaction.id, + }) + } + + const reactions = await octoRest.rest.reactions.listForIssueComment({ owner, repo, comment_id: triggerCommentId!, @@ -1063,29 +1097,28 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"` const eyesReaction = reactions.data.find((r) => r.user?.login === AGENT_USERNAME) if (!eyesReaction) return - await octoRest.rest.reactions.deleteForPullRequestComment({ + return await octoRest.rest.reactions.deleteForIssueComment({ owner, repo, comment_id: triggerCommentId!, reaction_id: eyesReaction.id, }) - return } - const reactions = await octoRest.rest.reactions.listForIssueComment({ + const reactions = await octoRest.rest.reactions.listForIssue({ owner, repo, - comment_id: triggerCommentId!, + issue_number: issueId!, content: AGENT_REACTION, }) const eyesReaction = reactions.data.find((r) => r.user?.login === AGENT_USERNAME) if (!eyesReaction) return - await octoRest.rest.reactions.deleteForIssueComment({ + await octoRest.rest.reactions.deleteForIssue({ owner, repo, - comment_id: triggerCommentId!, + issue_number: issueId!, reaction_id: eyesReaction.id, }) } @@ -1178,7 +1211,7 @@ query($owner: String!, $repo: String!, $number: Int!) { const comments = (issue.comments?.nodes || []) .filter((c) => { const id = parseInt(c.databaseId) - return id !== payload!.comment.id + return id !== triggerCommentId }) .map((c) => ` - ${c.author.login} at ${c.createdAt}: ${c.body}`) @@ -1306,7 +1339,7 @@ query($owner: String!, $repo: String!, $number: Int!) { const comments = (pr.comments?.nodes || []) .filter((c) => { const id = parseInt(c.databaseId) - return id !== payload!.comment.id + return id !== triggerCommentId }) .map((c) => `- ${c.author.login} at ${c.createdAt}: ${c.body}`) diff --git a/packages/opencode/src/cli/cmd/mcp.ts b/packages/opencode/src/cli/cmd/mcp.ts index 9ca4b3bff..b4ae8a37f 100644 --- a/packages/opencode/src/cli/cmd/mcp.ts +++ b/packages/opencode/src/cli/cmd/mcp.ts @@ -1,16 +1,41 @@ import { cmd } from "./cmd" import { Client } from "@modelcontextprotocol/sdk/client/index.js" import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js" +import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js" +import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js" import * as prompts from "@clack/prompts" import { UI } from "../ui" import { MCP } from "../../mcp" import { McpAuth } from "../../mcp/auth" +import { McpOAuthProvider } from "../../mcp/oauth-provider" import { Config } from "../../config/config" import { Instance } from "../../project/instance" +import { Installation } from "../../installation" import path from "path" -import os from "os" import { Global } from "../../global" +function getAuthStatusIcon(status: MCP.AuthStatus): string { + switch (status) { + case "authenticated": + return "✓" + case "expired": + return "⚠" + case "not_authenticated": + return "○" + } +} + +function getAuthStatusText(status: MCP.AuthStatus): string { + switch (status) { + case "authenticated": + return "authenticated" + case "expired": + return "expired" + case "not_authenticated": + return "not authenticated" + } +} + export const McpCommand = cmd({ command: "mcp", builder: (yargs) => @@ -19,6 +44,7 @@ export const McpCommand = cmd({ .command(McpListCommand) .command(McpAuthCommand) .command(McpLogoutCommand) + .command(McpDebugCommand) .demandCommand(), async handler() {}, }) @@ -94,10 +120,12 @@ export const McpAuthCommand = cmd({ command: "auth [name]", describe: "authenticate with an OAuth-enabled MCP server", builder: (yargs) => - yargs.positional("name", { - describe: "name of the MCP server", - type: "string", - }), + yargs + .positional("name", { + describe: "name of the MCP server", + type: "string", + }) + .command(McpAuthListCommand), async handler(args) { await Instance.provide({ directory: process.cwd(), @@ -108,20 +136,19 @@ export const McpAuthCommand = cmd({ const config = await Config.get() const mcpServers = config.mcp ?? {} - // Get OAuth-enabled servers - const oauthServers = Object.entries(mcpServers).filter(([_, cfg]) => cfg.type === "remote" && !!cfg.oauth) + // Get OAuth-capable servers (remote servers with oauth not explicitly disabled) + const oauthServers = Object.entries(mcpServers).filter( + ([_, cfg]) => cfg.type === "remote" && cfg.oauth !== false, + ) if (oauthServers.length === 0) { - prompts.log.warn("No OAuth-enabled MCP servers configured") - prompts.log.info("Add OAuth config to a remote MCP server in opencode.json:") + prompts.log.warn("No OAuth-capable MCP servers configured") + prompts.log.info("Remote MCP servers support OAuth by default. Add a remote server in opencode.json:") prompts.log.info(` "mcp": { "my-server": { "type": "remote", - "url": "https://example.com/mcp", - "oauth": { - "scope": "tools:read" - } + "url": "https://example.com/mcp" } }`) prompts.outro("Done") @@ -130,13 +157,24 @@ export const McpAuthCommand = cmd({ let serverName = args.name if (!serverName) { + // Build options with auth status + const options = await Promise.all( + oauthServers.map(async ([name, cfg]) => { + const authStatus = await MCP.getAuthStatus(name) + const icon = getAuthStatusIcon(authStatus) + const statusText = getAuthStatusText(authStatus) + const url = cfg.type === "remote" ? cfg.url : "" + return { + label: `${icon} ${name} (${statusText})`, + value: name, + hint: url, + } + }), + ) + const selected = await prompts.select({ message: "Select MCP server to authenticate", - options: oauthServers.map(([name, cfg]) => ({ - label: name, - value: name, - hint: cfg.type === "remote" ? cfg.url : undefined, - })), + options, }) if (prompts.isCancel(selected)) throw new UI.CancelledError() serverName = selected @@ -149,22 +187,24 @@ export const McpAuthCommand = cmd({ return } - if (serverConfig.type !== "remote" || !serverConfig.oauth) { - prompts.log.error(`MCP server ${serverName} does not have OAuth configured`) + if (serverConfig.type !== "remote" || serverConfig.oauth === false) { + prompts.log.error(`MCP server ${serverName} does not support OAuth (oauth is disabled)`) prompts.outro("Done") return } // Check if already authenticated - const hasTokens = await MCP.hasStoredTokens(serverName) - if (hasTokens) { + const authStatus = await MCP.getAuthStatus(serverName) + if (authStatus === "authenticated") { const confirm = await prompts.confirm({ - message: `${serverName} already has stored credentials. Re-authenticate?`, + message: `${serverName} already has valid credentials. Re-authenticate?`, }) if (prompts.isCancel(confirm) || !confirm) { prompts.outro("Cancelled") return } + } else if (authStatus === "expired") { + prompts.log.warn(`${serverName} has expired credentials. Re-authenticating...`) } const spinner = prompts.spinner() @@ -207,6 +247,46 @@ export const McpAuthCommand = cmd({ }, }) +export const McpAuthListCommand = cmd({ + command: "list", + aliases: ["ls"], + describe: "list OAuth-capable MCP servers and their auth status", + async handler() { + await Instance.provide({ + directory: process.cwd(), + async fn() { + UI.empty() + prompts.intro("MCP OAuth Status") + + const config = await Config.get() + const mcpServers = config.mcp ?? {} + + // Get OAuth-capable servers + const oauthServers = Object.entries(mcpServers).filter( + ([_, cfg]) => cfg.type === "remote" && cfg.oauth !== false, + ) + + if (oauthServers.length === 0) { + prompts.log.warn("No OAuth-capable MCP servers configured") + prompts.outro("Done") + return + } + + for (const [name, serverConfig] of oauthServers) { + const authStatus = await MCP.getAuthStatus(name) + const icon = getAuthStatusIcon(authStatus) + const statusText = getAuthStatusText(authStatus) + const url = serverConfig.type === "remote" ? serverConfig.url : "" + + prompts.log.info(`${icon} ${name} ${UI.Style.TEXT_DIM}${statusText}\n ${UI.Style.TEXT_DIM}${url}`) + } + + prompts.outro(`${oauthServers.length} OAuth-capable server(s)`) + }, + }) + }, +}) + export const McpLogoutCommand = cmd({ command: "logout [name]", describe: "remove OAuth credentials for an MCP server", @@ -398,3 +478,177 @@ export const McpAddCommand = cmd({ prompts.outro("MCP server added successfully") }, }) + +export const McpDebugCommand = cmd({ + command: "debug ", + describe: "debug OAuth connection for an MCP server", + builder: (yargs) => + yargs.positional("name", { + describe: "name of the MCP server", + type: "string", + demandOption: true, + }), + async handler(args) { + await Instance.provide({ + directory: process.cwd(), + async fn() { + UI.empty() + prompts.intro("MCP OAuth Debug") + + const config = await Config.get() + const mcpServers = config.mcp ?? {} + const serverName = args.name + + const serverConfig = mcpServers[serverName] + if (!serverConfig) { + prompts.log.error(`MCP server not found: ${serverName}`) + prompts.outro("Done") + return + } + + if (serverConfig.type !== "remote") { + prompts.log.error(`MCP server ${serverName} is not a remote server`) + prompts.outro("Done") + return + } + + if (serverConfig.oauth === false) { + prompts.log.warn(`MCP server ${serverName} has OAuth explicitly disabled`) + prompts.outro("Done") + return + } + + prompts.log.info(`Server: ${serverName}`) + prompts.log.info(`URL: ${serverConfig.url}`) + + // Check stored auth status + const authStatus = await MCP.getAuthStatus(serverName) + prompts.log.info(`Auth status: ${getAuthStatusIcon(authStatus)} ${getAuthStatusText(authStatus)}`) + + const entry = await McpAuth.get(serverName) + if (entry?.tokens) { + prompts.log.info(` Access token: ${entry.tokens.accessToken.substring(0, 20)}...`) + if (entry.tokens.expiresAt) { + const expiresDate = new Date(entry.tokens.expiresAt * 1000) + const isExpired = entry.tokens.expiresAt < Date.now() / 1000 + prompts.log.info(` Expires: ${expiresDate.toISOString()} ${isExpired ? "(EXPIRED)" : ""}`) + } + if (entry.tokens.refreshToken) { + prompts.log.info(` Refresh token: present`) + } + } + if (entry?.clientInfo) { + prompts.log.info(` Client ID: ${entry.clientInfo.clientId}`) + if (entry.clientInfo.clientSecretExpiresAt) { + const expiresDate = new Date(entry.clientInfo.clientSecretExpiresAt * 1000) + prompts.log.info(` Client secret expires: ${expiresDate.toISOString()}`) + } + } + + const spinner = prompts.spinner() + spinner.start("Testing connection...") + + // Test basic HTTP connectivity first + try { + const response = await fetch(serverConfig.url, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json, text/event-stream", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + method: "initialize", + params: { + protocolVersion: "2024-11-05", + capabilities: {}, + clientInfo: { name: "opencode-debug", version: Installation.VERSION }, + }, + id: 1, + }), + }) + + spinner.stop(`HTTP response: ${response.status} ${response.statusText}`) + + // Check for WWW-Authenticate header + const wwwAuth = response.headers.get("www-authenticate") + if (wwwAuth) { + prompts.log.info(`WWW-Authenticate: ${wwwAuth}`) + } + + if (response.status === 401) { + prompts.log.warn("Server returned 401 Unauthorized") + + // Try to discover OAuth metadata + const oauthConfig = typeof serverConfig.oauth === "object" ? serverConfig.oauth : undefined + const authProvider = new McpOAuthProvider( + serverName, + serverConfig.url, + { + clientId: oauthConfig?.clientId, + clientSecret: oauthConfig?.clientSecret, + scope: oauthConfig?.scope, + }, + { + onRedirect: async () => {}, + }, + ) + + prompts.log.info("Testing OAuth flow (without completing authorization)...") + + // Try creating transport with auth provider to trigger discovery + const transport = new StreamableHTTPClientTransport(new URL(serverConfig.url), { + authProvider, + }) + + try { + const client = new Client({ + name: "opencode-debug", + version: Installation.VERSION, + }) + await client.connect(transport) + prompts.log.success("Connection successful (already authenticated)") + await client.close() + } catch (error) { + if (error instanceof UnauthorizedError) { + prompts.log.info(`OAuth flow triggered: ${error.message}`) + + // Check if dynamic registration would be attempted + const clientInfo = await authProvider.clientInformation() + if (clientInfo) { + prompts.log.info(`Client ID available: ${clientInfo.client_id}`) + } else { + prompts.log.info("No client ID - dynamic registration will be attempted") + } + } else { + prompts.log.error(`Connection error: ${error instanceof Error ? error.message : String(error)}`) + } + } + } else if (response.status >= 200 && response.status < 300) { + prompts.log.success("Server responded successfully (no auth required or already authenticated)") + const body = await response.text() + try { + const json = JSON.parse(body) + if (json.result?.serverInfo) { + prompts.log.info(`Server info: ${JSON.stringify(json.result.serverInfo)}`) + } + } catch { + // Not JSON, ignore + } + } else { + prompts.log.warn(`Unexpected status: ${response.status}`) + const body = await response.text().catch(() => "") + if (body) { + prompts.log.info(`Response body: ${body.substring(0, 500)}`) + } + } + } catch (error) { + spinner.stop("Connection failed", 1) + prompts.log.error(`Error: ${error instanceof Error ? error.message : String(error)}`) + } + + prompts.outro("Debug complete") + }, + }) + }, +}) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index a892c83da..47940d0e2 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -14,7 +14,7 @@ import { Keybind } from "@/util/keybind" import { usePromptHistory, type PromptInfo } from "./history" import { type AutocompleteRef, Autocomplete } from "./autocomplete" import { useCommandDialog } from "../dialog-command" -import { useRenderer } from "@opentui/solid" +import { useRenderer, useTerminalDimensions } from "@opentui/solid" import { Editor } from "@tui/util/editor" import { useExit } from "../../context/exit" import { Clipboard } from "../../util/clipboard" @@ -120,6 +120,9 @@ export function Prompt(props: PromptProps) { const history = usePromptHistory() const command = useCommandDialog() const renderer = useRenderer() + const dimensions = useTerminalDimensions() + const tall = createMemo(() => dimensions().height > 40) + const wide = createMemo(() => dimensions().width > 120) const { theme, syntax } = useTheme() function promptModelWarning() { @@ -310,6 +313,11 @@ export function Prompt(props: PromptProps) { sdk.event.on(TuiEvent.PromptAppend.type, (evt) => { input.insertText(evt.properties.text) + setTimeout(() => { + input.getLayoutNode().markDirty() + input.gotoBufferEnd() + renderer.requestRender() + }, 0) }) createEffect(() => { @@ -876,19 +884,21 @@ export function Prompt(props: PromptProps) { cursorColor={theme.text} syntaxStyle={syntax()} /> - - - {store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "} - - - - - {local.model.parsed().model} - - {local.model.parsed().provider} - - - + + + + {store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "} + + + + + {local.model.parsed().model} + + {local.model.parsed().provider} + + + + - }> - - - {/* @ts-ignore // SpinnerOptions doesn't support marginLeft */} - - - {(() => { - const retry = createMemo(() => { - const s = status() - if (s.type !== "retry") return - return s - }) - const message = createMemo(() => { - const r = retry() - if (!r) return - if (r.message.includes("exceeded your current quota") && r.message.includes("gemini")) - return "gemini is way too hot right now" - if (r.message.length > 80) return r.message.slice(0, 80) + "..." - return r.message - }) - const isTruncated = createMemo(() => { - const r = retry() - if (!r) return false - return r.message.length > 120 - }) - const [seconds, setSeconds] = createSignal(0) - onMount(() => { - const timer = setInterval(() => { - const next = retry()?.next - if (next) setSeconds(Math.round((next - Date.now()) / 1000)) - }, 1000) - - onCleanup(() => { - clearInterval(timer) + + + + + {/* @ts-ignore // SpinnerOptions doesn't support marginLeft */} + + + {(() => { + const retry = createMemo(() => { + const s = status() + if (s.type !== "retry") return + return s }) - }) - const handleMessageClick = () => { - const r = retry() - if (!r) return - if (isTruncated()) { - DialogAlert.show(dialog, "Retry Error", r.message) + const message = createMemo(() => { + const r = retry() + if (!r) return + if (r.message.includes("exceeded your current quota") && r.message.includes("gemini")) + return "gemini is way too hot right now" + if (r.message.length > 80) return r.message.slice(0, 80) + "..." + return r.message + }) + const isTruncated = createMemo(() => { + const r = retry() + if (!r) return false + return r.message.length > 120 + }) + const [seconds, setSeconds] = createSignal(0) + onMount(() => { + const timer = setInterval(() => { + const next = retry()?.next + if (next) setSeconds(Math.round((next - Date.now()) / 1000)) + }, 1000) + + onCleanup(() => { + clearTimeout(timer) + }) + }) + const handleMessageClick = () => { + const r = retry() + if (!r) return + if (isTruncated()) { + DialogAlert.show(dialog, "Retry Error", r.message) + } } - } - const retryText = () => { - const r = retry() - if (!r) return "" - const baseMessage = message() - const truncatedHint = isTruncated() ? " (click to expand)" : "" - const retryInfo = ` [retrying ${seconds() > 0 ? `in ${seconds()}s ` : ""}attempt #${r.attempt}]` - return baseMessage + truncatedHint + retryInfo - } + const retryText = () => { + const r = retry() + if (!r) return "" + const baseMessage = message() + const truncatedHint = isTruncated() ? " (click to expand)" : "" + const retryInfo = ` [retrying ${seconds() > 0 ? `in ${seconds()}s ` : ""}attempt #${r.attempt}]` + return baseMessage + truncatedHint + retryInfo + } - return ( - - - {retryText()} - - - ) - })()} + return ( + + + {retryText()} + + + ) + })()} + + 0 ? theme.primary : theme.text}> + esc{" "} + 0 ? theme.primary : theme.textMuted }}> + {store.interrupt > 0 ? "again to interrupt" : "interrupt"} + + - 0 ? theme.primary : theme.text}> - esc{" "} - 0 ? theme.primary : theme.textMuted }}> - {store.interrupt > 0 ? "again to interrupt" : "interrupt"} - - - - - - - - + + + + + {store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "} + + + + + {local.model.parsed().model} + + {local.model.parsed().provider} + + + + + + + + + {keybind.print("agent_cycle")} switch agent + + - {keybind.print("command_list")} commands + {keybind.print("sidebar_toggle")} sidebar - - - - esc exit shell mode - - - - - + + + {keybind.print("command_list")} commands + + + + + esc exit shell mode + + + + diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx index 86317d62a..ff17b5567 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx @@ -54,7 +54,7 @@ export function DialogMessage(props: { { title: "Copy", value: "message.copy", - description: "copy message text to clipboard", + description: "message text to clipboard", onSelect: async (dialog) => { const msg = message() if (!msg) return diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/dialog-subagent.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/dialog-subagent.tsx index a9446b20d..c5ef70ef0 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/dialog-subagent.tsx @@ -11,7 +11,7 @@ export function DialogSubagent(props: { sessionID: string }) { { title: "Open", value: "subagent.view", - description: "open the subagent's session", + description: "the subagent's session", onSelect: (dialog) => { route.navigate({ type: "session", diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx index bfdbfa51b..cf6abef47 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx @@ -1,121 +1,140 @@ import { type Accessor, createMemo, Match, Show, Switch } from "solid-js" import { useRouteData } from "@tui/context/route" import { useSync } from "@tui/context/sync" -import { pipe, sumBy } from "remeda" import { useTheme } from "@tui/context/theme" -import { SplitBorder, EmptyBorder } from "@tui/component/border" -import type { AssistantMessage, Session } from "@opencode-ai/sdk/v2" -import { useDirectory } from "../../context/directory" +import { EmptyBorder } from "@tui/component/border" +import type { Session } from "@opencode-ai/sdk/v2" import { useKeybind } from "../../context/keybind" +import { useTerminalDimensions } from "@opentui/solid" -const Title = (props: { session: Accessor }) => { +const Title = (props: { session: Accessor; truncate?: boolean }) => { const { theme } = useTheme() return ( - + # {props.session().title} ) } -const ContextInfo = (props: { context: Accessor; cost: Accessor }) => { - const { theme } = useTheme() - return ( - - - {props.context()} ({props.cost()}) - - - ) -} - export function Header() { const route = useRouteData("session") const sync = useSync() const session = createMemo(() => sync.session.get(route.sessionID)!) - const messages = createMemo(() => sync.data.message[route.sessionID] ?? []) const shareEnabled = createMemo(() => sync.data.config.share !== "disabled") - - const cost = createMemo(() => { - const total = pipe( - messages(), - sumBy((x) => (x.role === "assistant" ? x.cost : 0)), - ) - return new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - }).format(total) - }) - - const context = createMemo(() => { - const last = messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage - if (!last) return - const total = - last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write - const model = sync.data.provider.find((x) => x.id === last.providerID)?.models[last.modelID] - let result = total.toLocaleString() - if (model?.limit.context) { - result += " " + Math.round((total / model.limit.context) * 100) + "%" - } - return result - }) + const showShare = createMemo(() => shareEnabled() && !session()?.share?.url) const { theme } = useTheme() const keybind = useKeybind() + const dimensions = useTerminalDimensions() + const tall = createMemo(() => dimensions().height > 40) return ( - - - - - Subagent session - - - Prev {keybind.print("session_child_cycle_reverse")} - - - Next {keybind.print("session_child_cycle")} - - - - - - - - - <ContextInfo context={context} cost={cost} /> - </box> - <Show when={shareEnabled()}> - <box flexDirection="row" justifyContent="space-between" gap={1}> - <box flexGrow={1} flexShrink={1}> - <Switch> - <Match when={session().share?.url}> - <text fg={theme.textMuted} wrapMode="word"> - {session().share!.url} - </text> - </Match> - <Match when={true}> - <text fg={theme.text} wrapMode="word"> - /share <span style={{ fg: theme.textMuted }}>copy link</span> - </text> - </Match> - </Switch> - </box> + <box + height={1} + border={["top"]} + borderColor={theme.backgroundPanel} + customBorderChars={ + theme.backgroundPanel.a !== 0 + ? { + ...EmptyBorder, + horizontal: "▄", + } + : { + ...EmptyBorder, + horizontal: " ", + } + } + /> + </box> + <box + border={["left"]} + borderColor={theme.border} + customBorderChars={{ + ...EmptyBorder, + vertical: "┃", + bottomLeft: "╹", + }} + > + <box + paddingTop={tall() ? 1 : 0} + paddingBottom={tall() ? 1 : 0} + paddingLeft={2} + paddingRight={1} + flexShrink={0} + flexGrow={1} + backgroundColor={theme.backgroundPanel} + > + <Switch> + <Match when={session()?.parentID}> + <box flexDirection="row" gap={2}> + <text fg={theme.text}> + <b>Subagent session</b> + </text> + <text fg={theme.text}> + Parent <span style={{ fg: theme.textMuted }}>{keybind.print("session_parent")}</span> + </text> + <text fg={theme.text}> + Prev <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle_reverse")}</span> + </text> + <text fg={theme.text}> + Next <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle")}</span> + </text> + <box flexGrow={1} flexShrink={1} /> + <Show when={showShare()}> + <text fg={theme.textMuted} wrapMode="none" flexShrink={0}> + /share{" "} + </text> + </Show> </box> - </Show> - </Match> - </Switch> + </Match> + <Match when={true}> + <box flexDirection="row" justifyContent="space-between" gap={1}> + <Title session={session} truncate={!tall()} /> + <Show when={showShare()}> + <text fg={theme.textMuted} wrapMode="none" flexShrink={0}> + /share{" "} + </text> + </Show> + </box> + </Match> + </Switch> + </box> + </box> + <box + height={1} + border={["left"]} + borderColor={theme.border} + customBorderChars={{ + ...EmptyBorder, + vertical: theme.backgroundPanel.a !== 0 ? "╹" : " ", + }} + > + <box + height={1} + border={["bottom"]} + borderColor={theme.backgroundPanel} + customBorderChars={ + theme.backgroundPanel.a !== 0 + ? { + ...EmptyBorder, + horizontal: "▀", + } + : { + ...EmptyBorder, + horizontal: " ", + } + } + /> </box> </box> ) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 95b4d3344..0a357c795 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -22,6 +22,7 @@ import { ScrollBoxRenderable, addDefaultParsers, MacOSScrollAccel, + RGBA, type ScrollAcceleration, } from "@opentui/core" import { Prompt, type PromptRef } from "@tui/component/prompt" @@ -131,13 +132,15 @@ export function Session() { const [diffWrapMode, setDiffWrapMode] = createSignal<"word" | "none">("word") const wide = createMemo(() => dimensions().width > 120) + const tall = createMemo(() => dimensions().height > 40) const sidebarVisible = createMemo(() => { if (session()?.parentID) return false if (sidebar() === "show") return true if (sidebar() === "auto" && wide()) return true return false }) - const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4) + const sidebarOverlay = createMemo(() => sidebarVisible() && !wide()) + const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() && !sidebarOverlay() ? 42 : 0) - 4) const scrollAcceleration = createMemo(() => { const tui = sync.data.config.tui @@ -234,6 +237,52 @@ export function Session() { let prompt: PromptRef const keybind = useKeybind() + // Helper: Find next visible message boundary in direction + const findNextVisibleMessage = (direction: "next" | "prev"): string | null => { + const children = scroll.getChildren() + const messagesList = messages() + const scrollTop = scroll.y + + // Get visible messages sorted by position, filtering for valid non-synthetic, non-ignored content + const visibleMessages = children + .filter((c) => { + if (!c.id) return false + const message = messagesList.find((m) => m.id === c.id) + if (!message) return false + + // Check if message has valid non-synthetic, non-ignored text parts + const parts = sync.data.part[message.id] + if (!parts || !Array.isArray(parts)) return false + + return parts.some((part) => part && part.type === "text" && !part.synthetic && !part.ignored) + }) + .sort((a, b) => a.y - b.y) + + if (visibleMessages.length === 0) return null + + if (direction === "next") { + // Find first message below current position + return visibleMessages.find((c) => c.y > scrollTop + 10)?.id ?? null + } + // Find last message above current position + return [...visibleMessages].reverse().find((c) => c.y < scrollTop - 10)?.id ?? null + } + + // Helper: Scroll to message in direction or fallback to page scroll + const scrollToMessage = (direction: "next" | "prev", dialog: ReturnType<typeof useDialog>) => { + const targetID = findNextVisibleMessage(direction) + + if (!targetID) { + scroll.scrollBy(direction === "next" ? scroll.height : -scroll.height) + dialog.clear() + return + } + + const child = scroll.getChildren().find((c) => c.id === targetID) + if (child) scroll.scrollBy(child.y - scroll.y - 1) + dialog.clear() + } + useKeyboard((evt) => { if (dialog.stack.length > 0) return @@ -667,6 +716,22 @@ export function Session() { } }, }, + { + title: "Next message", + value: "session.message.next", + keybind: "messages_next", + category: "Session", + disabled: true, + onSelect: (dialog) => scrollToMessage("next", dialog), + }, + { + title: "Previous message", + value: "session.message.previous", + keybind: "messages_previous", + category: "Session", + disabled: true, + onSelect: (dialog) => scrollToMessage("prev", dialog), + }, { title: "Copy last assistant message", value: "messages.copy", @@ -841,6 +906,23 @@ export function Session() { dialog.clear() }, }, + { + title: "Go to parent session", + value: "session.parent", + keybind: "session_parent", + category: "Session", + disabled: true, + onSelect: (dialog) => { + const parentID = session()?.parentID + if (parentID) { + navigate({ + type: "session", + sessionID: parentID, + }) + } + dialog.clear() + }, + }, ]) const revertInfo = createMemo(() => session()?.revert) @@ -915,7 +997,7 @@ export function Session() { <box flexDirection="row"> <box flexGrow={1} paddingBottom={1} paddingTop={1} paddingLeft={2} paddingRight={2} gap={1}> <Show when={session()}> - <Show when={!sidebarVisible()}> + <Show when={!sidebarVisible() || sidebarOverlay()}> <Header /> </Show> <scrollbox @@ -1087,15 +1169,33 @@ export function Session() { </Match> </Switch> </box> - <Show when={!sidebarVisible()}> + <Show when={(!sidebarVisible() || sidebarOverlay()) && tall()}> <Footer /> </Show> </Show> <Toast /> </box> - <Show when={sidebarVisible()}> + <Show when={sidebarVisible() && !sidebarOverlay()}> <Sidebar sessionID={route.sessionID} /> </Show> + <Show when={sidebarOverlay()}> + <box + position="absolute" + left={0} + top={0} + width={dimensions().width} + height={dimensions().height} + backgroundColor={RGBA.fromInts(0, 0, 0, 150)} + zIndex={100} + flexDirection="row" + justifyContent="flex-end" + onMouseUp={() => setSidebar("hide")} + > + <box onMouseUp={(e) => e.stopPropagation()}> + <Sidebar sessionID={route.sessionID} /> + </box> + </box> + </Show> </box> </context.Provider> ) @@ -1642,33 +1742,15 @@ ToolRegistry.register<typeof ListTool>({ ToolRegistry.register<typeof TaskTool>({ name: "task", - container: "inline", + container: "block", render(props) { const { theme } = useTheme() const keybind = useKeybind() const dialog = useDialog() const renderer = useRenderer() - const [hover, setHover] = createSignal(false) return ( - <box - border={["left"]} - customBorderChars={SplitBorder.customBorderChars} - borderColor={theme.background} - paddingTop={1} - paddingBottom={1} - paddingLeft={2} - marginTop={1} - gap={1} - backgroundColor={hover() ? theme.backgroundElement : theme.backgroundPanel} - onMouseOver={() => setHover(true)} - onMouseOut={() => setHover(false)} - onMouseUp={() => { - const id = props.metadata.sessionId - if (renderer.getSelection()?.getSelectedText() || !id) return - dialog.replace(() => <DialogSubagent sessionID={id} />) - }} - > + <> <ToolTitle icon="◉" fallback="Delegating..." when={props.input.subagent_type ?? props.input.description}> {Locale.titlecase(props.input.subagent_type ?? "unknown")} Task "{props.input.description}" </ToolTitle> @@ -1691,7 +1773,7 @@ ToolRegistry.register<typeof TaskTool>({ {keybind.print("session_child_cycle")}, {keybind.print("session_child_cycle_reverse")} <span style={{ fg: theme.textMuted }}> to navigate between subagent sessions</span> </text> - </box> + </> ) }, }) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx index 6c74e04fa..0bc4e860f 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx @@ -29,6 +29,16 @@ export function Sidebar(props: { sessionID: string }) { // Sort MCP servers alphabetically for consistent display order const mcpEntries = createMemo(() => Object.entries(sync.data.mcp).sort(([a], [b]) => a.localeCompare(b))) + // Count connected and error MCP servers for collapsed header display + const connectedMcpCount = createMemo(() => mcpEntries().filter(([_, item]) => item.status === "connected").length) + const errorMcpCount = createMemo( + () => + mcpEntries().filter( + ([_, item]) => + item.status === "failed" || item.status === "needs_auth" || item.status === "needs_client_registration", + ).length, + ) + const cost = createMemo(() => { const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0) return new Intl.NumberFormat("en-US", { @@ -62,6 +72,7 @@ export function Sidebar(props: { sessionID: string }) { <box backgroundColor={theme.backgroundPanel} width={42} + height="100%" paddingTop={1} paddingBottom={1} paddingLeft={2} @@ -97,6 +108,13 @@ export function Sidebar(props: { sessionID: string }) { </Show> <text fg={theme.text}> <b>MCP</b> + <Show when={!expanded.mcp}> + <span style={{ fg: theme.textMuted }}> + {" "} + ({connectedMcpCount()} active + {errorMcpCount() > 0 ? `, ${errorMcpCount()} error${errorMcpCount() > 1 ? "s" : ""}` : ""}) + </span> + </Show> </text> </box> <Show when={mcpEntries().length <= 2 || expanded.mcp}> diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 031bdd31b..cb93c0ebf 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -414,6 +414,7 @@ export namespace Config { .object({ edit: Permission.optional(), bash: z.union([Permission, z.record(z.string(), Permission)]).optional(), + skill: z.union([Permission, z.record(z.string(), Permission)]).optional(), webfetch: Permission.optional(), doom_loop: Permission.optional(), external_directory: Permission.optional(), @@ -456,6 +457,8 @@ export namespace Config { .describe("Scroll messages down by half page"), messages_first: z.string().optional().default("ctrl+g,home").describe("Navigate to first message"), messages_last: z.string().optional().default("ctrl+alt+g,end").describe("Navigate to last message"), + messages_next: z.string().optional().default("none").describe("Navigate to next message"), + messages_previous: z.string().optional().default("none").describe("Navigate to previous message"), messages_last_user: z.string().optional().default("none").describe("Navigate to last user message"), messages_copy: z.string().optional().default("<leader>y").describe("Copy message"), messages_undo: z.string().optional().default("<leader>u").describe("Undo message"), @@ -560,6 +563,7 @@ export namespace Config { history_next: z.string().optional().default("down").describe("Next history item"), session_child_cycle: z.string().optional().default("<leader>right").describe("Next child session"), session_child_cycle_reverse: z.string().optional().default("<leader>left").describe("Previous child session"), + session_parent: z.string().optional().default("<leader>up").describe("Go to parent session"), terminal_suspend: z.string().optional().default("ctrl+z").describe("Suspend terminal"), terminal_title_toggle: z.string().optional().default("none").describe("Toggle terminal title"), }) @@ -761,6 +765,7 @@ export namespace Config { .object({ edit: Permission.optional(), bash: z.union([Permission, z.record(z.string(), Permission)]).optional(), + skill: z.union([Permission, z.record(z.string(), Permission)]).optional(), webfetch: Permission.optional(), doom_loop: Permission.optional(), external_directory: Permission.optional(), diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 00d9e8c38..22b714b85 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -240,7 +240,8 @@ export namespace Ripgrep { if (done) break buffer += decoder.decode(value, { stream: true }) - const lines = buffer.split("\n") + // Handle both Unix (\n) and Windows (\r\n) line endings + const lines = buffer.split(/\r?\n/) buffer = lines.pop() || "" for (const line of lines) { @@ -379,7 +380,8 @@ export namespace Ripgrep { return [] } - const lines = result.text().trim().split("\n").filter(Boolean) + // Handle both Unix (\n) and Windows (\r\n) line endings + const lines = result.text().trim().split(/\r?\n/).filter(Boolean) // Parse JSON lines from ripgrep output return lines diff --git a/packages/opencode/src/lsp/language.ts b/packages/opencode/src/lsp/language.ts index 12792c7c2..620944a8e 100644 --- a/packages/opencode/src/lsp/language.ts +++ b/packages/opencode/src/lsp/language.ts @@ -111,4 +111,6 @@ export const LANGUAGE_EXTENSIONS: Record<string, string> = { ".tfvars": "terraform-vars", ".hcl": "hcl", ".nix": "nix", + ".typ": "typst", + ".typc": "typst", } as const diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 9390259a8..b432e5a5d 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -1747,6 +1747,27 @@ export namespace LSPServer { }, } + export const Clojure: Info = { + id: "clojure-lsp", + extensions: [".clj", ".cljs", ".cljc", ".edn"], + root: NearestRoot(["deps.edn", "project.clj", "shadow-cljs.edn", "bb.edn", "build.boot"]), + async spawn(root) { + let bin = Bun.which("clojure-lsp") + if (!bin && process.platform === "win32") { + bin = Bun.which("clojure-lsp.exe") + } + if (!bin) { + log.info("clojure-lsp not found, please install clojure-lsp first") + return + } + return { + process: spawn(bin, ["listen"], { + cwd: root, + }), + } + }, + } + export const Nixd: Info = { id: "nixd", extensions: [".nix"], @@ -1777,4 +1798,98 @@ export namespace LSPServer { } }, } + + export const Tinymist: Info = { + id: "tinymist", + extensions: [".typ", ".typc"], + root: NearestRoot(["typst.toml"]), + async spawn(root) { + let bin = Bun.which("tinymist", { + PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, + }) + + if (!bin) { + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return + log.info("downloading tinymist from GitHub releases") + + const response = await fetch("https://api.github.com/repos/Myriad-Dreamin/tinymist/releases/latest") + if (!response.ok) { + log.error("Failed to fetch tinymist release info") + return + } + + const release = (await response.json()) as { + tag_name?: string + assets?: { name?: string; browser_download_url?: string }[] + } + + const platform = process.platform + const arch = process.arch + + const tinymistArch = arch === "arm64" ? "aarch64" : "x86_64" + let tinymistPlatform: string + let ext: string + + if (platform === "darwin") { + tinymistPlatform = "apple-darwin" + ext = "tar.gz" + } else if (platform === "win32") { + tinymistPlatform = "pc-windows-msvc" + ext = "zip" + } else { + tinymistPlatform = "unknown-linux-gnu" + ext = "tar.gz" + } + + const assetName = `tinymist-${tinymistArch}-${tinymistPlatform}.${ext}` + + const assets = release.assets ?? [] + const asset = assets.find((a) => a.name === assetName) + if (!asset?.browser_download_url) { + log.error(`Could not find asset ${assetName} in tinymist release`) + return + } + + const downloadResponse = await fetch(asset.browser_download_url) + if (!downloadResponse.ok) { + log.error("Failed to download tinymist") + return + } + + const tempPath = path.join(Global.Path.bin, assetName) + await Bun.file(tempPath).write(downloadResponse) + + if (ext === "zip") { + const ok = await Archive.extractZip(tempPath, Global.Path.bin) + .then(() => true) + .catch((error) => { + log.error("Failed to extract tinymist archive", { error }) + return false + }) + if (!ok) return + } else { + await $`tar -xzf ${tempPath} --strip-components=1`.cwd(Global.Path.bin).quiet().nothrow() + } + + await fs.rm(tempPath, { force: true }) + + bin = path.join(Global.Path.bin, "tinymist" + (platform === "win32" ? ".exe" : "")) + + if (!(await Bun.file(bin).exists())) { + log.error("Failed to extract tinymist binary") + return + } + + if (platform !== "win32") { + await $`chmod +x ${bin}`.quiet().nothrow() + } + + log.info("installed tinymist", { bin }) + } + + return { + process: spawn(bin, { cwd: root }), + } + }, + } } diff --git a/packages/opencode/src/mcp/auth.ts b/packages/opencode/src/mcp/auth.ts index 6ebb95698..7f7dbd156 100644 --- a/packages/opencode/src/mcp/auth.ts +++ b/packages/opencode/src/mcp/auth.ts @@ -121,4 +121,15 @@ export namespace McpAuth { await set(mcpName, entry) } } + + /** + * Check if stored tokens are expired. + * Returns null if no tokens exist, false if no expiry or not expired, true if expired. + */ + export async function isTokenExpired(mcpName: string): Promise<boolean | null> { + const entry = await get(mcpName) + if (!entry?.tokens) return null + if (!entry.tokens.expiresAt) return false + return entry.tokens.expiresAt < Date.now() / 1000 + } } diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 2f8e4ace4..40ee25565 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -15,6 +15,8 @@ import { withTimeout } from "@/util/timeout" import { McpOAuthProvider } from "./oauth-provider" import { McpOAuthCallback } from "./oauth-callback" import { McpAuth } from "./auth" +import { Bus } from "@/bus" +import { TuiEvent } from "@/cli/cmd/tui/event" import open from "open" export namespace MCP { @@ -251,10 +253,24 @@ export namespace MCP { status: "needs_client_registration" as const, error: "Server does not support dynamic client registration. Please provide clientId in config.", } + // Show toast for needs_client_registration + Bus.publish(TuiEvent.ToastShow, { + title: "MCP Authentication Required", + message: `Server "${key}" requires a pre-registered client ID. Add clientId to your config.`, + variant: "warning", + duration: 8000, + }).catch((e) => log.debug("failed to show toast", { error: e })) } else { // Store transport for later finishAuth call pendingOAuthTransports.set(key, transport) status = { status: "needs_auth" as const } + // Show toast for needs_auth + Bus.publish(TuiEvent.ToastShow, { + title: "MCP Authentication Required", + message: `Server "${key}" requires authentication. Run: opencode mcp auth ${key}`, + variant: "warning", + duration: 8000, + }).catch((e) => log.debug("failed to show toast", { error: e })) } break } @@ -623,4 +639,16 @@ export namespace MCP { const entry = await McpAuth.get(mcpName) return !!entry?.tokens } + + export type AuthStatus = "authenticated" | "expired" | "not_authenticated" + + /** + * Get the authentication status for an MCP server. + */ + export async function getAuthStatus(mcpName: string): Promise<AuthStatus> { + const hasTokens = await hasStoredTokens(mcpName) + if (!hasTokens) return "not_authenticated" + const expired = await McpAuth.isTokenExpired(mcpName) + return expired ? "expired" : "authenticated" + } } diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index b8d4dadbd..b11ca9368 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -25,6 +25,15 @@ import { createOpenAI } from "@ai-sdk/openai" import { createOpenAICompatible } from "@ai-sdk/openai-compatible" import { createOpenRouter, type LanguageModelV2 } from "@openrouter/ai-sdk-provider" import { createOpenaiCompatible as createGitHubCopilotOpenAICompatible } from "./sdk/openai-compatible/src" +import { createXai } from "@ai-sdk/xai" +import { createMistral } from "@ai-sdk/mistral" +import { createGroq } from "@ai-sdk/groq" +import { createDeepInfra } from "@ai-sdk/deepinfra" +import { createCerebras } from "@ai-sdk/cerebras" +import { createCohere } from "@ai-sdk/cohere" +import { createGateway } from "@ai-sdk/gateway" +import { createTogetherAI } from "@ai-sdk/togetherai" +import { createPerplexity } from "@ai-sdk/perplexity" export namespace Provider { const log = Log.create({ service: "provider" }) @@ -39,6 +48,15 @@ export namespace Provider { "@ai-sdk/openai": createOpenAI, "@ai-sdk/openai-compatible": createOpenAICompatible, "@openrouter/ai-sdk-provider": createOpenRouter, + "@ai-sdk/xai": createXai, + "@ai-sdk/mistral": createMistral, + "@ai-sdk/groq": createGroq, + "@ai-sdk/deepinfra": createDeepInfra, + "@ai-sdk/cerebras": createCerebras, + "@ai-sdk/cohere": createCohere, + "@ai-sdk/gateway": createGateway, + "@ai-sdk/togetherai": createTogetherAI, + "@ai-sdk/perplexity": createPerplexity, // @ts-ignore (TODO: kill this code so we dont have to maintain it) "@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible, } @@ -67,6 +85,8 @@ export namespace Provider { const env = Env.all() if (input.env.some((item) => env[item])) return true if (await Auth.get(input.id)) return true + const config = await Config.get() + if (config.provider?.["opencode"]?.options?.apiKey) return true return false })() diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 76403a4ed..d86fe9022 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -216,6 +216,7 @@ export namespace ProviderTransform { if (id.includes("claude")) return undefined if (id.includes("gemini-3-pro")) return 1.0 if (id.includes("glm-4.6")) return 1.0 + if (id.includes("glm-4.7")) return 1.0 if (id.includes("minimax-m2")) return 1.0 if (id.includes("kimi-k2")) { if (id.includes("thinking")) return 1.0 diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 680ee3024..706ca03e0 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -48,6 +48,7 @@ import { upgradeWebSocket, websocket } from "hono/bun" import { errors } from "./error" import { Pty } from "@/pty" import { AskQuestion } from "@/askquestion" +import { Installation } from "@/installation" // @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 globalThis.AI_SDK_LOG_WARNINGS = false @@ -97,6 +98,27 @@ export namespace Server { } }) .use(cors()) + .get( + "/global/health", + describeRoute({ + summary: "Get health", + description: "Get health information about the OpenCode server.", + operationId: "global.health", + responses: { + 200: { + description: "Health information", + content: { + "application/json": { + schema: resolver(z.object({ healthy: z.literal(true), version: z.string() })), + }, + }, + }, + }, + }), + async (c) => { + return c.json({ healthy: true, version: Installation.VERSION }) + }, + ) .get( "/global/event", describeRoute({ @@ -2656,10 +2678,10 @@ export namespace Server { }, ) .all("/*", async (c) => { - return proxy(`https://desktop.opencode.ai${c.req.path}`, { + return proxy(`https://app.opencode.ai${c.req.path}`, { ...c.req, headers: { - host: "desktop.opencode.ai", + host: "app.opencode.ai", }, }) }), diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 3e4c8020d..339ba2f42 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -40,6 +40,8 @@ export namespace SessionCompaction { export const PRUNE_MINIMUM = 20_000 export const PRUNE_PROTECT = 40_000 + const PRUNE_PROTECTED_TOOLS = ["skill"] + // goes backwards through parts until there are 40_000 tokens worth of tool // calls. then erases output of previous tool calls. idea is to throw away old // tool calls that are no longer relevant. @@ -61,6 +63,8 @@ export namespace SessionCompaction { const part = msg.parts[partIndex] if (part.type === "tool") if (part.state.status === "completed") { + if (PRUNE_PROTECTED_TOOLS.includes(part.tool)) continue + if (part.state.time.compacted) break loop const estimate = Token.estimate(part.state.output) total += estimate @@ -126,12 +130,15 @@ export namespace SessionCompaction { model, abort: input.abort, }) - // Allow plugins to inject context for compaction + // Allow plugins to inject context or replace compaction prompt const compacting = await Plugin.trigger( "experimental.session.compacting", { sessionID: input.sessionID }, - { context: [] }, + { context: [], prompt: undefined }, ) + const defaultPrompt = + "Provide a detailed prompt for continuing our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next considering new session will not have access to our conversation." + const promptText = compacting.prompt ?? [defaultPrompt, ...compacting.context].join("\n\n") const result = await processor.process({ user: userMessage, agent, @@ -146,10 +153,7 @@ export namespace SessionCompaction { content: [ { type: "text", - text: [ - "Provide a detailed prompt for continuing our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next considering new session will not have access to our conversation.", - ...compacting.context, - ].join("\n\n"), + text: promptText, }, ], }, diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 694e22e32..db0467d32 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -534,11 +534,7 @@ export namespace SessionPrompt { agent, abort, sessionID, - system: [ - ...(await SystemPrompt.environment()), - ...(await SystemPrompt.skills()), - ...(await SystemPrompt.custom()), - ], + system: [...(await SystemPrompt.environment()), ...(await SystemPrompt.custom())], messages: [ ...MessageV2.toModelMessage(sessionMessages), ...(isLastStep @@ -590,7 +586,8 @@ export namespace SessionPrompt { mergeDeep(await ToolRegistry.enabled(input.agent)), mergeDeep(input.tools ?? {}), ) - for (const item of await ToolRegistry.tools(input.model.providerID)) { + + for (const item of await ToolRegistry.tools(input.model.providerID, input.agent)) { if (item.id === "askquestion" && input.client !== "tui") continue if (Wildcard.all(item.id, enabledTools) === false) continue const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters)) diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index a9d0586b4..300943881 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -14,7 +14,6 @@ import PROMPT_POLARIS from "./prompt/polaris.txt" import PROMPT_BEAST from "./prompt/beast.txt" import PROMPT_GEMINI from "./prompt/gemini.txt" import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt" -import PROMPT_COMPACTION from "./prompt/compaction.txt" import PROMPT_CODEX from "./prompt/codex.txt" import type { Provider } from "@/provider/provider" @@ -118,25 +117,4 @@ export namespace SystemPrompt { ) return Promise.all(found).then((result) => result.filter(Boolean)) } - - export async function skills() { - const all = await Skill.all() - if (all.length === 0) return [] - - const lines = [ - "You have access to skills listed in `<available_skills>`. When a task matches a skill's description, read its SKILL.md file to get detailed instructions.", - "", - "<available_skills>", - ] - for (const skill of all) { - lines.push(" <skill>") - lines.push(` <name>${skill.name}</name>`) - lines.push(` <description>${skill.description}</description>`) - lines.push(` <location>${skill.location}</location>`) - lines.push(" </skill>") - } - lines.push("</available_skills>") - - return [lines.join("\n")] - } } diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index 88182c5de..41df88f8b 100644 --- a/packages/opencode/src/skill/skill.ts +++ b/packages/opencode/src/skill/skill.ts @@ -1,7 +1,5 @@ -import path from "path" import z from "zod" import { Config } from "../config/config" -import { Filesystem } from "../util/filesystem" import { Instance } from "../project/instance" import { NamedError } from "@opencode-ai/util/error" import { ConfigMarkdown } from "../config/markdown" @@ -9,35 +7,12 @@ import { Log } from "../util/log" export namespace Skill { const log = Log.create({ service: "skill" }) - - // Name: 1-64 chars, lowercase alphanumeric and hyphens, no consecutive hyphens, can't start/end with hyphen - const NAME_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/ - - export const Frontmatter = z.object({ - name: z - .string() - .min(1) - .max(64) - .refine((val) => NAME_REGEX.test(val), { - message: - "Name must be lowercase alphanumeric with hyphens, no consecutive hyphens, cannot start or end with hyphen", - }), - description: z.string().min(1).max(1024), - license: z.string().optional(), - compatibility: z.string().max(500).optional(), - metadata: z.record(z.string(), z.string()).optional(), + export const Info = z.object({ + name: z.string(), + description: z.string(), + location: z.string(), }) - - export type Frontmatter = z.infer<typeof Frontmatter> - - export interface Info { - name: string - description: string - location: string - license?: string - compatibility?: string - metadata?: Record<string, string> - } + export type Info = z.infer<typeof Info> export const InvalidError = NamedError.create( "SkillInvalidError", @@ -57,15 +32,12 @@ export namespace Skill { }), ) - const SKILL_GLOB = new Bun.Glob("skill/*/SKILL.md") - const CLAUDE_SKILL_GLOB = new Bun.Glob("*/SKILL.md") + const SKILL_GLOB = new Bun.Glob("skill/**/SKILL.md") - async function discover(): Promise<string[]> { + export const state = Instance.state(async () => { const directories = await Config.directories() + const skills: Record<string, Info> = {} - const paths: string[] = [] - - // Scan skill/ subdirectory in config directories (.opencode/, ~/.opencode/, etc.) for (const dir of directories) { for await (const match of SKILL_GLOB.scan({ cwd: dir, @@ -73,82 +45,39 @@ export namespace Skill { onlyFiles: true, followSymlinks: true, })) { - paths.push(match) + const md = await ConfigMarkdown.parse(match) + if (!md) { + continue + } + + const parsed = Info.pick({ name: true, description: true }).safeParse(md.data) + if (!parsed.success) continue + + // Warn on duplicate skill names + if (skills[parsed.data.name]) { + log.warn("duplicate skill name", { + name: parsed.data.name, + existing: skills[parsed.data.name].location, + duplicate: match, + }) + } + + skills[parsed.data.name] = { + name: parsed.data.name, + description: parsed.data.description, + location: match, + } } } - // Also scan .claude/skills/ walking up from cwd to worktree - for await (const dir of Filesystem.up({ - targets: [".claude/skills"], - start: Instance.directory, - stop: Instance.worktree, - })) { - for await (const match of CLAUDE_SKILL_GLOB.scan({ - cwd: dir, - absolute: true, - onlyFiles: true, - followSymlinks: true, - })) { - paths.push(match) - } - } - - return paths - } - - async function load(skillMdPath: string): Promise<Info> { - const md = await ConfigMarkdown.parse(skillMdPath) - if (!md.data) { - throw new InvalidError({ - path: skillMdPath, - message: "SKILL.md must have YAML frontmatter", - }) - } - - const parsed = Frontmatter.safeParse(md.data) - if (!parsed.success) { - throw new InvalidError({ - path: skillMdPath, - issues: parsed.error.issues, - }) - } - - const frontmatter = parsed.data - const skillDir = path.dirname(skillMdPath) - const dirName = path.basename(skillDir) - - if (frontmatter.name !== dirName) { - throw new NameMismatchError({ - path: skillMdPath, - expected: dirName, - actual: frontmatter.name, - }) - } - - return { - name: frontmatter.name, - description: frontmatter.description, - location: skillMdPath, - license: frontmatter.license, - compatibility: frontmatter.compatibility, - metadata: frontmatter.metadata, - } - } - - export const state = Instance.state(async () => { - const paths = await discover() - const skills: Info[] = [] - - for (const skillPath of paths) { - const info = await load(skillPath) - log.info("loaded skill", { name: info.name, location: info.location }) - skills.push(info) - } - return skills }) - export async function all(): Promise<Info[]> { - return state() + export async function get(name: string) { + return state().then((x) => x[name]) + } + + export async function all() { + return state().then((x) => Object.values(x)) } } diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts index 99af448ba..d73bc1616 100644 --- a/packages/opencode/src/tool/grep.ts +++ b/packages/opencode/src/tool/grep.ts @@ -49,7 +49,8 @@ export const GrepTool = Tool.define("grep", { throw new Error(`ripgrep failed: ${errorOutput}`) } - const lines = output.trim().split("\n") + // Handle both Unix (\n) and Windows (\r\n) line endings + const lines = output.trim().split(/\r?\n/) const matches = [] for (const line of lines) { diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index 27426ad24..fd81c4864 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -60,10 +60,12 @@ export const ReadTool = Tool.define("read", { } const block = iife(() => { - const whitelist = [".env.sample", ".example"] + const basename = path.basename(filepath) + const whitelist = [".env.sample", ".env.example", ".example", ".env.template"] - if (whitelist.some((w) => filepath.endsWith(w))) return false - if (filepath.includes(".env")) return true + if (whitelist.some((w) => basename.endsWith(w))) return false + // Block .env, .env.local, .env.production, etc. but not .envrc + if (/^\.env(\.|$)/.test(basename)) return true return false }) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 0b1bd3e71..3dad4d0a4 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -11,6 +11,7 @@ import { WebFetchTool } from "./webfetch" import { WriteTool } from "./write" import { InvalidTool } from "./invalid" import { AskQuestionTool } from "./askquestion" +import { SkillTool } from "./skill" import type { Agent } from "../agent/agent" import { Tool } from "./tool" import { Instance } from "../project/instance" @@ -105,6 +106,7 @@ export namespace ToolRegistry { WebSearchTool, CodeSearchTool, AskQuestionTool, + SkillTool, ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []), ...(config.experimental?.batch_tool === true ? [BatchTool] : []), ...custom, @@ -115,7 +117,7 @@ export namespace ToolRegistry { return all().then((x) => x.map((t) => t.id)) } - export async function tools(providerID: string) { + export async function tools(providerID: string, agent?: Agent.Info) { const tools = await all() const result = await Promise.all( tools @@ -130,7 +132,7 @@ export namespace ToolRegistry { using _ = log.time(t.id) return { id: t.id, - ...(await t.init()), + ...(await t.init({ agent })), } }), ) @@ -152,6 +154,10 @@ export namespace ToolRegistry { result["codesearch"] = false result["websearch"] = false } + // Disable skill tool if all skills are denied + if (agent.permission.skill["*"] === "deny" && Object.keys(agent.permission.skill).length === 1) { + result["skill"] = false + } return result } diff --git a/packages/opencode/src/tool/skill.ts b/packages/opencode/src/tool/skill.ts new file mode 100644 index 000000000..2503f7639 --- /dev/null +++ b/packages/opencode/src/tool/skill.ts @@ -0,0 +1,100 @@ +import path from "path" +import z from "zod" +import { Tool } from "./tool" +import { Skill } from "../skill" +import { Agent } from "../agent/agent" +import { Permission } from "../permission" +import { Wildcard } from "../util/wildcard" +import { ConfigMarkdown } from "../config/markdown" + +const parameters = z.object({ + name: z.string().describe("The skill identifier from available_skills (e.g., 'code-review')"), +}) + +export const SkillTool: Tool.Info<typeof parameters> = { + id: "skill", + async init(ctx) { + const skills = await Skill.all() + + // Filter skills by agent permissions if agent provided + let accessibleSkills = skills + if (ctx?.agent) { + const permissions = ctx.agent.permission.skill + accessibleSkills = skills.filter((skill) => { + const action = Wildcard.all(skill.name, permissions) + return action !== "deny" + }) + } + + return { + description: [ + "Load a skill to get detailed instructions for a specific task.", + "Skills provide specialized knowledge and step-by-step guidance.", + "Use this when a task matches an available skill's description.", + "<available_skills>", + ...accessibleSkills.flatMap((skill) => [ + ` <skill>`, + ` <name>${skill.name}</name>`, + ` <description>${skill.description}</description>`, + ` </skill>`, + ]), + "</available_skills>", + ].join(" "), + parameters, + async execute(params, ctx) { + const agent = await Agent.get(ctx.agent) + + const skill = await Skill.get(params.name) + + if (!skill) { + const available = await Skill.all().then((x) => x.map((s) => s.name).join(", ")) + throw new Error(`Skill "${params.name}" not found. Available skills: ${available || "none"}`) + } + + // Check permission using Wildcard.all on the skill name + const permissions = agent.permission.skill + const action = Wildcard.all(params.name, permissions) + + if (action === "deny") { + throw new Permission.RejectedError( + ctx.sessionID, + "skill", + ctx.callID, + { skill: params.name }, + `Access to skill "${params.name}" is denied for agent "${agent.name}".`, + ) + } + + if (action === "ask") { + await Permission.ask({ + type: "skill", + pattern: params.name, + sessionID: ctx.sessionID, + messageID: ctx.messageID, + callID: ctx.callID, + title: `Load skill: ${skill.name}`, + metadata: { name: skill.name, description: skill.description }, + }) + } + + // Load and parse skill content + const parsed = await ConfigMarkdown.parse(skill.location) + const dir = path.dirname(skill.location) + + // Format output similar to plugin pattern + const output = [`## Skill: ${skill.name}`, "", `**Base directory**: ${dir}`, "", parsed.content.trim()].join( + "\n", + ) + + return { + title: `Loaded skill: ${skill.name}`, + output, + metadata: { + name: skill.name, + dir, + }, + } + }, + } + }, +} diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index 80b6abe8c..acee24902 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -1,11 +1,16 @@ import z from "zod" import type { MessageV2 } from "../session/message-v2" +import type { Agent } from "../agent/agent" export namespace Tool { interface Metadata { [key: string]: any } + export interface InitContext { + agent?: Agent.Info + } + export type Context<M extends Metadata = Metadata> = { sessionID: string messageID: string @@ -17,7 +22,7 @@ export namespace Tool { } export interface Info<Parameters extends z.ZodType = z.ZodType, M extends Metadata = Metadata> { id: string - init: () => Promise<{ + init: (ctx?: InitContext) => Promise<{ description: string parameters: Parameters execute( @@ -42,8 +47,8 @@ export namespace Tool { ): Info<Parameters, Result> { return { id, - init: async () => { - const toolInfo = init instanceof Function ? await init() : init + init: async (ctx) => { + const toolInfo = init instanceof Function ? await init(ctx) : init const execute = toolInfo.execute toolInfo.execute = (args, ctx) => { try { diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index 3d7bc4c23..4a1d75f9f 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -65,55 +65,7 @@ description: Another test skill. }) }) -test("throws error for invalid skill name format", async () => { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - const skillDir = path.join(dir, ".opencode", "skill", "InvalidName") - await Bun.write( - path.join(skillDir, "SKILL.md"), - `--- -name: InvalidName -description: A skill with invalid name. ---- -`, - ) - }, - }) - - await Instance.provide({ - directory: tmp.path, - fn: async () => { - await expect(Skill.all()).rejects.toThrow() - }, - }) -}) - -test("throws error when name doesn't match directory", async () => { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - const skillDir = path.join(dir, ".opencode", "skill", "dir-name") - await Bun.write( - path.join(skillDir, "SKILL.md"), - `--- -name: different-name -description: A skill with mismatched name. ---- -`, - ) - }, - }) - - await Instance.provide({ - directory: tmp.path, - fn: async () => { - await expect(Skill.all()).rejects.toThrow("SkillNameMismatchError") - }, - }) -}) - -test("throws error for missing frontmatter", async () => { +test("skips skills with missing frontmatter", async () => { await using tmp = await tmpdir({ git: true, init: async (dir) => { @@ -128,78 +80,11 @@ Just some content without YAML frontmatter. }, }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - await expect(Skill.all()).rejects.toThrow("SkillInvalidError") - }, - }) -}) - -test("parses optional fields", async () => { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - const skillDir = path.join(dir, ".opencode", "skill", "full-skill") - await Bun.write( - path.join(skillDir, "SKILL.md"), - `--- -name: full-skill -description: A skill with all optional fields. -license: MIT -compatibility: Requires Node.js 18+ -metadata: - author: test-author - version: "1.0" ---- - -# Full Skill -`, - ) - }, - }) - await Instance.provide({ directory: tmp.path, fn: async () => { const skills = await Skill.all() - expect(skills.length).toBe(1) - expect(skills[0].license).toBe("MIT") - expect(skills[0].compatibility).toBe("Requires Node.js 18+") - expect(skills[0].metadata).toEqual({ - author: "test-author", - version: "1.0", - }) - }, - }) -}) - -test("ignores unknown frontmatter fields", async () => { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - const skillDir = path.join(dir, ".opencode", "skill", "extra-fields") - await Bun.write( - path.join(skillDir, "SKILL.md"), - `--- -name: extra-fields -description: A skill with extra unknown fields. -allowed-tools: Bash Read Write -some-other-field: ignored ---- - -# Extra Fields Skill -`, - ) - }, - }) - - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const skills = await Skill.all() - expect(skills.length).toBe(1) - expect(skills[0].name).toBe("extra-fields") + expect(skills).toEqual([]) }, }) }) @@ -216,76 +101,31 @@ test("returns empty array when no skills exist", async () => { }) }) -test("SystemPrompt.skills() returns empty array when no skills", async () => { - await using tmp = await tmpdir({ git: true }) +// test("discovers skills from .claude/skills/ directory", async () => { +// await using tmp = await tmpdir({ +// git: true, +// init: async (dir) => { +// const skillDir = path.join(dir, ".claude", "skills", "claude-skill") +// await Bun.write( +// path.join(skillDir, "SKILL.md"), +// `--- +// name: claude-skill +// description: A skill in the .claude/skills directory. +// --- - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const result = await SystemPrompt.skills() - expect(result).toEqual([]) - }, - }) -}) +// # Claude Skill +// `, +// ) +// }, +// }) -test("SystemPrompt.skills() returns XML block with skills", async () => { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - const skillDir = path.join(dir, ".opencode", "skill", "example-skill") - await Bun.write( - path.join(skillDir, "SKILL.md"), - `--- -name: example-skill -description: An example skill for testing XML output. ---- - -# Example -`, - ) - }, - }) - - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const result = await SystemPrompt.skills() - expect(result.length).toBe(1) - expect(result[0]).toContain("<available_skills>") - expect(result[0]).toContain("<name>example-skill</name>") - expect(result[0]).toContain("<description>An example skill for testing XML output.</description>") - expect(result[0]).toContain("SKILL.md</location>") - expect(result[0]).toContain("</available_skills>") - expect(result[0]).toContain("When a task matches a skill's description") - }, - }) -}) - -test("discovers skills from .claude/skills/ directory", async () => { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - const skillDir = path.join(dir, ".claude", "skills", "claude-skill") - await Bun.write( - path.join(skillDir, "SKILL.md"), - `--- -name: claude-skill -description: A skill in the .claude/skills directory. ---- - -# Claude Skill -`, - ) - }, - }) - - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const skills = await Skill.all() - expect(skills.length).toBe(1) - expect(skills[0].name).toBe("claude-skill") - expect(skills[0].location).toContain(".claude/skills/claude-skill/SKILL.md") - }, - }) -}) +// await Instance.provide({ +// directory: tmp.path, +// fn: async () => { +// const skills = await Skill.all() +// expect(skills.length).toBe(1) +// expect(skills[0].name).toBe("claude-skill") +// expect(skills[0].location).toContain(".claude/skills/claude-skill/SKILL.md") +// }, +// }) +// }) diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts new file mode 100644 index 000000000..f3da666a0 --- /dev/null +++ b/packages/opencode/test/tool/grep.test.ts @@ -0,0 +1,108 @@ +import { describe, expect, test } from "bun:test" +import path from "path" +import { GrepTool } from "../../src/tool/grep" +import { Instance } from "../../src/project/instance" +import { tmpdir } from "../fixture/fixture" + +const ctx = { + sessionID: "test", + messageID: "", + callID: "", + agent: "build", + abort: AbortSignal.any([]), + metadata: () => {}, +} + +const projectRoot = path.join(__dirname, "../..") + +describe("tool.grep", () => { + test("basic search", async () => { + await Instance.provide({ + directory: projectRoot, + fn: async () => { + const grep = await GrepTool.init() + const result = await grep.execute( + { + pattern: "export", + path: path.join(projectRoot, "src/tool"), + include: "*.ts", + }, + ctx, + ) + expect(result.metadata.matches).toBeGreaterThan(0) + expect(result.output).toContain("Found") + }, + }) + }) + + test("no matches returns correct output", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write(path.join(dir, "test.txt"), "hello world") + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const grep = await GrepTool.init() + const result = await grep.execute( + { + pattern: "xyznonexistentpatternxyz123", + path: tmp.path, + }, + ctx, + ) + expect(result.metadata.matches).toBe(0) + expect(result.output).toBe("No files found") + }, + }) + }) + + test("handles CRLF line endings in output", async () => { + // This test verifies the regex split handles both \n and \r\n + await using tmp = await tmpdir({ + init: async (dir) => { + // Create a test file with content + await Bun.write(path.join(dir, "test.txt"), "line1\nline2\nline3") + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const grep = await GrepTool.init() + const result = await grep.execute( + { + pattern: "line", + path: tmp.path, + }, + ctx, + ) + expect(result.metadata.matches).toBeGreaterThan(0) + }, + }) + }) +}) + +describe("CRLF regex handling", () => { + test("regex correctly splits Unix line endings", () => { + const unixOutput = "file1.txt|1|content1\nfile2.txt|2|content2\nfile3.txt|3|content3" + const lines = unixOutput.trim().split(/\r?\n/) + expect(lines.length).toBe(3) + expect(lines[0]).toBe("file1.txt|1|content1") + expect(lines[2]).toBe("file3.txt|3|content3") + }) + + test("regex correctly splits Windows CRLF line endings", () => { + const windowsOutput = "file1.txt|1|content1\r\nfile2.txt|2|content2\r\nfile3.txt|3|content3" + const lines = windowsOutput.trim().split(/\r?\n/) + expect(lines.length).toBe(3) + expect(lines[0]).toBe("file1.txt|1|content1") + expect(lines[2]).toBe("file3.txt|3|content3") + }) + + test("regex handles mixed line endings", () => { + const mixedOutput = "file1.txt|1|content1\nfile2.txt|2|content2\r\nfile3.txt|3|content3" + const lines = mixedOutput.trim().split(/\r?\n/) + expect(lines.length).toBe(3) + }) +}) diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts new file mode 100644 index 000000000..47a7aee2a --- /dev/null +++ b/packages/opencode/test/tool/read.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, test } from "bun:test" +import path from "path" +import { ReadTool } from "../../src/tool/read" +import { Instance } from "../../src/project/instance" +import { tmpdir } from "../fixture/fixture" + +const ctx = { + sessionID: "test", + messageID: "", + callID: "", + agent: "build", + abort: AbortSignal.any([]), + metadata: () => {}, +} + +describe("tool.read env file blocking", () => { + test.each([ + [".env", true], + [".env.local", true], + [".env.production", true], + [".env.sample", false], + [".env.example", false], + [".envrc", false], + ["environment.ts", false], + ])("%s blocked=%s", async (filename, blocked) => { + await using tmp = await tmpdir({ + init: (dir) => Bun.write(path.join(dir, filename), "content"), + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const read = await ReadTool.init() + const promise = read.execute({ filePath: path.join(tmp.path, filename) }, ctx) + if (blocked) { + await expect(promise).rejects.toThrow("blocked") + } else { + expect((await promise).output).toContain("content") + } + }, + }) + }) +}) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index fd3fd9f51..babe21271 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.0.186", + "version": "1.0.191", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index 487e6ed3e..fbc0e710c 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -192,10 +192,16 @@ export interface Hooks { }, ) => Promise<void> /** - * Called before session compaction starts. Allows plugins to append - * additional context to the compaction prompt. + * Called before session compaction starts. Allows plugins to customize + * the compaction prompt. + * + * - `context`: Additional context strings appended to the default prompt + * - `prompt`: If set, replaces the default compaction prompt entirely */ - "experimental.session.compacting"?: (input: { sessionID: string }, output: { context: string[] }) => Promise<void> + "experimental.session.compacting"?: ( + input: { sessionID: string }, + output: { context: string[]; prompt?: string }, + ) => Promise<void> "experimental.text.complete"?: ( input: { sessionID: string; messageID: string; partID: string }, output: { text: string }, diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 1751488b1..6278ebaef 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.0.186", + "version": "1.0.191", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 964112d81..06993d3f9 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -858,6 +858,14 @@ export type KeybindsConfig = { * Navigate to last message */ messages_last?: string + /** + * Navigate to next message + */ + messages_next?: string + /** + * Navigate to previous message + */ + messages_previous?: string /** * Navigate to last user message */ diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index fa7a86463..97bc92b86 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -30,6 +30,7 @@ import type { FormatterStatusResponses, GlobalDisposeResponses, GlobalEventResponses, + GlobalHealthResponses, InstanceDisposeResponses, LspStatusResponses, McpAddErrors, @@ -188,6 +189,18 @@ class HeyApiRegistry<T> { } export class Global extends HeyApiClient { + /** + * Get health + * + * Get health information about the OpenCode server. + */ + public health<ThrowOnError extends boolean = false>(options?: Options<never, ThrowOnError>) { + return (options?.client ?? this.client).get<GlobalHealthResponses, unknown, ThrowOnError>({ + url: "/global/health", + ...options, + }) + } + /** * Get global events * diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index f7c0e88a5..1372765e3 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -547,6 +547,48 @@ export type EventSessionCompacted = { } } +export type EventTuiPromptAppend = { + type: "tui.prompt.append" + properties: { + text: string + } +} + +export type EventTuiCommandExecute = { + type: "tui.command.execute" + properties: { + command: + | "session.list" + | "session.new" + | "session.share" + | "session.interrupt" + | "session.compact" + | "session.page.up" + | "session.page.down" + | "session.half.page.up" + | "session.half.page.down" + | "session.first" + | "session.last" + | "prompt.clear" + | "prompt.submit" + | "agent.cycle" + | string + } +} + +export type EventTuiToastShow = { + type: "tui.toast.show" + properties: { + title?: string + message: string + variant: "info" | "success" | "warning" | "error" + /** + * Duration in milliseconds + */ + duration?: number + } +} + export type EventCommandExecuted = { type: "command.executed" properties: { @@ -639,48 +681,6 @@ export type EventVcsBranchUpdated = { } } -export type EventTuiPromptAppend = { - type: "tui.prompt.append" - properties: { - text: string - } -} - -export type EventTuiCommandExecute = { - type: "tui.command.execute" - properties: { - command: - | "session.list" - | "session.new" - | "session.share" - | "session.interrupt" - | "session.compact" - | "session.page.up" - | "session.page.down" - | "session.half.page.up" - | "session.half.page.down" - | "session.first" - | "session.last" - | "prompt.clear" - | "prompt.submit" - | "agent.cycle" - | string - } -} - -export type EventTuiToastShow = { - type: "tui.toast.show" - properties: { - title?: string - message: string - variant: "info" | "success" | "warning" | "error" - /** - * Duration in milliseconds - */ - duration?: number - } -} - export type Pty = { id: string title: string @@ -752,6 +752,9 @@ export type Event = | EventSessionStatus | EventSessionIdle | EventSessionCompacted + | EventTuiPromptAppend + | EventTuiCommandExecute + | EventTuiToastShow | EventCommandExecuted | EventSessionCreated | EventSessionUpdated @@ -760,9 +763,6 @@ export type Event = | EventSessionError | EventFileWatcherUpdated | EventVcsBranchUpdated - | EventTuiPromptAppend - | EventTuiCommandExecute - | EventTuiToastShow | EventPtyCreated | EventPtyUpdated | EventPtyExited @@ -890,6 +890,14 @@ export type KeybindsConfig = { * Navigate to last message */ messages_last?: string + /** + * Navigate to next message + */ + messages_next?: string + /** + * Navigate to previous message + */ + messages_previous?: string /** * Navigate to last user message */ @@ -1114,6 +1122,10 @@ export type KeybindsConfig = { * Previous child session */ session_child_cycle_reverse?: string + /** + * Go to parent session + */ + session_parent?: string /** * Suspend terminal */ @@ -1155,6 +1167,13 @@ export type AgentConfig = { | { [key: string]: "ask" | "allow" | "deny" } + skill?: + | "ask" + | "allow" + | "deny" + | { + [key: string]: "ask" | "allow" | "deny" + } webfetch?: "ask" | "allow" | "deny" doom_loop?: "ask" | "allow" | "deny" external_directory?: "ask" | "allow" | "deny" @@ -1181,6 +1200,13 @@ export type AgentConfig = { | { [key: string]: "ask" | "allow" | "deny" } + skill?: + | "ask" + | "allow" + | "deny" + | { + [key: string]: "ask" | "allow" | "deny" + } webfetch?: "ask" | "allow" | "deny" doom_loop?: "ask" | "allow" | "deny" external_directory?: "ask" | "allow" | "deny" @@ -1500,6 +1526,13 @@ export type Config = { | { [key: string]: "ask" | "allow" | "deny" } + skill?: + | "ask" + | "allow" + | "deny" + | { + [key: string]: "ask" | "allow" | "deny" + } webfetch?: "ask" | "allow" | "deny" doom_loop?: "ask" | "allow" | "deny" external_directory?: "ask" | "allow" | "deny" @@ -1780,6 +1813,9 @@ export type Agent = { bash: { [key: string]: "ask" | "allow" | "deny" } + skill: { + [key: string]: "ask" | "allow" | "deny" + } webfetch?: "ask" | "allow" | "deny" doom_loop?: "ask" | "allow" | "deny" external_directory?: "ask" | "allow" | "deny" @@ -1861,6 +1897,25 @@ export type WellKnownAuth = { export type Auth = OAuth | ApiAuth | WellKnownAuth +export type GlobalHealthData = { + body?: never + path?: never + query?: never + url: "/global/health" +} + +export type GlobalHealthResponses = { + /** + * Health information + */ + 200: { + healthy: true + version: string + } +} + +export type GlobalHealthResponse = GlobalHealthResponses[keyof GlobalHealthResponses] + export type GlobalEventData = { body?: never path?: never diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 12699ee2b..588b130b9 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -6,6 +6,41 @@ "version": "1.0.0" }, "paths": { + "/global/health": { + "get": { + "operationId": "global.health", + "summary": "Get health", + "description": "Get health information about the OpenCode server.", + "responses": { + "200": { + "description": "Health information", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "healthy": { + "type": "boolean", + "const": true + }, + "version": { + "type": "string" + } + }, + "required": ["healthy", "version"] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.global.health({\n ...\n})" + } + ] + } + }, "/global/event": { "get": { "operationId": "global.event", @@ -6499,6 +6534,98 @@ }, "required": ["type", "properties"] }, + "Event.tui.prompt.append": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.prompt.append" + }, + "properties": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": ["text"] + } + }, + "required": ["type", "properties"] + }, + "Event.tui.command.execute": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.command.execute" + }, + "properties": { + "type": "object", + "properties": { + "command": { + "anyOf": [ + { + "type": "string", + "enum": [ + "session.list", + "session.new", + "session.share", + "session.interrupt", + "session.compact", + "session.page.up", + "session.page.down", + "session.half.page.up", + "session.half.page.down", + "session.first", + "session.last", + "prompt.clear", + "prompt.submit", + "agent.cycle" + ] + }, + { + "type": "string" + } + ] + } + }, + "required": ["command"] + } + }, + "required": ["type", "properties"] + }, + "Event.tui.toast.show": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.toast.show" + }, + "properties": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "message": { + "type": "string" + }, + "variant": { + "type": "string", + "enum": ["info", "success", "warning", "error"] + }, + "duration": { + "description": "Duration in milliseconds", + "default": 5000, + "type": "number" + } + }, + "required": ["message", "variant"] + } + }, + "required": ["type", "properties"] + }, "Event.command.executed": { "type": "object", "properties": { @@ -6793,98 +6920,6 @@ }, "required": ["type", "properties"] }, - "Event.tui.prompt.append": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "tui.prompt.append" - }, - "properties": { - "type": "object", - "properties": { - "text": { - "type": "string" - } - }, - "required": ["text"] - } - }, - "required": ["type", "properties"] - }, - "Event.tui.command.execute": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "tui.command.execute" - }, - "properties": { - "type": "object", - "properties": { - "command": { - "anyOf": [ - { - "type": "string", - "enum": [ - "session.list", - "session.new", - "session.share", - "session.interrupt", - "session.compact", - "session.page.up", - "session.page.down", - "session.half.page.up", - "session.half.page.down", - "session.first", - "session.last", - "prompt.clear", - "prompt.submit", - "agent.cycle" - ] - }, - { - "type": "string" - } - ] - } - }, - "required": ["command"] - } - }, - "required": ["type", "properties"] - }, - "Event.tui.toast.show": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "tui.toast.show" - }, - "properties": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "message": { - "type": "string" - }, - "variant": { - "type": "string", - "enum": ["info", "success", "warning", "error"] - }, - "duration": { - "description": "Duration in milliseconds", - "default": 5000, - "type": "number" - } - }, - "required": ["message", "variant"] - } - }, - "required": ["type", "properties"] - }, "Pty": { "type": "object", "properties": { @@ -7079,6 +7114,15 @@ { "$ref": "#/components/schemas/Event.session.compacted" }, + { + "$ref": "#/components/schemas/Event.tui.prompt.append" + }, + { + "$ref": "#/components/schemas/Event.tui.command.execute" + }, + { + "$ref": "#/components/schemas/Event.tui.toast.show" + }, { "$ref": "#/components/schemas/Event.command.executed" }, @@ -7103,15 +7147,6 @@ { "$ref": "#/components/schemas/Event.vcs.branch.updated" }, - { - "$ref": "#/components/schemas/Event.tui.prompt.append" - }, - { - "$ref": "#/components/schemas/Event.tui.command.execute" - }, - { - "$ref": "#/components/schemas/Event.tui.toast.show" - }, { "$ref": "#/components/schemas/Event.pty.created" }, @@ -7308,6 +7343,16 @@ "default": "ctrl+alt+g,end", "type": "string" }, + "messages_next": { + "description": "Navigate to next message", + "default": "none", + "type": "string" + }, + "messages_previous": { + "description": "Navigate to previous message", + "default": "none", + "type": "string" + }, "messages_last_user": { "description": "Navigate to last user message", "default": "none", @@ -7588,6 +7633,11 @@ "default": "<leader>left", "type": "string" }, + "session_parent": { + "description": "Go to parent session", + "default": "<leader>up", + "type": "string" + }, "terminal_suspend": { "description": "Suspend terminal", "default": "ctrl+z", @@ -7672,6 +7722,24 @@ } ] }, + "skill": { + "anyOf": [ + { + "type": "string", + "enum": ["ask", "allow", "deny"] + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "enum": ["ask", "allow", "deny"] + } + } + ] + }, "webfetch": { "type": "string", "enum": ["ask", "allow", "deny"] @@ -8380,6 +8448,24 @@ } ] }, + "skill": { + "anyOf": [ + { + "type": "string", + "enum": ["ask", "allow", "deny"] + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "enum": ["ask", "allow", "deny"] + } + } + ] + }, "webfetch": { "type": "string", "enum": ["ask", "allow", "deny"] @@ -9189,6 +9275,16 @@ "enum": ["ask", "allow", "deny"] } }, + "skill": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "enum": ["ask", "allow", "deny"] + } + }, "webfetch": { "type": "string", "enum": ["ask", "allow", "deny"] @@ -9202,7 +9298,7 @@ "enum": ["ask", "allow", "deny"] } }, - "required": ["edit", "bash"] + "required": ["edit", "bash", "skill"] }, "model": { "type": "object", diff --git a/packages/slack/package.json b/packages/slack/package.json index 1d85ced4e..4be3b2ef1 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.0.186", + "version": "1.0.191", "type": "module", "scripts": { "dev": "bun run src/index.ts", diff --git a/packages/tauri/.gitignore b/packages/tauri/.gitignore deleted file mode 100644 index a547bf36d..000000000 --- a/packages/tauri/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/packages/tauri/README.md b/packages/tauri/README.md deleted file mode 100644 index b381dcf5b..000000000 --- a/packages/tauri/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tauri + Vanilla TS - -This template should help get you started developing with Tauri in vanilla HTML, CSS and Typescript. - -## Recommended IDE Setup - -- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) diff --git a/packages/tauri/package.json b/packages/tauri/package.json deleted file mode 100644 index ff068c65a..000000000 --- a/packages/tauri/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@opencode-ai/tauri", - "private": true, - "version": "1.0.186", - "type": "module", - "scripts": { - "typecheck": "tsgo -b", - "predev": "bun ./scripts/predev.ts", - "dev": "vite", - "build": "bun run typecheck && vite build", - "preview": "vite preview", - "tauri": "tauri" - }, - "dependencies": { - "@opencode-ai/desktop": "workspace:*", - "@solid-primitives/storage": "catalog:", - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-dialog": "~2", - "@tauri-apps/plugin-opener": "^2", - "@tauri-apps/plugin-os": "~2", - "@tauri-apps/plugin-process": "~2", - "@tauri-apps/plugin-shell": "~2", - "@tauri-apps/plugin-store": "~2", - "@tauri-apps/plugin-updater": "~2", - "@tauri-apps/plugin-http": "~2", - "@tauri-apps/plugin-window-state": "~2", - "solid-js": "catalog:" - }, - "devDependencies": { - "@actions/artifact": "4.0.0", - "@tauri-apps/cli": "^2", - "@types/bun": "catalog:", - "@typescript/native-preview": "catalog:", - "typescript": "~5.6.2", - "vite": "catalog:" - } -} diff --git a/packages/tauri/vite.config.ts b/packages/tauri/vite.config.ts deleted file mode 100644 index ead3d8a8d..000000000 --- a/packages/tauri/vite.config.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { defineConfig } from "vite" -import desktopPlugin from "@opencode-ai/desktop/vite" - -const host = process.env.TAURI_DEV_HOST - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [desktopPlugin], - // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` - // - // 1. prevent Vite from obscuring rust errors - clearScreen: false, - // 2. tauri expects a fixed port, fail if that port is not available - server: { - port: 1420, - strictPort: true, - host: host || false, - hmr: host - ? { - protocol: "ws", - host, - port: 1421, - } - : undefined, - watch: { - // 3. tell Vite to ignore watching `src-tauri` - ignored: ["**/src-tauri/**"], - }, - }, -}) diff --git a/packages/ui/package.json b/packages/ui/package.json index f1fd5b9d8..47a002967 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.0.186", + "version": "1.0.191", "type": "module", "exports": { "./*": "./src/components/*.tsx", diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 4f0645bf8..973a06230 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -23,6 +23,7 @@ import { DiffChanges } from "./diff-changes" import { Markdown } from "./markdown" import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/util/path" import { checksum } from "@opencode-ai/util/encode" +import { createAutoScroll } from "../hooks" interface Diagnostic { range: { @@ -330,6 +331,7 @@ export interface ToolProps { metadata: Record<string, any> tool: string output?: string + status?: string hideDetails?: boolean defaultOpen?: boolean } @@ -398,6 +400,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { tool={part.tool} metadata={metadata} output={part.state.status === "completed" ? part.state.output : undefined} + status={part.state.status} hideDetails={props.hideDetails} defaultOpen={props.defaultOpen} /> @@ -561,6 +564,10 @@ ToolRegistry.register({ const summary = () => (props.metadata.summary ?? []) as { id: string; tool: string; state: { status: string; title?: string } }[] + const autoScroll = createAutoScroll({ + working: () => true, + }) + return ( <BasicTool icon="task" @@ -571,8 +578,8 @@ ToolRegistry.register({ subtitle: props.input.description, }} > - <div data-component="tool-output" data-scrollable> - <div data-component="task-tools"> + <div ref={autoScroll.scrollRef} onScroll={autoScroll.handleScroll} data-component="tool-output" data-scrollable> + <div ref={autoScroll.contentRef} data-component="task-tools"> <For each={summary()}> {(item) => { const info = getToolInfo(item.tool) diff --git a/packages/ui/src/components/session-message-rail.css b/packages/ui/src/components/session-message-rail.css index dc2352c22..5729e4920 100644 --- a/packages/ui/src/components/session-message-rail.css +++ b/packages/ui/src/components/session-message-rail.css @@ -17,7 +17,7 @@ display: none; } -@container (min-width: 72rem) { +@container (min-width: 88rem) { [data-slot="session-message-rail-compact"] { display: none; } diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 71e6689f6..e49e70864 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -3,7 +3,7 @@ import { useData } from "../context" import { useDiffComponent } from "../context/diff" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { checksum } from "@opencode-ai/util/encode" -import { batch, createEffect, createMemo, For, Match, onCleanup, ParentProps, Show, Switch } from "solid-js" +import { createEffect, createMemo, For, Match, onCleanup, ParentProps, Show, Switch } from "solid-js" import { createResizeObserver } from "@solid-primitives/resize-observer" import { DiffChanges } from "./diff-changes" import { Typewriter } from "./typewriter" @@ -19,6 +19,7 @@ import { Button } from "./button" import { Spinner } from "./spinner" import { createStore } from "solid-js/store" import { DateTime, DurationUnit, Interval } from "luxon" +import { createAutoScroll } from "../hooks" function computeStatusFromPart(part: PartType | undefined): string | undefined { if (!part) return undefined @@ -233,17 +234,14 @@ export function SessionTurn( }) } - let scrollRef: HTMLDivElement | undefined + const autoScroll = createAutoScroll({ + working, + onUserInteracted: props.onUserInteracted, + }) + const [store, setStore] = createStore({ - contentRef: undefined as HTMLDivElement | undefined, stickyTitleRef: undefined as HTMLDivElement | undefined, stickyTriggerRef: undefined as HTMLDivElement | undefined, - lastScrollTop: 0, - lastScrollHeight: 0, - lastContainerWidth: 0, - autoScrolled: false, - userScrolled: false, - reflowing: false, stickyHeaderHeight: 0, retrySeconds: 0, status: rawStatus(), @@ -265,104 +263,6 @@ export function SessionTurn( onCleanup(() => clearInterval(timer)) }) - function handleScroll() { - if (!scrollRef || store.autoScrolled) return - - const scrollTop = scrollRef.scrollTop - const scrollHeight = scrollRef.scrollHeight - - if (store.reflowing) { - batch(() => { - setStore("lastScrollTop", scrollTop) - setStore("lastScrollHeight", scrollHeight) - }) - return - } - - const scrollHeightChanged = Math.abs(scrollHeight - store.lastScrollHeight) > 10 - const scrollTopDelta = scrollTop - store.lastScrollTop - - if (scrollHeightChanged && scrollTopDelta < 0) { - const heightRatio = store.lastScrollHeight > 0 ? scrollHeight / store.lastScrollHeight : 1 - const expectedScrollTop = store.lastScrollTop * heightRatio - if (Math.abs(scrollTop - expectedScrollTop) < 100) { - batch(() => { - setStore("lastScrollTop", scrollTop) - setStore("lastScrollHeight", scrollHeight) - }) - return - } - } - - const reset = scrollTop <= 0 && store.lastScrollTop > 0 && working() && !store.userScrolled - if (reset) { - batch(() => { - setStore("lastScrollTop", scrollTop) - setStore("lastScrollHeight", scrollHeight) - }) - requestAnimationFrame(scrollToBottom) - return - } - - const scrolledUp = scrollTop < store.lastScrollTop - 50 && !scrollHeightChanged - if (scrolledUp && working()) { - setStore("userScrolled", true) - props.onUserInteracted?.() - } - - batch(() => { - setStore("lastScrollTop", scrollTop) - setStore("lastScrollHeight", scrollHeight) - }) - } - - function handleInteraction() { - if (working()) { - setStore("userScrolled", true) - props.onUserInteracted?.() - } - } - - function scrollToBottom() { - if (!scrollRef || store.userScrolled || !working()) return - setStore("autoScrolled", true) - requestAnimationFrame(() => { - scrollRef?.scrollTo({ top: scrollRef.scrollHeight, behavior: "smooth" }) - requestAnimationFrame(() => { - batch(() => { - setStore("lastScrollTop", scrollRef?.scrollTop ?? 0) - setStore("lastScrollHeight", scrollRef?.scrollHeight ?? 0) - setStore("autoScrolled", false) - }) - }) - }) - } - - createResizeObserver( - () => store.contentRef, - ({ width }) => { - const widthChanged = Math.abs(width - store.lastContainerWidth) > 5 - if (widthChanged && store.lastContainerWidth > 0) { - setStore("reflowing", true) - requestAnimationFrame(() => { - requestAnimationFrame(() => { - setStore("reflowing", false) - if (working() && !store.userScrolled) { - scrollToBottom() - } - }) - }) - } else if (!store.reflowing) { - scrollToBottom() - } - setStore("lastContainerWidth", width) - }, - ) - - createEffect(() => { - if (!working()) setStore("userScrolled", false) - }) - createResizeObserver( () => store.stickyTitleRef, ({ height }) => { @@ -412,12 +312,17 @@ export function SessionTurn( return ( <div data-component="session-turn" class={props.classes?.root}> - <div ref={scrollRef} onScroll={handleScroll} data-slot="session-turn-content" class={props.classes?.content}> - <div onClick={handleInteraction}> + <div + ref={autoScroll.scrollRef} + onScroll={autoScroll.handleScroll} + data-slot="session-turn-content" + class={props.classes?.content} + > + <div onClick={autoScroll.handleInteraction}> <Show when={message()}> {(msg) => ( <div - ref={(el) => setStore("contentRef", el)} + ref={autoScroll.contentRef} data-message={msg().id} data-slot="session-turn-message-container" class={props.classes?.container} diff --git a/packages/ui/src/hooks/create-auto-scroll.tsx b/packages/ui/src/hooks/create-auto-scroll.tsx new file mode 100644 index 000000000..d201fe36b --- /dev/null +++ b/packages/ui/src/hooks/create-auto-scroll.tsx @@ -0,0 +1,135 @@ +import { batch, createEffect } from "solid-js" +import { createStore } from "solid-js/store" +import { createResizeObserver } from "@solid-primitives/resize-observer" + +export interface AutoScrollOptions { + working: () => boolean + onUserInteracted?: () => void +} + +export function createAutoScroll(options: AutoScrollOptions) { + let scrollRef: HTMLElement | undefined + const [store, setStore] = createStore({ + contentRef: undefined as HTMLElement | undefined, + lastScrollTop: 0, + lastScrollHeight: 0, + lastContentWidth: 0, + autoScrolled: false, + userScrolled: false, + reflowing: false, + }) + + function scrollToBottom() { + if (!scrollRef || store.userScrolled || !options.working()) return + setStore("autoScrolled", true) + requestAnimationFrame(() => { + scrollRef?.scrollTo({ top: scrollRef.scrollHeight, behavior: "smooth" }) + requestAnimationFrame(() => { + batch(() => { + setStore("lastScrollTop", scrollRef?.scrollTop ?? 0) + setStore("lastScrollHeight", scrollRef?.scrollHeight ?? 0) + setStore("autoScrolled", false) + }) + }) + }) + } + + function handleScroll() { + if (!scrollRef || store.autoScrolled) return + + const scrollTop = scrollRef.scrollTop + const scrollHeight = scrollRef.scrollHeight + + if (store.reflowing) { + batch(() => { + setStore("lastScrollTop", scrollTop) + setStore("lastScrollHeight", scrollHeight) + }) + return + } + + const scrollHeightChanged = Math.abs(scrollHeight - store.lastScrollHeight) > 10 + const scrollTopDelta = scrollTop - store.lastScrollTop + + // Handle reflow-caused scroll position changes + if (scrollHeightChanged && scrollTopDelta < 0) { + const heightRatio = store.lastScrollHeight > 0 ? scrollHeight / store.lastScrollHeight : 1 + const expectedScrollTop = store.lastScrollTop * heightRatio + if (Math.abs(scrollTop - expectedScrollTop) < 100) { + batch(() => { + setStore("lastScrollTop", scrollTop) + setStore("lastScrollHeight", scrollHeight) + }) + return + } + } + + // Handle reset to top while working + const reset = scrollTop <= 0 && store.lastScrollTop > 0 && options.working() && !store.userScrolled + if (reset) { + batch(() => { + setStore("lastScrollTop", scrollTop) + setStore("lastScrollHeight", scrollHeight) + }) + requestAnimationFrame(scrollToBottom) + return + } + + // Detect intentional scroll up + const scrolledUp = scrollTop < store.lastScrollTop - 50 && !scrollHeightChanged + if (scrolledUp && options.working()) { + setStore("userScrolled", true) + options.onUserInteracted?.() + } + + batch(() => { + setStore("lastScrollTop", scrollTop) + setStore("lastScrollHeight", scrollHeight) + }) + } + + function handleInteraction() { + if (options.working()) { + setStore("userScrolled", true) + options.onUserInteracted?.() + } + } + + // Reset userScrolled when work completes + createEffect(() => { + if (!options.working()) setStore("userScrolled", false) + }) + + // Handle content resize + createResizeObserver( + () => store.contentRef, + ({ width }) => { + const widthChanged = Math.abs(width - store.lastContentWidth) > 5 + if (widthChanged && store.lastContentWidth > 0) { + setStore("reflowing", true) + requestAnimationFrame(() => { + requestAnimationFrame(() => { + setStore("reflowing", false) + if (options.working() && !store.userScrolled) { + scrollToBottom() + } + }) + }) + } else if (!store.reflowing) { + scrollToBottom() + } + setStore("lastContentWidth", width) + }, + ) + + return { + scrollRef: (el: HTMLElement | undefined) => { + scrollRef = el + }, + contentRef: (el: HTMLElement | undefined) => setStore("contentRef", el), + handleScroll, + handleInteraction, + scrollToBottom, + userScrolled: () => store.userScrolled, + } +} diff --git a/packages/ui/src/hooks/index.ts b/packages/ui/src/hooks/index.ts index 7eef78091..1c90a2e49 100644 --- a/packages/ui/src/hooks/index.ts +++ b/packages/ui/src/hooks/index.ts @@ -1 +1,2 @@ export * from "./use-filtered-list" +export * from "./create-auto-scroll" diff --git a/packages/ui/src/pierre/index.ts b/packages/ui/src/pierre/index.ts index f83fc82a2..824d96b11 100644 --- a/packages/ui/src/pierre/index.ts +++ b/packages/ui/src/pierre/index.ts @@ -10,6 +10,31 @@ export type DiffProps<T = {}> = FileDiffOptions<T> & { } const unsafeCSS = ` +[data-diffs] { + --diffs-bg: light-dark(var(--diffs-light-bg), var(--diffs-dark-bg)); + --diffs-bg-buffer: var(--diffs-bg-buffer-override, light-dark( color-mix(in lab, var(--diffs-bg) 92%, var(--diffs-mixer)), color-mix(in lab, var(--diffs-bg) 92%, var(--diffs-mixer)))); + --diffs-bg-hover: var(--diffs-bg-hover-override, light-dark( color-mix(in lab, var(--diffs-bg) 97%, var(--diffs-mixer)), color-mix(in lab, var(--diffs-bg) 91%, var(--diffs-mixer)))); + --diffs-bg-context: var(--diffs-bg-context-override, light-dark( color-mix(in lab, var(--diffs-bg) 98.5%, var(--diffs-mixer)), color-mix(in lab, var(--diffs-bg) 92.5%, var(--diffs-mixer)))); + --diffs-bg-separator: var(--diffs-bg-separator-override, light-dark( color-mix(in lab, var(--diffs-bg) 96%, var(--diffs-mixer)), color-mix(in lab, var(--diffs-bg) 85%, var(--diffs-mixer)))); + --diffs-fg: light-dark(var(--diffs-light), var(--diffs-dark)); + --diffs-fg-number: var(--diffs-fg-number-override, light-dark(color-mix(in lab, var(--diffs-fg) 65%, var(--diffs-bg)), color-mix(in lab, var(--diffs-fg) 65%, var(--diffs-bg)))); + --diffs-deletion-base: var(--diffs-deletion-color-override, light-dark(var(--diffs-light-deletion-color, var(--diffs-deletion-color, rgb(255, 0, 0))), var(--diffs-dark-deletion-color, var(--diffs-deletion-color, rgb(255, 0, 0))))); + --diffs-addition-base: var(--diffs-addition-color-override, light-dark(var(--diffs-light-addition-color, var(--diffs-addition-color, rgb(0, 255, 0))), var(--diffs-dark-addition-color, var(--diffs-addition-color, rgb(0, 255, 0))))); + --diffs-modified-base: var(--diffs-modified-color-override, light-dark(var(--diffs-light-modified-color, var(--diffs-modified-color, rgb(0, 0, 255))), var(--diffs-dark-modified-color, var(--diffs-modified-color, rgb(0, 0, 255))))); + --diffs-bg-deletion: var(--diffs-bg-deletion-override, light-dark( color-mix(in lab, var(--diffs-bg) 98%, var(--diffs-deletion-base)), color-mix(in lab, var(--diffs-bg) 92%, var(--diffs-deletion-base)))); + --diffs-bg-deletion-number: var(--diffs-bg-deletion-number-override, light-dark( color-mix(in lab, var(--diffs-bg) 91%, var(--diffs-deletion-base)), color-mix(in lab, var(--diffs-bg) 85%, var(--diffs-deletion-base)))); + --diffs-bg-deletion-hover: var(--diffs-bg-deletion-hover-override, light-dark( color-mix(in lab, var(--diffs-bg) 80%, var(--diffs-deletion-base)), color-mix(in lab, var(--diffs-bg) 75%, var(--diffs-deletion-base)))); + --diffs-bg-deletion-emphasis: var(--diffs-bg-deletion-emphasis-override, light-dark(rgb(from var(--diffs-deletion-base) r g b / 0.7), rgb(from var(--diffs-deletion-base) r g b / 0.1))); + --diffs-bg-addition: var(--diffs-bg-addition-override, light-dark( color-mix(in lab, var(--diffs-bg) 98%, var(--diffs-addition-base)), color-mix(in lab, var(--diffs-bg) 92%, var(--diffs-addition-base)))); + --diffs-bg-addition-number: var(--diffs-bg-addition-number-override, light-dark( color-mix(in lab, var(--diffs-bg) 91%, var(--diffs-addition-base)), color-mix(in lab, var(--diffs-bg) 85%, var(--diffs-addition-base)))); + --diffs-bg-addition-hover: var(--diffs-bg-addition-hover-override, light-dark( color-mix(in lab, var(--diffs-bg) 80%, var(--diffs-addition-base)), color-mix(in lab, var(--diffs-bg) 70%, var(--diffs-addition-base)))); + --diffs-bg-addition-emphasis: var(--diffs-bg-addition-emphasis-override, light-dark(rgb(from var(--diffs-addition-base) r g b / 0.07), rgb(from var(--diffs-addition-base) r g b / 0.1))); + --diffs-selection-base: var(--diffs-modified-base); + --diffs-selection-number-fg: light-dark( color-mix(in lab, var(--diffs-selection-base) 65%, var(--diffs-mixer)), color-mix(in lab, var(--diffs-selection-base) 75%, var(--diffs-mixer))); + --diffs-bg-selection: var(--diffs-bg-selection-override, light-dark( color-mix(in lab, var(--diffs-bg) 82%, var(--diffs-selection-base)), color-mix(in lab, var(--diffs-bg) 75%, var(--diffs-selection-base)))); + --diffs-bg-selection-number: var(--diffs-bg-selection-number-override, light-dark( color-mix(in lab, var(--diffs-bg) 75%, var(--diffs-selection-base)), color-mix(in lab, var(--diffs-bg) 60%, var(--diffs-selection-base)))); +} + [data-diffs-header], [data-diffs] { [data-separator-wrapper] { diff --git a/packages/util/package.json b/packages/util/package.json index f2fb03145..9194b1e9f 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.0.186", + "version": "1.0.191", "private": true, "type": "module", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 9b2609dec..6f9ba171c 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/web", "type": "module", - "version": "1.0.186", + "version": "1.0.191", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", @@ -28,7 +28,6 @@ "marked-shiki": "1.2.1", "rehype-autolink-headings": "7.1.0", "remeda": "catalog:", - "sharp": "0.32.5", "shiki": "3.4.2", "solid-js": "catalog:", "toolbeam-docs-theme": "0.4.8" diff --git a/packages/web/src/content/docs/ecosystem.mdx b/packages/web/src/content/docs/ecosystem.mdx index 8f7b201b6..48ae971fe 100644 --- a/packages/web/src/content/docs/ecosystem.mdx +++ b/packages/web/src/content/docs/ecosystem.mdx @@ -29,6 +29,7 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw | [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Enables AI agents to run background processes in a PTY, send interactive input to them. | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Track OpenCode usage with Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x faster code editing with Morph Fast Apply API and lazy edit markers | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible | | [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-powered automatic Zellij session naming based on OpenCode context | @@ -44,6 +45,7 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw | [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | Template for building OpenCode plugins | | [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Neovim frontend for opencode - a terminal-based AI coding agent | | [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Vercel AI SDK provider for using OpenCode via @opencode-ai/sdk | +| [OpenChamber](https://github.com/btriapitsyn/openchamber) | Web / Desktop App and VS Code Extension for OpenCode | --- diff --git a/packages/web/src/content/docs/github.mdx b/packages/web/src/content/docs/github.mdx index bd792f92f..25c3ce927 100644 --- a/packages/web/src/content/docs/github.mdx +++ b/packages/web/src/content/docs/github.mdx @@ -109,6 +109,7 @@ OpenCode can be triggered by the following GitHub events: | `issue_comment` | Comment on an issue or PR | Mention `/opencode` or `/oc` in your comment. OpenCode reads the issue/PR context and can create branches, open PRs, or reply with explanations. | | `pull_request_review_comment` | Comment on specific code lines in a PR | Mention `/opencode` or `/oc` while reviewing code. OpenCode receives file path, line numbers, and diff context for precise responses. | | `schedule` | Cron-based schedule | Run OpenCode on a schedule using the `prompt` input. Useful for automated code reviews, reports, or maintenance tasks. OpenCode can create issues or PRs as needed. | +| `pull_request` | PR opened or updated | Automatically trigger OpenCode when PRs are opened, synchronized, or reopened. Useful for automated reviews without needing to leave a comment. | ### Schedule Example @@ -150,6 +151,43 @@ For scheduled events, the `prompt` input is **required** since there's no commen --- +### Pull Request Example + +Automatically review PRs when they are opened or updated: + +```yaml title=".github/workflows/opencode-review.yml" +name: opencode-review + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + review: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + pull-requests: read + issues: read + steps: + - uses: actions/checkout@v4 + - uses: sst/opencode/github@latest + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + with: + model: anthropic/claude-sonnet-4-20250514 + prompt: | + Review this pull request: + - Check for code quality issues + - Look for potential bugs + - Suggest improvements +``` + +For `pull_request` events, if no `prompt` is provided, OpenCode defaults to reviewing the pull request. + +--- + ## Custom prompts Override the default prompt to customize OpenCode's behavior for your workflow. diff --git a/packages/web/src/content/docs/gitlab.mdx b/packages/web/src/content/docs/gitlab.mdx index 66c006c80..e8f43f85f 100644 --- a/packages/web/src/content/docs/gitlab.mdx +++ b/packages/web/src/content/docs/gitlab.mdx @@ -3,12 +3,55 @@ title: GitLab description: Use OpenCode in GitLab issues and merge requests. --- +OpenCode integrates with your GitLab workflow through your GitLab CI/CD pipeline or with GitLab Duo. + +In both cases, OpenCode will run on your GitLab runners. + +--- + +## GitLab CI + +OpenCode works in a regular GitLab pipeline. You can build it into a pipeline as a [CI component](https://docs.gitlab.com/ee/ci/components/) + +Here we are using a community-created CI/CD component for OpenCode — [nagyv/gitlab-opencode](https://gitlab.com/nagyv/gitlab-opencode). + +--- + +### Features + +- **Use custom configuration per job**: Configure OpenCode with a custom configuration directory, for example `./config/#custom-directory` to enable or disable functionality per OpenCode invocation. +- **Minimal setup**: The CI component sets up OpenCode in the background, you only need to create the OpenCode configuration and the initial prompt. +- **Flexible**: The CI component supports several inputs for customizing its behavior + +--- + +### Setup + +1. Store your OpenCode authentication JSON as a File type CI environment variables under **Settings** > **CI/CD** > **Variables**. Make sure to mark them as "Masked and hidden". +2. Add the following to your `.gitlab-ci.yml` file. + + ```yaml title=".gitlab-ci.yml" + include: + - component: $CI_SERVER_FQDN/nagyv/gitlab-opencode/opencode@1.0.0 + inputs: + config_dir: ${CI_PROJECT_DIR}/opencode-config + auth_json: $OPENCODE_AUTH_JSON # The variable name for your OpenCode authentication JSON + command: optional-custom-command + message: "Your prompt here" + ``` + +For more inputs and use cases [check out the docs](https://gitlab.com/explore/catalog/nagyv/gitlab-opencode) for this component. + +--- + +## GitLab Duo + OpenCode integrates with your GitLab workflow. Mention `@opencode` in a comment, and OpenCode will execute tasks within your GitLab CI pipeline. --- -## Features +### Features - **Triage issues**: Ask OpenCode to look into an issue and explain it to you. - **Fix and implement**: Ask OpenCode to fix an issue or implement a feature. @@ -17,7 +60,7 @@ Mention `@opencode` in a comment, and OpenCode will execute tasks within your Gi --- -## Setup +### Setup OpenCode runs in your GitLab CI/CD pipeline, here's what you'll need to set it up: @@ -113,7 +156,7 @@ You can refer to the [GitLab CLI agents docs](https://docs.gitlab.com/user/duo_a --- -## Examples +### Examples Here are some examples of how you can use OpenCode in GitLab. diff --git a/packages/web/src/content/docs/keybinds.mdx b/packages/web/src/content/docs/keybinds.mdx index 35610af51..b7e370825 100644 --- a/packages/web/src/content/docs/keybinds.mdx +++ b/packages/web/src/content/docs/keybinds.mdx @@ -32,6 +32,8 @@ OpenCode has a list of keybinds that you can customize through the OpenCode conf "messages_half_page_down": "ctrl+alt+d", "messages_first": "ctrl+g,home", "messages_last": "ctrl+alt+g,end", + "messages_next": "none", + "messages_previous": "none", "messages_copy": "<leader>y", "messages_undo": "<leader>u", "messages_redo": "<leader>r", diff --git a/packages/web/src/content/docs/lsp.mdx b/packages/web/src/content/docs/lsp.mdx index df97dc3ff..230f782d3 100644 --- a/packages/web/src/content/docs/lsp.mdx +++ b/packages/web/src/content/docs/lsp.mdx @@ -17,6 +17,7 @@ OpenCode comes with several built-in LSP servers for popular languages: | bash | .sh, .bash, .zsh, .ksh | Auto-installs bash-language-server | | clangd | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | Auto-installs for C/C++ projects | | csharp | .cs | `.NET SDK` installed | +| clojure-lsp | .clj, .cljs, .cljc, .edn | `clojure-lsp` command available | | dart | .dart | `dart` command available | | deno | .ts, .tsx, .js, .jsx, .mjs | `deno` command available (auto-detects deno.json/deno.jsonc) | | elixir-ls | .ex, .exs | `elixir` command available | @@ -36,6 +37,7 @@ OpenCode comes with several built-in LSP servers for popular languages: | sourcekit-lsp | .swift, .objc, .objcpp | `swift` installed (`xcode` on macOS) | | svelte | .svelte | Auto-installs for Svelte projects | | terraform | .tf, .tfvars | Auto-installs from GitHub releases | +| tinymist | .typ, .typc | Auto-installs from GitHub releases | | typescript | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | `typescript` dependency in project | | vue | .vue | Auto-installs for Vue projects | | yaml-ls | .yaml, .yml | Auto-installs Red Hat yaml-language-server | diff --git a/packages/web/src/content/docs/plugins.mdx b/packages/web/src/content/docs/plugins.mdx index 6982b4c93..6be545482 100644 --- a/packages/web/src/content/docs/plugins.mdx +++ b/packages/web/src/content/docs/plugins.mdx @@ -233,3 +233,30 @@ Include any state that should persist across compaction: ``` The `experimental.session.compacting` hook fires before the LLM generates a continuation summary. Use it to inject domain-specific context that the default compaction prompt would miss. + +You can also replace the compaction prompt entirely by setting `output.prompt`: + +```ts title=".opencode/plugin/custom-compaction.ts" +import type { Plugin } from "@opencode-ai/plugin" + +export const CustomCompactionPlugin: Plugin = async (ctx) => { + return { + "experimental.session.compacting": async (input, output) => { + // Replace the entire compaction prompt + output.prompt = ` +You are generating a continuation prompt for a multi-agent swarm session. + +Summarize: +1. The current task and its status +2. Which files are being modified and by whom +3. Any blockers or dependencies between agents +4. The next steps to complete the work + +Format as a structured prompt that a new agent can use to resume work. +` + }, + } +} +``` + +When `output.prompt` is set, it completely replaces the default compaction prompt. The `output.context` array is ignored in this case. diff --git a/packages/web/src/content/docs/sdk.mdx b/packages/web/src/content/docs/sdk.mdx index 4f9a2959f..1ff843103 100644 --- a/packages/web/src/content/docs/sdk.mdx +++ b/packages/web/src/content/docs/sdk.mdx @@ -123,6 +123,23 @@ The SDK exposes all server APIs through a type-safe client. --- +### Global + +| Method | Description | Response | +| ----------------- | ------------------------------- | ------------------------------------ | +| `global.health()` | Check server health and version | `{ healthy: true, version: string }` | + +--- + +#### Examples + +```javascript +const health = await client.global.health() +console.log(health.data.version) +``` + +--- + ### App | Method | Description | Response | diff --git a/packages/web/src/content/docs/server.mdx b/packages/web/src/content/docs/server.mdx index 60aff2003..427d8f505 100644 --- a/packages/web/src/content/docs/server.mdx +++ b/packages/web/src/content/docs/server.mdx @@ -68,11 +68,12 @@ The opencode server exposes the following APIs. --- -### Global Events +### Global -| Method | Path | Description | Response | -| ------ | --------------- | ------------------------------ | ------------ | -| `GET` | `/global/event` | Get global events (SSE stream) | Event stream | +| Method | Path | Description | Response | +| ------ | ---------------- | ------------------------------ | ------------------------------------ | +| `GET` | `/global/health` | Get server health and version | `{ healthy: true, version: string }` | +| `GET` | `/global/event` | Get global events (SSE stream) | Event stream | --- diff --git a/packages/web/src/content/docs/skills.mdx b/packages/web/src/content/docs/skills.mdx index 217d4b3d2..c1d433a83 100644 --- a/packages/web/src/content/docs/skills.mdx +++ b/packages/web/src/content/docs/skills.mdx @@ -4,7 +4,7 @@ description: "Define reusable behavior via SKILL.md definitions" --- Agent skills let OpenCode discover reusable instructions from your repo or home directory. -When a conversation matches a skill, the agent is prompted to read its `SKILL.md`. +Skills are loaded on-demand via the native `skill` tool—agents see available skills and can load the full content when needed. --- @@ -97,24 +97,123 @@ Ask clarifying questions if the target versioning scheme is unclear. --- -## Recognize prompt injection +## Recognize tool description -OpenCode adds an `<available_skills>` XML block to the system prompt. -Each entry includes the skill name, description, and its discovered location. +OpenCode lists available skills in the `skill` tool description. +Each entry includes the skill name and description: ```xml <available_skills> <skill> <name>git-release</name> <description>Create consistent releases and changelogs</description> - <location>.opencode/skill/git-release/SKILL.md</location> </skill> </available_skills> ``` +The agent loads a skill by calling the tool: + +``` +skill({ name: "git-release" }) +``` + +--- + +## Configure permissions + +Control which skills agents can access using pattern-based permissions in `opencode.json`: + +```json +{ + "permission": { + "skill": { + "pr-review": "allow", + "internal-*": "deny", + "experimental-*": "ask", + "*": "allow" + } + } +} +``` + +| Permission | Behavior | +| ---------- | ----------------------------------------- | +| `allow` | Skill loads immediately | +| `deny` | Skill hidden from agent, access rejected | +| `ask` | User prompted for approval before loading | + +Patterns support wildcards: `internal-*` matches `internal-docs`, `internal-tools`, etc. + +--- + +## Override per agent + +Give specific agents different permissions than the global defaults. + +**For custom agents** (in agent frontmatter): + +```yaml +--- +permission: + skill: + "documents-*": "allow" +--- +``` + +**For built-in agents** (in `opencode.json`): + +```json +{ + "agent": { + "plan": { + "permission": { + "skill": { + "internal-*": "allow" + } + } + } + } +} +``` + +--- + +## Disable the skill tool + +Completely disable skills for agents that shouldn't use them: + +**For custom agents**: + +```yaml +--- +tools: + skill: false +--- +``` + +**For built-in agents**: + +```json +{ + "agent": { + "plan": { + "tools": { + "skill": false + } + } + } +} +``` + +When disabled, the `<available_skills>` section is omitted entirely. + --- ## Troubleshoot loading -If a skill does not show up, verify the folder name matches `name` exactly. -Also check that `SKILL.md` is spelled in all caps and includes frontmatter. +If a skill does not show up: + +1. Verify `SKILL.md` is spelled in all caps +2. Check that frontmatter includes `name` and `description` +3. Ensure skill names are unique across all locations +4. Check permissions—skills with `deny` are hidden from agents diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 134271ca1..4b268ab4f 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -77,6 +77,7 @@ You can also access our models through the following API endpoints. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| GLM 4.7 | glm-4.7-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | | GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -110,6 +111,7 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | --------------------------------- | ------ | ------ | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | | Grok Code Fast 1 | Free | Free | Free | - | +| GLM 4.7 | Free | Free | Free | - | | GLM 4.6 | $0.60 | $2.20 | $0.10 | - | | Kimi K2 | $0.40 | $2.50 | - | - | | Kimi K2 Thinking | $0.40 | $2.50 | - | - | diff --git a/script/publish-start.ts b/script/publish-start.ts index 229435ddf..a846df14d 100755 --- a/script/publish-start.ts +++ b/script/publish-start.ts @@ -17,16 +17,9 @@ if (!Script.preview) { .then((data: any) => data.version) const log = - await $`git log v${previous}..HEAD --oneline --format="%h %s" -- packages/opencode packages/sdk packages/plugin packages/tauri packages/desktop`.text() + await $`git log v${previous}..HEAD --oneline --format="%h %s" -- packages/opencode packages/sdk packages/plugin packages/desktop packages/app`.text() - const commits = log - .split("\n") - .filter((line) => line && !line.match(/^\w+ (ignore:|test:|chore:|ci:)/i)) - .join("\n") - - const opencode = await createOpencode() - const session = await opencode.client.session.create() - console.log("generating changelog since " + previous) + const commits = log.split("\n").filter((line) => line && !line.match(/^\w+ (ignore:|test:|chore:|ci:)/i)) const team = [ "actions-user", @@ -41,20 +34,25 @@ if (!Script.preview) { "opencode-agent[bot]", ] - const raw = await opencode.client.session - .prompt({ - path: { - id: session.data!.id, - }, - body: { - model: { - providerID: "opencode", - modelID: "gemini-3-flash", + async function generateChangelog() { + const opencode = await createOpencode() + const session = await opencode.client.session.create() + console.log("generating changelog since " + previous) + + const raw = await opencode.client.session + .prompt({ + path: { + id: session.data!.id, }, - parts: [ - { - type: "text", - text: ` + body: { + model: { + providerID: "opencode", + modelID: "gemini-3-flash", + }, + parts: [ + { + type: "text", + text: ` Analyze these commits and generate a changelog of all notable user facing changes, grouped by area. Each commit below includes: @@ -62,11 +60,11 @@ if (!Script.preview) { - [areas: ...] showing which areas of the codebase were modified Commits between ${previous} and HEAD: - ${commits} + ${commits.join("\n")} Group the changes into these categories based on the [areas: ...] tags (omit any category with no changes): - **TUI**: Changes to "opencode" area (the terminal/CLI interface) - - **Desktop**: Changes to "desktop" or "tauri" areas (the desktop application) + - **Desktop**: Changes to "app" or "tauri" areas (the desktop application) - **SDK**: Changes to "sdk" or "plugin" areas (the SDK and plugin system) - **Extensions**: Changes to "extensions/zed", "extensions/vscode", or "github" areas (editor extensions and GitHub Action) - **Other**: Any user-facing changes that don't fit the above categories @@ -105,20 +103,34 @@ if (!Script.preview) { - Added OIDC_BASE_URL support for custom GitHub App installations (@elithrar) </example> `, - }, - ], - }, - }) - .then((x) => x.data?.parts?.find((y) => y.type === "text")?.text) - for (const line of raw?.split("\n") ?? []) { - if (line.startsWith("- ")) { - notes.push(line) + }, + ], + }, + }) + .then((x) => x.data?.parts?.find((y) => y.type === "text")?.text) + opencode.server.close() + return raw + } + + const timeout = new Promise<null>((resolve) => setTimeout(() => resolve(null), 120_000)) + const raw = await Promise.race([generateChangelog(), timeout]) + + if (raw) { + for (const line of raw.split("\n")) { + if (line.startsWith("- ")) { + notes.push(line) + } + } + console.log("---- Generated Changelog ----") + console.log(notes.join("\n")) + console.log("-----------------------------") + } else { + console.log("Changelog generation timed out, using raw commits") + for (const commit of commits) { + const message = commit.replace(/^\w+ /, "") + notes.push(`- ${message}`) } } - console.log("---- Generated Changelog ----") - console.log(notes.join("\n")) - console.log("-----------------------------") - opencode.server.close() const compare = await $`gh api "/repos/sst/opencode/compare/v${previous}...HEAD" --jq '.commits[] | {login: .author.login, message: .commit.message}'`.text() diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index be3139886..791533d63 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.0.186", + "version": "1.0.191", "publisher": "sst-dev", "repository": { "type": "git", diff --git a/sdks/vscode/src/extension.ts b/sdks/vscode/src/extension.ts index 63d8d332e..105ab0293 100644 --- a/sdks/vscode/src/extension.ts +++ b/sdks/vscode/src/extension.ts @@ -35,7 +35,7 @@ export function activate(context: vscode.ExtensionContext) { if (terminal.name === TERMINAL_NAME) { // @ts-ignore const port = terminal.creationOptions.env?.["_EXTENSION_OPENCODE_PORT"] - port ? await appendPrompt(parseInt(port), fileRef) : terminal.sendText(fileRef) + port ? await appendPrompt(parseInt(port), fileRef) : terminal.sendText(fileRef, false) terminal.show() } }) diff --git a/sst-env.d.ts b/sst-env.d.ts index 2c182ec35..813d654a0 100644 --- a/sst-env.d.ts +++ b/sst-env.d.ts @@ -148,6 +148,10 @@ declare module "sst" { "name": string "type": "sst.cloudflare.Bucket" } + "ZenDataNew": { + "name": string + "type": "sst.cloudflare.Bucket" + } } } /// <reference path="sst-env.d.ts" />