diff --git a/.github/workflows/auto-label-tui.yml b/.github/workflows/auto-label-tui.yml index 834562e1..8f2d80b5 100644 --- a/.github/workflows/auto-label-tui.yml +++ b/.github/workflows/auto-label-tui.yml @@ -20,8 +20,8 @@ jobs: const title = issue.title; const description = issue.body || ''; - // Check for web/desktop keywords - const webPattern = /\b(web|desktop)\b/i; + // Check for "opencode web" keyword + const webPattern = /(opencode web)/i; const isWebRelated = webPattern.test(title) || webPattern.test(description); // Check for version patterns like v1.0.x or 1.0.x diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index df1a36f7..a339c7da 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,6 +12,10 @@ on: - major - minor - patch + version: + description: "Override version (optional)" + required: false + type: string concurrency: ${{ github.workflow }}-${{ github.ref }} @@ -62,6 +66,7 @@ jobs: ./script/publish.ts env: OPENCODE_BUMP: ${{ inputs.bump }} + OPENCODE_VERSION: ${{ inputs.version }} OPENCODE_CHANNEL: latest NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }} diff --git a/.github/workflows/sync-zed-extension.yml b/.github/workflows/sync-zed-extension.yml new file mode 100644 index 00000000..c9dea7bb --- /dev/null +++ b/.github/workflows/sync-zed-extension.yml @@ -0,0 +1,34 @@ +name: "sync-zed-extension" + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + zed: + name: Release Zed Extension + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: ./.github/actions/setup-bun + + - name: Get version tag + id: get_tag + run: | + if [ "${{ github.event_name }}" = "release" ]; then + TAG="${{ github.event.release.tag_name }}" + else + TAG=$(git tag --list 'v[0-9]*.*' --sort=-version:refname | head -n 1) + fi + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "Using tag: ${TAG}" + + - name: Sync Zed extension + run: | + ./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }} + env: + GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }} diff --git a/opencode.json b/.opencode/opencode.json similarity index 100% rename from opencode.json rename to .opencode/opencode.json diff --git a/AGENTS.md b/AGENTS.md index 9c6111b0..22b305da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,27 +21,27 @@ 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" - } - } - ] - } +"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" +} +} +] +} } diff --git a/STATS.md b/STATS.md index 27c8d283..30e0f85a 100644 --- a/STATS.md +++ b/STATS.md @@ -130,3 +130,9 @@ | 2025-11-02 | 644,067 (+7,967) | 590,004 (+8,198) | 1,234,071 (+16,165) | | 2025-11-03 | 653,130 (+9,063) | 597,139 (+7,135) | 1,250,269 (+16,198) | | 2025-11-04 | 663,912 (+10,782) | 608,056 (+10,917) | 1,271,968 (+21,699) | +| 2025-11-05 | 675,074 (+11,162) | 619,690 (+11,634) | 1,294,764 (+22,796) | +| 2025-11-06 | 686,252 (+11,178) | 630,885 (+11,195) | 1,317,137 (+22,373) | +| 2025-11-07 | 696,646 (+10,394) | 642,146 (+11,261) | 1,338,792 (+21,655) | +| 2025-11-08 | 706,035 (+9,389) | 653,489 (+11,343) | 1,359,524 (+20,732) | +| 2025-11-09 | 713,462 (+7,427) | 660,459 (+6,970) | 1,373,921 (+14,397) | +| 2025-11-10 | 722,288 (+8,826) | 668,225 (+7,766) | 1,390,513 (+16,592) | diff --git a/bun.lock b/bun.lock index e18ad950..5ed6e8c5 100644 --- a/bun.lock +++ b/bun.lock @@ -11,7 +11,7 @@ "@tsconfig/bun": "catalog:", "husky": "9.1.7", "prettier": "3.6.2", - "sst": "3.17.19", + "sst": "3.17.23", "turbo": "2.5.6", }, }, @@ -39,7 +39,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.0.23", + "version": "1.0.55", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -66,7 +66,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.0.23", + "version": "1.0.55", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -90,7 +90,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.0.23", + "version": "1.0.55", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -106,12 +106,15 @@ "@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.23", + "version": "1.0.55", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -121,6 +124,7 @@ "@solid-primitives/event-bus": "1.1.2", "@solid-primitives/resize-observer": "2.1.3", "@solid-primitives/scroll": "2.1.3", + "@solid-primitives/storage": "4.3.3", "@solidjs/meta": "catalog:", "@solidjs/router": "0.15.3", "@thisbeyond/solid-dnd": "0.7.5", @@ -150,7 +154,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.0.23", + "version": "1.0.55", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "22.0.0", @@ -166,14 +170,14 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.0.23", + "version": "1.0.55", "bin": { "opencode": "./bin/opencode", }, "dependencies": { "@actions/core": "1.11.1", "@actions/github": "6.0.1", - "@agentclientprotocol/sdk": "0.4.9", + "@agentclientprotocol/sdk": "0.5.1", "@clack/prompts": "1.0.0-alpha.1", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", @@ -184,8 +188,8 @@ "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", - "@opentui/core": "0.1.33", - "@opentui/solid": "0.1.33", + "@opentui/core": "0.0.0-20251108-0c7899b1", + "@opentui/solid": "0.0.0-20251108-0c7899b1", "@parcel/watcher": "2.5.1", "@pierre/precision-diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -207,6 +211,7 @@ "partial-json": "0.1.7", "remeda": "catalog:", "solid-js": "catalog:", + "strip-ansi": "7.1.2", "tree-sitter-bash": "0.25.0", "turndown": "7.2.0", "ulid": "catalog:", @@ -243,7 +248,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.0.23", + "version": "1.0.55", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -263,7 +268,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.0.23", + "version": "1.0.55", "devDependencies": { "@hey-api/openapi-ts": "0.81.0", "@tsconfig/node22": "catalog:", @@ -274,7 +279,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.0.23", + "version": "1.0.55", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -287,7 +292,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.0.23", + "version": "1.0.55", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -317,7 +322,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.0.23", + "version": "1.0.55", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -351,7 +356,6 @@ "trustedDependencies": [ "sharp", "esbuild", - "tree-sitter", "web-tree-sitter", "tree-sitter-bash", ], @@ -364,7 +368,7 @@ "@hono/zod-validator": "0.4.2", "@kobalte/core": "0.13.11", "@openauthjs/openauth": "0.0.0-20250322224806", - "@pierre/precision-diffs": "0.4.1", + "@pierre/precision-diffs": "0.4.4", "@solidjs/meta": "0.29.4", "@tailwindcss/vite": "4.1.11", "@tsconfig/bun": "1.0.9", @@ -401,7 +405,7 @@ "@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="], - "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.4.9", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-ExwH828LaTGoTTjxuw49l+fwOLA+Yx0+qkWn1TcHMOsY5mVI9CUfkj7ZDhv2klgZ7mJeT+lxX/Dn/KINv1AkNQ=="], + "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.5.1", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg=="], "@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@2.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="], @@ -571,7 +575,7 @@ "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="], - "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.8", "", { "peerDependencies": { "unenv": "2.0.0-rc.21", "workerd": "^1.20250927.0" }, "optionalPeers": ["workerd"] }, "sha512-Ky929MfHh+qPhwCapYrRPwPVHtA2Ioex/DbGZyskGyNRDe9Ru3WThYZivyNVaPy5ergQSgMs9OKrM9Ajtz9F6w=="], + "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.9", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "^1.20250927.0" }, "optionalPeers": ["workerd"] }, "sha512-Drm7qlTKnvncEv+DANiQNEonq0H0LyIsoFZYJ6tJ8OhAoy5udIE8yp6BsVDYcIjcYLIybp4M7c/P7ly/56SoHg=="], "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20251011.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0DirVP+Z82RtZLlK2B+VhLOkk+ShBqDYO/jhcRw4oVlp0TOvk3cOVZChrt3+y3NV8Y/PYgTEywzLKFSziK4wCg=="], @@ -601,7 +605,7 @@ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], - "@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="], + "@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="], "@emotion/is-prop-valid": ["@emotion/is-prop-valid@0.8.8", "", { "dependencies": { "@emotion/memoize": "0.7.4" } }, "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA=="], @@ -611,57 +615,57 @@ "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.11", "", { "os": "android", "cpu": "arm64" }, "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.11", "", { "os": "android", "cpu": "x64" }, "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.11", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.11", "", { "os": "linux", "cpu": "arm" }, "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.11", "", { "os": "linux", "cpu": "ia32" }, "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.11", "", { "os": "linux", "cpu": "x64" }, "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.11", "", { "os": "none", "cpu": "x64" }, "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.11", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.11", "", { "os": "openbsd", "cpu": "x64" }, "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.11", "", { "os": "sunos", "cpu": "x64" }, "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.11", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], "@expressive-code/core": ["@expressive-code/core@0.41.3", "", { "dependencies": { "@ctrl/tinycolor": "^4.0.4", "hast-util-select": "^6.0.2", "hast-util-to-html": "^9.0.1", "hast-util-to-text": "^4.0.1", "hastscript": "^9.0.0", "postcss": "^8.4.38", "postcss-nested": "^6.0.1", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1" } }, "sha512-9qzohqU7O0+JwMEEgQhnBPOw5DtsQRBXhW++5fvEywsuX44vCGGof1SL5OvPElvNgaWZ4pFZAFSlkNOkGyLwSQ=="], @@ -961,21 +965,21 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@opentui/core": ["@opentui/core@0.1.33", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.33", "@opentui/core-darwin-x64": "0.1.33", "@opentui/core-linux-arm64": "0.1.33", "@opentui/core-linux-x64": "0.1.33", "@opentui/core-win32-arm64": "0.1.33", "@opentui/core-win32-x64": "0.1.33", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-vwHdrPIqnsY6YnG2JTNhenHSsx+HUPYrQTBZdmEfCj9ROGVzKgUKbSDH1xGK2OtSNRb2KVBg4XaMpq0bie6afQ=="], + "@opentui/core": ["@opentui/core@0.0.0-20251108-0c7899b1", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.0.0-20251108-0c7899b1", "@opentui/core-darwin-x64": "0.0.0-20251108-0c7899b1", "@opentui/core-linux-arm64": "0.0.0-20251108-0c7899b1", "@opentui/core-linux-x64": "0.0.0-20251108-0c7899b1", "@opentui/core-win32-arm64": "0.0.0-20251108-0c7899b1", "@opentui/core-win32-x64": "0.0.0-20251108-0c7899b1", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-uJ7wbVw2v5NnL6g3v72SjPLUwMl2wqOejUEo8t4NeBA8nsboSxggqkrqOYf6OOmCADoAqyFDY7akZMsz6HMZtg=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.33", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JBvzcP2V7fT9KxFAMenHRd/t72qPP5IL5kzge2uok1T7t2nw3Wa+CWI5s6FYP42p2b1W9qZkv5Fno5gA7OAYuQ=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.0.0-20251108-0c7899b1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DS9CmFmZZjwe6PIhz6zhZAsDx11DtyMFDxn8V3On2b8G892aBG6rHYtBBnsM28/1GGEJBTeDQ/jUXPVd6FNJ/g=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.33", "", { "os": "darwin", "cpu": "x64" }, "sha512-x7DY6VCkAky10z/2o4UkkuNW/nIvoX7uAh3dJOHWZCLbiKywSFvFk3QZVVcH5BMk4tOOophYTzika4s4HpaeMg=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.0.0-20251108-0c7899b1", "", { "os": "darwin", "cpu": "x64" }, "sha512-K4XwdmT6FTShn7EG8AKliPzO5H59R0XUlZi9+kfRVW59IIJtna5wxbu69SkA28dFoWj5i4yDumwoBI+tI7T6vg=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.33", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBc1EdkVxsLBtqGjXM2BYpBJLa57ogcrSADSZbc5cQkPu0muSGzUwBbVnVZJUjWEfk6n5jcd4dDmLezVoQga0A=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.0.0-20251108-0c7899b1", "", { "os": "linux", "cpu": "arm64" }, "sha512-3JUmxZeSvxV5yU7NEXSecy5Z1/LcVUMy1oWyusZgp96X0CTYAXMrolZt9IJDGO5raeO7JId1UaJmWW0r4DR8TA=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.33", "", { "os": "linux", "cpu": "x64" }, "sha512-3oVL5mrLlKLUc1lc4v7xS3BJ9N7PnnimbGwAvlnVpfaAygotAs1XkPcjsUe6ItMnSJyi0FWiDHUE2+GiDtM5Nw=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.0.0-20251108-0c7899b1", "", { "os": "linux", "cpu": "x64" }, "sha512-i/AQWGyanpPRpk9NK7Ze1tn+d5bqzM9wZFKNB3rd9d2Vbt/ROgBJItG6igz8vzKPKgnlHK4Gw9b5iG5sbjpd+Q=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.33", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q68v7wssE+r0OG1KIGfi7m3fnu8KOK4ZNg9ML6EwE47VF9/bqgUe+6fPiXh5mmHzTwof7nAOdXCf052av5/upQ=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.0.0-20251108-0c7899b1", "", { "os": "win32", "cpu": "arm64" }, "sha512-C7JLWuNN3w2txiVx3demwNwogVi4DQB5ZNHy2b09++kd2m449/RwGPyLcKpuoTzU4s/usYOeY4TxKIAd8cKedQ=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.33", "", { "os": "win32", "cpu": "x64" }, "sha512-PvuchmUnbMCUXXMzfle/WTzhNGIdJ6RGCCoclx3YVUyNUVuUicPf42OEV+td2m81/Hr3CgcLn98HYX1TLIzPrw=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.0.0-20251108-0c7899b1", "", { "os": "win32", "cpu": "x64" }, "sha512-mpOryp37YaHlTsN70LhiSn9hJJBktbyhlH/eB3N2K7H1ANYQVrekgBJ3rDxlH1GDVtRz6vLS3IDlyK75qNX4pg=="], - "@opentui/solid": ["@opentui/solid@0.1.33", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.33", "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-bWSALdGJ2j51zwZ2gK1ZIBxFgauHq+V1ejEnyd4XamYMdWfpAKU+AUWDVLbpx1T9XG1oAnycJZfYX7BsZdVOOg=="], + "@opentui/solid": ["@opentui/solid@0.0.0-20251108-0c7899b1", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.0.0-20251108-0c7899b1", "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-tcsYnFGH/KBlQNG0IyZE2bisnm5NwN/w7theuWga3L1zoXqZqA5dQHutAVg4zkq5l/YKULeDI4jBlvz0lzH88A=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], @@ -1033,7 +1037,7 @@ "@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="], - "@pierre/precision-diffs": ["@pierre/precision-diffs@0.4.1", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/transformers": "3.13.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.13.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-AoozHakINGyNJFgbYc/1PlDK0yunrAxbtXEMBe9fdu8RLkNjVtYRTLw7EF2mM/YuVoVRjj2HT/2VJ4a2rMyDOA=="], + "@pierre/precision-diffs": ["@pierre/precision-diffs@0.4.4", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/transformers": "3.14.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.14.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-9bhWs+hsz1i0/SMIrzce+fFrSec8aLIFrJYTGHATlynmQovngIWz1Gc+XwGigvY4+zSMksrGPzO5HiaNlvRqtQ=="], "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], @@ -1041,7 +1045,7 @@ "@poppinss/colors": ["@poppinss/colors@4.1.5", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw=="], - "@poppinss/dumper": ["@poppinss/dumper@0.6.4", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ=="], + "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="], "@poppinss/exception": ["@poppinss/exception@1.2.2", "", {}, "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg=="], @@ -1187,7 +1191,7 @@ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], - "@sindresorhus/is": ["@sindresorhus/is@7.1.0", "", {}, "sha512-7F/yz2IphV39hiS2zB4QYVkivrptHHh0K8qJJd9HhuWSdvf8AN7NpebW3CcDZDBQsUPMoDKWsY2WWgW7bqOcfA=="], + "@sindresorhus/is": ["@sindresorhus/is@7.1.1", "", {}, "sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ=="], "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], @@ -1205,7 +1209,7 @@ "@smithy/abort-controller": ["@smithy/abort-controller@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ=="], - "@smithy/config-resolver": ["@smithy/config-resolver@4.4.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-BciDJ5hkyYEGBBKMbjGB1A/Zq8bYZ41Zo9BMnGdKF6QD1fY4zIkYx6zui/0CHaVGnv6h0iy8y4rnPX9CPCAPyQ=="], + "@smithy/config-resolver": ["@smithy/config-resolver@4.4.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-4Jys0ni2tB2VZzgslbEgszZyMdTkPOFGA8g+So/NjR8oy6Qwaq4eSwsrRI+NMtb0Dq4kqCzGUu/nGUx7OM/xfw=="], "@smithy/core": ["@smithy/core@3.17.2", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-stream": "^4.5.5", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ=="], @@ -1267,7 +1271,7 @@ "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-GwaGjv/QLuL/QHQaqhf/maM7+MnRFQQs7Bsl6FlaeK6lm6U7mV5AAnVabw68cIoMl5FQFyKK62u7RWRzWL25OQ=="], - "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.7", "", { "dependencies": { "@smithy/config-resolver": "^4.4.1", "@smithy/credential-provider-imds": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-6hinjVqec0WYGsqN7h9hL/ywfULmJJNXGXnNZW7jrIn/cFuC/aVlVaiDfBIJEvKcOrmN8/EgsW69eY0gXABeHw=="], + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.8", "", { "dependencies": { "@smithy/config-resolver": "^4.4.2", "@smithy/credential-provider-imds": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-gIoTf9V/nFSIZ0TtgDNLd+Ws59AJvijmMDYrOozoMHPJaG9cMRdqNO50jZTlbM6ydzQYY8L/mQ4tKSw/TB+s6g=="], "@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg=="], @@ -1309,6 +1313,8 @@ "@solid-primitives/static-store": ["@solid-primitives/static-store@0.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw=="], + "@solid-primitives/storage": ["@solid-primitives/storage@4.3.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "@tauri-apps/plugin-store": "*", "solid-js": "^1.6.12" }, "optionalPeers": ["@tauri-apps/plugin-store"] }, "sha512-ACbNwMZ1s8VAvld6EUXkDkX/US3IhtlPLxg6+B2s9MwNUugwdd51I98LPEaHrdLpqPmyzqgoJe0TxEFlf3Dqrw=="], + "@solid-primitives/trigger": ["@solid-primitives/trigger@1.2.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-IWoptVc0SWYgmpBPpCMehS5b07+tpFcvw15tOQ3QbXedSYn6KP8zCjPkHNzMxcOvOicTneleeZDP7lqmz+PQ6g=="], "@solid-primitives/utils": ["@solid-primitives/utils@6.3.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="], @@ -1319,7 +1325,7 @@ "@solidjs/start": ["@solidjs/start@1.2.0", "", { "dependencies": { "@tanstack/server-functions-plugin": "1.121.21", "@vinxi/plugin-directives": "^0.5.0", "@vinxi/server-components": "^0.5.0", "cookie-es": "^2.0.0", "defu": "^6.1.2", "error-stack-parser": "^2.1.4", "html-to-image": "^1.11.11", "radix3": "^1.1.0", "seroval": "^1.0.2", "seroval-plugins": "^1.0.2", "shiki": "^1.26.1", "source-map-js": "^1.0.2", "terracotta": "^1.0.4", "tinyglobby": "^0.2.2", "vite-plugin-solid": "^2.11.1" }, "peerDependencies": { "vinxi": "^0.5.7" } }, "sha512-SRv1g3R+4sxZnxCBPK1IedtLKsPhPJ7W/Yv4xEHjM4jJGPWi3ed35/yd0D5zhRK0C7zJIkZKbhnR/S3g8JUD5w=="], - "@speed-highlight/core": ["@speed-highlight/core@1.2.8", "", {}, "sha512-IGytNtnUnPIobIbOq5Y6LIlqiHNX+vnToQIS7lj6L5819C+rA8TXRDkkG8vePsiBOGcoW9R6i+dp2YBUKdB09Q=="], + "@speed-highlight/core": ["@speed-highlight/core@1.2.12", "", {}, "sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA=="], "@standard-community/standard-json": ["@standard-community/standard-json@0.3.5", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "@valibot/to-json-schema": "^1.3.0", "arktype": "^2.1.20", "effect": "^3.16.8", "quansync": "^0.2.11", "sury": "^10.0.0", "typebox": "^1.0.17", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-to-json-schema"] }, "sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA=="], @@ -1429,6 +1435,8 @@ "@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], + "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], + "@types/promise.allsettled": ["@types/promise.allsettled@1.0.6", "", {}, "sha512-wA0UT0HeT2fGHzIFV9kWpYz5mdoyLxKrTgMdZQM++5h6pYAFH73HXcQhefg24nD1yivUFEn5KU+EF4b+CXJ4Wg=="], "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], @@ -1447,7 +1455,7 @@ "@types/scheduler": ["@types/scheduler@0.26.0", "", {}, "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA=="], - "@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="], + "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="], "@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="], @@ -1515,6 +1523,8 @@ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + "ai": ["ai@5.0.8", "", { "dependencies": { "@ai-sdk/gateway": "1.0.4", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.1", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-qbnhj046UvG30V1S5WhjBn+RBGEAmi8PSZWqMhRsE3EPxvO5BcePXTZFA23e9MYyWS9zr4Vm8Mv3wQXwLmtIBw=="], "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], @@ -1589,7 +1599,7 @@ "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], - "axios": ["axios@1.13.1", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw=="], + "axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -1617,13 +1627,13 @@ "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.1", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-v2yl0TnaZTdEnelkKtXZGnotiV6qATBlnNuUMrHl6v9Lmmrh9mw9RYyImPU7/4RahumSwQS1k2oKXcRfXcbjJw=="], + "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=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.8.21", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.25", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA=="], "bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="], @@ -1701,7 +1711,7 @@ "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001752", "", {}, "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g=="], + "caniuse-lite": ["caniuse-lite@1.0.30001753", "", {}, "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -1739,6 +1749,8 @@ "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], + "cloudflare": ["cloudflare@5.2.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-dVzqDpPFYR9ApEC9e+JJshFJZXcw4HzM8W+3DHzO5oy9+8rLC53G7x6fEf9A7/gSuSCxuvndzui5qJKftfIM9A=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], @@ -1919,7 +1931,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "electron-to-chromium": ["electron-to-chromium@1.5.244", "", {}, "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw=="], + "electron-to-chromium": ["electron-to-chromium@1.5.245", "", {}, "sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ=="], "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], @@ -1961,7 +1973,7 @@ "esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="], - "esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="], + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "esbuild-plugin-copy": ["esbuild-plugin-copy@2.1.1", "", { "dependencies": { "chalk": "^4.1.2", "chokidar": "^3.5.3", "fs-extra": "^10.0.1", "globby": "^11.0.3" }, "peerDependencies": { "esbuild": ">= 0.14.0" } }, "sha512-Bk66jpevTcV8KMFzZI1P7MZKZ+uDcrZm2G2egZ2jNIvVnivDpodZI+/KnpL3Jnap0PBdIHU7HwFGB8r+vV5CVw=="], @@ -2065,7 +2077,11 @@ "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - "form-data": ["form-data@2.5.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="], + "form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="], + + "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="], + + "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="], "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], @@ -2255,6 +2271,8 @@ "human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="], + "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], + "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], "i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="], @@ -2665,7 +2683,7 @@ "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], - "miniflare": ["miniflare@4.20251011.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.20251011.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-Qbw1Z8HTYM1adWl6FAtzhrj34/6dPRDPwdYOx21dkae8a/EaxbMzRIPbb4HKVGMVvtqbK1FaRCgDLVLolNzGHg=="], + "miniflare": ["miniflare@4.20251011.2", "", { "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.20251011.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-5oAaz6lqZus4QFwzEJiNtgpjZR2TBVwBeIhOW33V4gu+l23EukpKja831tFIX2o6sOD/hqZmKZHplOrWl3YGtQ=="], "minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], @@ -2713,6 +2731,8 @@ "node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="], + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], @@ -2749,11 +2769,11 @@ "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], - "ofetch": ["ofetch@1.5.0", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-A7llJ7eZyziA5xq9//3ZurA8OhFqtS99K5/V1sLBJ5j137CM/OAjlbA/TEJXBuOWwOfLqih+oH5U3ran4za1FQ=="], + "ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], - "oidc-token-hash": ["oidc-token-hash@5.1.1", "", {}, "sha512-D7EmwxJV6DsEB6vOFLrBM2OzsVgQzgPWyHlV2OOAVj772n+WTXpudC9e9u5BVKQnYwaD30Ivhi9b+4UeBcGu9g=="], + "oidc-token-hash": ["oidc-token-hash@5.2.0", "", {}, "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw=="], "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="], @@ -3175,23 +3195,23 @@ "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], - "sst": ["sst@3.17.19", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.19", "sst-darwin-x64": "3.17.19", "sst-linux-arm64": "3.17.19", "sst-linux-x64": "3.17.19", "sst-linux-x86": "3.17.19", "sst-win32-arm64": "3.17.19", "sst-win32-x64": "3.17.19", "sst-win32-x86": "3.17.19" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-j0FlQhFZW+QWCczzqfPr6fZAF0Um7lP1tbGdd7zkbjFlxdk9BUBI4CYXUnopC6KaTMtjvpfg3XRF7v0bDc9g+A=="], + "sst": ["sst@3.17.23", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.23", "sst-darwin-x64": "3.17.23", "sst-linux-arm64": "3.17.23", "sst-linux-x64": "3.17.23", "sst-linux-x86": "3.17.23", "sst-win32-arm64": "3.17.23", "sst-win32-x64": "3.17.23", "sst-win32-x86": "3.17.23" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-TwKgUgDnZdc1Swe+bvCNeyO4dQnYz5cTodMpYj3jlXZdK9/KNz0PVxT1f0u5E76i1pmilXrUBL/f7iiMPw4RDg=="], - "sst-darwin-arm64": ["sst-darwin-arm64@3.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6FeEgPqXkRT3o5qV0xktJ1eUiscJiPLBcGaxOxIEClpkVggZM83hO7Nizx/cAaAMhr1XQhbOZcKYueDHPdUY+Q=="], + "sst-darwin-arm64": ["sst-darwin-arm64@3.17.23", "", { "os": "darwin", "cpu": "arm64" }, "sha512-R6kvmF+rUideOoU7KBs2SdvrIupoE+b+Dor/eq9Uo4Dojj7KvYDZI/EDm8sSCbbcx/opiWeyNqKtlnLEdCxE6g=="], - "sst-darwin-x64": ["sst-darwin-x64@3.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-/z78dxfLHG8FtOhpjMnYSpKSdQjfdyKbq+cL3eud2+g2BQr7IyQ8BWNGimk2oadh38V3r6dO1/5aVJh3x3l1rg=="], + "sst-darwin-x64": ["sst-darwin-x64@3.17.23", "", { "os": "darwin", "cpu": "x64" }, "sha512-WW4P1S35iYCifQXxD+sE3wuzcN+LHLpuKMaNoaBqEcWGZnH3IPaDJ7rpLF0arkDAo/z3jZmWWzOCkr0JuqJ8vQ=="], - "sst-linux-arm64": ["sst-linux-arm64@3.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-vbcMjiuLVxZ7352ajGlMqsS4J5AkAYvjLmsEALySUBVQhJUO9U7pk2P+Orfn702ZcO+6+NkGG9AL/g3K9EM1Tg=="], + "sst-linux-arm64": ["sst-linux-arm64@3.17.23", "", { "os": "linux", "cpu": "arm64" }, "sha512-TjtNqgIh7RlAWgPLFCAt0mXvIB+J7WjmRvIRrAdX0mXsndOiBJ/DMOgXSLVsIWHCfPj8MIEot/hWpnJgXgIeag=="], - "sst-linux-x64": ["sst-linux-x64@3.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-gkNNmuHyvKjcb7RwMyoUH4wtgd7/bH7vUlMbcVsDzwt38y7+iTxyPMbcihucw42wDQRaDJtkDneSqj08U+MTFQ=="], + "sst-linux-x64": ["sst-linux-x64@3.17.23", "", { "os": "linux", "cpu": "x64" }, "sha512-qdqJiEbYfCjZlI3F/TA6eoIU7JXVkEEI/UMILNf2JWhky0KQdCW2Xyz+wb6c0msVJCWdUM/uj+1DaiP2eXvghw=="], - "sst-linux-x86": ["sst-linux-x86@3.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-Bsvunkh4onZRVv4Rxq7bT/63qQOg2KJoQKhAQtFkJdbri/cOA2QWkzqH8+pC5Sv9rSvbcIJAEIhMXILC0pqCJw=="], + "sst-linux-x86": ["sst-linux-x86@3.17.23", "", { "os": "linux", "cpu": "none" }, "sha512-aGmUujIvoNlmAABEGsOgfY1rxD9koC6hN8bnTLbDI+oI/u/zjHYh50jsbL0p3TlaHpwF/lxP3xFSuT6IKp+KgA=="], - "sst-win32-arm64": ["sst-win32-arm64@3.17.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-dKxR4v24AODJLHiT9yNena0JUgyz3cHyCi6HZyxyG3dXyWncMe1ZXMXIgs1ZEUcU4XeYM2HVy+Nnz4KB1US1Kg=="], + "sst-win32-arm64": ["sst-win32-arm64@3.17.23", "", { "os": "win32", "cpu": "arm64" }, "sha512-ZxdkGqYDrrZGz98rijDCN+m5yuCcwD6Bc9/6hubLsvdpNlVorUqzpg801Ec97xSK0nIC9g6pNiRyxAcsQQstUg=="], - "sst-win32-x64": ["sst-win32-x64@3.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-zgxSkGWZ1dewAr4R3slN/d3X9yumQDvAUOlJiX/6QE9Z67t/XNlow4+5i3L2oz4WHAFi59Un12YxbfM+RsBDmA=="], + "sst-win32-x64": ["sst-win32-x64@3.17.23", "", { "os": "win32", "cpu": "x64" }, "sha512-yc9cor4MS49Ccy2tQCF1tf6M81yLeSGzGL+gjhUxpVKo2pN3bxl3w70eyU/mTXSEeyAmG9zEfbt6FNu4sy5cUA=="], - "sst-win32-x86": ["sst-win32-x86@3.17.19", "", { "os": "win32", "cpu": "none" }, "sha512-z8S0kyb0ibz9Q3cNYDpcKYX47jys7j/mdebC8HUhtED1qKEAfqQ1vsR+zvWyN64Z9Ijj7aPi1KwNV6Et3d7F8g=="], + "sst-win32-x86": ["sst-win32-x86@3.17.23", "", { "os": "win32", "cpu": "none" }, "sha512-DIp3s54IpNAfdYjSRt6McvkbEPQDMxUu6RUeRAd2C+FcTJgTloon/ghAPQBaDgu2VoVgymjcJARO/XyfKcCLOQ=="], "stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="], @@ -3243,7 +3263,7 @@ "strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="], - "style-to-js": ["style-to-js@1.1.18", "", { "dependencies": { "style-to-object": "1.0.11" } }, "sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg=="], + "style-to-js": ["style-to-js@1.1.19", "", { "dependencies": { "style-to-object": "1.0.12" } }, "sha512-Ev+SgeqiNGT1ufsXyVC5RrJRXdrkRJ1Gol9Qw7Pb72YCKJXrBvP0ckZhBeVSrw2m06DJpei2528uIpjMb4TsoQ=="], "style-to-object": ["style-to-object@1.0.12", "", { "dependencies": { "inline-style-parser": "0.2.6" } }, "sha512-ddJqYnoT4t97QvN2C95bCgt+m7AAgXjVnkk/jxAfmp7EAB8nnqqZYEbMd3em7/vEomDb2LAQKAy1RFfv41mdNw=="], @@ -3271,7 +3291,7 @@ "terracotta": ["terracotta@1.0.6", "", { "dependencies": { "solid-use": "^0.9.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-yVrmT/Lg6a3tEbeYEJH8ksb1PYkR5FA9k5gr1TchaSNIiA2ZWs5a+koEbePXwlBP0poaV7xViZ/v50bQFcMgqw=="], - "terser": ["terser@5.44.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w=="], + "terser": ["terser@5.44.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw=="], "text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="], @@ -3305,8 +3325,6 @@ "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], - "tree-sitter": ["tree-sitter@0.25.0", "", { "dependencies": { "node-addon-api": "^8.3.0", "node-gyp-build": "^4.8.4" } }, "sha512-PGZZzFW63eElZJDe/b/R/LbsjDDYJa5UEjLZJB59RQsMX+fo0j54fqBPn1MGKav/QNa0JR0zBiVaikYDWCj5KQ=="], - "tree-sitter-bash": ["tree-sitter-bash@0.25.0", "", { "dependencies": { "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.25.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-gZtlj9+qFS81qKxpLfD6H0UssQ3QBc/F0nKkPsiFDyfQF2YBqYvglFJUzchrPpVhZe9kLZTrJ9n2J6lmka69Vg=="], "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], @@ -3421,7 +3439,7 @@ "unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="], - "unstorage": ["unstorage@1.17.1", "", { "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.4.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-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ=="], + "unstorage": ["unstorage@1.17.2", "", { "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.0", "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-cKEsD6iBWJgOMJ6vW1ID/SYuqNf8oN4yqRk8OYqaVQ3nnkJXOT1PSpaMh2QfzLs78UN5kSNRD2c/mgjT8tX7+w=="], "untun": ["untun@0.1.3", "", { "dependencies": { "citty": "^0.1.5", "consola": "^3.2.3", "pathe": "^1.1.1" }, "bin": { "untun": "bin/untun.mjs" } }, "sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ=="], @@ -3479,6 +3497,8 @@ "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], + "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], + "web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="], "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], @@ -3507,7 +3527,7 @@ "workerd": ["workerd@1.20251011.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251011.0", "@cloudflare/workerd-darwin-arm64": "1.20251011.0", "@cloudflare/workerd-linux-64": "1.20251011.0", "@cloudflare/workerd-linux-arm64": "1.20251011.0", "@cloudflare/workerd-windows-64": "1.20251011.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Dq35TLPEJAw7BuYQMkN3p9rge34zWMU2Gnd4DSJFeVqld4+DAO2aPG7+We2dNIAyM97S8Y9BmHulbQ00E0HC7Q=="], - "wrangler": ["wrangler@4.45.3", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.8", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251011.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.21", "workerd": "1.20251011.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251011.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-0ddEA9t4HeBgSVTVTcqtBHl7Z5CorWZ8tGgTQCP5XuL+9E1TJRwS6t/zzG51Ruwjb17SZYCaLchoM8V629S8cw=="], + "wrangler": ["wrangler@4.45.4", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.9", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251011.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20251011.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251011.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-niXT7B463wQi7WXIHjYK8txgWhuKQLrGmhjoR58SnPhlkq4wGjd3rFrkVyRc/O58clGTfs672BSGOph4XMoQKw=="], "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], @@ -3545,7 +3565,7 @@ "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], - "youch": ["youch@4.1.0-beta.11", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-sQi6PERyO/mT8w564ojOVeAlYTtVQmC2GaktQAf+IdI75/GKIggosBuvyVXvEV+FATAT6RbLdIjFoiIId4ozoQ=="], + "youch": ["youch@4.1.0-beta.12", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.5", "@speed-highlight/core": "^1.2.9", "cookie-es": "^2.0.0", "youch-core": "^0.3.3" } }, "sha512-X+AQ2EdigcZb2h1XQmBMm19TrrfKXxEXWpnf8ThbARwiiSf/pA7MvRTCj5VHCI9z3vjJBsDeqWWyvaI9Bfp9Pg=="], "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], @@ -3589,6 +3609,8 @@ "@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="], + "@astrojs/markdown-remark/shiki": ["shiki@3.14.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="], + "@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.8", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.4", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.13.0", "smol-toml": "^1.4.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-uFNyFWadnULWK2cOw4n0hLKeu+xaVWeuECdP10cQ3K2fkybtTlhb7J7TcScdjmS8Yps7oje9S/ehYMfZrhrgCg=="], "@astrojs/sitemap/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -3609,13 +3631,13 @@ "@cloudflare/kv-asset-handler/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], - "@cloudflare/unenv-preset/unenv": ["unenv@2.0.0-rc.21", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.7", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A=="], + "@cloudflare/unenv-preset/unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], "@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=="], "@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.13.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/engine-javascript": "3.13.0", "@shikijs/engine-oniguruma": "3.13.0", "@shikijs/langs": "3.13.0", "@shikijs/themes": "3.13.0", "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g=="], + "@expressive-code/plugin-shiki/shiki": ["shiki@3.14.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="], "@hono/zod-validator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -3725,11 +3747,11 @@ "@parcel/watcher-wasm/napi-wasm": ["napi-wasm@1.1.3", "", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="], - "@pierre/precision-diffs/@shikijs/core": ["@shikijs/core@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="], + "@pierre/precision-diffs/@shikijs/core": ["@shikijs/core@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="], - "@pierre/precision-diffs/@shikijs/transformers": ["@shikijs/transformers@3.13.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/types": "3.13.0" } }, "sha512-833lcuVzcRiG+fXvgslWsM2f4gHpjEgui1ipIknSizRuTgMkNZupiXE5/TVJ6eSYfhNBFhBZKkReKWO2GgYmqA=="], + "@pierre/precision-diffs/@shikijs/transformers": ["@shikijs/transformers@3.14.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/types": "3.14.0" } }, "sha512-i67zQnY9wLMMnKasonVW1L9fKneSLZDj1ePsA4o0AZWU4uUobmJY9baRDa36z+a9/g0aG76/2tybQvm4hrwxIQ=="], - "@pierre/precision-diffs/shiki": ["shiki@3.13.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/engine-javascript": "3.13.0", "@shikijs/engine-oniguruma": "3.13.0", "@shikijs/langs": "3.13.0", "@shikijs/themes": "3.13.0", "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g=="], + "@pierre/precision-diffs/shiki": ["shiki@3.14.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="], "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], @@ -3751,15 +3773,17 @@ "@slack/web-api/eventemitter3": ["eventemitter3@3.1.2", "", {}, "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="], + "@slack/web-api/form-data": ["form-data@2.5.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="], + "@slack/web-api/p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="], "@solidjs/start/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="], "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.6.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], @@ -3775,6 +3799,8 @@ "@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="], + "@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="], + "@vercel/nft/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@vercel/nft/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], @@ -3801,16 +3827,14 @@ "astro/diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="], - "astro/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "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.14.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="], + "astro/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=="], "astro/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "axios/form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="], - "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], "babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], @@ -3849,7 +3873,7 @@ "dir-glob/path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], - "dot-prop/type-fest": ["type-fest@5.1.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-wQ531tuWvB6oK+pchHIu5lHe5f5wpSCqB8Kf4dWQRbOYc9HTge7JL0G4Qd44bh6QuJCccIzL3bugb8GI0MwHrg=="], + "dot-prop/type-fest": ["type-fest@5.2.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA=="], "drizzle-kit/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=="], @@ -3885,8 +3909,6 @@ "gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - "gel/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "giget/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "giget/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], @@ -4029,9 +4051,7 @@ "send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], - "sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - - "sitemap/sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="], + "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=="], @@ -4047,8 +4067,6 @@ "strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], - "style-to-js/style-to-object": ["style-to-object@1.0.11", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow=="], - "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], "sucrase/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], @@ -4059,8 +4077,6 @@ "token-types/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "tree-sitter/node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="], - "tree-sitter-bash/node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="], "tw-to-css/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], @@ -4097,7 +4113,7 @@ "wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], - "wrangler/unenv": ["unenv@2.0.0-rc.21", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.7", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A=="], + "wrangler/unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -4133,11 +4149,23 @@ "@actions/github/@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + "@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/core@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="], + + "@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="], + + "@astrojs/markdown-remark/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="], + + "@astrojs/markdown-remark/shiki/@shikijs/langs": ["@shikijs/langs@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="], + + "@astrojs/markdown-remark/shiki/@shikijs/themes": ["@shikijs/themes@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="], + + "@astrojs/markdown-remark/shiki/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="], + "@astrojs/mdx/@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.4", "", {}, "sha512-lDA9MqE8WGi7T/t2BMi+EAXhs4Vcvr94Gqx3q15cFEz8oFZMO4/SFBqYr/UcmNlvW+35alowkVj+w9VhLvs5Cw=="], "@astrojs/mdx/@astrojs/markdown-remark/@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="], - "@astrojs/mdx/@astrojs/markdown-remark/shiki": ["shiki@3.13.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/engine-javascript": "3.13.0", "@shikijs/engine-oniguruma": "3.13.0", "@shikijs/langs": "3.13.0", "@shikijs/themes": "3.13.0", "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g=="], + "@astrojs/mdx/@astrojs/markdown-remark/shiki": ["shiki@3.14.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="], "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], @@ -4191,17 +4219,17 @@ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], - "@expressive-code/plugin-shiki/shiki/@shikijs/core": ["@shikijs/core@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="], + "@expressive-code/plugin-shiki/shiki/@shikijs/core": ["@shikijs/core@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="], - "@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="], + "@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="], - "@expressive-code/plugin-shiki/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="], + "@expressive-code/plugin-shiki/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="], - "@expressive-code/plugin-shiki/shiki/@shikijs/langs": ["@shikijs/langs@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ=="], + "@expressive-code/plugin-shiki/shiki/@shikijs/langs": ["@shikijs/langs@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="], - "@expressive-code/plugin-shiki/shiki/@shikijs/themes": ["@shikijs/themes@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg=="], + "@expressive-code/plugin-shiki/shiki/@shikijs/themes": ["@shikijs/themes@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="], - "@expressive-code/plugin-shiki/shiki/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="], + "@expressive-code/plugin-shiki/shiki/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="], "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], @@ -4327,19 +4355,21 @@ "@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@pierre/precision-diffs/@shikijs/core/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="], + "@pierre/precision-diffs/@shikijs/core/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="], - "@pierre/precision-diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="], + "@pierre/precision-diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="], - "@pierre/precision-diffs/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="], + "@pierre/precision-diffs/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="], - "@pierre/precision-diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="], + "@pierre/precision-diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="], - "@pierre/precision-diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ=="], + "@pierre/precision-diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="], - "@pierre/precision-diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg=="], + "@pierre/precision-diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="], - "@pierre/precision-diffs/shiki/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="], + "@pierre/precision-diffs/shiki/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="], + + "@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], @@ -4379,7 +4409,19 @@ "archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "axios/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "astro/sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "astro/shiki/@shikijs/core": ["@shikijs/core@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="], + + "astro/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="], + + "astro/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="], + + "astro/shiki/@shikijs/langs": ["@shikijs/langs@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="], + + "astro/shiki/@shikijs/themes": ["@shikijs/themes@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="], + + "astro/shiki/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="], "babel-plugin-module-resolver/glob/minimatch": ["minimatch@8.0.4", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA=="], @@ -4387,6 +4429,8 @@ "babel-plugin-module-resolver/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "c12/pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], @@ -4499,7 +4543,7 @@ "opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="], - "parse-bmfont-xml/xml2js/sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="], + "parse-bmfont-xml/xml2js/sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="], "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=="], @@ -4523,8 +4567,6 @@ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "style-to-js/style-to-object/inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="], - "sucrase/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], "sucrase/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -4607,17 +4649,17 @@ "@actions/github/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], - "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/core@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="], + "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/core@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="], - "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="], + "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="], - "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="], + "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="], - "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/langs": ["@shikijs/langs@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ=="], + "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/langs": ["@shikijs/langs@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="], - "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/themes": ["@shikijs/themes@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg=="], + "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/themes": ["@shikijs/themes@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="], - "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="], + "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="], "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], @@ -4677,6 +4719,8 @@ "@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + "@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], "@vercel/nft/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -4685,8 +4729,6 @@ "archiver-utils/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "axios/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "babel-plugin-module-resolver/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "babel-plugin-module-resolver/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], @@ -4749,7 +4791,7 @@ "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="], - "nitropack/c12/giget/nypm/tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="], + "nitropack/c12/giget/nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], "opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], diff --git a/github/index.ts b/github/index.ts index 789aad89..b681ff92 100644 --- a/github/index.ts +++ b/github/index.ts @@ -171,9 +171,7 @@ try { const summary = await summarize(response) await pushToLocalBranch(summary) } - const hasShared = prData.comments.nodes.some((c) => - c.body.includes(`${useShareUrl()}/s/${shareId}`), - ) + const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${useShareUrl()}/s/${shareId}`)) await updateComment(`${response}${footer({ image: !hasShared })}`) } // Fork PR @@ -185,9 +183,7 @@ try { const summary = await summarize(response) await pushToForkBranch(summary, prData) } - const hasShared = prData.comments.nodes.some((c) => - c.body.includes(`${useShareUrl()}/s/${shareId}`), - ) + const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${useShareUrl()}/s/${shareId}`)) await updateComment(`${response}${footer({ image: !hasShared })}`) } } @@ -368,9 +364,7 @@ async function getAccessToken() { if (!response.ok) { const responseJson = (await response.json()) as { error?: string } - throw new Error( - `App token exchange failed: ${response.status} ${response.statusText} - ${responseJson.error}`, - ) + throw new Error(`App token exchange failed: ${response.status} ${response.statusText} - ${responseJson.error}`) } const responseJson = (await response.json()) as { token: string } @@ -411,12 +405,8 @@ async function getUserPrompt() { // ie. Image // ie. [api.json](https://github.com/user-attachments/files/21433810/api.json) // ie. ![Image](https://github.com/user-attachments/assets/xxxx) - const mdMatches = prompt.matchAll( - /!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi, - ) - const tagMatches = prompt.matchAll( - //gi, - ) + const mdMatches = prompt.matchAll(/!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi) + const tagMatches = prompt.matchAll(//gi) const matches = [...mdMatches, ...tagMatches].sort((a, b) => a.index - b.index) console.log("Images", JSON.stringify(matches, null, 2)) @@ -443,8 +433,7 @@ async function getUserPrompt() { // Replace img tag with file path, ie. @image.png const replacement = `@${filename}` - prompt = - prompt.slice(0, start + offset) + replacement + prompt.slice(start + offset + tag.length) + prompt = prompt.slice(0, start + offset) + replacement + prompt.slice(start + offset + tag.length) offset += replacement.length - tag.length const contentType = res.headers.get("content-type") @@ -512,12 +501,7 @@ async function subscribeSessionEvents() { ? JSON.stringify(part.state.input) : "Unknown" console.log() - console.log( - color + `|`, - "\x1b[0m\x1b[2m" + ` ${tool.padEnd(7, " ")}`, - "", - "\x1b[0m" + title, - ) + console.log(color + `|`, "\x1b[0m\x1b[2m" + ` ${tool.padEnd(7, " ")}`, "", "\x1b[0m" + title) } if (part.type === "text") { @@ -729,8 +713,7 @@ async function assertPermissions() { throw new Error(`Failed to check permissions for user ${actor}: ${error}`) } - if (!["admin", "write"].includes(permission)) - throw new Error(`User ${actor} does not have write permissions`) + if (!["admin", "write"].includes(permission)) throw new Error(`User ${actor} does not have write permissions`) } async function updateComment(body: string) { @@ -774,9 +757,7 @@ function footer(opts?: { image?: boolean }) { return `${titleAlt}\n` })() - const shareUrl = shareId - ? `[opencode session](${useShareUrl()}/s/${shareId})  |  ` - : "" + const shareUrl = shareId ? `[opencode session](${useShareUrl()}/s/${shareId})  |  ` : "" return `\n\n${image}${shareUrl}[github run](${useEnvRunUrl()})` } @@ -959,13 +940,9 @@ function buildPromptDataForPR(pr: GitHubPullRequest) { }) .map((c) => `- ${c.author.login} at ${c.createdAt}: ${c.body}`) - const files = (pr.files.nodes || []).map( - (f) => `- ${f.path} (${f.changeType}) +${f.additions}/-${f.deletions}`, - ) + const files = (pr.files.nodes || []).map((f) => `- ${f.path} (${f.changeType}) +${f.additions}/-${f.deletions}`) const reviewData = (pr.reviews.nodes || []).map((r) => { - const comments = (r.comments.nodes || []).map( - (c) => ` - ${c.path}:${c.line ?? "?"}: ${c.body}`, - ) + const comments = (r.comments.nodes || []).map((c) => ` - ${c.path}:${c.line ?? "?"}: ${c.body}`) return [ `- ${r.author.login} at ${r.submittedAt}:`, ` - Review body: ${r.body}`, @@ -987,15 +964,9 @@ function buildPromptDataForPR(pr: GitHubPullRequest) { `Deletions: ${pr.deletions}`, `Total Commits: ${pr.commits.totalCount}`, `Changed Files: ${pr.files.nodes.length} files`, - ...(comments.length > 0 - ? ["", ...comments, ""] - : []), - ...(files.length > 0 - ? ["", ...files, ""] - : []), - ...(reviewData.length > 0 - ? ["", ...reviewData, ""] - : []), + ...(comments.length > 0 ? ["", ...comments, ""] : []), + ...(files.length > 0 ? ["", ...files, ""] : []), + ...(reviewData.length > 0 ? ["", ...reviewData, ""] : []), "", ].join("\n") } diff --git a/github/sst-env.d.ts b/github/sst-env.d.ts index f742a120..6b69016e 100644 --- a/github/sst-env.d.ts +++ b/github/sst-env.d.ts @@ -6,4 +6,4 @@ /// import "sst" -export {} \ No newline at end of file +export {} diff --git a/infra/console.ts b/infra/console.ts index 7fbf92bf..d8e4e5bd 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -61,13 +61,7 @@ export const auth = new sst.cloudflare.Worker("AuthApi", { domain: `auth.${domain}`, handler: "packages/console/function/src/auth.ts", url: true, - link: [ - database, - authStorage, - GITHUB_CLIENT_ID_CONSOLE, - GITHUB_CLIENT_SECRET_CONSOLE, - GOOGLE_CLIENT_ID, - ], + link: [database, authStorage, GITHUB_CLIENT_ID_CONSOLE, GITHUB_CLIENT_SECRET_CONSOLE, GOOGLE_CLIENT_ID], }) //////////////// @@ -112,6 +106,7 @@ const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", { const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", { properties: { value: stripeWebhook.secret }, }) +const gatewayKv = new sst.cloudflare.Kv("GatewayKv") //////////////// // CONSOLE @@ -142,6 +137,13 @@ new sst.cloudflare.x.SolidStart("Console", { EMAILOCTOPUS_API_KEY, AWS_SES_ACCESS_KEY_ID, AWS_SES_SECRET_ACCESS_KEY, + ...($dev + ? [ + new sst.Secret("CLOUDFLARE_DEFAULT_ACCOUNT_ID", process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID!), + new sst.Secret("CLOUDFLARE_API_TOKEN", process.env.CLOUDFLARE_API_TOKEN!), + ] + : []), + gatewayKv, ], environment: { //VITE_DOCS_URL: web.url.apply((url) => url!), diff --git a/logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json b/logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json index 7c57ef35..41cb01a2 100644 --- a/logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json +++ b/logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json @@ -1,15 +1,15 @@ { - "keep": { - "days": true, - "amount": 14 - }, - "auditLog": "/home/thdxr/dev/projects/sst/opencode/logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json", - "files": [ - { - "date": 1759827172859, - "name": "/home/thdxr/dev/projects/sst/opencode/logs/mcp-puppeteer-2025-10-07.log", - "hash": "a3d98b26edd793411b968a0d24cfeee8332138e282023c3b83ec169d55c67f16" - } - ], - "hashType": "sha256" -} \ No newline at end of file + "keep": { + "days": true, + "amount": 14 + }, + "auditLog": "/home/thdxr/dev/projects/sst/opencode/logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json", + "files": [ + { + "date": 1759827172859, + "name": "/home/thdxr/dev/projects/sst/opencode/logs/mcp-puppeteer-2025-10-07.log", + "hash": "a3d98b26edd793411b968a0d24cfeee8332138e282023c3b83ec169d55c67f16" + } + ], + "hashType": "sha256" +} diff --git a/package.json b/package.json index 55125896..0e6ab4c6 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@tsconfig/bun": "1.0.9", "@cloudflare/workers-types": "4.20251008.0", "@openauthjs/openauth": "0.0.0-20250322224806", - "@pierre/precision-diffs": "0.4.1", + "@pierre/precision-diffs": "0.4.4", "@solidjs/meta": "0.29.4", "@tailwindcss/vite": "4.1.11", "diff": "8.0.2", @@ -52,7 +52,7 @@ "@tsconfig/bun": "catalog:", "husky": "9.1.7", "prettier": "3.6.2", - "sst": "3.17.19", + "sst": "3.17.23", "turbo": "2.5.6" }, "dependencies": { @@ -66,7 +66,7 @@ "license": "MIT", "prettier": { "semi": false, - "printWidth": 100 + "printWidth": 120 }, "trustedDependencies": [ "esbuild", diff --git a/packages/console/app/.gitignore b/packages/console/app/.gitignore index 751513ce..fb29f05d 100644 --- a/packages/console/app/.gitignore +++ b/packages/console/app/.gitignore @@ -23,6 +23,9 @@ app.config.timestamp_*.js # Temp gitignore +# Generated files +public/sitemap.xml + # System Files .DS_Store Thumbs.db diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 7e06d824..a00e6baa 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -5,9 +5,9 @@ "typecheck": "tsgo --noEmit", "dev": "vinxi dev --host 0.0.0.0", "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev", - "build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json", + "build": "./script/generate-sitemap.ts && vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json", "start": "vinxi start", - "version": "1.0.23" + "version": "1.0.55" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/packages/console/app/script/generate-sitemap.ts b/packages/console/app/script/generate-sitemap.ts new file mode 100755 index 00000000..6cbffcb8 --- /dev/null +++ b/packages/console/app/script/generate-sitemap.ts @@ -0,0 +1,103 @@ +#!/usr/bin/env bun +import { readdir, writeFile } from "fs/promises" +import { join, dirname } from "path" +import { fileURLToPath } from "url" +import { config } from "../src/config.js" + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const BASE_URL = config.baseUrl +const PUBLIC_DIR = join(__dirname, "../public") +const ROUTES_DIR = join(__dirname, "../src/routes") +const DOCS_DIR = join(__dirname, "../../../web/src/content/docs") + +interface SitemapEntry { + url: string + priority: number + changefreq: string +} + +async function getMainRoutes(): Promise { + const routes: SitemapEntry[] = [] + + // Add main static routes + const staticRoutes = [ + { path: "/", priority: 1.0, changefreq: "daily" }, + { path: "/enterprise", priority: 0.8, changefreq: "weekly" }, + { path: "/brand", priority: 0.6, changefreq: "monthly" }, + { path: "/zen", priority: 0.8, changefreq: "weekly" }, + ] + + for (const route of staticRoutes) { + routes.push({ + url: `${BASE_URL}${route.path}`, + priority: route.priority, + changefreq: route.changefreq, + }) + } + + return routes +} + +async function getDocsRoutes(): Promise { + const routes: SitemapEntry[] = [] + + try { + const files = await readdir(DOCS_DIR) + + for (const file of files) { + if (!file.endsWith(".mdx")) continue + + const slug = file.replace(".mdx", "") + const path = slug === "index" ? "/docs/" : `/docs/${slug}` + + routes.push({ + url: `${BASE_URL}${path}`, + priority: slug === "index" ? 0.9 : 0.7, + changefreq: "weekly", + }) + } + } catch (error) { + console.error("Error reading docs directory:", error) + } + + return routes +} + +function generateSitemapXML(entries: SitemapEntry[]): string { + const urls = entries + .map( + (entry) => ` + ${entry.url} + ${entry.changefreq} + ${entry.priority} + `, + ) + .join("\n") + + return ` + +${urls} +` +} + +async function main() { + console.log("Generating sitemap...") + + const mainRoutes = await getMainRoutes() + const docsRoutes = await getDocsRoutes() + + const allRoutes = [...mainRoutes, ...docsRoutes] + + console.log(`Found ${mainRoutes.length} main routes`) + console.log(`Found ${docsRoutes.length} docs routes`) + console.log(`Total: ${allRoutes.length} routes`) + + const xml = generateSitemapXML(allRoutes) + + const outputPath = join(PUBLIC_DIR, "sitemap.xml") + await writeFile(outputPath, xml, "utf-8") + + console.log(`✓ Sitemap generated at ${outputPath}`) +} + +main() diff --git a/packages/console/app/src/app.tsx b/packages/console/app/src/app.tsx index bc396121..1cf96364 100644 --- a/packages/console/app/src/app.tsx +++ b/packages/console/app/src/app.tsx @@ -12,7 +12,7 @@ export default function App() { root={(props) => ( opencode - + {props.children} )} diff --git a/packages/console/app/src/component/dropdown.css b/packages/console/app/src/component/dropdown.css index 982367c6..242940e6 100644 --- a/packages/console/app/src/component/dropdown.css +++ b/packages/console/app/src/component/dropdown.css @@ -77,4 +77,4 @@ background-color: var(--color-accent-alpha); } } -} \ No newline at end of file +} diff --git a/packages/console/app/src/component/modal.css b/packages/console/app/src/component/modal.css index 23b6831c..1f47f395 100644 --- a/packages/console/app/src/component/modal.css +++ b/packages/console/app/src/component/modal.css @@ -63,4 +63,4 @@ font-weight: 600; color: var(--color-text); } -} \ No newline at end of file +} diff --git a/packages/console/app/src/config.ts b/packages/console/app/src/config.ts index 097764b8..40108e96 100644 --- a/packages/console/app/src/config.ts +++ b/packages/console/app/src/config.ts @@ -2,6 +2,9 @@ * Application-wide constants and configuration */ export const config = { + // Base URL + baseUrl: "https://opencode.ai", + // GitHub github: { repoUrl: "https://github.com/sst/opencode", diff --git a/packages/console/app/src/lib/github.ts b/packages/console/app/src/lib/github.ts index dab31775..bc49d2e6 100644 --- a/packages/console/app/src/lib/github.ts +++ b/packages/console/app/src/lib/github.ts @@ -7,10 +7,7 @@ export const github = query(async () => { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", } - const apiBaseUrl = config.github.repoUrl.replace( - "https://github.com/", - "https://api.github.com/repos/", - ) + const apiBaseUrl = config.github.repoUrl.replace("https://github.com/", "https://api.github.com/repos/") try { const [meta, releases, contributors] = await Promise.all([ fetch(apiBaseUrl, { headers }).then((res) => res.json()), diff --git a/packages/console/app/src/routes/brand/index.css b/packages/console/app/src/routes/brand/index.css index 5a445f29..d3c0d052 100644 --- a/packages/console/app/src/routes/brand/index.css +++ b/packages/console/app/src/routes/brand/index.css @@ -264,7 +264,7 @@ [data-component="brand-content"] { padding: 4rem 5rem; - h2 { + h1 { font-size: 1.5rem; font-weight: 500; color: var(--color-text-strong); @@ -299,7 +299,6 @@ transition: all 0.2s ease; text-decoration: none; - &:hover:not(:disabled) { background: var(--color-background-strong-hover); } @@ -385,23 +384,21 @@ 0 1px 2px -1px rgba(19, 16, 16, 0.12); @media (max-width: 40rem) { - box-shadow: - 0 0 0 1px rgba(19, 16, 16, 0.16) + box-shadow: 0 0 0 1px rgba(19, 16, 16, 0.16); } - &:hover { background: var(--color-background); } &:active { transform: scale(0.98); - box-shadow: 0 0 0 1px rgba(19, 16, 16, 0.08), 0 6px 8px -8px rgba(19, 16, 16, 0.50); + box-shadow: + 0 0 0 1px rgba(19, 16, 16, 0.08), + 0 6px 8px -8px rgba(19, 16, 16, 0.5); } } - - @media (max-width: 60rem) { padding: 2rem 1.5rem; } diff --git a/packages/console/app/src/routes/brand/index.tsx b/packages/console/app/src/routes/brand/index.tsx index 4570c73c..6aac4517 100644 --- a/packages/console/app/src/routes/brand/index.tsx +++ b/packages/console/app/src/routes/brand/index.tsx @@ -1,6 +1,7 @@ import "./index.css" -import { Title, Meta } from "@solidjs/meta" +import { Title, Meta, Link } from "@solidjs/meta" import { Header } from "~/component/header" +import { config } from "~/config" import { Footer } from "~/component/footer" import { Legal } from "~/component/legal" import previewLogoLight from "../../asset/brand/preview-opencode-logo-light.png" @@ -53,26 +54,21 @@ export default function Brand() { return (
OpenCode | Brand +
-

Brand guidelines

+

Brand guidelines

Resources and assets to help you work with the OpenCode brand.

- - - - + +
+
+ + {(err: any) =>
{err()}
} +
+ + } > - {createCheckoutUrlSubmission.pending ? "Loading..." : "Add Balance"} - + +
}> - +
- - ----} - > + + ----}> •••• - {balanceInfo()?.paymentMethodLast4} + {billingInfo()?.paymentMethodLast4} - + Linked to Stripe
- + diff --git a/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.module.css b/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.module.css index 4f0f8b2e..a45bbf1a 100644 --- a/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.module.css @@ -93,4 +93,4 @@ margin: 0; line-height: 1.4; } -} \ No newline at end of file +} diff --git a/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx index dbeda115..77c01796 100644 --- a/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx @@ -1,16 +1,10 @@ -import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router" +import { json, action, useParams, createAsync, useSubmission } from "@solidjs/router" import { createEffect, Show } from "solid-js" import { createStore } from "solid-js/store" import { withActor } from "~/context/auth.withActor" import { Billing } from "@opencode-ai/console-core/billing.js" import styles from "./monthly-limit-section.module.css" - -const getBillingInfo = query(async (workspaceID: string) => { - "use server" - return withActor(async () => { - return await Billing.get() - }, workspaceID) -}, "billing.get") +import { queryBillingInfo } from "../../common" const setMonthlyLimit = action(async (form: FormData) => { "use server" @@ -28,7 +22,7 @@ const setMonthlyLimit = action(async (form: FormData) => { .catch((e) => ({ error: e.message as string })), workspaceID, ), - { revalidate: getBillingInfo.key }, + { revalidate: queryBillingInfo.key }, ) }, "billing.setMonthlyLimit") @@ -36,7 +30,7 @@ export function MonthlyLimitSection() { const params = useParams() const submission = useSubmission(setMonthlyLimit) const [store, setStore] = createStore({ show: false }) - const balanceInfo = createAsync(() => getBillingInfo(params.id)) + const billingInfo = createAsync(() => queryBillingInfo(params.id)) let input: HTMLInputElement @@ -68,13 +62,13 @@ export function MonthlyLimitSection() {

Monthly Limit

-

Set a monthly spending limit for your account.

+

Set a monthly usage limit for your account.

- {balanceInfo()?.monthlyLimit ? $ : null} - {balanceInfo()?.monthlyLimit ?? "-"} + {billingInfo()?.monthlyLimit ? $ : null} + {billingInfo()?.monthlyLimit ?? "-"}
- No spending limit set.

}> + No usage limit set.

}>

Current usage for {new Date().toLocaleDateString("en-US", { month: "long", timeZone: "UTC" })} is $ {(() => { - const dateLastUsed = balanceInfo()?.timeMonthlyUsageUpdated + const dateLastUsed = billingInfo()?.timeMonthlyUsageUpdated if (!dateLastUsed) return "0" const current = new Date().toLocaleDateString("en-US", { @@ -128,7 +122,7 @@ export function MonthlyLimitSection() { timeZone: "UTC", }) if (current !== lastUsed) return "0" - return ((balanceInfo()?.monthlyUsage ?? 0) / 100000000).toFixed(2) + return ((billingInfo()?.monthlyUsage ?? 0) / 100000000).toFixed(2) })()} .

diff --git a/packages/console/app/src/routes/workspace/[id]/billing/reload-section.module.css b/packages/console/app/src/routes/workspace/[id]/billing/reload-section.module.css index 08fb8524..e37bad69 100644 --- a/packages/console/app/src/routes/workspace/[id]/billing/reload-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/billing/reload-section.module.css @@ -34,6 +34,206 @@ } } + [data-slot="create-form"] { + display: flex; + flex-direction: column; + gap: var(--space-3); + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + margin-top: var(--space-4); + + [data-slot="form-field"] { + display: flex; + flex-direction: column; + gap: var(--space-2); + + label { + display: flex; + flex-direction: column; + gap: var(--space-2); + } + + [data-slot="field-label"] { + font-size: var(--font-size-sm); + font-weight: 500; + color: var(--color-text-muted); + } + + [data-slot="toggle-container"] { + display: flex; + align-items: center; + } + + input[data-component="input"] { + flex: 1; + padding: var(--space-2) var(--space-3); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + background-color: var(--color-bg); + color: var(--color-text); + font-size: var(--font-size-sm); + font-family: var(--font-mono); + + &:focus { + outline: none; + border-color: var(--color-accent); + } + + &::placeholder { + color: var(--color-text-disabled); + } + } + } + + [data-slot="input-row"] { + display: flex; + flex-direction: row; + gap: var(--space-3); + + @media (max-width: 40rem) { + flex-direction: column; + gap: var(--space-2); + } + } + + [data-slot="input-field"] { + display: flex; + flex-direction: column; + gap: var(--space-1); + flex: 1; + + p { + line-height: 1.2; + margin: 0; + color: var(--color-text-muted); + font-size: var(--font-size-sm); + } + + input[data-component="input"] { + flex: 1; + padding: var(--space-2) var(--space-3); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + background-color: var(--color-bg); + color: var(--color-text); + font-size: var(--font-size-sm); + line-height: 1.5; + min-width: 0; + + &:focus { + outline: none; + border-color: var(--color-accent); + box-shadow: 0 0 0 3px var(--color-accent-alpha); + } + + &::placeholder { + color: var(--color-text-disabled); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: var(--color-bg-surface); + } + } + + [data-slot="field-with-connector"] { + display: flex; + align-items: center; + gap: var(--space-2); + + [data-slot="field-connector"] { + font-size: var(--font-size-sm); + color: var(--color-text-muted); + white-space: nowrap; + } + + input[data-component="input"] { + flex: 1; + min-width: 80px; + } + } + } + + [data-slot="form-actions"] { + display: flex; + gap: var(--space-2); + margin-top: var(--space-1); + } + + [data-slot="form-error"] { + color: var(--color-danger); + font-size: var(--font-size-sm); + line-height: 1.4; + margin-top: calc(var(--space-1) * -1); + } + + [data-slot="model-toggle-label"] { + position: relative; + display: inline-block; + width: 2.5rem; + height: 1.5rem; + cursor: pointer; + + input { + opacity: 0; + width: 0; + height: 0; + } + + span { + position: absolute; + inset: 0; + background-color: #ccc; + border: 1px solid #bbb; + border-radius: 1.5rem; + transition: all 0.3s ease; + cursor: pointer; + + &::before { + content: ""; + position: absolute; + top: 50%; + left: 0.125rem; + width: 1.25rem; + height: 1.25rem; + background-color: white; + border: 1px solid #ddd; + border-radius: 50%; + transform: translateY(-50%); + transition: all 0.3s ease; + } + } + + input:checked + span { + background-color: #21ad0e; + border-color: #148605; + + &::before { + transform: translateX(1rem) translateY(-50%); + } + } + + &:hover span { + box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2); + } + + input:checked:hover + span { + box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.3); + } + + &:has(input:disabled) { + cursor: not-allowed; + } + + input:disabled + span { + opacity: 0.5; + cursor: not-allowed; + } + } + } + [data-slot="reload-error"] { display: flex; align-items: center; @@ -54,7 +254,8 @@ gap: var(--space-2); margin: 0; flex-shrink: 0; + padding: 0; + border: none; } } } - diff --git a/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx index 6be6ddf3..8dcc4da9 100644 --- a/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx @@ -1,17 +1,19 @@ -import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router" -import { Show } from "solid-js" +import { json, action, useParams, createAsync, useSubmission } from "@solidjs/router" +import { createEffect, Show } from "solid-js" +import { createStore } from "solid-js/store" import { withActor } from "~/context/auth.withActor" import { Billing } from "@opencode-ai/console-core/billing.js" import { Database, eq } from "@opencode-ai/console-core/drizzle/index.js" import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js" import styles from "./reload-section.module.css" +import { queryBillingInfo } from "../../common" const reload = action(async (form: FormData) => { "use server" const workspaceID = form.get("workspaceID")?.toString() if (!workspaceID) return { error: "Workspace ID is required" } return json(await withActor(() => Billing.reload(), workspaceID), { - revalidate: getBillingInfo.key, + revalidate: queryBillingInfo.key, }) }, "billing.reload") @@ -20,12 +22,27 @@ const setReload = action(async (form: FormData) => { const workspaceID = form.get("workspaceID")?.toString() if (!workspaceID) return { error: "Workspace ID is required" } const reloadValue = form.get("reload")?.toString() === "true" + const amountStr = form.get("reloadAmount")?.toString() + const triggerStr = form.get("reloadTrigger")?.toString() + + const reloadAmount = amountStr && amountStr.trim() !== "" ? parseInt(amountStr) : null + const reloadTrigger = triggerStr && triggerStr.trim() !== "" ? parseInt(triggerStr) : null + + if (reloadValue) { + if (reloadAmount === null || reloadAmount < Billing.RELOAD_AMOUNT_MIN) + return { error: `Reload amount must be at least $${Billing.RELOAD_AMOUNT_MIN}` } + if (reloadTrigger === null || reloadTrigger < Billing.RELOAD_TRIGGER_MIN) + return { error: `Balance trigger must be at least $${Billing.RELOAD_TRIGGER_MIN}` } + } + return json( await Database.use((tx) => tx .update(BillingTable) .set({ reload: reloadValue, + ...(reloadAmount !== null ? { reloadAmount } : {}), + ...(reloadTrigger !== null ? { reloadTrigger } : {}), ...(reloadValue ? { reloadError: null, @@ -35,22 +52,43 @@ const setReload = action(async (form: FormData) => { }) .where(eq(BillingTable.workspaceID, workspaceID)), ), - { revalidate: getBillingInfo.key }, + { revalidate: queryBillingInfo.key }, ) }, "billing.setReload") -const getBillingInfo = query(async (workspaceID: string) => { - "use server" - return withActor(async () => { - return await Billing.get() - }, workspaceID) -}, "billing.get") - export function ReloadSection() { const params = useParams() - const balanceInfo = createAsync(() => getBillingInfo(params.id)) + const billingInfo = createAsync(() => queryBillingInfo(params.id)) const setReloadSubmission = useSubmission(setReload) const reloadSubmission = useSubmission(reload) + const [store, setStore] = createStore({ + show: false, + reload: false, + reloadAmount: "", + reloadTrigger: "", + }) + + createEffect(() => { + if (!setReloadSubmission.pending && setReloadSubmission.result && !(setReloadSubmission.result as any).error) { + setStore("show", false) + } + }) + + function show() { + while (true) { + setReloadSubmission.clear() + if (!setReloadSubmission.result) break + } + const info = billingInfo()! + setStore("show", true) + setStore("reload", info.reload ? true : true) + setStore("reloadAmount", info.reloadAmount.toString()) + setStore("reloadTrigger", info.reloadTrigger.toString()) + } + + function hide() { + setStore("show", false) + } return (
@@ -58,44 +96,102 @@ export function ReloadSection() {

Auto Reload

Auto reload is disabled. Enable to automatically reload when balance is low.

+

+ Auto reload is disabled. Enable to automatically reload when balance is low. +

} >

- We'll automatically reload $20 (+$1.23 processing fee) when it reaches{" "} - $5. + Auto reload is enabled. We'll reload ${billingInfo()?.reloadAmount} (+$1.23 processing fee) + when balance reaches ${billingInfo()?.reloadTrigger}.

-
- - - -
+
-
- + +
+
+ +
+ +
+
+

Reload $

+ setStore("reloadAmount", e.currentTarget.value)} + placeholder={billingInfo()?.reloadAmount.toString()} + disabled={!store.reload} + /> +
+
+

When balance reaches $

+ setStore("reloadTrigger", e.currentTarget.value)} + placeholder={billingInfo()?.reloadTrigger.toString()} + disabled={!store.reload} + /> +
+
+ + + {(err: any) =>
{err()}
} +
+ +
+ + +
+
+
+ +

Reload failed at{" "} - {balanceInfo()?.timeReloadError!.toLocaleString("en-US", { + {billingInfo()?.timeReloadError!.toLocaleString("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", second: "2-digit", })} - . Reason: {balanceInfo()?.reloadError?.replace(/\.$/, "")}. Please update your payment - method and try again. + . Reason: {billingInfo()?.reloadError?.replace(/\.$/, "")}. Please update your payment method and try + again.

@@ -104,8 +200,8 @@ export function ReloadSection() {
- -
+
+
) } diff --git a/packages/console/app/src/routes/workspace/[id]/index.tsx b/packages/console/app/src/routes/workspace/[id]/index.tsx index 8f7678f2..45f67ca3 100644 --- a/packages/console/app/src/routes/workspace/[id]/index.tsx +++ b/packages/console/app/src/routes/workspace/[id]/index.tsx @@ -1,22 +1,32 @@ +import { Show, createMemo } from "solid-js" +import { createStore } from "solid-js/store" +import { createAsync, useParams, useAction, useSubmission } from "@solidjs/router" import { NewUserSection } from "./new-user-section" import { UsageSection } from "./usage-section" import { ModelSection } from "./model-section" import { ProviderSection } from "./provider-section" import { IconLogo } from "~/component/icon" -import { createAsync, useParams, useAction, useSubmission } from "@solidjs/router" -import { querySessionInfo, queryBillingInfo, createCheckoutUrl } from "../common" -import { Show, createMemo } from "solid-js" +import { querySessionInfo, queryBillingInfo, createCheckoutUrl, formatBalance } from "../common" export default function () { const params = useParams() const userInfo = createAsync(() => querySessionInfo(params.id)) const billingInfo = createAsync(() => queryBillingInfo(params.id)) - const createCheckoutUrlAction = useAction(createCheckoutUrl) - const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl) - - const balanceAmount = createMemo(() => { - return ((billingInfo()?.balance ?? 0) / 100000000).toFixed(2) + const checkoutAction = useAction(createCheckoutUrl) + const checkoutSubmission = useSubmission(createCheckoutUrl) + const [store, setStore] = createStore({ + checkoutRedirecting: false, }) + const balance = createMemo(() => formatBalance(billingInfo()?.balance ?? 0)) + + async function onClickCheckout() { + const baseUrl = window.location.href + const checkout = await checkoutAction(params.id, billingInfo()!.reloadAmount, baseUrl, baseUrl) + if (checkout && checkout.data) { + setStore("checkoutRedirecting", true) + window.location.href = checkout.data + } + } return (
@@ -38,21 +48,15 @@ export default function () { } > - Current balance ${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()} + Current balance ${balance()} diff --git a/packages/console/app/src/routes/workspace/[id]/keys/key-section.module.css b/packages/console/app/src/routes/workspace/[id]/keys/key-section.module.css index 1066b7f0..5705549e 100644 --- a/packages/console/app/src/routes/workspace/[id]/keys/key-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/keys/key-section.module.css @@ -171,7 +171,6 @@ } @media (max-width: 40rem) { - th, td { padding: var(--space-2) var(--space-3); @@ -181,8 +180,7 @@ th { &:nth-child(3) - /* Date */ - { + /* Date */ { display: none; } } @@ -190,11 +188,10 @@ td { &:nth-child(3) - /* Date */ - { + /* Date */ { display: none; } } } } -} \ No newline at end of file +} diff --git a/packages/console/app/src/routes/workspace/[id]/members/role-dropdown.css b/packages/console/app/src/routes/workspace/[id]/members/role-dropdown.css index 29f55a97..7a64fd9c 100644 --- a/packages/console/app/src/routes/workspace/[id]/members/role-dropdown.css +++ b/packages/console/app/src/routes/workspace/[id]/members/role-dropdown.css @@ -69,4 +69,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/console/app/src/routes/workspace/[id]/model-section.tsx b/packages/console/app/src/routes/workspace/[id]/model-section.tsx index 964f7dac..7a1980eb 100644 --- a/packages/console/app/src/routes/workspace/[id]/model-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/model-section.tsx @@ -5,15 +5,7 @@ import { withActor } from "~/context/auth.withActor" import { ZenData } from "@opencode-ai/console-core/model.js" import styles from "./model-section.module.css" import { querySessionInfo } from "../common" -import { - IconAlibaba, - IconAnthropic, - IconMoonshotAI, - IconOpenAI, - IconStealth, - IconXai, - IconZai, -} from "~/component/icon" +import { IconAlibaba, IconAnthropic, IconMoonshotAI, IconOpenAI, IconStealth, IconXai, IconZai } from "~/component/icon" const getModelLab = (modelId: string) => { if (modelId.startsWith("claude")) return "Anthropic" @@ -31,6 +23,7 @@ const getModelsInfo = query(async (workspaceID: string) => { return { all: Object.entries(ZenData.list().models) .filter(([id, _model]) => !["claude-3-5-haiku", "minimax-m2"].includes(id)) + .filter(([id, _model]) => !id.startsWith("an-")) .sort(([_idA, modelA], [_idB, modelB]) => modelA.name.localeCompare(modelB.name)) .map(([id, model]) => ({ id, name: model.name })), disabled: await Model.listDisabled(), @@ -75,8 +68,7 @@ export function ModelSection() {

Models

- Manage which models workspace members can access.{" "} - Learn more. + Manage which models workspace members can access. Learn more.

diff --git a/packages/console/app/src/routes/workspace/[id]/new-user-section.module.css b/packages/console/app/src/routes/workspace/[id]/new-user-section.module.css index aaad823a..bb58df79 100644 --- a/packages/console/app/src/routes/workspace/[id]/new-user-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/new-user-section.module.css @@ -140,4 +140,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/console/app/src/routes/workspace/[id]/provider-section.module.css b/packages/console/app/src/routes/workspace/[id]/provider-section.module.css index 1a450d3d..1dc7085b 100644 --- a/packages/console/app/src/routes/workspace/[id]/provider-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/provider-section.module.css @@ -128,7 +128,6 @@ } @media (max-width: 40rem) { - th, td { padding: var(--space-2) var(--space-3); @@ -136,4 +135,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/console/app/src/routes/workspace/[id]/provider-section.tsx b/packages/console/app/src/routes/workspace/[id]/provider-section.tsx index 6ec8477b..5419ed7f 100644 --- a/packages/console/app/src/routes/workspace/[id]/provider-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/provider-section.tsx @@ -22,7 +22,9 @@ const removeProvider = action(async (form: FormData) => { if (!provider) return { error: "Provider is required" } const workspaceID = form.get("workspaceID")?.toString() if (!workspaceID) return { error: "Workspace ID is required" } - return json(await withActor(() => Provider.remove({ provider }), workspaceID), { revalidate: listProviders.key }) + return json(await withActor(() => Provider.remove({ provider }), workspaceID), { + revalidate: listProviders.key, + }) }, "provider.remove") const saveProvider = action(async (form: FormData) => { diff --git a/packages/console/app/src/routes/workspace/[id]/settings/settings-section.module.css b/packages/console/app/src/routes/workspace/[id]/settings/settings-section.module.css index 058fbe30..6764a053 100644 --- a/packages/console/app/src/routes/workspace/[id]/settings/settings-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/settings/settings-section.module.css @@ -31,7 +31,7 @@ margin: 0; } - >button { + > button { align-self: flex-start; } } @@ -80,7 +80,7 @@ } } - >button[type="reset"] { + > button[type="reset"] { align-self: flex-start; } @@ -91,4 +91,4 @@ margin-top: calc(var(--space-1) * -1); } } -} \ No newline at end of file +} diff --git a/packages/console/app/src/routes/workspace/common.tsx b/packages/console/app/src/routes/workspace/common.tsx index 69bfebe9..a6eaaeb1 100644 --- a/packages/console/app/src/routes/workspace/common.tsx +++ b/packages/console/app/src/routes/workspace/common.tsx @@ -1,6 +1,6 @@ import { Resource } from "@opencode-ai/console-resource" import { Actor } from "@opencode-ai/console-core/actor.js" -import { action, query } from "@solidjs/router" +import { action, json, query } from "@solidjs/router" import { withActor } from "~/context/auth.withActor" import { Billing } from "@opencode-ai/console-core/billing.js" import { User } from "@opencode-ai/console-core/user.js" @@ -34,6 +34,11 @@ export function formatDateUTC(date: Date) { return date.toLocaleDateString("en-US", options) } +export function formatBalance(amount: number) { + const balance = ((amount ?? 0) / 100000000).toFixed(2) + return balance === "-0.00" ? "0.00" : balance +} + export async function getLastSeenWorkspaceID() { "use server" return withActor(async () => { @@ -62,23 +67,40 @@ export const querySessionInfo = query(async (workspaceID: string) => { return withActor(() => { return { isAdmin: Actor.userRole() === "admin", - isBeta: - Resource.App.stage === "production" - ? workspaceID === "wrk_01K46JDFR0E75SG2Q8K172KF3Y" - : true, + isBeta: Resource.App.stage === "production" ? workspaceID === "wrk_01K46JDFR0E75SG2Q8K172KF3Y" : true, } }, workspaceID) }, "session.get") export const createCheckoutUrl = action( - async (workspaceID: string, successUrl: string, cancelUrl: string) => { + async (workspaceID: string, amount: number, successUrl: string, cancelUrl: string) => { "use server" - return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID) + return json( + await withActor( + () => + Billing.generateCheckoutUrl({ amount, successUrl, cancelUrl }) + .then((data) => ({ error: undefined, data })) + .catch((e) => ({ + error: e.message as string, + data: undefined, + })), + workspaceID, + ), + ) }, "checkoutUrl", ) export const queryBillingInfo = query(async (workspaceID: string) => { "use server" - return withActor(() => Billing.get(), workspaceID) + return withActor(async () => { + const billing = await Billing.get() + return { + ...billing, + reloadAmount: billing.reloadAmount ?? Billing.RELOAD_AMOUNT, + reloadAmountMin: Billing.RELOAD_AMOUNT_MIN, + reloadTrigger: billing.reloadTrigger ?? Billing.RELOAD_TRIGGER, + reloadTriggerMin: Billing.RELOAD_TRIGGER_MIN, + } + }, workspaceID) }, "billing.get") diff --git a/packages/console/app/src/routes/zen/index.css b/packages/console/app/src/routes/zen/index.css index 661517c4..fbdd1530 100644 --- a/packages/console/app/src/routes/zen/index.css +++ b/packages/console/app/src/routes/zen/index.css @@ -277,7 +277,7 @@ body { margin-bottom: 24px; } - strong { + h1 { font-size: 28px; color: var(--color-text-strong); font-weight: 500; diff --git a/packages/console/app/src/routes/zen/index.tsx b/packages/console/app/src/routes/zen/index.tsx index 08e58e16..4eab4dcb 100644 --- a/packages/console/app/src/routes/zen/index.tsx +++ b/packages/console/app/src/routes/zen/index.tsx @@ -3,6 +3,7 @@ import { createAsync, query, redirect } from "@solidjs/router" import { Title, Meta, Link } from "@solidjs/meta" import { HttpHeader } from "@solidjs/start" import zenLogoLight from "../../asset/zen-ornate-light.svg" +import { config } from "~/config" import zenLogoDark from "../../asset/zen-ornate-dark.svg" import compareVideo from "../../asset/lander/opencode-comparison-min.mp4" import compareVideoPoster from "../../asset/lander/opencode-comparison-poster.png" @@ -30,6 +31,7 @@ export default function Home() {
OpenCode Zen | A curated set of reliable optimized models for coding agents + @@ -42,7 +44,7 @@ export default function Home() {
zen logo light zen logo dark - Reliable optimized models for coding agents +

Reliable optimized models for coding agents

Zen gives you access to a curated set of AI models that OpenCode has tested and benchmarked specifically for coding agents. No need to worry about inconsistent performance and quality, use validated models diff --git a/packages/console/app/src/routes/zen/util/error.ts b/packages/console/app/src/routes/zen/util/error.ts index dfc7e9fc..f1e13146 100644 --- a/packages/console/app/src/routes/zen/util/error.ts +++ b/packages/console/app/src/routes/zen/util/error.ts @@ -3,3 +3,4 @@ export class CreditsError extends Error {} export class MonthlyLimitError extends Error {} export class UserLimitError extends Error {} export class ModelError extends Error {} +export class RateLimitError extends Error {} diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 0d46e858..edaac3a7 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -12,18 +12,14 @@ import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js" import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js" import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js" import { logger } from "./logger" -import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError } from "./error" -import { - createBodyConverter, - createStreamPartConverter, - createResponseConverter, -} from "./provider/provider" +import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError, RateLimitError } from "./error" +import { createBodyConverter, createStreamPartConverter, createResponseConverter } from "./provider/provider" import { anthropicHelper } from "./provider/anthropic" import { openaiHelper } from "./provider/openai" import { oaCompatHelper } from "./provider/openai-compatible" +import { createRateLimiter } from "./rateLimiter" type ZenData = Awaited> -type Model = ZenData["models"][string] export async function handler( input: APIEvent, @@ -32,6 +28,10 @@ export async function handler( parseApiKey: (headers: Headers) => string | undefined }, ) { + type AuthInfo = Awaited> + type ModelInfo = Awaited> + type ProviderInfo = Awaited> + const FREE_WORKSPACES = [ "wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank "wrk_01K6W1A3VE0KMNVSCQT43BG2SX", // opencode bench @@ -39,6 +39,7 @@ export async function handler( try { const body = await input.request.json() + const ip = input.request.headers.get("x-real-ip") ?? "" logger.metric({ is_tream: !!body.stream, session: input.request.headers.get("x-opencode-session"), @@ -46,13 +47,11 @@ export async function handler( }) const zenData = ZenData.list() const modelInfo = validateModel(zenData, body.model) - const providerInfo = selectProvider( - zenData, - modelInfo, - input.request.headers.get("x-real-ip") ?? "", - ) + const providerInfo = selectProvider(zenData, modelInfo, ip) const authInfo = await authenticate(modelInfo, providerInfo) - validateBilling(modelInfo, authInfo) + const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip) + await rateLimiter?.check() + validateBilling(authInfo, modelInfo) validateModelSettings(authInfo) updateProviderKey(authInfo, providerInfo) logger.metric({ provider: providerInfo.id }) @@ -67,7 +66,7 @@ export async function handler( }), ) logger.debug("REQUEST URL: " + reqUrl) - logger.debug("REQUEST: " + reqBody) + logger.debug("REQUEST: " + reqBody.substring(0, 300) + "...") const res = await fetch(reqUrl, { method: "POST", headers: (() => { @@ -92,9 +91,6 @@ export async function handler( } } logger.debug("STATUS: " + res.status + " " + res.statusText) - if (res.status === 400 || res.status === 503) { - logger.debug("RESPONSE: " + (await res.text())) - } // Handle non-streaming response if (!body.stream) { @@ -103,6 +99,7 @@ export async function handler( const body = JSON.stringify(responseConverter(json)) logger.metric({ response_length: body.length }) logger.debug("RESPONSE: " + body) + await rateLimiter?.track() await trackUsage(authInfo, modelInfo, providerInfo, json.usage) await reload(authInfo) return new Response(body, { @@ -131,6 +128,7 @@ export async function handler( response_length: responseLength, "timestamp.last_byte": Date.now(), }) + await rateLimiter?.track() const usage = usageParser.retrieve() if (usage) { await trackUsage(authInfo, modelInfo, providerInfo, usage) @@ -205,6 +203,15 @@ export async function handler( { status: 401 }, ) + if (error instanceof RateLimitError) + return new Response( + JSON.stringify({ + type: "error", + error: { type: error.constructor.name, message: error.message }, + }), + { status: 429 }, + ) + return new Response( JSON.stringify({ type: "error", @@ -229,12 +236,8 @@ export async function handler( return { id: modelId, ...modelData } } - function selectProvider( - zenData: ZenData, - model: Awaited>, - ip: string, - ) { - const providers = model.providers + function selectProvider(zenData: ZenData, modelInfo: ModelInfo, ip: string) { + const providers = modelInfo.providers .filter((provider) => !provider.disabled) .flatMap((provider) => Array(provider.weight ?? 1).fill(provider)) @@ -247,26 +250,22 @@ export async function handler( throw new ModelError(`Provider ${provider.id} not supported`) } - const format = zenData.providers[provider.id].format - return { ...provider, ...zenData.providers[provider.id], - ...(format === "anthropic" - ? anthropicHelper - : format === "openai" - ? openaiHelper - : oaCompatHelper), + ...(() => { + const format = zenData.providers[provider.id].format + if (format === "anthropic") return anthropicHelper + if (format === "openai") return openaiHelper + return oaCompatHelper + })(), } } - async function authenticate( - model: Awaited>, - providerInfo: Awaited>, - ) { + async function authenticate(modelInfo: ModelInfo, providerInfo: ProviderInfo) { const apiKey = opts.parseApiKey(input.request.headers) if (!apiKey) { - if (model.allowAnonymous) return + if (modelInfo.allowAnonymous) return throw new AuthError("Missing API key.") } @@ -281,6 +280,7 @@ export async function handler( monthlyLimit: BillingTable.monthlyLimit, monthlyUsage: BillingTable.monthlyUsage, timeMonthlyUsageUpdated: BillingTable.timeMonthlyUsageUpdated, + reloadTrigger: BillingTable.reloadTrigger, }, user: { id: UserTable.id, @@ -296,20 +296,11 @@ export async function handler( .from(KeyTable) .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID)) .innerJoin(BillingTable, eq(BillingTable.workspaceID, KeyTable.workspaceID)) - .innerJoin( - UserTable, - and(eq(UserTable.workspaceID, KeyTable.workspaceID), eq(UserTable.id, KeyTable.userID)), - ) - .leftJoin( - ModelTable, - and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, model.id)), - ) + .innerJoin(UserTable, and(eq(UserTable.workspaceID, KeyTable.workspaceID), eq(UserTable.id, KeyTable.userID))) + .leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, modelInfo.id))) .leftJoin( ProviderTable, - and( - eq(ProviderTable.workspaceID, KeyTable.workspaceID), - eq(ProviderTable.provider, providerInfo.id), - ), + and(eq(ProviderTable.workspaceID, KeyTable.workspaceID), eq(ProviderTable.provider, providerInfo.id)), ) .where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted))) .then((rows) => rows[0]), @@ -332,11 +323,11 @@ export async function handler( } } - function validateBilling(model: Model, authInfo: Awaited>) { + function validateBilling(authInfo: AuthInfo, modelInfo: ModelInfo) { if (!authInfo) return if (authInfo.provider?.credentials) return if (authInfo.isFree) return - if (model.allowAnonymous) return + if (modelInfo.allowAnonymous) return const billing = authInfo.billing if (!billing.paymentMethodID) @@ -380,39 +371,24 @@ export async function handler( } } - function validateModelSettings(authInfo: Awaited>) { + function validateModelSettings(authInfo: AuthInfo) { if (!authInfo) return if (authInfo.isDisabled) throw new ModelError("Model is disabled") } - function updateProviderKey( - authInfo: Awaited>, - providerInfo: Awaited>, - ) { + function updateProviderKey(authInfo: AuthInfo, providerInfo: ProviderInfo) { if (!authInfo) return if (!authInfo.provider?.credentials) return providerInfo.apiKey = authInfo.provider.credentials } - async function trackUsage( - authInfo: Awaited>, - modelInfo: ReturnType, - providerInfo: Awaited>, - usage: any, - ) { - const { - inputTokens, - outputTokens, - reasoningTokens, - cacheReadTokens, - cacheWrite5mTokens, - cacheWrite1hTokens, - } = providerInfo.normalizeUsage(usage) + async function trackUsage(authInfo: AuthInfo, modelInfo: ModelInfo, providerInfo: ProviderInfo, usage: any) { + const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } = + providerInfo.normalizeUsage(usage) const modelCost = modelInfo.cost200K && - inputTokens + (cacheReadTokens ?? 0) + (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0) > - 200_000 + inputTokens + (cacheReadTokens ?? 0) + (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0) > 200_000 ? modelInfo.cost200K : modelInfo.cost @@ -463,8 +439,7 @@ export async function handler( if (!authInfo) return - const cost = - authInfo.isFree || authInfo.provider?.credentials ? 0 : centsToMicroCents(totalCostInCent) + const cost = authInfo.isFree || authInfo.provider?.credentials ? 0 : centsToMicroCents(totalCostInCent) await Database.transaction(async (tx) => { await tx.insert(UsageTable).values({ workspaceID: authInfo.workspaceID, @@ -504,9 +479,7 @@ export async function handler( `, timeMonthlyUsageUpdated: sql`now()`, }) - .where( - and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id)), - ) + .where(and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id))) }) await Database.use((tx) => @@ -517,7 +490,7 @@ export async function handler( ) } - async function reload(authInfo: Awaited>) { + async function reload(authInfo: AuthInfo) { if (!authInfo) return if (authInfo.isFree) return if (authInfo.provider?.credentials) return @@ -532,11 +505,11 @@ export async function handler( and( eq(BillingTable.workspaceID, authInfo.workspaceID), eq(BillingTable.reload, true), - lt(BillingTable.balance, centsToMicroCents(Billing.CHARGE_THRESHOLD)), - or( - isNull(BillingTable.timeReloadLockedTill), - lt(BillingTable.timeReloadLockedTill, sql`now()`), + lt( + BillingTable.balance, + centsToMicroCents((authInfo.billing.reloadTrigger ?? Billing.RELOAD_TRIGGER) * 100), ), + or(isNull(BillingTable.timeReloadLockedTill), lt(BillingTable.timeReloadLockedTill, sql`now()`)), ), ), ) diff --git a/packages/console/app/src/routes/zen/util/provider/anthropic.ts b/packages/console/app/src/routes/zen/util/provider/anthropic.ts index 603d8917..d8d1cd74 100644 --- a/packages/console/app/src/routes/zen/util/provider/anthropic.ts +++ b/packages/console/app/src/routes/zen/util/provider/anthropic.ts @@ -98,7 +98,10 @@ export function fromAnthropicRequest(body: any): CommonRequest { typeof (src as any).media_type === "string" && typeof (src as any).data === "string" ) - return { type: "image_url", image_url: { url: `data:${(src as any).media_type};base64,${(src as any).data}` } } + return { + type: "image_url", + image_url: { url: `data:${(src as any).media_type};base64,${(src as any).data}` }, + } return undefined } @@ -165,7 +168,11 @@ export function fromAnthropicRequest(body: any): CommonRequest { .filter((t: any) => t && typeof t === "object" && "input_schema" in t) .map((t: any) => ({ type: "function", - function: { name: (t as any).name, description: (t as any).description, parameters: (t as any).input_schema }, + function: { + name: (t as any).name, + description: (t as any).description, + parameters: (t as any).input_schema, + }, })) : undefined @@ -452,7 +459,12 @@ export function toAnthropicResponse(resp: CommonResponse) { } catch { input = (tc as any).function.arguments } - content.push({ type: "tool_use", id: (tc as any).id, name: (tc as any).function.name, input }) + content.push({ + type: "tool_use", + id: (tc as any).id, + name: (tc as any).function.name, + input, + }) } } } @@ -511,13 +523,22 @@ export function fromAnthropicChunk(chunk: string): CommonChunk | string { if (json.type === "content_block_start") { const cb = json.content_block if (cb?.type === "text") { - out.choices.push({ index: json.index ?? 0, delta: { role: "assistant", content: "" }, finish_reason: null }) + out.choices.push({ + index: json.index ?? 0, + delta: { role: "assistant", content: "" }, + finish_reason: null, + }) } else if (cb?.type === "tool_use") { out.choices.push({ index: json.index ?? 0, delta: { tool_calls: [ - { index: json.index ?? 0, id: cb.id, type: "function", function: { name: cb.name, arguments: "" } }, + { + index: json.index ?? 0, + id: cb.id, + type: "function", + function: { name: cb.name, arguments: "" }, + }, ], }, finish_reason: null, @@ -532,7 +553,9 @@ export function fromAnthropicChunk(chunk: string): CommonChunk | string { } else if (d?.type === "input_json_delta") { out.choices.push({ index: json.index ?? 0, - delta: { tool_calls: [{ index: json.index ?? 0, function: { arguments: d.partial_json } }] }, + delta: { + tool_calls: [{ index: json.index ?? 0, function: { arguments: d.partial_json } }], + }, finish_reason: null, }) } diff --git a/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts b/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts index daf65027..8a9170ef 100644 --- a/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts +++ b/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts @@ -532,7 +532,9 @@ export function toOaCompatibleChunk(chunk: CommonChunk): string { total_tokens: chunk.usage.total_tokens, ...(chunk.usage.prompt_tokens_details?.cached_tokens ? { - prompt_tokens_details: { cached_tokens: chunk.usage.prompt_tokens_details.cached_tokens }, + prompt_tokens_details: { + cached_tokens: chunk.usage.prompt_tokens_details.cached_tokens, + }, } : {}), } diff --git a/packages/console/app/src/routes/zen/util/provider/openai.ts b/packages/console/app/src/routes/zen/util/provider/openai.ts index d1730099..e79e8357 100644 --- a/packages/console/app/src/routes/zen/util/provider/openai.ts +++ b/packages/console/app/src/routes/zen/util/provider/openai.ts @@ -77,7 +77,10 @@ export function fromOpenaiRequest(body: any): CommonRequest { typeof (s as any).media_type === "string" && typeof (s as any).data === "string" ) - return { type: "image_url", image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` } } + return { + type: "image_url", + image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` }, + } return undefined } @@ -153,7 +156,11 @@ export function fromOpenaiRequest(body: any): CommonRequest { } if ((m as any).role === "tool") { - msgs.push({ role: "tool", tool_call_id: (m as any).tool_call_id, content: (m as any).content }) + msgs.push({ + role: "tool", + tool_call_id: (m as any).tool_call_id, + content: (m as any).content, + }) continue } } @@ -210,7 +217,10 @@ export function toOpenaiRequest(body: CommonRequest) { typeof (s as any).media_type === "string" && typeof (s as any).data === "string" ) - return { type: "input_image", image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` } } + return { + type: "input_image", + image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` }, + } return undefined } @@ -498,7 +508,9 @@ export function fromOpenaiChunk(chunk: string): CommonChunk | string { if (typeof name === "string" && name.length > 0) { out.choices.push({ index: 0, - delta: { tool_calls: [{ index: 0, id, type: "function", function: { name, arguments: "" } }] }, + delta: { + tool_calls: [{ index: 0, id, type: "function", function: { name, arguments: "" } }], + }, finish_reason: null, }) } @@ -555,7 +567,12 @@ export function toOpenaiChunk(chunk: CommonChunk): string { const model = chunk.model if (d.content) { - const data = { id, type: "response.output_text.delta", delta: d.content, response: { id, model } } + const data = { + id, + type: "response.output_text.delta", + delta: d.content, + response: { id, model }, + } return `event: response.output_text.delta\ndata: ${JSON.stringify(data)}` } @@ -565,7 +582,13 @@ export function toOpenaiChunk(chunk: CommonChunk): string { const data = { type: "response.output_item.added", output_index: 0, - item: { id: tc.id, type: "function_call", name: tc.function.name, call_id: tc.id, arguments: "" }, + item: { + id: tc.id, + type: "function_call", + name: tc.function.name, + call_id: tc.id, + arguments: "", + }, } return `event: response.output_item.added\ndata: ${JSON.stringify(data)}` } @@ -593,7 +616,11 @@ export function toOpenaiChunk(chunk: CommonChunk): string { } : undefined - const data: any = { id, type: "response.completed", response: { id, model, ...(usage ? { usage } : {}) } } + const data: any = { + id, + type: "response.completed", + response: { id, model, ...(usage ? { usage } : {}) }, + } return `event: response.completed\ndata: ${JSON.stringify(data)}` } diff --git a/packages/console/app/src/routes/zen/util/rateLimiter.ts b/packages/console/app/src/routes/zen/util/rateLimiter.ts new file mode 100644 index 00000000..b3c03681 --- /dev/null +++ b/packages/console/app/src/routes/zen/util/rateLimiter.ts @@ -0,0 +1,35 @@ +import { Resource } from "@opencode-ai/console-resource" +import { RateLimitError } from "./error" +import { logger } from "./logger" + +export function createRateLimiter(model: string, limit: number | undefined, ip: string) { + if (!limit) return + + const now = Date.now() + const currKey = `usage:${ip}:${model}:${buildYYYYMMDDHH(now)}` + const prevKey = `usage:${ip}:${model}:${buildYYYYMMDDHH(now - 3_600_000)}` + let currRate: number + let prevRate: number + + return { + track: async () => { + await Resource.GatewayKv.put(currKey, currRate + 1, { expirationTtl: 3600 }) + }, + check: async () => { + const values = await Resource.GatewayKv.get([currKey, prevKey]) + const prevValue = values?.get(prevKey) + const currValue = values?.get(currKey) + prevRate = prevValue ? parseInt(prevValue) : 0 + currRate = currValue ? parseInt(currValue) : 0 + logger.debug(`rate limit ${model} prev/curr: ${prevRate}/${currRate}`) + if (prevRate + currRate >= limit) throw new RateLimitError(`Rate limit exceeded. Please try again later.`) + }, + } +} + +function buildYYYYMMDDHH(timestamp: number) { + return new Date(timestamp) + .toISOString() + .replace(/[^0-9]/g, "") + .substring(0, 10) +} diff --git a/packages/console/app/src/routes/zen/v1/models.ts b/packages/console/app/src/routes/zen/v1/models.ts index 3d0c3147..ee2b3ab5 100644 --- a/packages/console/app/src/routes/zen/v1/models.ts +++ b/packages/console/app/src/routes/zen/v1/models.ts @@ -50,10 +50,7 @@ export async function GET(input: APIEvent) { }) .from(KeyTable) .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID)) - .leftJoin( - ModelTable, - and(eq(ModelTable.workspaceID, KeyTable.workspaceID), isNull(ModelTable.timeDeleted)), - ) + .leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), isNull(ModelTable.timeDeleted))) .where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted))) .then((rows) => rows.map((row) => row.model)), ) diff --git a/packages/console/app/sst-env.d.ts b/packages/console/app/sst-env.d.ts index 9b9de732..bd558821 100644 --- a/packages/console/app/sst-env.d.ts +++ b/packages/console/app/sst-env.d.ts @@ -6,4 +6,4 @@ /// import "sst" -export {} \ No newline at end of file +export {} diff --git a/packages/console/core/migrations/meta/0018_snapshot.json b/packages/console/core/migrations/meta/0018_snapshot.json index 398ba261..3e3c64c7 100644 --- a/packages/console/core/migrations/meta/0018_snapshot.json +++ b/packages/console/core/migrations/meta/0018_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -486,17 +473,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -504,10 +486,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -599,10 +578,7 @@ "indexes": { "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true } }, @@ -610,10 +586,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -670,9 +643,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -680,9 +651,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -699,4 +668,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0019_snapshot.json b/packages/console/core/migrations/meta/0019_snapshot.json index 8f1afeb5..9a0d4d24 100644 --- a/packages/console/core/migrations/meta/0019_snapshot.json +++ b/packages/console/core/migrations/meta/0019_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -486,17 +473,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -504,10 +486,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -599,10 +578,7 @@ "indexes": { "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true } }, @@ -610,10 +586,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -670,9 +643,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -680,9 +651,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -699,4 +668,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0020_snapshot.json b/packages/console/core/migrations/meta/0020_snapshot.json index 662093f5..9defceb5 100644 --- a/packages/console/core/migrations/meta/0020_snapshot.json +++ b/packages/console/core/migrations/meta/0020_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -486,17 +473,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -504,10 +486,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -592,10 +571,7 @@ "indexes": { "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true } }, @@ -603,10 +579,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -663,9 +636,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -673,9 +644,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -692,4 +661,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0021_snapshot.json b/packages/console/core/migrations/meta/0021_snapshot.json index b285e34f..64d3e9d2 100644 --- a/packages/console/core/migrations/meta/0021_snapshot.json +++ b/packages/console/core/migrations/meta/0021_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -486,17 +473,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -504,10 +486,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -599,10 +578,7 @@ "indexes": { "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true } }, @@ -610,10 +586,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -670,9 +643,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -680,9 +651,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -699,4 +668,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0022_snapshot.json b/packages/console/core/migrations/meta/0022_snapshot.json index 9486ee34..8a1c4e7d 100644 --- a/packages/console/core/migrations/meta/0022_snapshot.json +++ b/packages/console/core/migrations/meta/0022_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -486,17 +473,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -504,10 +486,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -613,18 +592,12 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true } }, @@ -632,10 +605,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -692,9 +662,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -702,9 +670,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -721,4 +687,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0023_snapshot.json b/packages/console/core/migrations/meta/0023_snapshot.json index 8a40e42a..4f6f6628 100644 --- a/packages/console/core/migrations/meta/0023_snapshot.json +++ b/packages/console/core/migrations/meta/0023_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -486,17 +473,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -504,10 +486,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -613,32 +592,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -646,10 +615,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -706,9 +672,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -716,9 +680,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -735,4 +697,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0024_snapshot.json b/packages/console/core/migrations/meta/0024_snapshot.json index 4f50945d..1ef25970 100644 --- a/packages/console/core/migrations/meta/0024_snapshot.json +++ b/packages/console/core/migrations/meta/0024_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -486,17 +473,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -504,10 +486,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -599,32 +578,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -632,10 +601,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -692,9 +658,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -702,9 +666,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -721,4 +683,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0025_snapshot.json b/packages/console/core/migrations/meta/0025_snapshot.json index 4b0cef0c..6746a6e8 100644 --- a/packages/console/core/migrations/meta/0025_snapshot.json +++ b/packages/console/core/migrations/meta/0025_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -493,17 +480,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -511,10 +493,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -606,32 +585,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -639,10 +608,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -699,9 +665,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -709,9 +673,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -728,4 +690,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0026_snapshot.json b/packages/console/core/migrations/meta/0026_snapshot.json index 543ab44c..d3c7dc49 100644 --- a/packages/console/core/migrations/meta/0026_snapshot.json +++ b/packages/console/core/migrations/meta/0026_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -493,9 +480,7 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true } }, @@ -503,10 +488,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -598,32 +580,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -631,10 +603,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -691,9 +660,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -701,9 +668,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -720,4 +685,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0027_snapshot.json b/packages/console/core/migrations/meta/0027_snapshot.json index 9b691022..408766f7 100644 --- a/packages/console/core/migrations/meta/0027_snapshot.json +++ b/packages/console/core/migrations/meta/0027_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -479,9 +466,7 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true } }, @@ -489,10 +474,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -584,32 +566,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -617,10 +589,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -677,9 +646,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -687,9 +654,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -706,4 +671,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0028_snapshot.json b/packages/console/core/migrations/meta/0028_snapshot.json index 8242ae52..827cb53c 100644 --- a/packages/console/core/migrations/meta/0028_snapshot.json +++ b/packages/console/core/migrations/meta/0028_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -479,9 +466,7 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true } }, @@ -489,10 +474,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -584,32 +566,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -617,10 +589,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -677,9 +646,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -687,9 +654,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -706,4 +671,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0029_snapshot.json b/packages/console/core/migrations/meta/0029_snapshot.json index 959004f3..d235697c 100644 --- a/packages/console/core/migrations/meta/0029_snapshot.json +++ b/packages/console/core/migrations/meta/0029_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -479,9 +466,7 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true } }, @@ -489,10 +474,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -605,32 +587,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -638,10 +610,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -698,9 +667,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -708,9 +675,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -727,4 +692,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0030_snapshot.json b/packages/console/core/migrations/meta/0030_snapshot.json index 6a6eb38c..66978dfa 100644 --- a/packages/console/core/migrations/meta/0030_snapshot.json +++ b/packages/console/core/migrations/meta/0030_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -479,9 +466,7 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true } }, @@ -489,10 +474,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -549,10 +531,7 @@ "indexes": { "model_workspace_model": { "name": "model_workspace_model", - "columns": [ - "workspace_id", - "model" - ], + "columns": ["workspace_id", "model"], "isUnique": true } }, @@ -560,10 +539,7 @@ "compositePrimaryKeys": { "model_workspace_id_id_pk": { "name": "model_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -676,32 +652,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -709,10 +675,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -769,9 +732,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -779,9 +740,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -798,4 +757,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0031_snapshot.json b/packages/console/core/migrations/meta/0031_snapshot.json index ba964881..c4716592 100644 --- a/packages/console/core/migrations/meta/0031_snapshot.json +++ b/packages/console/core/migrations/meta/0031_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -398,10 +388,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -479,9 +466,7 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true } }, @@ -489,10 +474,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -549,10 +531,7 @@ "indexes": { "model_workspace_model": { "name": "model_workspace_model", - "columns": [ - "workspace_id", - "model" - ], + "columns": ["workspace_id", "model"], "isUnique": true } }, @@ -560,10 +539,7 @@ "compositePrimaryKeys": { "model_workspace_id_id_pk": { "name": "model_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -627,10 +603,7 @@ "indexes": { "workspace_provider": { "name": "workspace_provider", - "columns": [ - "workspace_id", - "provider" - ], + "columns": ["workspace_id", "provider"], "isUnique": true } }, @@ -638,10 +611,7 @@ "compositePrimaryKeys": { "provider_workspace_id_id_pk": { "name": "provider_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -754,32 +724,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -787,10 +747,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -847,9 +804,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -857,9 +812,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -876,4 +829,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0032_snapshot.json b/packages/console/core/migrations/meta/0032_snapshot.json index 344fde6f..51e84a1d 100644 --- a/packages/console/core/migrations/meta/0032_snapshot.json +++ b/packages/console/core/migrations/meta/0032_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -180,9 +178,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -190,10 +186,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -280,10 +273,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -405,10 +395,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -486,9 +473,7 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true } }, @@ -496,10 +481,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -556,10 +538,7 @@ "indexes": { "model_workspace_model": { "name": "model_workspace_model", - "columns": [ - "workspace_id", - "model" - ], + "columns": ["workspace_id", "model"], "isUnique": true } }, @@ -567,10 +546,7 @@ "compositePrimaryKeys": { "model_workspace_id_id_pk": { "name": "model_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -634,10 +610,7 @@ "indexes": { "workspace_provider": { "name": "workspace_provider", - "columns": [ - "workspace_id", - "provider" - ], + "columns": ["workspace_id", "provider"], "isUnique": true } }, @@ -645,10 +618,7 @@ "compositePrimaryKeys": { "provider_workspace_id_id_pk": { "name": "provider_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -761,32 +731,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -794,10 +754,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -854,9 +811,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -864,9 +819,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -883,4 +836,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0033_snapshot.json b/packages/console/core/migrations/meta/0033_snapshot.json index eb682adc..76d4720e 100644 --- a/packages/console/core/migrations/meta/0033_snapshot.json +++ b/packages/console/core/migrations/meta/0033_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -187,9 +185,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -197,10 +193,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -287,10 +280,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -412,10 +402,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -493,9 +480,7 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true } }, @@ -503,10 +488,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -563,10 +545,7 @@ "indexes": { "model_workspace_model": { "name": "model_workspace_model", - "columns": [ - "workspace_id", - "model" - ], + "columns": ["workspace_id", "model"], "isUnique": true } }, @@ -574,10 +553,7 @@ "compositePrimaryKeys": { "model_workspace_id_id_pk": { "name": "model_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -641,10 +617,7 @@ "indexes": { "workspace_provider": { "name": "workspace_provider", - "columns": [ - "workspace_id", - "provider" - ], + "columns": ["workspace_id", "provider"], "isUnique": true } }, @@ -652,10 +625,7 @@ "compositePrimaryKeys": { "provider_workspace_id_id_pk": { "name": "provider_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -768,32 +738,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -801,10 +761,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -861,9 +818,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -871,9 +826,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -890,4 +843,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0034_snapshot.json b/packages/console/core/migrations/meta/0034_snapshot.json index 36acbdef..e9c999be 100644 --- a/packages/console/core/migrations/meta/0034_snapshot.json +++ b/packages/console/core/migrations/meta/0034_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -117,10 +115,7 @@ "indexes": { "provider": { "name": "provider", - "columns": [ - "provider", - "subject" - ], + "columns": ["provider", "subject"], "isUnique": true } }, @@ -257,9 +252,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -267,10 +260,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -357,10 +347,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -482,10 +469,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -563,9 +547,7 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true } }, @@ -573,10 +555,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -633,10 +612,7 @@ "indexes": { "model_workspace_model": { "name": "model_workspace_model", - "columns": [ - "workspace_id", - "model" - ], + "columns": ["workspace_id", "model"], "isUnique": true } }, @@ -644,10 +620,7 @@ "compositePrimaryKeys": { "model_workspace_id_id_pk": { "name": "model_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -711,10 +684,7 @@ "indexes": { "workspace_provider": { "name": "workspace_provider", - "columns": [ - "workspace_id", - "provider" - ], + "columns": ["workspace_id", "provider"], "isUnique": true } }, @@ -722,10 +692,7 @@ "compositePrimaryKeys": { "provider_workspace_id_id_pk": { "name": "provider_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -838,32 +805,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -871,10 +828,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -931,9 +885,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -941,9 +893,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -960,4 +910,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0035_snapshot.json b/packages/console/core/migrations/meta/0035_snapshot.json index 7478337b..815d120e 100644 --- a/packages/console/core/migrations/meta/0035_snapshot.json +++ b/packages/console/core/migrations/meta/0035_snapshot.json @@ -102,17 +102,12 @@ "indexes": { "provider": { "name": "provider", - "columns": [ - "provider", - "subject" - ], + "columns": ["provider", "subject"], "isUnique": true }, "account_id": { "name": "account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false } }, @@ -249,9 +244,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -259,10 +252,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -349,10 +339,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -474,10 +461,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -555,9 +539,7 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true } }, @@ -565,10 +547,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -625,10 +604,7 @@ "indexes": { "model_workspace_model": { "name": "model_workspace_model", - "columns": [ - "workspace_id", - "model" - ], + "columns": ["workspace_id", "model"], "isUnique": true } }, @@ -636,10 +612,7 @@ "compositePrimaryKeys": { "model_workspace_id_id_pk": { "name": "model_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -703,10 +676,7 @@ "indexes": { "workspace_provider": { "name": "workspace_provider", - "columns": [ - "workspace_id", - "provider" - ], + "columns": ["workspace_id", "provider"], "isUnique": true } }, @@ -714,10 +684,7 @@ "compositePrimaryKeys": { "provider_workspace_id_id_pk": { "name": "provider_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -830,32 +797,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -863,10 +820,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -923,9 +877,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -933,9 +885,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -952,4 +902,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0036_snapshot.json b/packages/console/core/migrations/meta/0036_snapshot.json index b030e30e..926b143e 100644 --- a/packages/console/core/migrations/meta/0036_snapshot.json +++ b/packages/console/core/migrations/meta/0036_snapshot.json @@ -43,9 +43,7 @@ "compositePrimaryKeys": { "account_id_pk": { "name": "account_id_pk", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -109,17 +107,12 @@ "indexes": { "provider": { "name": "provider", - "columns": [ - "provider", - "subject" - ], + "columns": ["provider", "subject"], "isUnique": true }, "account_id": { "name": "account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false } }, @@ -127,9 +120,7 @@ "compositePrimaryKeys": { "auth_id_pk": { "name": "auth_id_pk", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -263,9 +254,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -273,10 +262,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -363,10 +349,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -488,10 +471,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -569,9 +549,7 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true } }, @@ -579,10 +557,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -639,10 +614,7 @@ "indexes": { "model_workspace_model": { "name": "model_workspace_model", - "columns": [ - "workspace_id", - "model" - ], + "columns": ["workspace_id", "model"], "isUnique": true } }, @@ -650,10 +622,7 @@ "compositePrimaryKeys": { "model_workspace_id_id_pk": { "name": "model_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -717,10 +686,7 @@ "indexes": { "workspace_provider": { "name": "workspace_provider", - "columns": [ - "workspace_id", - "provider" - ], + "columns": ["workspace_id", "provider"], "isUnique": true } }, @@ -728,10 +694,7 @@ "compositePrimaryKeys": { "provider_workspace_id_id_pk": { "name": "provider_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -844,32 +807,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -877,10 +830,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -937,9 +887,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -947,9 +895,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -966,4 +912,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/0037_snapshot.json b/packages/console/core/migrations/meta/0037_snapshot.json index 690bae87..8a80ea52 100644 --- a/packages/console/core/migrations/meta/0037_snapshot.json +++ b/packages/console/core/migrations/meta/0037_snapshot.json @@ -43,9 +43,7 @@ "compositePrimaryKeys": { "account_id_pk": { "name": "account_id_pk", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -109,17 +107,12 @@ "indexes": { "provider": { "name": "provider", - "columns": [ - "provider", - "subject" - ], + "columns": ["provider", "subject"], "isUnique": true }, "account_id": { "name": "account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false } }, @@ -127,9 +120,7 @@ "compositePrimaryKeys": { "auth_id_pk": { "name": "auth_id_pk", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -277,9 +268,7 @@ "indexes": { "global_customer_id": { "name": "global_customer_id", - "columns": [ - "customer_id" - ], + "columns": ["customer_id"], "isUnique": true } }, @@ -287,10 +276,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -377,10 +363,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -502,10 +485,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -583,9 +563,7 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true } }, @@ -593,10 +571,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -653,10 +628,7 @@ "indexes": { "model_workspace_model": { "name": "model_workspace_model", - "columns": [ - "workspace_id", - "model" - ], + "columns": ["workspace_id", "model"], "isUnique": true } }, @@ -664,10 +636,7 @@ "compositePrimaryKeys": { "model_workspace_id_id_pk": { "name": "model_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -731,10 +700,7 @@ "indexes": { "workspace_provider": { "name": "workspace_provider", - "columns": [ - "workspace_id", - "provider" - ], + "columns": ["workspace_id", "provider"], "isUnique": true } }, @@ -742,10 +708,7 @@ "compositePrimaryKeys": { "provider_workspace_id_id_pk": { "name": "provider_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -858,32 +821,22 @@ "indexes": { "user_account_id": { "name": "user_account_id", - "columns": [ - "workspace_id", - "account_id" - ], + "columns": ["workspace_id", "account_id"], "isUnique": true }, "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true }, "global_account_id": { "name": "global_account_id", - "columns": [ - "account_id" - ], + "columns": ["account_id"], "isUnique": false }, "global_email": { "name": "global_email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": false } }, @@ -891,10 +844,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -951,9 +901,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -961,9 +909,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -980,4 +926,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/meta/_journal.json b/packages/console/core/migrations/meta/_journal.json index f2c6c6fc..250fe59b 100644 --- a/packages/console/core/migrations/meta/_journal.json +++ b/packages/console/core/migrations/meta/_journal.json @@ -269,4 +269,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/packages/console/core/package.json b/packages/console/core/package.json index c1ab2347..04421bbc 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.23", + "version": "1.0.55", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/core/script/lookup-user.ts b/packages/console/core/script/lookup-user.ts index af9bcc3a..1ae18c4d 100644 --- a/packages/console/core/script/lookup-user.ts +++ b/packages/console/core/script/lookup-user.ts @@ -8,22 +8,15 @@ if (!email) { process.exit(1) } -const authData = await printTable("Auth", (tx) => - tx.select().from(AuthTable).where(eq(AuthTable.subject, email)), -) +const authData = await printTable("Auth", (tx) => tx.select().from(AuthTable).where(eq(AuthTable.subject, email))) if (authData.length === 0) { console.error("User not found") process.exit(1) } -await printTable("Auth", (tx) => - tx.select().from(AuthTable).where(eq(AuthTable.accountID, authData[0].accountID)), -) +await printTable("Auth", (tx) => tx.select().from(AuthTable).where(eq(AuthTable.accountID, authData[0].accountID))) -function printTable( - title: string, - callback: (tx: Database.TxOrDb) => Promise, -): Promise { +function printTable(title: string, callback: (tx: Database.TxOrDb) => Promise): Promise { return Database.use(async (tx) => { const data = await callback(tx) console.log(`== ${title} ==`) diff --git a/packages/console/core/script/reset-db.ts b/packages/console/core/script/reset-db.ts index bd00e196..02d49890 100644 --- a/packages/console/core/script/reset-db.ts +++ b/packages/console/core/script/reset-db.ts @@ -8,14 +8,6 @@ import { KeyTable } from "../src/schema/key.sql.js" if (Resource.App.stage !== "frank") throw new Error("This script is only for frank") -for (const table of [ - AccountTable, - BillingTable, - KeyTable, - PaymentTable, - UsageTable, - UserTable, - WorkspaceTable, -]) { +for (const table of [AccountTable, BillingTable, KeyTable, PaymentTable, UsageTable, UserTable, WorkspaceTable]) { await Database.use((tx) => tx.delete(table)) } diff --git a/packages/console/core/script/update-models.ts b/packages/console/core/script/update-models.ts index e7a24551..807d5782 100755 --- a/packages/console/core/script/update-models.ts +++ b/packages/console/core/script/update-models.ts @@ -7,7 +7,6 @@ import { ZenData } from "../src/model" const root = path.resolve(process.cwd(), "..", "..", "..") const models = await $`bun sst secret list`.cwd(root).text() -console.log("models", models) // read the line starting with "ZEN_MODELS" const oldValue1 = models diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts index 70bf1bc3..34871814 100644 --- a/packages/console/core/src/billing.ts +++ b/packages/console/core/src/billing.ts @@ -10,13 +10,12 @@ import { centsToMicroCents } from "./util/price" import { User } from "./user" export namespace Billing { - export const CHARGE_NAME = "opencode credits" - export const CHARGE_FEE_NAME = "processing fee" - export const CHARGE_AMOUNT = 2000 // $20 - export const CHARGE_AMOUNT_DOLLAR = 20 - export const CHARGE_FEE = 123 // Stripe fee 4.4% + $0.30 - export const CHARGE_THRESHOLD_DOLLAR = 5 - export const CHARGE_THRESHOLD = 500 // $5 + export const ITEM_CREDIT_NAME = "opencode credits" + export const ITEM_FEE_NAME = "processing fee" + export const RELOAD_AMOUNT = 20 + export const RELOAD_AMOUNT_MIN = 10 + export const RELOAD_TRIGGER = 5 + export const RELOAD_TRIGGER_MIN = 5 export const stripe = () => new Stripe(Resource.STRIPE_SECRET_KEY.value, { apiVersion: "2025-03-31.basil", @@ -33,6 +32,8 @@ export namespace Billing { paymentMethodLast4: BillingTable.paymentMethodLast4, balance: BillingTable.balance, reload: BillingTable.reload, + reloadAmount: BillingTable.reloadAmount, + reloadTrigger: BillingTable.reloadTrigger, monthlyLimit: BillingTable.monthlyLimit, monthlyUsage: BillingTable.monthlyUsage, timeMonthlyUsageUpdated: BillingTable.timeMonthlyUsageUpdated, @@ -67,17 +68,28 @@ export namespace Billing { ) } + export const calculateFeeInCents = (x: number) => { + // math: x = total - (total * 0.044 + 0.30) + // math: x = total * (1-0.044) - 0.30 + // math: (x + 0.30) / 0.956 = total + return Math.round(((x + 30) / 0.956) * 0.044 + 30) + } + export const reload = async () => { - const { customerID, paymentMethodID } = await Database.use((tx) => + const billing = await Database.use((tx) => tx .select({ customerID: BillingTable.customerID, paymentMethodID: BillingTable.paymentMethodID, + reloadAmount: BillingTable.reloadAmount, }) .from(BillingTable) .where(eq(BillingTable.workspaceID, Actor.workspace())) .then((rows) => rows[0]), ) + const customerID = billing.customerID + const paymentMethodID = billing.paymentMethodID + const amountInCents = (billing.reloadAmount ?? Billing.RELOAD_AMOUNT) * 100 const paymentID = Identifier.create("payment") let invoice try { @@ -89,18 +101,18 @@ export namespace Billing { currency: "usd", }) await Billing.stripe().invoiceItems.create({ - amount: Billing.CHARGE_AMOUNT, + amount: amountInCents, currency: "usd", customer: customerID!, - description: CHARGE_NAME, invoice: draft.id!, + description: ITEM_CREDIT_NAME, }) await Billing.stripe().invoiceItems.create({ - amount: Billing.CHARGE_FEE, + amount: calculateFeeInCents(amountInCents), currency: "usd", customer: customerID!, - description: CHARGE_FEE_NAME, invoice: draft.id!, + description: ITEM_FEE_NAME, }) await Billing.stripe().invoices.finalizeInvoice(draft.id!) invoice = await Billing.stripe().invoices.pay(draft.id!, { @@ -128,7 +140,7 @@ export namespace Billing { await tx .update(BillingTable) .set({ - balance: sql`${BillingTable.balance} + ${centsToMicroCents(CHARGE_AMOUNT)}`, + balance: sql`${BillingTable.balance} + ${centsToMicroCents(amountInCents)}`, reloadError: null, timeReloadError: null, }) @@ -136,7 +148,7 @@ export namespace Billing { await tx.insert(PaymentTable).values({ workspaceID: Actor.workspace(), id: paymentID, - amount: centsToMicroCents(CHARGE_AMOUNT), + amount: centsToMicroCents(amountInCents), invoiceID: invoice.id!, paymentID: invoice.payments?.data[0].payment.payment_intent as string, customerID, @@ -159,13 +171,19 @@ export namespace Billing { z.object({ successUrl: z.string(), cancelUrl: z.string(), + amount: z.number().optional(), }), async (input) => { const user = Actor.assert("user") - const { successUrl, cancelUrl } = input + const { successUrl, cancelUrl, amount } = input + + if (amount !== undefined && amount < Billing.RELOAD_AMOUNT_MIN) { + throw new Error(`Amount must be at least $${Billing.RELOAD_AMOUNT_MIN}`) + } const email = await User.getAuthEmail(user.properties.userID) const customer = await Billing.get() + const amountInCents = (amount ?? customer.reloadAmount ?? Billing.RELOAD_AMOUNT) * 100 const session = await Billing.stripe().checkout.sessions.create({ mode: "payment", billing_address_collection: "required", @@ -173,20 +191,16 @@ export namespace Billing { { price_data: { currency: "usd", - product_data: { - name: CHARGE_NAME, - }, - unit_amount: CHARGE_AMOUNT, + product_data: { name: ITEM_CREDIT_NAME }, + unit_amount: amountInCents, }, quantity: 1, }, { price_data: { currency: "usd", - product_data: { - name: CHARGE_FEE_NAME, - }, - unit_amount: CHARGE_FEE, + product_data: { name: ITEM_FEE_NAME }, + unit_amount: calculateFeeInCents(amountInCents), }, quantity: 1, }, @@ -218,6 +232,7 @@ export namespace Billing { }, metadata: { workspaceID: Actor.workspace(), + amount: amountInCents.toString(), }, success_url: successUrl, cancel_url: cancelUrl, diff --git a/packages/console/core/src/model.ts b/packages/console/core/src/model.ts index 30cc15e4..46b2aa55 100644 --- a/packages/console/core/src/model.ts +++ b/packages/console/core/src/model.ts @@ -24,6 +24,7 @@ export namespace ZenData { cost: ModelCostSchema, cost200K: ModelCostSchema.optional(), allowAnonymous: z.boolean().optional(), + rateLimit: z.number().optional(), providers: z.array( z.object({ id: z.string(), @@ -60,9 +61,7 @@ export namespace Model { export const enable = fn(z.object({ model: z.string() }), ({ model }) => { Actor.assertAdmin() return Database.use((db) => - db - .delete(ModelTable) - .where(and(eq(ModelTable.workspaceID, Actor.workspace()), eq(ModelTable.model, model))), + db.delete(ModelTable).where(and(eq(ModelTable.workspaceID, Actor.workspace()), eq(ModelTable.model, model))), ) }) diff --git a/packages/console/core/sst-env.d.ts b/packages/console/core/sst-env.d.ts index 01407434..bcd7c265 100644 --- a/packages/console/core/sst-env.d.ts +++ b/packages/console/core/sst-env.d.ts @@ -6,99 +6,108 @@ import "sst" declare module "sst" { export interface Resource { - "ADMIN_SECRET": { - "type": "sst.sst.Secret" - "value": string + ADMIN_SECRET: { + type: "sst.sst.Secret" + value: string } - "AUTH_API_URL": { - "type": "sst.sst.Linkable" - "value": string + AUTH_API_URL: { + type: "sst.sst.Linkable" + value: string } - "AWS_SES_ACCESS_KEY_ID": { - "type": "sst.sst.Secret" - "value": string + AWS_SES_ACCESS_KEY_ID: { + type: "sst.sst.Secret" + value: string } - "AWS_SES_SECRET_ACCESS_KEY": { - "type": "sst.sst.Secret" - "value": string + AWS_SES_SECRET_ACCESS_KEY: { + type: "sst.sst.Secret" + value: string } - "Console": { - "type": "sst.cloudflare.SolidStart" - "url": string + CLOUDFLARE_API_TOKEN: { + type: "sst.sst.Secret" + value: string } - "Database": { - "database": string - "host": string - "password": string - "port": number - "type": "sst.sst.Linkable" - "username": string + CLOUDFLARE_DEFAULT_ACCOUNT_ID: { + type: "sst.sst.Secret" + value: string } - "Desktop": { - "type": "sst.cloudflare.StaticSite" - "url": string + Console: { + type: "sst.cloudflare.SolidStart" + url: string } - "EMAILOCTOPUS_API_KEY": { - "type": "sst.sst.Secret" - "value": string + Database: { + database: string + host: string + password: string + port: number + type: "sst.sst.Linkable" + username: string } - "GITHUB_APP_ID": { - "type": "sst.sst.Secret" - "value": string + Desktop: { + type: "sst.cloudflare.StaticSite" + url: string } - "GITHUB_APP_PRIVATE_KEY": { - "type": "sst.sst.Secret" - "value": string + EMAILOCTOPUS_API_KEY: { + type: "sst.sst.Secret" + value: string } - "GITHUB_CLIENT_ID_CONSOLE": { - "type": "sst.sst.Secret" - "value": string + GITHUB_APP_ID: { + type: "sst.sst.Secret" + value: string } - "GITHUB_CLIENT_SECRET_CONSOLE": { - "type": "sst.sst.Secret" - "value": string + GITHUB_APP_PRIVATE_KEY: { + type: "sst.sst.Secret" + value: string } - "GOOGLE_CLIENT_ID": { - "type": "sst.sst.Secret" - "value": string + GITHUB_CLIENT_ID_CONSOLE: { + type: "sst.sst.Secret" + value: string } - "HONEYCOMB_API_KEY": { - "type": "sst.sst.Secret" - "value": string + GITHUB_CLIENT_SECRET_CONSOLE: { + type: "sst.sst.Secret" + value: string } - "STRIPE_SECRET_KEY": { - "type": "sst.sst.Secret" - "value": string + GOOGLE_CLIENT_ID: { + type: "sst.sst.Secret" + value: string } - "STRIPE_WEBHOOK_SECRET": { - "type": "sst.sst.Linkable" - "value": string + HONEYCOMB_API_KEY: { + type: "sst.sst.Secret" + value: string } - "Web": { - "type": "sst.cloudflare.Astro" - "url": string + STRIPE_SECRET_KEY: { + type: "sst.sst.Secret" + value: string } - "ZEN_MODELS1": { - "type": "sst.sst.Secret" - "value": string + STRIPE_WEBHOOK_SECRET: { + type: "sst.sst.Linkable" + value: string } - "ZEN_MODELS2": { - "type": "sst.sst.Secret" - "value": string + Web: { + type: "sst.cloudflare.Astro" + url: string + } + ZEN_MODELS1: { + type: "sst.sst.Secret" + value: string + } + ZEN_MODELS2: { + type: "sst.sst.Secret" + value: string } } } -// cloudflare -import * as cloudflare from "@cloudflare/workers-types"; +// cloudflare +import * as cloudflare from "@cloudflare/workers-types" declare module "sst" { export interface Resource { - "Api": cloudflare.Service - "AuthApi": cloudflare.Service - "AuthStorage": cloudflare.KVNamespace - "Bucket": cloudflare.R2Bucket - "LogProcessor": cloudflare.Service + Api: cloudflare.Service + AuthApi: cloudflare.Service + AuthStorage: cloudflare.KVNamespace + Bucket: cloudflare.R2Bucket + GatewayKv: cloudflare.KVNamespace + LogProcessor: cloudflare.Service } } import "sst" -export {} \ No newline at end of file +export {} diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 0c6e0288..26af4c7c 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.23", + "version": "1.0.55", "$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 01407434..bcd7c265 100644 --- a/packages/console/function/sst-env.d.ts +++ b/packages/console/function/sst-env.d.ts @@ -6,99 +6,108 @@ import "sst" declare module "sst" { export interface Resource { - "ADMIN_SECRET": { - "type": "sst.sst.Secret" - "value": string + ADMIN_SECRET: { + type: "sst.sst.Secret" + value: string } - "AUTH_API_URL": { - "type": "sst.sst.Linkable" - "value": string + AUTH_API_URL: { + type: "sst.sst.Linkable" + value: string } - "AWS_SES_ACCESS_KEY_ID": { - "type": "sst.sst.Secret" - "value": string + AWS_SES_ACCESS_KEY_ID: { + type: "sst.sst.Secret" + value: string } - "AWS_SES_SECRET_ACCESS_KEY": { - "type": "sst.sst.Secret" - "value": string + AWS_SES_SECRET_ACCESS_KEY: { + type: "sst.sst.Secret" + value: string } - "Console": { - "type": "sst.cloudflare.SolidStart" - "url": string + CLOUDFLARE_API_TOKEN: { + type: "sst.sst.Secret" + value: string } - "Database": { - "database": string - "host": string - "password": string - "port": number - "type": "sst.sst.Linkable" - "username": string + CLOUDFLARE_DEFAULT_ACCOUNT_ID: { + type: "sst.sst.Secret" + value: string } - "Desktop": { - "type": "sst.cloudflare.StaticSite" - "url": string + Console: { + type: "sst.cloudflare.SolidStart" + url: string } - "EMAILOCTOPUS_API_KEY": { - "type": "sst.sst.Secret" - "value": string + Database: { + database: string + host: string + password: string + port: number + type: "sst.sst.Linkable" + username: string } - "GITHUB_APP_ID": { - "type": "sst.sst.Secret" - "value": string + Desktop: { + type: "sst.cloudflare.StaticSite" + url: string } - "GITHUB_APP_PRIVATE_KEY": { - "type": "sst.sst.Secret" - "value": string + EMAILOCTOPUS_API_KEY: { + type: "sst.sst.Secret" + value: string } - "GITHUB_CLIENT_ID_CONSOLE": { - "type": "sst.sst.Secret" - "value": string + GITHUB_APP_ID: { + type: "sst.sst.Secret" + value: string } - "GITHUB_CLIENT_SECRET_CONSOLE": { - "type": "sst.sst.Secret" - "value": string + GITHUB_APP_PRIVATE_KEY: { + type: "sst.sst.Secret" + value: string } - "GOOGLE_CLIENT_ID": { - "type": "sst.sst.Secret" - "value": string + GITHUB_CLIENT_ID_CONSOLE: { + type: "sst.sst.Secret" + value: string } - "HONEYCOMB_API_KEY": { - "type": "sst.sst.Secret" - "value": string + GITHUB_CLIENT_SECRET_CONSOLE: { + type: "sst.sst.Secret" + value: string } - "STRIPE_SECRET_KEY": { - "type": "sst.sst.Secret" - "value": string + GOOGLE_CLIENT_ID: { + type: "sst.sst.Secret" + value: string } - "STRIPE_WEBHOOK_SECRET": { - "type": "sst.sst.Linkable" - "value": string + HONEYCOMB_API_KEY: { + type: "sst.sst.Secret" + value: string } - "Web": { - "type": "sst.cloudflare.Astro" - "url": string + STRIPE_SECRET_KEY: { + type: "sst.sst.Secret" + value: string } - "ZEN_MODELS1": { - "type": "sst.sst.Secret" - "value": string + STRIPE_WEBHOOK_SECRET: { + type: "sst.sst.Linkable" + value: string } - "ZEN_MODELS2": { - "type": "sst.sst.Secret" - "value": string + Web: { + type: "sst.cloudflare.Astro" + url: string + } + ZEN_MODELS1: { + type: "sst.sst.Secret" + value: string + } + ZEN_MODELS2: { + type: "sst.sst.Secret" + value: string } } } -// cloudflare -import * as cloudflare from "@cloudflare/workers-types"; +// cloudflare +import * as cloudflare from "@cloudflare/workers-types" declare module "sst" { export interface Resource { - "Api": cloudflare.Service - "AuthApi": cloudflare.Service - "AuthStorage": cloudflare.KVNamespace - "Bucket": cloudflare.R2Bucket - "LogProcessor": cloudflare.Service + Api: cloudflare.Service + AuthApi: cloudflare.Service + AuthStorage: cloudflare.KVNamespace + Bucket: cloudflare.R2Bucket + GatewayKv: cloudflare.KVNamespace + LogProcessor: cloudflare.Service } } import "sst" -export {} \ No newline at end of file +export {} diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 30e772b6..ef70beb6 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.23", + "version": "1.0.55", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/console/mail/sst-env.d.ts b/packages/console/mail/sst-env.d.ts index 9b9de732..bd558821 100644 --- a/packages/console/mail/sst-env.d.ts +++ b/packages/console/mail/sst-env.d.ts @@ -6,4 +6,4 @@ /// import "sst" -export {} \ No newline at end of file +export {} diff --git a/packages/console/resource/package.json b/packages/console/resource/package.json index 6553feed..f110f6c2 100644 --- a/packages/console/resource/package.json +++ b/packages/console/resource/package.json @@ -13,6 +13,9 @@ } }, "devDependencies": { - "@tsconfig/node22": "22.0.2" + "@cloudflare/workers-types": "catalog:", + "@tsconfig/node22": "22.0.2", + "@types/node": "catalog:", + "cloudflare": "5.2.0" } } diff --git a/packages/console/resource/resource.node.ts b/packages/console/resource/resource.node.ts index d7dbb6c6..f63d7bad 100644 --- a/packages/console/resource/resource.node.ts +++ b/packages/console/resource/resource.node.ts @@ -1 +1,58 @@ -export { Resource } from "sst" +import type { KVNamespaceListOptions, KVNamespaceListResult, KVNamespacePutOptions } from "@cloudflare/workers-types" +import { Resource as ResourceBase } from "sst" +import Cloudflare from "cloudflare" + +export const Resource = new Proxy( + {}, + { + get(_target, prop: keyof typeof ResourceBase) { + const value = ResourceBase[prop] + // @ts-ignore + if ("type" in value && value.type === "sst.cloudflare.Kv") { + const client = new Cloudflare({ + apiToken: ResourceBase.CLOUDFLARE_API_TOKEN.value, + }) + // @ts-ignore + const namespaceId = value.namespaceId + const accountId = ResourceBase.CLOUDFLARE_DEFAULT_ACCOUNT_ID.value + return { + get: (k: string | string[]) => { + const isMulti = Array.isArray(k) + return client.kv.namespaces + .bulkGet(namespaceId, { + keys: Array.isArray(k) ? k : [k], + account_id: accountId, + }) + .then((result) => (isMulti ? new Map(Object.entries(result?.values ?? {})) : result?.values?.[k])) + }, + put: (k: string, v: string, opts?: KVNamespacePutOptions) => + client.kv.namespaces.values.update(namespaceId, k, { + account_id: accountId, + value: v, + expiration: opts?.expiration, + expiration_ttl: opts?.expirationTtl, + metadata: opts?.metadata, + }), + delete: (k: string) => + client.kv.namespaces.values.delete(namespaceId, k, { + account_id: accountId, + }), + list: (opts?: KVNamespaceListOptions): Promise> => + client.kv.namespaces.keys + .list(namespaceId, { + account_id: accountId, + prefix: opts?.prefix ?? undefined, + }) + .then((result) => { + return { + keys: result.result, + list_complete: true, + cacheStatus: null, + } + }), + } + } + return value + }, + }, +) as Record diff --git a/packages/console/resource/sst-env.d.ts b/packages/console/resource/sst-env.d.ts index 01407434..bcd7c265 100644 --- a/packages/console/resource/sst-env.d.ts +++ b/packages/console/resource/sst-env.d.ts @@ -6,99 +6,108 @@ import "sst" declare module "sst" { export interface Resource { - "ADMIN_SECRET": { - "type": "sst.sst.Secret" - "value": string + ADMIN_SECRET: { + type: "sst.sst.Secret" + value: string } - "AUTH_API_URL": { - "type": "sst.sst.Linkable" - "value": string + AUTH_API_URL: { + type: "sst.sst.Linkable" + value: string } - "AWS_SES_ACCESS_KEY_ID": { - "type": "sst.sst.Secret" - "value": string + AWS_SES_ACCESS_KEY_ID: { + type: "sst.sst.Secret" + value: string } - "AWS_SES_SECRET_ACCESS_KEY": { - "type": "sst.sst.Secret" - "value": string + AWS_SES_SECRET_ACCESS_KEY: { + type: "sst.sst.Secret" + value: string } - "Console": { - "type": "sst.cloudflare.SolidStart" - "url": string + CLOUDFLARE_API_TOKEN: { + type: "sst.sst.Secret" + value: string } - "Database": { - "database": string - "host": string - "password": string - "port": number - "type": "sst.sst.Linkable" - "username": string + CLOUDFLARE_DEFAULT_ACCOUNT_ID: { + type: "sst.sst.Secret" + value: string } - "Desktop": { - "type": "sst.cloudflare.StaticSite" - "url": string + Console: { + type: "sst.cloudflare.SolidStart" + url: string } - "EMAILOCTOPUS_API_KEY": { - "type": "sst.sst.Secret" - "value": string + Database: { + database: string + host: string + password: string + port: number + type: "sst.sst.Linkable" + username: string } - "GITHUB_APP_ID": { - "type": "sst.sst.Secret" - "value": string + Desktop: { + type: "sst.cloudflare.StaticSite" + url: string } - "GITHUB_APP_PRIVATE_KEY": { - "type": "sst.sst.Secret" - "value": string + EMAILOCTOPUS_API_KEY: { + type: "sst.sst.Secret" + value: string } - "GITHUB_CLIENT_ID_CONSOLE": { - "type": "sst.sst.Secret" - "value": string + GITHUB_APP_ID: { + type: "sst.sst.Secret" + value: string } - "GITHUB_CLIENT_SECRET_CONSOLE": { - "type": "sst.sst.Secret" - "value": string + GITHUB_APP_PRIVATE_KEY: { + type: "sst.sst.Secret" + value: string } - "GOOGLE_CLIENT_ID": { - "type": "sst.sst.Secret" - "value": string + GITHUB_CLIENT_ID_CONSOLE: { + type: "sst.sst.Secret" + value: string } - "HONEYCOMB_API_KEY": { - "type": "sst.sst.Secret" - "value": string + GITHUB_CLIENT_SECRET_CONSOLE: { + type: "sst.sst.Secret" + value: string } - "STRIPE_SECRET_KEY": { - "type": "sst.sst.Secret" - "value": string + GOOGLE_CLIENT_ID: { + type: "sst.sst.Secret" + value: string } - "STRIPE_WEBHOOK_SECRET": { - "type": "sst.sst.Linkable" - "value": string + HONEYCOMB_API_KEY: { + type: "sst.sst.Secret" + value: string } - "Web": { - "type": "sst.cloudflare.Astro" - "url": string + STRIPE_SECRET_KEY: { + type: "sst.sst.Secret" + value: string } - "ZEN_MODELS1": { - "type": "sst.sst.Secret" - "value": string + STRIPE_WEBHOOK_SECRET: { + type: "sst.sst.Linkable" + value: string } - "ZEN_MODELS2": { - "type": "sst.sst.Secret" - "value": string + Web: { + type: "sst.cloudflare.Astro" + url: string + } + ZEN_MODELS1: { + type: "sst.sst.Secret" + value: string + } + ZEN_MODELS2: { + type: "sst.sst.Secret" + value: string } } } -// cloudflare -import * as cloudflare from "@cloudflare/workers-types"; +// cloudflare +import * as cloudflare from "@cloudflare/workers-types" declare module "sst" { export interface Resource { - "Api": cloudflare.Service - "AuthApi": cloudflare.Service - "AuthStorage": cloudflare.KVNamespace - "Bucket": cloudflare.R2Bucket - "LogProcessor": cloudflare.Service + Api: cloudflare.Service + AuthApi: cloudflare.Service + AuthStorage: cloudflare.KVNamespace + Bucket: cloudflare.R2Bucket + GatewayKv: cloudflare.KVNamespace + LogProcessor: cloudflare.Service } } import "sst" -export {} \ No newline at end of file +export {} diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 34188445..25b278c4 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/desktop", - "version": "1.0.23", + "version": "1.0.55", "description": "", "type": "module", "scripts": { @@ -31,6 +31,7 @@ "@solid-primitives/event-bus": "1.1.2", "@solid-primitives/resize-observer": "2.1.3", "@solid-primitives/scroll": "2.1.3", + "@solid-primitives/storage": "4.3.3", "@solidjs/meta": "catalog:", "@solidjs/router": "0.15.3", "@thisbeyond/solid-dnd": "0.7.5", @@ -45,9 +46,5 @@ "solid-list": "catalog:", "tailwindcss": "catalog:", "virtua": "catalog:" - }, - "prettier": { - "semi": false, - "printWidth": 120 } } diff --git a/packages/desktop/src/components/code.tsx b/packages/desktop/src/components/code.tsx deleted file mode 100644 index 325f7b63..00000000 --- a/packages/desktop/src/components/code.tsx +++ /dev/null @@ -1,846 +0,0 @@ -import { bundledLanguages, type BundledLanguage, type ShikiTransformer } from "shiki" -import { splitProps, type ComponentProps, createEffect, onMount, onCleanup, createMemo, createResource } from "solid-js" -import { useLocal, type TextSelection } from "@/context/local" -import { getFileExtension, getNodeOffsetInLine, getSelectionInContainer } from "@/utils" -import { useShiki } from "@opencode-ai/ui" - -type DefinedSelection = Exclude - -interface Props extends ComponentProps<"div"> { - code: string - path: string -} - -export function Code(props: Props) { - const ctx = useLocal() - const highlighter = useShiki() - const [local, others] = splitProps(props, ["class", "classList", "code", "path"]) - const lang = createMemo(() => { - const ext = getFileExtension(local.path) - if (ext in bundledLanguages) return ext - return "text" - }) - - let container: HTMLDivElement | undefined - let isProgrammaticSelection = false - - const ranges = createMemo(() => { - const items = ctx.context.all() as Array<{ type: "file"; path: string; selection?: DefinedSelection }> - const result: DefinedSelection[] = [] - for (const item of items) { - if (item.path !== local.path) continue - const selection = item.selection - if (!selection) continue - result.push(selection) - } - return result - }) - - const createLineNumberTransformer = (selections: DefinedSelection[]): ShikiTransformer => { - const highlighted = new Set() - for (const selection of selections) { - const startLine = selection.startLine - const endLine = selection.endLine - const start = Math.max(1, Math.min(startLine, endLine)) - const end = Math.max(start, Math.max(startLine, endLine)) - const count = end - start + 1 - if (count <= 0) continue - const values = Array.from({ length: count }, (_, index) => start + index) - for (const value of values) highlighted.add(value) - } - return { - name: "line-number-highlight", - line(node, index) { - if (!highlighted.has(index)) return - this.addClassToHast(node, "line-number-highlight") - const children = node.children - if (!Array.isArray(children)) return - for (const child of children) { - if (!child || typeof child !== "object") continue - const element = child as { type?: string; properties?: { className?: string[] } } - if (element.type !== "element") continue - const className = element.properties?.className - if (!Array.isArray(className)) continue - const matches = className.includes("diff-oldln") || className.includes("diff-newln") - if (!matches) continue - if (className.includes("line-number-highlight")) continue - className.push("line-number-highlight") - } - }, - } - } - - const [html] = createResource( - () => ranges(), - async (activeRanges) => { - if (!highlighter.getLoadedLanguages().includes(lang())) { - await highlighter.loadLanguage(lang() as BundledLanguage) - } - return highlighter.codeToHtml(local.code || "", { - lang: lang() && lang() in bundledLanguages ? lang() : "text", - theme: "opencode", - transformers: [transformerUnifiedDiff(), transformerDiffGroups(), createLineNumberTransformer(activeRanges)], - }) as string - }, - ) - - onMount(() => { - if (!container) return - - let ticking = false - const onScroll = () => { - if (!container) return - // if (ctx.file.active()?.path !== local.path) return - if (ticking) return - ticking = true - requestAnimationFrame(() => { - ticking = false - ctx.file.scroll(local.path, container!.scrollTop) - }) - } - - const onSelectionChange = () => { - if (!container) return - if (isProgrammaticSelection) return - // if (ctx.file.active()?.path !== local.path) return - const d = getSelectionInContainer(container) - if (!d) return - const p = ctx.file.node(local.path)?.selection - if (p && p.startLine === d.sl && p.endLine === d.el && p.startChar === d.sch && p.endChar === d.ech) return - ctx.file.select(local.path, { startLine: d.sl, startChar: d.sch, endLine: d.el, endChar: d.ech }) - } - - const MOD = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) ? "Meta" : "Control" - const onKeyDown = (e: KeyboardEvent) => { - // if (ctx.file.active()?.path !== local.path) return - const ae = document.activeElement as HTMLElement | undefined - const tag = (ae?.tagName || "").toLowerCase() - const inputFocused = !!ae && (tag === "input" || tag === "textarea" || ae.isContentEditable) - if (inputFocused) return - if (e.getModifierState(MOD) && e.key.toLowerCase() === "a") { - e.preventDefault() - if (!container) return - const element = container.querySelector("code") as HTMLElement | undefined - if (!element) return - const lines = Array.from(element.querySelectorAll(".line")) - if (!lines.length) return - const r = document.createRange() - const last = lines[lines.length - 1] - r.selectNodeContents(last) - const lastLen = r.toString().length - ctx.file.select(local.path, { startLine: 1, startChar: 0, endLine: lines.length, endChar: lastLen }) - } - } - - container.addEventListener("scroll", onScroll) - document.addEventListener("selectionchange", onSelectionChange) - document.addEventListener("keydown", onKeyDown) - - onCleanup(() => { - container?.removeEventListener("scroll", onScroll) - document.removeEventListener("selectionchange", onSelectionChange) - document.removeEventListener("keydown", onKeyDown) - }) - }) - - // Restore scroll position from store when content is ready - createEffect(() => { - const content = html() - if (!container || !content) return - const top = ctx.file.node(local.path)?.scrollTop - if (top !== undefined && container.scrollTop !== top) container.scrollTop = top - }) - - // Sync selection from store -> DOM - createEffect(() => { - const content = html() - if (!container || !content) return - // if (ctx.file.active()?.path !== local.path) return - const codeEl = container.querySelector("code") as HTMLElement | undefined - if (!codeEl) return - const target = ctx.file.node(local.path)?.selection - const current = getSelectionInContainer(container) - const sel = window.getSelection() - if (!sel) return - if (!target) { - if (current) { - isProgrammaticSelection = true - sel.removeAllRanges() - queueMicrotask(() => { - isProgrammaticSelection = false - }) - } - return - } - const matches = !!( - current && - current.sl === target.startLine && - current.sch === target.startChar && - current.el === target.endLine && - current.ech === target.endChar - ) - if (matches) return - const lines = Array.from(codeEl.querySelectorAll(".line")) - if (lines.length === 0) return - let sIdx = Math.max(0, target.startLine - 1) - let eIdx = Math.max(0, target.endLine - 1) - let sChar = Math.max(0, target.startChar || 0) - let eChar = Math.max(0, target.endChar || 0) - if (sIdx > eIdx || (sIdx === eIdx && sChar > eChar)) { - const ti = sIdx - sIdx = eIdx - eIdx = ti - const tc = sChar - sChar = eChar - eChar = tc - } - if (eChar === 0 && eIdx > sIdx) { - eIdx = eIdx - 1 - eChar = Number.POSITIVE_INFINITY - } - if (sIdx >= lines.length) return - if (eIdx >= lines.length) eIdx = lines.length - 1 - const s = getNodeOffsetInLine(lines[sIdx], sChar) ?? { node: lines[sIdx], offset: 0 } - const e = getNodeOffsetInLine(lines[eIdx], eChar) ?? { node: lines[eIdx], offset: lines[eIdx].childNodes.length } - const range = document.createRange() - range.setStart(s.node, s.offset) - range.setEnd(e.node, e.offset) - isProgrammaticSelection = true - sel.removeAllRanges() - sel.addRange(range) - queueMicrotask(() => { - isProgrammaticSelection = false - }) - }) - - // Build/toggle split layout and apply folding (both unified and split) - createEffect(() => { - const content = html() - if (!container || !content) return - const view = ctx.file.view(local.path) - - const pres = Array.from(container.querySelectorAll("pre")) - if (pres.length === 0) return - const originalPre = pres[0] - - const split = container.querySelector(".diff-split") - if (view === "diff-split") { - applySplitDiff(container) - const next = container.querySelector(".diff-split") - if (next) next.style.display = "" - originalPre.style.display = "none" - } else { - if (split) split.style.display = "none" - originalPre.style.display = "" - } - - const expanded = ctx.file.folded(local.path) - if (view === "diff-split") { - const left = container.querySelector(".diff-split pre:nth-child(1) code") - const right = container.querySelector(".diff-split pre:nth-child(2) code") - if (left) - applyDiffFolding(left, 3, { expanded, onExpand: (key) => ctx.file.unfold(local.path, key), side: "left" }) - if (right) - applyDiffFolding(right, 3, { expanded, onExpand: (key) => ctx.file.unfold(local.path, key), side: "right" }) - } else { - const code = container.querySelector("pre code") - if (code) - applyDiffFolding(code, 3, { - expanded, - onExpand: (key) => ctx.file.unfold(local.path, key), - }) - } - }) - - // Highlight groups + scroll coupling - const clearHighlights = () => { - if (!container) return - container.querySelectorAll(".diff-selected").forEach((el) => el.classList.remove("diff-selected")) - } - - const applyHighlight = (idx: number, scroll?: boolean) => { - if (!container) return - const view = ctx.file.view(local.path) - if (view === "raw") return - - clearHighlights() - - const nodes: HTMLElement[] = [] - if (view === "diff-split") { - const left = container.querySelector(".diff-split pre:nth-child(1) code") - const right = container.querySelector(".diff-split pre:nth-child(2) code") - if (left) - nodes.push(...Array.from(left.querySelectorAll(`[data-chgrp="${idx}"][data-diff="remove"]`))) - if (right) - nodes.push(...Array.from(right.querySelectorAll(`[data-chgrp="${idx}"][data-diff="add"]`))) - } else { - const code = container.querySelector("pre code") - if (code) nodes.push(...Array.from(code.querySelectorAll(`[data-chgrp="${idx}"]`))) - } - - for (const n of nodes) n.classList.add("diff-selected") - if (scroll && nodes.length) nodes[0].scrollIntoView({ block: "center", behavior: "smooth" }) - } - - const countGroups = () => { - if (!container) return 0 - const code = container.querySelector("pre code") - if (!code) return 0 - const set = new Set() - for (const el of Array.from(code.querySelectorAll(".diff-line[data-chgrp]"))) { - const v = el.getAttribute("data-chgrp") - if (v != undefined) set.add(v) - } - return set.size - } - - let lastIdx: number | undefined = undefined - let lastView: string | undefined - let lastContent: string | undefined - let lastRawIdx: number | undefined = undefined - createEffect(() => { - const content = html() - if (!container || !content) return - const view = ctx.file.view(local.path) - const raw = ctx.file.changeIndex(local.path) - if (raw === undefined) return - const total = countGroups() - if (total <= 0) return - const next = ((raw % total) + total) % total - - const navigated = lastRawIdx !== undefined && lastRawIdx !== raw - - if (next !== raw) { - ctx.file.setChangeIndex(local.path, next) - applyHighlight(next, true) - } else { - if (lastView !== view || lastContent !== content) applyHighlight(next) - if ((lastIdx !== undefined && lastIdx !== next) || navigated) applyHighlight(next, true) - } - - lastRawIdx = raw - lastIdx = next - lastView = view - lastContent = content - }) - - return ( -

{ - container = el - }} - innerHTML={html()} - class=" - font-mono text-xs tracking-wide overflow-y-auto h-full - [&]:[counter-reset:line] - [&_pre]:focus-visible:outline-none - [&_pre]:overflow-x-auto [&_pre]:no-scrollbar - [&_code]:min-w-full [&_code]:inline-block - [&_.tab]:relative - [&_.tab::before]:content['⇥'] - [&_.tab::before]:absolute - [&_.tab::before]:opacity-0 - [&_.space]:relative - [&_.space::before]:content-['·'] - [&_.space::before]:absolute - [&_.space::before]:opacity-0 - [&_.line]:inline-block [&_.line]:w-full - [&_.line]:hover:bg-background-element - [&_.line::before]:sticky [&_.line::before]:left-0 - [&_.line::before]:w-12 [&_.line::before]:pr-4 - [&_.line::before]:z-10 - [&_.line::before]:bg-background-panel - [&_.line::before]:text-text-muted/60 - [&_.line::before]:text-right [&_.line::before]:inline-block - [&_.line::before]:select-none - [&_.line::before]:[counter-increment:line] - [&_.line::before]:content-[counter(line)] - [&_.line-number-highlight]:bg-accent/20 - [&_.line-number-highlight::before]:bg-accent/40! - [&_.line-number-highlight::before]:text-background-panel! - [&_code.code-diff_.line::before]:content-[''] - [&_code.code-diff_.line::before]:w-0 - [&_code.code-diff_.line::before]:pr-0 - [&_.diff-split_code.code-diff::before]:w-10 - [&_.diff-split_.diff-newln]:left-0 - [&_.diff-oldln]:sticky [&_.diff-oldln]:left-0 - [&_.diff-oldln]:w-10 [&_.diff-oldln]:pr-2 - [&_.diff-oldln]:z-40 - [&_.diff-oldln]:text-text-muted/60 - [&_.diff-oldln]:text-right [&_.diff-oldln]:inline-block - [&_.diff-oldln]:select-none - [&_.diff-oldln]:bg-background-panel - [&_.diff-newln]:sticky [&_.diff-newln]:left-10 - [&_.diff-newln]:w-10 [&_.diff-newln]:pr-2 - [&_.diff-newln]:z-40 - [&_.diff-newln]:text-text-muted/60 - [&_.diff-newln]:text-right [&_.diff-newln]:inline-block - [&_.diff-newln]:select-none - [&_.diff-newln]:bg-background-panel - [&_.diff-add]:bg-success/20! - [&_.diff-add.diff-selected]:bg-success/50! - [&_.diff-add_.diff-oldln]:bg-success! - [&_.diff-add_.diff-oldln]:text-background-panel! - [&_.diff-add_.diff-newln]:bg-success! - [&_.diff-add_.diff-newln]:text-background-panel! - [&_.diff-remove]:bg-error/20! - [&_.diff-remove.diff-selected]:bg-error/50! - [&_.diff-remove_.diff-newln]:bg-error! - [&_.diff-remove_.diff-newln]:text-background-panel! - [&_.diff-remove_.diff-oldln]:bg-error! - [&_.diff-remove_.diff-oldln]:text-background-panel! - [&_.diff-sign]:inline-block [&_.diff-sign]:px-2 [&_.diff-sign]:select-none - [&_.diff-blank]:bg-background-element - [&_.diff-blank_.diff-oldln]:bg-background-element - [&_.diff-blank_.diff-newln]:bg-background-element - [&_.diff-collapsed]:block! [&_.diff-collapsed]:w-full [&_.diff-collapsed]:relative - [&_.diff-collapsed]:select-none - [&_.diff-collapsed]:bg-info/20 [&_.diff-collapsed]:hover:bg-info/40! - [&_.diff-collapsed]:text-info/80 [&_.diff-collapsed]:hover:text-info - [&_.diff-collapsed]:text-xs - [&_.diff-collapsed_.diff-oldln]:bg-info! - [&_.diff-collapsed_.diff-newln]:bg-info! - " - classList={{ - ...(local.classList || {}), - [local.class ?? ""]: !!local.class, - }} - {...others} - >
- ) -} - -function transformerUnifiedDiff(): ShikiTransformer { - const kinds = new Map() - const meta = new Map() - let isDiff = false - - return { - name: "unified-diff", - preprocess(input) { - kinds.clear() - meta.clear() - isDiff = false - - const ls = input.split(/\r?\n/) - const out: Array = [] - let oldNo = 0 - let newNo = 0 - let inHunk = false - - for (let i = 0; i < ls.length; i++) { - const s = ls[i] - - const m = s.match(/^@@\s*-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s*@@/) - if (m) { - isDiff = true - inHunk = true - oldNo = parseInt(m[1], 10) - newNo = parseInt(m[3], 10) - continue - } - - if ( - /^diff --git /.test(s) || - /^Index: /.test(s) || - /^--- /.test(s) || - /^\+\+\+ /.test(s) || - /^[=]{3,}$/.test(s) || - /^\*{3,}$/.test(s) || - /^\\ No newline at end of file$/.test(s) - ) { - isDiff = true - continue - } - - if (!inHunk) { - out.push(s) - continue - } - - if (/^\+/.test(s)) { - out.push(s) - const ln = out.length - kinds.set(ln, "add") - meta.set(ln, { new: newNo, sign: "+" }) - newNo++ - continue - } - - if (/^-/.test(s)) { - out.push(s) - const ln = out.length - kinds.set(ln, "remove") - meta.set(ln, { old: oldNo, sign: "-" }) - oldNo++ - continue - } - - if (/^ /.test(s)) { - out.push(s) - const ln = out.length - kinds.set(ln, "context") - meta.set(ln, { old: oldNo, new: newNo }) - oldNo++ - newNo++ - continue - } - - // fallback in hunks - out.push(s) - } - - return out.join("\n").trimEnd() - }, - code(node) { - if (isDiff) this.addClassToHast(node, "code-diff") - }, - pre(node) { - if (isDiff) this.addClassToHast(node, "code-diff") - }, - line(node, line) { - if (!isDiff) return - const kind = kinds.get(line) - if (!kind) return - - const m = meta.get(line) || {} - - this.addClassToHast(node, "diff-line") - this.addClassToHast(node, `diff-${kind}`) - node.properties = node.properties || {} - ;(node.properties as any)["data-diff"] = kind - if (m.old != undefined) (node.properties as any)["data-old"] = String(m.old) - if (m.new != undefined) (node.properties as any)["data-new"] = String(m.new) - - const oldSpan = { - type: "element", - tagName: "span", - properties: { className: ["diff-oldln"] }, - children: [{ type: "text", value: m.old != undefined ? String(m.old) : " " }], - } - const newSpan = { - type: "element", - tagName: "span", - properties: { className: ["diff-newln"] }, - children: [{ type: "text", value: m.new != undefined ? String(m.new) : " " }], - } - - if (kind === "add" || kind === "remove" || kind === "context") { - const first = (node.children && (node.children as any[])[0]) as any - if (first && first.type === "element" && first.children && first.children.length > 0) { - const t = first.children[0] - if (t && t.type === "text" && typeof t.value === "string" && t.value.length > 0) { - const ch = t.value[0] - if (ch === "+" || ch === "-" || ch === " ") t.value = t.value.slice(1) - } - } - } - - const signSpan = { - type: "element", - tagName: "span", - properties: { className: ["diff-sign"] }, - children: [{ type: "text", value: (m as any).sign || " " }], - } - - // @ts-expect-error hast typing across versions - node.children = [oldSpan, newSpan, signSpan, ...(node.children || [])] - }, - } -} - -function transformerDiffGroups(): ShikiTransformer { - let group = -1 - let inGroup = false - return { - name: "diff-groups", - pre() { - group = -1 - inGroup = false - }, - line(node) { - const props = (node.properties || {}) as any - const kind = props["data-diff"] as string | undefined - if (kind === "add" || kind === "remove") { - if (!inGroup) { - group += 1 - inGroup = true - } - ;(node.properties as any)["data-chgrp"] = String(group) - } else { - inGroup = false - } - }, - } -} - -function applyDiffFolding( - root: HTMLElement, - context = 3, - options?: { expanded?: string[]; onExpand?: (key: string) => void; side?: "left" | "right" }, -) { - if (!root.classList.contains("code-diff")) return - - // Cleanup: unwrap previous collapsed blocks and remove toggles - const blocks = Array.from(root.querySelectorAll(".diff-collapsed-block")) - for (const block of blocks) { - const p = block.parentNode - if (!p) { - block.remove() - continue - } - while (block.firstChild) p.insertBefore(block.firstChild, block) - block.remove() - } - const toggles = Array.from(root.querySelectorAll(".diff-collapsed")) - for (const t of toggles) t.remove() - - const lines = Array.from(root.querySelectorAll(".diff-line")) - if (lines.length === 0) return - - const n = lines.length - const isChange = lines.map((l) => l.dataset["diff"] === "add" || l.dataset["diff"] === "remove") - const isContext = lines.map((l) => l.dataset["diff"] === "context") - if (!isChange.some(Boolean)) return - - const visible = new Array(n).fill(false) as boolean[] - for (let i = 0; i < n; i++) if (isChange[i]) visible[i] = true - for (let i = 0; i < n; i++) { - if (isChange[i]) { - const s = Math.max(0, i - context) - const e = Math.min(n - 1, i + context) - for (let j = s; j <= e; j++) if (isContext[j]) visible[j] = true - } - } - - type Range = { start: number; end: number } - const ranges: Range[] = [] - let i = 0 - while (i < n) { - if (!visible[i] && isContext[i]) { - let j = i - while (j + 1 < n && !visible[j + 1] && isContext[j + 1]) j++ - ranges.push({ start: i, end: j }) - i = j + 1 - } else { - i++ - } - } - - for (const r of ranges) { - const start = lines[r.start] - const end = lines[r.end] - const count = r.end - r.start + 1 - const minCollapse = 20 - if (count < minCollapse) { - continue - } - - // Wrap the entire collapsed chunk (including trailing newline) so it takes no space - const block = document.createElement("span") - block.className = "diff-collapsed-block" - start.parentElement?.insertBefore(block, start) - - let cur: Node | undefined = start - while (cur) { - const next: Node | undefined = cur.nextSibling || undefined - block.appendChild(cur) - if (cur === end) { - // Also move the newline after the last line into the block - if (next && next.nodeType === Node.TEXT_NODE && (next.textContent || "").startsWith("\n")) { - block.appendChild(next) - } - break - } - cur = next - } - - block.style.display = "none" - const row = document.createElement("span") - row.className = "line diff-collapsed" - row.setAttribute("data-kind", "collapsed") - row.setAttribute("data-count", String(count)) - row.setAttribute("tabindex", "0") - row.setAttribute("role", "button") - - const oldln = document.createElement("span") - oldln.className = "diff-oldln" - oldln.textContent = " " - - const newln = document.createElement("span") - newln.className = "diff-newln" - newln.textContent = " " - - const sign = document.createElement("span") - sign.className = "diff-sign" - sign.textContent = "…" - - const label = document.createElement("span") - label.textContent = `show ${count} unchanged line${count > 1 ? "s" : ""}` - - const key = `o${start.dataset["old"] || ""}-${end.dataset["old"] || ""}:n${start.dataset["new"] || ""}-${end.dataset["new"] || ""}` - - const show = (record = true) => { - if (record) options?.onExpand?.(key) - const p = block.parentNode - if (p) { - while (block.firstChild) p.insertBefore(block.firstChild, block) - block.remove() - } - row.remove() - } - - row.addEventListener("click", () => show(true)) - row.addEventListener("keydown", (ev) => { - if (ev.key === "Enter" || ev.key === " ") { - ev.preventDefault() - show(true) - } - }) - - block.parentElement?.insertBefore(row, block) - if (!options?.side || options.side === "left") row.appendChild(oldln) - if (!options?.side || options.side === "right") row.appendChild(newln) - row.appendChild(sign) - row.appendChild(label) - - if (options?.expanded && options.expanded.includes(key)) { - show(false) - } - } -} - -function applySplitDiff(container: HTMLElement) { - const pres = Array.from(container.querySelectorAll("pre")) - if (pres.length === 0) return - const originalPre = pres[0] - const originalCode = originalPre.querySelector("code") as HTMLElement | undefined - if (!originalCode || !originalCode.classList.contains("code-diff")) return - - // Rebuild split each time to match current content - const existing = container.querySelector(".diff-split") - if (existing) existing.remove() - - const grid = document.createElement("div") - grid.className = "diff-split grid grid-cols-2 gap-x-6" - - const makeColumn = () => { - const pre = document.createElement("pre") - pre.className = originalPre.className - const code = document.createElement("code") - code.className = originalCode.className - pre.appendChild(code) - return { pre, code } - } - - const left = makeColumn() - const right = makeColumn() - - // Helpers - const cloneSide = (line: HTMLElement, side: "old" | "new"): HTMLElement => { - const clone = line.cloneNode(true) as HTMLElement - const oldln = clone.querySelector(".diff-oldln") - const newln = clone.querySelector(".diff-newln") - if (side === "old") { - if (newln) newln.remove() - } else { - if (oldln) oldln.remove() - } - return clone - } - - const blankLine = (side: "old" | "new", kind: "add" | "remove"): HTMLElement => { - const span = document.createElement("span") - span.className = "line diff-line diff-blank" - span.setAttribute("data-diff", kind) - const ln = document.createElement("span") - ln.className = side === "old" ? "diff-oldln" : "diff-newln" - ln.textContent = " " - span.appendChild(ln) - return span - } - - const lines = Array.from(originalCode.querySelectorAll(".diff-line")) - let i = 0 - while (i < lines.length) { - const cur = lines[i] - const kind = cur.dataset["diff"] - - if (kind === "context") { - left.code.appendChild(cloneSide(cur, "old")) - left.code.appendChild(document.createTextNode("\n")) - right.code.appendChild(cloneSide(cur, "new")) - right.code.appendChild(document.createTextNode("\n")) - i++ - continue - } - - if (kind === "remove") { - // Batch consecutive removes and following adds, then pair - const removes: HTMLElement[] = [] - const adds: HTMLElement[] = [] - let j = i - while (j < lines.length && lines[j].dataset["diff"] === "remove") { - removes.push(lines[j]) - j++ - } - let k = j - while (k < lines.length && lines[k].dataset["diff"] === "add") { - adds.push(lines[k]) - k++ - } - - const pairs = Math.min(removes.length, adds.length) - for (let p = 0; p < pairs; p++) { - left.code.appendChild(cloneSide(removes[p], "old")) - left.code.appendChild(document.createTextNode("\n")) - right.code.appendChild(cloneSide(adds[p], "new")) - right.code.appendChild(document.createTextNode("\n")) - } - for (let p = pairs; p < removes.length; p++) { - left.code.appendChild(cloneSide(removes[p], "old")) - left.code.appendChild(document.createTextNode("\n")) - right.code.appendChild(blankLine("new", "remove")) - right.code.appendChild(document.createTextNode("\n")) - } - for (let p = pairs; p < adds.length; p++) { - left.code.appendChild(blankLine("old", "add")) - left.code.appendChild(document.createTextNode("\n")) - right.code.appendChild(cloneSide(adds[p], "new")) - right.code.appendChild(document.createTextNode("\n")) - } - - i = k - continue - } - - if (kind === "add") { - // Run of adds not preceded by removes - const adds: HTMLElement[] = [] - let j = i - while (j < lines.length && lines[j].dataset["diff"] === "add") { - adds.push(lines[j]) - j++ - } - for (let p = 0; p < adds.length; p++) { - left.code.appendChild(blankLine("old", "add")) - left.code.appendChild(document.createTextNode("\n")) - right.code.appendChild(cloneSide(adds[p], "new")) - right.code.appendChild(document.createTextNode("\n")) - } - i = j - continue - } - - // Any other kind: mirror as context - left.code.appendChild(cloneSide(cur, "old")) - left.code.appendChild(document.createTextNode("\n")) - right.code.appendChild(cloneSide(cur, "new")) - right.code.appendChild(document.createTextNode("\n")) - i++ - } - - grid.appendChild(left.pre) - grid.appendChild(right.pre) - container.appendChild(grid) -} diff --git a/packages/desktop/src/components/file-tree.tsx b/packages/desktop/src/components/file-tree.tsx index a5d19f51..1347ecae 100644 --- a/packages/desktop/src/components/file-tree.tsx +++ b/packages/desktop/src/components/file-tree.tsx @@ -77,7 +77,7 @@ export default function FileTree(props: { (open ? local.file.expand(node.path) : local.file.collapse(node.path))} > @@ -85,7 +85,7 @@ export default function FileTree(props: { diff --git a/packages/desktop/src/components/message-progress.tsx b/packages/desktop/src/components/message-progress.tsx index c0037f57..a9be2ae5 100644 --- a/packages/desktop/src/components/message-progress.tsx +++ b/packages/desktop/src/components/message-progress.tsx @@ -70,9 +70,8 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa const lastPart = createMemo(() => resolvedParts().slice(-1)?.at(0)) const rawStatus = createMemo(() => { - const defaultStatus = "Working..." const last = lastPart() - if (!last) return defaultStatus + if (!last) return undefined if (last.type === "tool") { switch (last.tool) { @@ -102,7 +101,7 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa } else if (last.type === "text") { return "Gathering thoughts..." } - return defaultStatus + return undefined }) const [status, setStatus] = createSignal(rawStatus()) @@ -111,11 +110,11 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa createEffect(() => { const newStatus = rawStatus() - if (newStatus === status()) return + if (newStatus === status() || !newStatus) return const timeSinceLastChange = Date.now() - lastStatusChange - if (timeSinceLastChange >= 1000) { + if (timeSinceLastChange >= 1500) { setStatus(newStatus) lastStatusChange = Date.now() if (statusTimeout) { @@ -145,7 +144,7 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa {/* )} */} {/* */}
- {status()} + {status() ?? "Considering next steps..."}
0}>
void class?: string ref?: (el: HTMLDivElement) => void } export const PromptInput: Component = (props) => { + const navigate = useNavigate() + const sdk = useSDK() + const sync = useSync() const local = useLocal() + const session = useSession() let editorRef!: HTMLDivElement - const defaultParts = [{ type: "text", content: "", start: 0, end: 0 } as const] const [store, setStore] = createStore<{ - contentParts: ContentPart[] popoverIsOpen: boolean }>({ - contentParts: defaultParts, popoverIsOpen: false, }) - const isEmpty = createMemo(() => isEqual(store.contentParts, defaultParts)) + createEffect(() => { + session.id + editorRef.focus() + }) + const isFocused = createFocusSignal(() => editorRef) const handlePaste = (event: ClipboardEvent) => { @@ -71,14 +61,16 @@ export const PromptInput: Component = (props) => { } }) + const handleFileSelect = (path: string | undefined) => { + if (!path) return + addPart({ type: "file", path, content: "@" + getFilename(path), start: 0, end: 0 }) + setStore("popoverIsOpen", false) + } + const { flat, active, onInput, onKeyDown, refetch } = useFilteredList({ items: local.file.searchFilesAndDirectories, key: (x) => x, - onSelect: (path) => { - if (!path) return - addPart({ type: "file", path, content: "@" + getFilename(path), start: 0, end: 0 }) - setStore("popoverIsOpen", false) - }, + onSelect: handleFileSelect, }) createEffect(() => { @@ -88,10 +80,10 @@ export const PromptInput: Component = (props) => { createEffect( on( - () => store.contentParts, + () => session.prompt.current(), (currentParts) => { const domParts = parseFromDOM() - if (isEqual(currentParts, domParts)) return + if (isPromptEqual(currentParts, domParts)) return const selection = window.getSelection() let cursorPosition: number | null = null @@ -122,8 +114,18 @@ export const PromptInput: Component = (props) => { ), ) - const parseFromDOM = (): ContentPart[] => { - const newParts: ContentPart[] = [] + createEffect( + on( + () => session.prompt.cursor(), + (cursor) => { + if (cursor === undefined) return + queueMicrotask(() => setCursorPosition(editorRef, cursor)) + }, + ), + ) + + const parseFromDOM = (): Prompt => { + const newParts: Prompt = [] let position = 0 editorRef.childNodes.forEach((node) => { if (node.nodeType === Node.TEXT_NODE) { @@ -150,7 +152,7 @@ export const PromptInput: Component = (props) => { } } }) - if (newParts.length === 0) newParts.push(...defaultParts) + if (newParts.length === 0) newParts.push(...DEFAULT_PROMPT) return newParts } @@ -167,12 +169,13 @@ export const PromptInput: Component = (props) => { setStore("popoverIsOpen", false) } - setStore("contentParts", rawParts) + session.prompt.set(rawParts, cursorPosition) } const addPart = (part: ContentPart) => { const cursorPosition = getCursorPosition(editorRef) - const rawText = store.contentParts.map((p) => p.content).join("") + const prompt = session.prompt.current() + const rawText = prompt.map((p) => p.content).join("") const textBeforeCursor = rawText.substring(0, cursorPosition) const atMatch = textBeforeCursor.match(/@(\S*)$/) @@ -198,7 +201,7 @@ export const PromptInput: Component = (props) => { parts: nextParts, inserted, cursorPositionAfter, - } = store.contentParts.reduce( + } = prompt.reduce( (acc, item) => { if (acc.inserted) { acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length }) @@ -257,7 +260,7 @@ export const PromptInput: Component = (props) => { ) if (!inserted) { - const baseParts = store.contentParts.filter((item) => !(item.type === "text" && item.content === "")) + const baseParts = prompt.filter((item) => !(item.type === "text" && item.content === "")) const runningIndex = baseParts.reduce((sum, p) => sum + p.content.length, 0) const appendedAcc = { parts: [...baseParts] as ContentPart[], runningIndex } if (part.type === "text") { @@ -270,20 +273,27 @@ export const PromptInput: Component = (props) => { end: appendedAcc.runningIndex + part.content.length, }) } - const next = appendedAcc.parts.length > 0 ? appendedAcc.parts : defaultParts - setStore("contentParts", next) - setStore("popoverIsOpen", false) + const next = appendedAcc.parts.length > 0 ? appendedAcc.parts : DEFAULT_PROMPT const nextCursor = rawText.length + part.content.length + session.prompt.set(next, nextCursor) + setStore("popoverIsOpen", false) queueMicrotask(() => setCursorPosition(editorRef, nextCursor)) return } - setStore("contentParts", nextParts) + session.prompt.set(nextParts, cursorPositionAfter) setStore("popoverIsOpen", false) queueMicrotask(() => setCursorPosition(editorRef, cursorPositionAfter)) } + const abort = () => + sdk.client.session.abort({ + path: { + id: session.id!, + }, + }) + const handleKeyDown = (event: KeyboardEvent) => { if (store.popoverIsOpen && (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter")) { onKeyDown(event) @@ -293,14 +303,100 @@ export const PromptInput: Component = (props) => { if (event.key === "Enter" && !event.shiftKey) { handleSubmit(event) } + if (event.key === "Escape") { + if (store.popoverIsOpen) { + setStore("popoverIsOpen", false) + } else if (session.working()) { + abort() + } + } } - const handleSubmit = (event: Event) => { + const handleSubmit = async (event: Event) => { event.preventDefault() - if (store.contentParts.length > 0) { - props.onSubmit([...store.contentParts]) - setStore("contentParts", defaultParts) + const prompt = session.prompt.current() + const text = prompt.map((part) => part.content).join("") + if (text.trim().length === 0) { + if (session.working()) abort() + return } + + let existing = session.info() + if (!existing) { + const created = await sdk.client.session.create() + existing = created.data ?? undefined + if (existing) navigate(`/session/${existing.id}`) + } + if (!existing) return + + // if (!session.id) { + // session.layout.setOpenedTabs( + // session.layout.copyTabs("", session.id) + // } + + const toAbsolutePath = (path: string) => (path.startsWith("/") ? path : sync.absolute(path)) + const attachments = prompt.filter((part) => part.type === "file") + + // const activeFile = local.context.active() + // if (activeFile) { + // registerAttachment( + // activeFile.path, + // activeFile.selection, + // activeFile.name ?? formatAttachmentLabel(activeFile.path, activeFile.selection), + // ) + // } + + // for (const contextFile of local.context.all()) { + // registerAttachment( + // contextFile.path, + // contextFile.selection, + // formatAttachmentLabel(contextFile.path, contextFile.selection), + // ) + // } + + const attachmentParts = attachments.map((attachment) => { + const absolute = toAbsolutePath(attachment.path) + const query = attachment.selection + ? `?start=${attachment.selection.startLine}&end=${attachment.selection.endLine}` + : "" + return { + type: "file" as const, + mime: "text/plain", + url: `file://${absolute}${query}`, + filename: getFilename(attachment.path), + source: { + type: "file" as const, + text: { + value: attachment.content, + start: attachment.start, + end: attachment.end, + }, + path: absolute, + }, + } + }) + + session.layout.setActiveTab(undefined) + session.messages.setActive(undefined) + session.prompt.set(DEFAULT_PROMPT, 0) + + sdk.client.session.prompt({ + path: { id: existing.id }, + body: { + agent: local.agent.current()!.name, + model: { + modelID: local.model.current()!.id, + providerID: local.model.current()!.provider.id, + }, + parts: [ + { + type: "text", + text, + }, + ...attachmentParts, + ], + }, + }) } return ( @@ -310,11 +406,12 @@ export const PromptInput: Component = (props) => { 0} fallback={
No matching files
}> {(i) => ( -
handleFileSelect(i)} >
@@ -326,7 +423,7 @@ export const PromptInput: Component = (props) => {
-
+ )}
@@ -354,7 +451,7 @@ export const PromptInput: Component = (props) => { "[&>[data-type=file]]:text-icon-info-active": true, }} /> - +
Plan and build anything
@@ -419,29 +516,39 @@ export const PromptInput: Component = (props) => { )}
- + + +
+ Stop + ESC +
+
+ +
+ Send + +
+
+ + } + > + +
) } -function isEqual(arrA: ContentPart[], arrB: ContentPart[]): boolean { - if (arrA.length !== arrB.length) return false - for (let i = 0; i < arrA.length; i++) { - const partA = arrA[i] - const partB = arrB[i] - if (partA.type !== partB.type) return false - if (partA.type === "text" && partA.content !== (partB as TextPart).content) { - return false - } - if (partA.type === "file" && partA.path !== (partB as FileAttachmentPart).path) { - return false - } - } - return true -} - function getCursorPosition(parent: HTMLElement): number { const selection = window.getSelection() if (!selection || selection.rangeCount === 0) return 0 diff --git a/packages/desktop/src/components/theme.json b/packages/desktop/src/components/theme.json deleted file mode 100644 index 36a4d020..00000000 --- a/packages/desktop/src/components/theme.json +++ /dev/null @@ -1,558 +0,0 @@ -{ - "colors": { - "actionBar.toggledBackground": "var(--surface-raised-base)", - "activityBarBadge.background": "var(--surface-brand-base)", - "checkbox.border": "var(--border-base)", - "editor.background": "transparent", - "editor.foreground": "var(--text-base)", - "editor.inactiveSelectionBackground": "var(--surface-raised-base)", - "editor.selectionHighlightBackground": "var(--border-active)", - "editorIndentGuide.activeBackground1": "var(--border-weak-base)", - "editorIndentGuide.background1": "var(--border-weak-base)", - "input.placeholderForeground": "var(--text-weak)", - "list.activeSelectionIconForeground": "var(--text-base)", - "list.dropBackground": "var(--surface-raised-base)", - "menu.background": "var(--surface-base)", - "menu.border": "var(--border-base)", - "menu.foreground": "var(--text-base)", - "menu.selectionBackground": "var(--surface-interactive-base)", - "menu.separatorBackground": "var(--border-base)", - "ports.iconRunningProcessForeground": "var(--icon-success-base)", - "sideBarSectionHeader.background": "transparent", - "sideBarSectionHeader.border": "var(--border-weak-base)", - "sideBarTitle.foreground": "var(--text-weak)", - "statusBarItem.remoteBackground": "var(--surface-success-base)", - "statusBarItem.remoteForeground": "var(--text-base)", - "tab.lastPinnedBorder": "var(--border-weak-base)", - "tab.selectedBackground": "var(--surface-raised-base)", - "tab.selectedForeground": "var(--text-weak)", - "terminal.inactiveSelectionBackground": "var(--surface-raised-base)", - "widget.border": "var(--border-base)" - }, - "displayName": "opencode", - "name": "opencode", - "semanticHighlighting": true, - "semanticTokenColors": { - "customLiteral": "var(--syntax-function)", - "newOperator": "var(--syntax-operator)", - "numberLiteral": "var(--syntax-number)", - "stringLiteral": "var(--syntax-string)" - }, - "tokenColors": [ - { - "scope": [ - "meta.embedded", - "source.groovy.embedded", - "string meta.image.inline.markdown", - "variable.legacy.builtin.python" - ], - "settings": { - "foreground": "var(--text-base)" - } - }, - { - "scope": "emphasis", - "settings": { - "fontStyle": "italic" - } - }, - { - "scope": "strong", - "settings": { - "fontStyle": "bold" - } - }, - { - "scope": "header", - "settings": { - "foreground": "var(--markdown-heading)" - } - }, - { - "scope": "comment", - "settings": { - "foreground": "var(--syntax-comment)" - } - }, - { - "scope": "constant.language", - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": [ - "constant.numeric", - "variable.other.enummember", - "keyword.operator.plus.exponent", - "keyword.operator.minus.exponent" - ], - "settings": { - "foreground": "var(--syntax-number)" - } - }, - { - "scope": "constant.regexp", - "settings": { - "foreground": "var(--syntax-operator)" - } - }, - { - "scope": "entity.name.tag", - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": ["entity.name.tag.css", "entity.name.tag.less"], - "settings": { - "foreground": "var(--syntax-operator)" - } - }, - { - "scope": "entity.other.attribute-name", - "settings": { - "foreground": "var(--syntax-variable)" - } - }, - { - "scope": [ - "entity.other.attribute-name.class.css", - "source.css entity.other.attribute-name.class", - "entity.other.attribute-name.id.css", - "entity.other.attribute-name.parent-selector.css", - "entity.other.attribute-name.parent.less", - "source.css entity.other.attribute-name.pseudo-class", - "entity.other.attribute-name.pseudo-element.css", - "source.css.less entity.other.attribute-name.id", - "entity.other.attribute-name.scss" - ], - "settings": { - "foreground": "var(--syntax-operator)" - } - }, - { - "scope": "invalid", - "settings": { - "foreground": "var(--syntax-critical)" - } - }, - { - "scope": "markup.underline", - "settings": { - "fontStyle": "underline" - } - }, - { - "scope": "markup.bold", - "settings": { - "fontStyle": "bold", - "foreground": "var(--markdown-strong)" - } - }, - { - "scope": "markup.heading", - "settings": { - "fontStyle": "bold", - "foreground": "var(--theme-markdown-heading)" - } - }, - { - "scope": "markup.italic", - "settings": { - "fontStyle": "italic" - } - }, - { - "scope": "markup.strikethrough", - "settings": { - "fontStyle": "strikethrough" - } - }, - { - "scope": "markup.inserted", - "settings": { - "foreground": "var(--text-diff-add-base)" - } - }, - { - "scope": "markup.deleted", - "settings": { - "foreground": "var(--text-diff-delete-base)" - } - }, - { - "scope": "markup.changed", - "settings": { - "foreground": "var(--text-base)" - } - }, - { - "scope": "punctuation.definition.quote.begin.markdown", - "settings": { - "foreground": "var(--markdown-block-quote)" - } - }, - { - "scope": "punctuation.definition.list.begin.markdown", - "settings": { - "foreground": "var(--markdown-list-enumeration)" - } - }, - { - "scope": "markup.inline.raw", - "settings": { - "foreground": "var(--markdown-code)" - } - }, - { - "scope": "punctuation.definition.tag", - "settings": { - "foreground": "var(--syntax-punctuation)" - } - }, - { - "scope": ["meta.preprocessor", "entity.name.function.preprocessor"], - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": "meta.preprocessor.string", - "settings": { - "foreground": "var(--syntax-string)" - } - }, - { - "scope": "meta.preprocessor.numeric", - "settings": { - "foreground": "var(--syntax-number)" - } - }, - { - "scope": "meta.structure.dictionary.key.python", - "settings": { - "foreground": "var(--syntax-variable)" - } - }, - { - "scope": "meta.diff.header", - "settings": { - "foreground": "var(--text-weak)" - } - }, - { - "scope": "storage", - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": "storage.type", - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": ["storage.modifier", "keyword.operator.noexcept"], - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": ["string", "meta.embedded.assembly"], - "settings": { - "foreground": "var(--syntax-string)" - } - }, - { - "scope": "string.tag", - "settings": { - "foreground": "var(--syntax-string)" - } - }, - { - "scope": "string.value", - "settings": { - "foreground": "var(--syntax-string)" - } - }, - { - "scope": "string.regexp", - "settings": { - "foreground": "var(--syntax-operator)" - } - }, - { - "scope": [ - "punctuation.definition.template-expression.begin", - "punctuation.definition.template-expression.end", - "punctuation.section.embedded" - ], - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": ["meta.template.expression"], - "settings": { - "foreground": "var(--text-base)" - } - }, - { - "scope": [ - "support.type.vendored.property-name", - "support.type.property-name", - "source.css variable", - "source.coffee.embedded" - ], - "settings": { - "foreground": "var(--syntax-variable)" - } - }, - { - "scope": "keyword", - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": "keyword.control", - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": "keyword.operator", - "settings": { - "foreground": "var(--syntax-operator)" - } - }, - { - "scope": [ - "keyword.operator.new", - "keyword.operator.expression", - "keyword.operator.cast", - "keyword.operator.sizeof", - "keyword.operator.alignof", - "keyword.operator.typeid", - "keyword.operator.alignas", - "keyword.operator.instanceof", - "keyword.operator.logical.python", - "keyword.operator.wordlike" - ], - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": "keyword.other.unit", - "settings": { - "foreground": "var(--syntax-number)" - } - }, - { - "scope": ["punctuation.section.embedded.begin.php", "punctuation.section.embedded.end.php"], - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": "support.function.git-rebase", - "settings": { - "foreground": "var(--syntax-variable)" - } - }, - { - "scope": "constant.sha.git-rebase", - "settings": { - "foreground": "var(--syntax-number)" - } - }, - { - "scope": ["storage.modifier.import.java", "variable.language.wildcard.java", "storage.modifier.package.java"], - "settings": { - "foreground": "var(--text-base)" - } - }, - { - "scope": "variable.language", - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": [ - "entity.name.function", - "support.function", - "support.constant.handlebars", - "source.powershell variable.other.member", - "entity.name.operator.custom-literal" - ], - "settings": { - "foreground": "var(--syntax-function)" - } - }, - { - "scope": [ - "support.class", - "support.type", - "entity.name.type", - "entity.name.namespace", - "entity.other.attribute", - "entity.name.scope-resolution", - "entity.name.class", - "storage.type.numeric.go", - "storage.type.byte.go", - "storage.type.boolean.go", - "storage.type.string.go", - "storage.type.uintptr.go", - "storage.type.error.go", - "storage.type.rune.go", - "storage.type.cs", - "storage.type.generic.cs", - "storage.type.modifier.cs", - "storage.type.variable.cs", - "storage.type.annotation.java", - "storage.type.generic.java", - "storage.type.java", - "storage.type.object.array.java", - "storage.type.primitive.array.java", - "storage.type.primitive.java", - "storage.type.token.java", - "storage.type.groovy", - "storage.type.annotation.groovy", - "storage.type.parameters.groovy", - "storage.type.generic.groovy", - "storage.type.object.array.groovy", - "storage.type.primitive.array.groovy", - "storage.type.primitive.groovy" - ], - "settings": { - "foreground": "var(--syntax-type)" - } - }, - { - "scope": [ - "meta.type.cast.expr", - "meta.type.new.expr", - "support.constant.math", - "support.constant.dom", - "support.constant.json", - "entity.other.inherited-class", - "punctuation.separator.namespace.ruby" - ], - "settings": { - "foreground": "var(--syntax-type)" - } - }, - { - "scope": [ - "keyword.control", - "source.cpp keyword.operator.new", - "keyword.operator.delete", - "keyword.other.using", - "keyword.other.directive.using", - "keyword.other.operator", - "entity.name.operator" - ], - "settings": { - "foreground": "var(--syntax-operator)" - } - }, - { - "scope": [ - "variable", - "meta.definition.variable.name", - "support.variable", - "entity.name.variable", - "constant.other.placeholder" - ], - "settings": { - "foreground": "var(--syntax-variable)" - } - }, - { - "scope": ["variable.other.constant", "variable.other.enummember"], - "settings": { - "foreground": "var(--syntax-variable)" - } - }, - { - "scope": ["meta.object-literal.key"], - "settings": { - "foreground": "var(--syntax-variable)" - } - }, - { - "scope": [ - "support.constant.property-value", - "support.constant.font-name", - "support.constant.media-type", - "support.constant.media", - "constant.other.color.rgb-value", - "constant.other.rgb-value", - "support.constant.color" - ], - "settings": { - "foreground": "var(--syntax-string)" - } - }, - { - "scope": [ - "punctuation.definition.group.regexp", - "punctuation.definition.group.assertion.regexp", - "punctuation.definition.character-class.regexp", - "punctuation.character.set.begin.regexp", - "punctuation.character.set.end.regexp", - "keyword.operator.negation.regexp", - "support.other.parenthesis.regexp" - ], - "settings": { - "foreground": "var(--syntax-string)" - } - }, - { - "scope": [ - "constant.character.character-class.regexp", - "constant.other.character-class.set.regexp", - "constant.other.character-class.regexp", - "constant.character.set.regexp" - ], - "settings": { - "foreground": "var(--syntax-operator)" - } - }, - { - "scope": ["keyword.operator.or.regexp", "keyword.control.anchor.regexp"], - "settings": { - "foreground": "var(--syntax-operator)" - } - }, - { - "scope": "keyword.operator.quantifier.regexp", - "settings": { - "foreground": "var(--syntax-operator)" - } - }, - { - "scope": ["constant.character", "constant.other.option"], - "settings": { - "foreground": "var(--syntax-keyword)" - } - }, - { - "scope": "constant.character.escape", - "settings": { - "foreground": "var(--syntax-operator)" - } - }, - { - "scope": "entity.name.label", - "settings": { - "foreground": "var(--text-weak)" - } - } - ], - "type": "dark" -} diff --git a/packages/desktop/src/context/local.tsx b/packages/desktop/src/context/local.tsx index 8fc05c45..1785bdf0 100644 --- a/packages/desktop/src/context/local.tsx +++ b/packages/desktop/src/context/local.tsx @@ -5,6 +5,7 @@ import type { FileContent, FileNode, Model, Provider, File as FileStatus } from import { createSimpleContext } from "./helper" import { useSDK } from "./sdk" import { useSync } from "./sync" +import { makePersisted } from "@solid-primitives/storage" export type LocalFile = FileNode & Partial<{ @@ -195,18 +196,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const file = (() => { const [store, setStore] = createStore<{ node: Record - // opened: string[] - // active?: string }>({ node: Object.fromEntries(sync.data.node.map((x) => [x.path, x])), - // opened: [], }) - // const active = createMemo(() => { - // if (!store.active) return undefined - // return store.node[store.active] - // }) - // const opened = createMemo(() => store.opened.map((x) => store.node[x])) const changeset = createMemo(() => new Set(sync.data.changes.map((f) => f.path))) const changes = createMemo(() => Array.from(changeset()).sort((a, b) => a.localeCompare(b))) @@ -247,18 +240,18 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ return false } - const resetNode = (path: string) => { - setStore("node", path, { - loaded: undefined, - pinned: undefined, - content: undefined, - selection: undefined, - scrollTop: undefined, - folded: undefined, - view: undefined, - selectedChange: undefined, - }) - } + // const resetNode = (path: string) => { + // setStore("node", path, { + // loaded: undefined, + // pinned: undefined, + // content: undefined, + // selection: undefined, + // scrollTop: undefined, + // folded: undefined, + // view: undefined, + // selectedChange: undefined, + // }) + // } const relative = (path: string) => path.replace(sync.data.path.directory + "/", "") @@ -333,31 +326,21 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ sdk.event.listen((e) => { const event = e.details switch (event.type) { - case "message.part.updated": - const part = event.properties.part - if (part.type === "tool" && part.state.status === "completed") { - switch (part.tool) { - case "read": - break - case "edit": - // load(part.state.input["filePath"] as string) - break - default: - break - } - } - break case "file.watcher.updated": - // setTimeout(sync.load.changes, 1000) - // const relativePath = relative(event.properties.file) - // if (relativePath.startsWith(".git/")) return - // load(relativePath) + const relativePath = relative(event.properties.file) + if (relativePath.startsWith(".git/")) return + load(relativePath) break } }) return { - node: (path: string) => store.node[path], + node: async (path: string) => { + if (!store.node[path]) { + await init(path) + } + return store.node[path] + }, update: (path: string, node: LocalFile) => setStore("node", path, reconcile(node)), open, load, @@ -417,121 +400,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ searchFiles, searchFilesAndDirectories, relative, - // active, - // opened, - // close(path: string) { - // setStore("opened", (opened) => opened.filter((x) => x !== path)) - // if (store.active === path) { - // const index = store.opened.findIndex((f) => f === path) - // const previous = store.opened[Math.max(0, index - 1)] - // setStore("active", previous) - // } - // resetNode(path) - // }, - // move(path: string, to: number) { - // const index = store.opened.findIndex((f) => f === path) - // if (index === -1) return - // setStore( - // "opened", - // produce((opened) => { - // opened.splice(to, 0, opened.splice(index, 1)[0]) - // }), - // ) - // setStore("node", path, "pinned", true) - // }, - } - })() - - const session = (() => { - const [store, setStore] = createStore<{ - active?: string - tabs: Record< - string, - { - active?: string - opened: string[] - } - > - }>({ - tabs: { - "": { - opened: [], - }, - }, - }) - - const active = createMemo(() => { - if (!store.active) return undefined - return sync.session.get(store.active) - }) - - createEffect(() => { - if (!store.active) return - sync.session.sync(store.active) - - if (!store.tabs[store.active]) { - setStore("tabs", store.active, { - opened: [], - }) - } - }) - - const tabs = createMemo(() => store.tabs[store.active ?? ""]) - - return { - active, - setActive(sessionId: string | undefined) { - setStore("active", sessionId) - }, - clearActive() { - setStore("active", undefined) - }, - tabs, - copyTabs(from: string, to: string) { - setStore("tabs", to, { - opened: store.tabs[from]?.opened ?? [], - }) - }, - setActiveTab(tab: string | undefined) { - setStore("tabs", store.active ?? "", "active", tab) - }, - async open(tab: string) { - if (tab !== "chat") { - await file.open(tab) - } - if (!tabs()?.opened?.includes(tab)) { - setStore("tabs", store.active ?? "", "opened", [...(tabs()?.opened ?? []), tab]) - } - setStore("tabs", store.active ?? "", "active", tab) - }, - close(tab: string) { - batch(() => { - if (!tabs()) return - setStore("tabs", store.active ?? "", { - active: tabs()!.active, - opened: tabs()!.opened.filter((x) => x !== tab), - }) - if (tabs()!.active === tab) { - const index = tabs()!.opened.findIndex((f) => f === tab) - const previous = tabs()!.opened[Math.max(0, index - 1)] - setStore("tabs", store.active ?? "", "active", previous) - } - }) - }, - move(tab: string, to: number) { - if (!tabs()) return - const index = tabs()!.opened.findIndex((f) => f === tab) - if (index === -1) return - setStore( - "tabs", - store.active ?? "", - "opened", - produce((opened) => { - opened.splice(to, 0, opened.splice(index, 1)[0]) - }), - ) - // setStore("node", path, "pinned", true) - }, } })() @@ -589,12 +457,60 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } })() + const layout = (() => { + const [store, setStore] = makePersisted( + createStore({ + sidebar: { + opened: true, + width: 240, + }, + review: { + state: "closed" as "open" | "closed" | "tab", + }, + }), + { + name: "default-layout", + }, + ) + + return { + sidebar: { + opened: createMemo(() => store.sidebar.opened), + open() { + setStore("sidebar", "opened", true) + }, + close() { + setStore("sidebar", "opened", false) + }, + toggle() { + setStore("sidebar", "opened", (x) => !x) + }, + width: createMemo(() => store.sidebar.width), + resize(width: number) { + setStore("sidebar", "width", width) + }, + }, + review: { + state: createMemo(() => store.review?.state ?? "closed"), + open() { + setStore("review", "state", "open") + }, + close() { + setStore("review", "state", "closed") + }, + tab() { + setStore("review", "state", "tab") + }, + }, + } + })() + const result = { model, agent, file, - session, context, + layout, } return result }, diff --git a/packages/desktop/src/context/session.tsx b/packages/desktop/src/context/session.tsx new file mode 100644 index 00000000..61fed945 --- /dev/null +++ b/packages/desktop/src/context/session.tsx @@ -0,0 +1,217 @@ +import { createStore, produce } from "solid-js/store" +import { createSimpleContext } from "./helper" +import { batch, createEffect, createMemo } from "solid-js" +import { useSync } from "./sync" +import { makePersisted } from "@solid-primitives/storage" +import { TextSelection, useLocal } from "./local" +import { pipe, sumBy } from "remeda" +import { AssistantMessage } from "@opencode-ai/sdk" + +export const { use: useSession, provider: SessionProvider } = createSimpleContext({ + name: "Session", + init: (props: { sessionId?: string }) => { + const sync = useSync() + const local = useLocal() + + const [store, setStore] = makePersisted( + createStore<{ + prompt: Prompt + cursorPosition?: number + messageId?: string + tabs: { + active?: string + opened: string[] + } + }>({ + prompt: [{ type: "text", content: "", start: 0, end: 0 }], + tabs: { + opened: [], + }, + }), + { + name: props.sessionId ?? "new-session", + }, + ) + + createEffect(() => { + if (!props.sessionId) return + sync.session.sync(props.sessionId) + }) + + const info = createMemo(() => (props.sessionId ? sync.session.get(props.sessionId) : undefined)) + const messages = createMemo(() => (props.sessionId ? (sync.data.message[props.sessionId] ?? []) : [])) + const userMessages = createMemo(() => + messages() + .filter((m) => m.role === "user") + .sort((a, b) => b.id.localeCompare(a.id)), + ) + const lastUserMessage = createMemo(() => { + return userMessages()?.at(0) + }) + const activeMessage = createMemo(() => { + if (!store.messageId) return lastUserMessage() + return userMessages()?.find((m) => m.id === store.messageId) + }) + const working = createMemo(() => { + if (!props.sessionId) return false + const last = lastUserMessage() + if (!last) return false + const assistantMessages = sync.data.message[props.sessionId]?.filter( + (m) => m.role === "assistant" && m.parentID == last?.id, + ) as AssistantMessage[] + const error = assistantMessages?.find((m) => m?.error)?.error + return !last?.summary?.body && !error + }) + + 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 last = createMemo( + () => messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage, + ) + const model = createMemo(() => + last() ? sync.data.provider.find((x) => x.id === last().providerID)?.models[last().modelID] : undefined, + ) + const diffs = createMemo(() => (props.sessionId ? (sync.data.session_diff[props.sessionId] ?? []) : [])) + + const tokens = createMemo(() => { + if (!last()) return + const tokens = last().tokens + return tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write + }) + + const context = createMemo(() => { + const total = tokens() + const limit = model()?.limit.context + if (!total || !limit) return 0 + return Math.round((total / limit) * 100) + }) + + return { + id: props.sessionId, + info, + working, + diffs, + prompt: { + current: createMemo(() => store.prompt), + cursor: createMemo(() => store.cursorPosition), + dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)), + set(prompt: Prompt, cursorPosition?: number) { + batch(() => { + setStore("prompt", prompt) + if (cursorPosition !== undefined) setStore("cursorPosition", cursorPosition) + }) + }, + }, + messages: { + all: messages, + user: userMessages, + last: lastUserMessage, + active: activeMessage, + setActive(id: string | undefined) { + setStore("messageId", id) + }, + }, + usage: { + tokens, + cost, + context, + }, + layout: { + tabs: store.tabs, + setActiveTab(tab: string | undefined) { + setStore("tabs", "active", tab) + }, + setOpenedTabs(tabs: string[]) { + setStore("tabs", "opened", tabs) + }, + async openTab(tab: string) { + if (tab === "chat") { + setStore("tabs", "active", undefined) + return + } + if (tab.startsWith("file://")) { + await local.file.open(tab.replace("file://", "")) + } + if (tab !== "review") { + if (!store.tabs.opened.includes(tab)) { + setStore("tabs", "opened", [...store.tabs.opened, tab]) + } + } + setStore("tabs", "active", tab) + }, + closeTab(tab: string) { + batch(() => { + setStore( + "tabs", + "opened", + store.tabs.opened.filter((x) => x !== tab), + ) + if (store.tabs.active === tab) { + const index = store.tabs.opened.findIndex((f) => f === tab) + const previous = store.tabs.opened[Math.max(0, index - 1)] + setStore("tabs", "active", previous) + } + }) + }, + moveTab(tab: string, to: number) { + const index = store.tabs.opened.findIndex((f) => f === tab) + if (index === -1) return + setStore( + "tabs", + "opened", + produce((opened) => { + opened.splice(to, 0, opened.splice(index, 1)[0]) + }), + ) + // setStore("node", path, "pinned", true) + }, + }, + } + }, +}) + +interface PartBase { + content: string + start: number + end: number +} + +export interface TextPart extends PartBase { + type: "text" +} + +export interface FileAttachmentPart extends PartBase { + type: "file" + path: string + selection?: TextSelection +} + +export type ContentPart = TextPart | FileAttachmentPart +export type Prompt = ContentPart[] + +export const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }] + +export function isPromptEqual(promptA: Prompt, promptB: Prompt): boolean { + if (promptA.length !== promptB.length) return false + for (let i = 0; i < promptA.length; i++) { + const partA = promptA[i] + const partB = promptB[i] + if (partA.type !== partB.type) return false + if (partA.type === "text" && partA.content !== (partB as TextPart).content) { + return false + } + if (partA.type === "file" && partA.path !== (partB as FileAttachmentPart).path) { + return false + } + } + return true +} diff --git a/packages/desktop/src/context/sync.tsx b/packages/desktop/src/context/sync.tsx index 06fc0567..bc9491fd 100644 --- a/packages/desktop/src/context/sync.tsx +++ b/packages/desktop/src/context/sync.tsx @@ -9,7 +9,8 @@ import type { File, FileNode, Project, - Command, + FileDiff, + Todo, } from "@opencode-ai/sdk" import { createStore, produce, reconcile } from "solid-js/store" import { createMemo } from "solid-js" @@ -28,6 +29,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ config: Config path: Path session: Session[] + session_diff: { + [sessionID: string]: FileDiff[] + } + todo: { + [sessionID: string]: Todo[] + } + limit: number message: { [sessionID: string]: Message[] } @@ -44,6 +52,9 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ agent: [], provider: [], session: [], + session_diff: {}, + todo: {}, + limit: 10, message: {}, part: {}, node: [], @@ -68,6 +79,12 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ ) break } + case "session.diff": + setStore("session_diff", event.properties.sessionID, event.properties.diff) + break + case "todo.updated": + setStore("todo", event.properties.sessionID, event.properties.todos) + break case "message.updated": { const messages = store.message[event.properties.info.sessionID] if (!messages) { @@ -118,12 +135,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ path: () => sdk.client.path.get().then((x) => setStore("path", x.data!)), agent: () => sdk.client.app.agents().then((x) => setStore("agent", x.data ?? [])), session: () => - sdk.client.session.list().then((x) => - setStore( - "session", - (x.data ?? []).slice().sort((a, b) => a.id.localeCompare(b.id)), - ), - ), + sdk.client.session.list().then((x) => { + const sessions = (x.data ?? []) + .slice() + .sort((a, b) => a.id.localeCompare(b.id)) + .slice(0, store.limit) + setStore("session", sessions) + }), config: () => sdk.client.config.get().then((x) => setStore("config", x.data!)), changes: () => sdk.client.file.status().then((x) => setStore("changes", x.data!)), node: () => sdk.client.file.list({ query: { path: "/" } }).then((x) => setStore("node", x.data!)), @@ -167,22 +185,19 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ if (match.found) return store.session[match.index] return undefined }, - async sync(sessionID: string, isRetry = false) { - const [session, messages] = await Promise.all([ - sdk.client.session.get({ path: { id: sessionID } }), - sdk.client.session.messages({ path: { id: sessionID } }), + async sync(sessionID: string, _isRetry = false) { + const [session, messages, todo, diff] = await Promise.all([ + sdk.client.session.get({ path: { id: sessionID }, throwOnError: true }), + sdk.client.session.messages({ path: { id: sessionID }, query: { limit: 100 } }), + sdk.client.session.todo({ path: { id: sessionID } }), + sdk.client.session.diff({ path: { id: sessionID } }), ]) - - // If no messages and this might be a new session, retry after a delay - if (!isRetry && messages.data!.length === 0) { - setTimeout(() => this.sync(sessionID, true), 500) - return - } - setStore( produce((draft) => { const match = Binary.search(draft.session, sessionID, (s) => s.id) - draft.session[match.index] = session.data! + if (match.found) draft.session[match.index] = session.data! + if (!match.found) draft.session.splice(match.index, 0, session.data!) + draft.todo[sessionID] = todo.data ?? [] draft.message[sessionID] = messages .data!.map((x) => x.info) .slice() @@ -193,9 +208,15 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ .map(sanitizePart) .sort((a, b) => a.id.localeCompare(b.id)) } + draft.session_diff[sessionID] = diff.data ?? [] }), ) }, + fetch: async (count = 10) => { + setStore("limit", (x) => x + count) + await load.session() + }, + more: createMemo(() => store.session.length === store.limit), }, load, absolute, diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index 9fe5da2f..63d96ae8 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -3,18 +3,22 @@ import "@/index.css" import { render } from "solid-js/web" import { Router, Route } from "@solidjs/router" import { MetaProvider } from "@solidjs/meta" -import { Fonts, ShikiProvider, MarkedProvider } from "@opencode-ai/ui" +import { Fonts, MarkedProvider } from "@opencode-ai/ui" import { SDKProvider } from "./context/sdk" import { SyncProvider } from "./context/sync" import { LocalProvider } from "./context/local" -import Home from "@/pages" +import Layout from "@/pages/layout" +import SessionLayout from "@/pages/session-layout" +import Session from "@/pages/session" const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1" const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096" const url = new URLSearchParams(document.location.search).get("url") || - (location.hostname.includes("opencode.ai") ? `http://${host}:${port}` : "/") + (location.hostname.includes("opencode.ai") || location.hostname.includes("localhost") + ? `http://${host}:${port}` + : "/") const root = document.getElementById("root") if (import.meta.env.DEV && !(root instanceof HTMLElement)) { @@ -25,22 +29,22 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) { render( () => ( - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + ), root!, ) diff --git a/packages/desktop/src/pages/index.tsx b/packages/desktop/src/pages/index.tsx deleted file mode 100644 index 5f04c3db..00000000 --- a/packages/desktop/src/pages/index.tsx +++ /dev/null @@ -1,857 +0,0 @@ -import { - Button, - List, - SelectDialog, - Tooltip, - IconButton, - Tabs, - Icon, - Accordion, - Diff, - Collapsible, - DiffChanges, - Message, - Typewriter, - Card, - Code, -} from "@opencode-ai/ui" -import { FileIcon } from "@/ui" -import FileTree from "@/components/file-tree" -import { MessageProgress } from "@/components/message-progress" -import { For, onCleanup, onMount, Show, Match, Switch, createSignal, createEffect, createMemo } from "solid-js" -import { useLocal, type LocalFile } from "@/context/local" -import { createStore } from "solid-js/store" -import { getDirectory, getFilename } from "@/utils" -import { ContentPart, PromptInput } from "@/components/prompt-input" -import { DateTime } from "luxon" -import { - DragDropProvider, - DragDropSensors, - DragOverlay, - SortableProvider, - closestCenter, - createSortable, - useDragDropContext, -} from "@thisbeyond/solid-dnd" -import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd" -import type { JSX } from "solid-js" -import { useSync } from "@/context/sync" -import { useSDK } from "@/context/sdk" -import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk" -import { Markdown } from "@opencode-ai/ui" -import { Spinner } from "@/components/spinner" - -export default function Page() { - const local = useLocal() - const sync = useSync() - const sdk = useSDK() - const [store, setStore] = createStore({ - clickTimer: undefined as number | undefined, - fileSelectOpen: false, - }) - let inputRef!: HTMLDivElement - let messageScrollElement!: HTMLDivElement - const [activeItem, setActiveItem] = createSignal(undefined) - - const MOD = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) ? "Meta" : "Control" - - onMount(() => { - document.addEventListener("keydown", handleKeyDown) - }) - - onCleanup(() => { - document.removeEventListener("keydown", handleKeyDown) - }) - - const handleKeyDown = (event: KeyboardEvent) => { - if (event.getModifierState(MOD) && event.shiftKey && event.key.toLowerCase() === "p") { - event.preventDefault() - return - } - if (event.getModifierState(MOD) && event.key.toLowerCase() === "p") { - event.preventDefault() - setStore("fileSelectOpen", true) - return - } - - const focused = document.activeElement === inputRef - if (focused) { - if (event.key === "Escape") { - inputRef?.blur() - } - return - } - - // if (local.file.active()) { - // const active = local.file.active()! - // if (event.key === "Enter" && active.selection) { - // local.context.add({ - // type: "file", - // path: active.path, - // selection: { ...active.selection }, - // }) - // return - // } - // - // if (event.getModifierState(MOD)) { - // if (event.key.toLowerCase() === "a") { - // return - // } - // if (event.key.toLowerCase() === "c") { - // return - // } - // } - // } - - if (event.key.length === 1 && event.key !== "Unidentified" && !(event.ctrlKey || event.metaKey)) { - inputRef?.focus() - } - } - - const resetClickTimer = () => { - if (!store.clickTimer) return - clearTimeout(store.clickTimer) - setStore("clickTimer", undefined) - } - - const startClickTimer = () => { - const newClickTimer = setTimeout(() => { - setStore("clickTimer", undefined) - }, 300) - setStore("clickTimer", newClickTimer as unknown as number) - } - - const handleFileClick = async (file: LocalFile) => { - if (store.clickTimer) { - resetClickTimer() - local.file.update(file.path, { ...file, pinned: true }) - } else { - local.file.open(file.path) - startClickTimer() - } - } - - // const navigateChange = (dir: 1 | -1) => { - // const active = local.file.active() - // if (!active) return - // const current = local.file.changeIndex(active.path) - // const next = current === undefined ? (dir === 1 ? 0 : -1) : current + dir - // local.file.setChangeIndex(active.path, next) - // } - - const handleTabChange = (path: string) => { - local.session.setActiveTab(path) - if (path === "chat") return - local.session.open(path) - } - - const handleTabClose = (file: LocalFile) => { - local.session.close(file.path) - } - - const handleDragStart = (event: unknown) => { - const id = getDraggableId(event) - if (!id) return - setActiveItem(id) - } - - const handleDragOver = (event: DragEvent) => { - const { draggable, droppable } = event - if (draggable && droppable) { - const currentFiles = local.session.tabs()?.opened.map((file) => file) - const fromIndex = currentFiles?.indexOf(draggable.id.toString()) - const toIndex = currentFiles?.indexOf(droppable.id.toString()) - if (fromIndex !== toIndex && toIndex !== undefined) { - local.session.move(draggable.id.toString(), toIndex) - } - } - } - - const handleDragEnd = () => { - setActiveItem(undefined) - } - - // const scrollDiffItem = (element: HTMLElement) => { - // element.scrollIntoView({ block: "start", behavior: "instant" }) - // } - - const handleDiffTriggerClick = (event: MouseEvent) => { - // disabling scroll to diff for now - return - // const target = event.currentTarget as HTMLElement - // queueMicrotask(() => { - // if (target.getAttribute("aria-expanded") !== "true") return - // const item = target.closest('[data-slot="accordion-item"]') as HTMLElement | null - // if (!item) return - // scrollDiffItem(item) - // }) - } - - const handlePromptSubmit = async (parts: ContentPart[]) => { - const existingSession = local.session.active() - let session = existingSession - if (!session) { - const created = await sdk.client.session.create() - session = created.data ?? undefined - } - if (!session) return - - local.session.setActive(session.id) - if (!existingSession) { - local.session.copyTabs("", session.id) - } - local.session.setActiveTab(undefined) - const toAbsolutePath = (path: string) => (path.startsWith("/") ? path : sync.absolute(path)) - - const text = parts.map((part) => part.content).join("") - const attachments = parts.filter((part) => part.type === "file") - - // const activeFile = local.context.active() - // if (activeFile) { - // registerAttachment( - // activeFile.path, - // activeFile.selection, - // activeFile.name ?? formatAttachmentLabel(activeFile.path, activeFile.selection), - // ) - // } - - // for (const contextFile of local.context.all()) { - // registerAttachment( - // contextFile.path, - // contextFile.selection, - // formatAttachmentLabel(contextFile.path, contextFile.selection), - // ) - // } - - const attachmentParts = attachments.map((attachment) => { - const absolute = toAbsolutePath(attachment.path) - const query = attachment.selection - ? `?start=${attachment.selection.startLine}&end=${attachment.selection.endLine}` - : "" - return { - type: "file" as const, - mime: "text/plain", - url: `file://${absolute}${query}`, - filename: getFilename(attachment.path), - source: { - type: "file" as const, - text: { - value: attachment.content, - start: attachment.start, - end: attachment.end, - }, - path: absolute, - }, - } - }) - - await sdk.client.session.prompt({ - path: { id: session.id }, - body: { - agent: local.agent.current()!.name, - model: { - modelID: local.model.current()!.id, - providerID: local.model.current()!.provider.id, - }, - parts: [ - { - type: "text", - text, - }, - ...attachmentParts, - ], - }, - }) - } - - const handleNewSession = () => { - local.session.setActive(undefined) - inputRef?.focus() - } - - const TabVisual = (props: { file: LocalFile }): JSX.Element => { - return ( -
- - - {props.file.name} - - -
- ) - } - - const SortableTab = (props: { - file: LocalFile - onTabClick: (file: LocalFile) => void - onTabClose: (file: LocalFile) => void - }): JSX.Element => { - const sortable = createSortable(props.file.path) - - return ( - // @ts-ignore -
- -
- props.onTabClick(props.file)} - > - - props.onTabClose(props.file)} - /> - -
-
-
- ) - } - - const ConstrainDragYAxis = (): JSX.Element => { - const context = useDragDropContext() - if (!context) return <> - const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context - const transformer: Transformer = { - id: "constrain-y-axis", - order: 100, - callback: (transform) => ({ ...transform, y: 0 }), - } - onDragStart((event) => { - const id = getDraggableId(event) - if (!id) return - addTransformer("draggables", id, transformer) - }) - onDragEnd((event) => { - const id = getDraggableId(event) - if (!id) return - removeTransformer("draggables", id, transformer.id) - }) - return <> - } - - const getDraggableId = (event: unknown): string | undefined => { - if (typeof event !== "object" || event === null) return undefined - if (!("draggable" in event)) return undefined - const draggable = (event as { draggable?: { id?: unknown } }).draggable - if (!draggable) return undefined - return typeof draggable.id === "string" ? draggable.id : undefined - } - - return ( -
- -
-
-
- {getFilename(sync.data.path.directory)} -
-
- - x.id} - current={local.session.active()} - onSelect={(s) => local.session.setActive(s?.id)} - onHover={(s) => (!!s ? sync.session.sync(s?.id) : undefined)} - > - {(session) => { - const diffs = createMemo(() => session.summary?.diffs ?? []) - const filesChanged = createMemo(() => diffs().length) - const updated = DateTime.fromMillis(session.time.updated) - return ( - -
-
- - {session.title} - - - {Math.abs(updated.diffNow().as("seconds")) < 60 - ? "Now" - : updated - .toRelative({ style: "short", unit: ["days", "hours", "minutes"] }) - ?.replace(" ago", "") - ?.replace(/ days?/, "d") - ?.replace(" min.", "m") - ?.replace(" hr.", "h")} - -
-
- {`${filesChanged() || "No"} file${filesChanged() !== 1 ? "s" : ""} changed`} - -
-
-
- ) - }} -
-
-
-
- - - - -
- - -
Chat
- {/* */} - {/* */} - {/*
{local.session.context() ?? 0}%
*/} - {/*
*/} -
- {/* Review */} - - - {(file) => ( - - )} - - -
- setStore("fileSelectOpen", true)} - /> -
-
-
- -
- -
New session
-
- -
- {getDirectory(sync.data.path.directory)} - {getFilename(sync.data.path.directory)} -
-
-
- -
- Last modified  - - {DateTime.fromMillis(sync.data.project.time.created).toRelative()} - -
-
-
- } - > - {(session) => { - const [store, setStore] = createStore<{ - messageId?: string - }>() - - const messages = createMemo(() => sync.data.message[session().id] ?? []) - const userMessages = createMemo(() => - messages() - .filter((m) => m.role === "user") - .sort((a, b) => b.id.localeCompare(a.id)), - ) - const lastUserMessage = createMemo(() => { - return userMessages()?.at(0) - }) - const activeMessage = createMemo(() => { - if (!store.messageId) return lastUserMessage() - return userMessages()?.find((m) => m.id === store.messageId) - }) - - return ( -
-
- 1}> - - -
- - {(message) => { - const isActive = createMemo(() => activeMessage()?.id === message.id) - const [titled, setTitled] = createSignal(!!message.summary?.title) - const assistantMessages = createMemo(() => { - return sync.data.message[session().id]?.filter( - (m) => m.role === "assistant" && m.parentID == message.id, - ) as AssistantMessageType[] - }) - const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error) - const [completed, setCompleted] = createSignal(!!message.summary?.body || !!error()) - const [expanded, setExpanded] = createSignal(false) - const parts = createMemo(() => sync.data.part[message.id]) - const title = createMemo(() => message.summary?.title) - const summary = createMemo(() => message.summary?.body) - const diffs = createMemo(() => message.summary?.diffs ?? []) - const hasToolPart = createMemo(() => - assistantMessages() - ?.flatMap((m) => sync.data.part[m.id]) - .some((p) => p?.type === "tool"), - ) - const working = createMemo(() => !summary() && !error()) - - // allowing time for the animations to finish - createEffect(() => { - title() - setTimeout(() => setTitled(!!title()), 10_000) - }) - createEffect(() => { - const complete = !!summary() || !!error() - setTimeout(() => setCompleted(complete), 1200) - }) - - return ( - -
- {/* Title */} -
-
- - } - > -

- {title()} -

-
-
-
-
- -
- {/* Summary */} - -
-
-

- - Summary - Response - -

- - {(summary) => ( - *]:fade-up-text": !diffs().length }} - text={summary()} - /> - )} - -
- - - {(diff) => ( - - - -
-
- -
- - - {getDirectory(diff.file)}‎ - - - - {getFilename(diff.file)} - -
-
-
- - -
-
-
-
- - - -
- )} -
-
-
-
- - - {error()?.data?.message as string} - - - {/* Response */} -
- - - - - - - -
-
- - Hide details - Show details - -
- -
-
- -
- - {(assistantMessage) => { - const parts = createMemo( - () => sync.data.part[assistantMessage.id], - ) - return - }} - - - - {error()?.data?.message as string} - - -
-
-
-
-
-
-
-
- ) - }} -
-
-
-
- ) - }} - -
- - {/* */} - - {(file) => ( - - {(() => { - { - /* const view = local.file.view(file) */ - } - { - /* const showRaw = view === "raw" || !file.content?.diff */ - } - { - /* const code = showRaw ? (file.content?.content ?? "") : (file.content?.diff ?? "") */ - } - const node = local.file.node(file) - return ( - - ) - })()} - - )} - - - - {(() => { - const id = activeItem() - if (!id) return null - const draggedFile = local.file.node(id) - if (!draggedFile) return null - return ( -
- -
- ) - })()} -
- -
- { - inputRef = el - }} - onSubmit={handlePromptSubmit} - /> -
- - } - > -
    - - {(path) => ( -
  • - -
  • - )} -
    -
- -
- -
- - x} - onOpenChange={(open) => setStore("fileSelectOpen", open)} - onSelect={(x) => (x ? local.session.open(x) : undefined)} - > - {(i) => ( -
-
- -
- - {getDirectory(i)} - - {getFilename(i)} -
-
-
-
- )} -
-
- - ) -} diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx new file mode 100644 index 00000000..d8856400 --- /dev/null +++ b/packages/desktop/src/pages/layout.tsx @@ -0,0 +1,121 @@ +import { Button, Tooltip, DiffChanges, IconButton } from "@opencode-ai/ui" +import { createMemo, For, ParentProps, Show } from "solid-js" +import { DateTime } from "luxon" +import { useSync } from "@/context/sync" +import { A, useParams } from "@solidjs/router" +import { useLocal } from "@/context/local" + +export default function Layout(props: ParentProps) { + const params = useParams() + const sync = useSync() + const local = useLocal() + + return ( +
+ +
+ +
{props.children}
+
+
+ ) +} diff --git a/packages/desktop/src/pages/session-layout.tsx b/packages/desktop/src/pages/session-layout.tsx new file mode 100644 index 00000000..9a24608f --- /dev/null +++ b/packages/desktop/src/pages/session-layout.tsx @@ -0,0 +1,12 @@ +import { Show, type ParentProps } from "solid-js" +import { SessionProvider } from "@/context/session" +import { useParams } from "@solidjs/router" + +export default function Layout(props: ParentProps) { + const params = useParams() + return ( + + {props.children} + + ) +} diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx new file mode 100644 index 00000000..23d44cfc --- /dev/null +++ b/packages/desktop/src/pages/session.tsx @@ -0,0 +1,899 @@ +import { + SelectDialog, + IconButton, + Tabs, + Icon, + Accordion, + Diff, + Collapsible, + DiffChanges, + Message, + Typewriter, + Card, + Code, + Tooltip, + ProgressCircle, + Button, +} from "@opencode-ai/ui" +import { FileIcon } from "@/ui" +import { MessageProgress } from "@/components/message-progress" +import { + For, + onCleanup, + onMount, + Show, + Match, + Switch, + createSignal, + createEffect, + createMemo, + createResource, +} from "solid-js" +import { useLocal, type LocalFile } from "@/context/local" +import { createStore } from "solid-js/store" +import { getDirectory, getFilename } from "@/utils" +import { PromptInput } from "@/components/prompt-input" +import { DateTime } from "luxon" +import { + DragDropProvider, + DragDropSensors, + DragOverlay, + SortableProvider, + closestCenter, + createSortable, + useDragDropContext, +} from "@thisbeyond/solid-dnd" +import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd" +import type { JSX } from "solid-js" +import { useSync } from "@/context/sync" +import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk" +import { Markdown } from "@opencode-ai/ui" +import { Spinner } from "@/components/spinner" +import { useSession } from "@/context/session" + +export default function Page() { + const local = useLocal() + const sync = useSync() + const session = useSession() + const [store, setStore] = createStore({ + clickTimer: undefined as number | undefined, + fileSelectOpen: false, + activeDraggable: undefined as string | undefined, + }) + let inputRef!: HTMLDivElement + let messageScrollElement!: HTMLDivElement + + const MOD = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) ? "Meta" : "Control" + + onMount(() => { + document.addEventListener("keydown", handleKeyDown) + }) + + onCleanup(() => { + document.removeEventListener("keydown", handleKeyDown) + }) + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.getModifierState(MOD) && event.shiftKey && event.key.toLowerCase() === "p") { + event.preventDefault() + return + } + if (event.getModifierState(MOD) && event.key.toLowerCase() === "p") { + event.preventDefault() + setStore("fileSelectOpen", true) + return + } + + const focused = document.activeElement === inputRef + if (focused) { + if (event.key === "Escape") { + inputRef?.blur() + } + return + } + + // if (local.file.active()) { + // const active = local.file.active()! + // if (event.key === "Enter" && active.selection) { + // local.context.add({ + // type: "file", + // path: active.path, + // selection: { ...active.selection }, + // }) + // return + // } + // + // if (event.getModifierState(MOD)) { + // if (event.key.toLowerCase() === "a") { + // return + // } + // if (event.key.toLowerCase() === "c") { + // return + // } + // } + // } + + if (event.key.length === 1 && event.key !== "Unidentified" && !(event.ctrlKey || event.metaKey)) { + inputRef?.focus() + } + } + + const resetClickTimer = () => { + if (!store.clickTimer) return + clearTimeout(store.clickTimer) + setStore("clickTimer", undefined) + } + + const startClickTimer = () => { + const newClickTimer = setTimeout(() => { + setStore("clickTimer", undefined) + }, 300) + setStore("clickTimer", newClickTimer as unknown as number) + } + + const handleTabClick = async (tab: string) => { + if (store.clickTimer) { + resetClickTimer() + // local.file.update(file.path, { ...file, pinned: true }) + } else { + if (tab.startsWith("file://")) { + local.file.open(tab.replace("file://", "")) + } + startClickTimer() + } + } + + const handleDragStart = (event: unknown) => { + const id = getDraggableId(event) + if (!id) return + setStore("activeDraggable", id) + } + + const handleDragOver = (event: DragEvent) => { + const { draggable, droppable } = event + if (draggable && droppable) { + const currentTabs = session.layout.tabs.opened + const fromIndex = currentTabs?.indexOf(draggable.id.toString()) + const toIndex = currentTabs?.indexOf(droppable.id.toString()) + if (fromIndex !== toIndex && toIndex !== undefined) { + session.layout.moveTab(draggable.id.toString(), toIndex) + } + } + } + + const handleDragEnd = () => { + setStore("activeDraggable", undefined) + } + + const FileVisual = (props: { file: LocalFile }): JSX.Element => { + return ( +
+ + + {props.file.name} + + +
+ ) + } + + const SortableTab = (props: { + tab: string + onTabClick: (tab: string) => void + onTabClose: (tab: string) => void + }): JSX.Element => { + const sortable = createSortable(props.tab) + + const [file] = createResource( + () => props.tab, + async (tab) => { + if (tab.startsWith("file://")) { + return local.file.node(tab.replace("file://", "")) + } + return undefined + }, + ) + + return ( + // @ts-ignore +
+
+ props.onTabClick(props.tab)}> + + {(f) => } + + props.onTabClose(props.tab)} + /> + +
+
+ ) + } + + const ConstrainDragYAxis = (): JSX.Element => { + const context = useDragDropContext() + if (!context) return <> + const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context + const transformer: Transformer = { + id: "constrain-y-axis", + order: 100, + callback: (transform) => ({ ...transform, y: 0 }), + } + onDragStart((event) => { + const id = getDraggableId(event) + if (!id) return + addTransformer("draggables", id, transformer) + }) + onDragEnd((event) => { + const id = getDraggableId(event) + if (!id) return + removeTransformer("draggables", id, transformer.id) + }) + return <> + } + + const getDraggableId = (event: unknown): string | undefined => { + if (typeof event !== "object" || event === null) return undefined + if (!("draggable" in event)) return undefined + const draggable = (event as { draggable?: { id?: unknown } }).draggable + if (!draggable) return undefined + return typeof draggable.id === "string" ? draggable.id : undefined + } + + return ( +
+ + + + +
+ + +
Chat
+ + +
{session.usage.context() ?? 0}%
+
+
+ + + + + +
+
Review
+ +
+ {session.info()?.summary?.files ?? 0} +
+
+ +
+
+
+ + + {(tab) => } + + +
+ + setStore("fileSelectOpen", true)} + /> + +
+
+
+ +
+
+ + +
+ + + +
+
+ 1}> +
    + + {(message) => { + const assistantMessages = createMemo(() => { + if (!session.id) return [] + return sync.data.message[session.id]?.filter( + (m) => m.role === "assistant" && m.parentID == message.id, + ) as AssistantMessageType[] + }) + const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error) + const working = createMemo(() => !message.summary?.body && !error()) + + const handleClick = () => session.messages.setActive(message.id) + + return ( +
  • + + + {message.summary?.title} +
+ } + > + + + + + ) + }} + + + +
+ + {(message) => { + const isActive = createMemo(() => session.messages.active()?.id === message.id) + const [titled, setTitled] = createSignal(!!message.summary?.title) + const assistantMessages = createMemo(() => { + if (!session.id) return [] + return sync.data.message[session.id]?.filter( + (m) => m.role === "assistant" && m.parentID == message.id, + ) as AssistantMessageType[] + }) + const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error) + const [completed, setCompleted] = createSignal(!!message.summary?.body || !!error()) + const [detailsExpanded, setDetailsExpanded] = createSignal(false) + const parts = createMemo(() => sync.data.part[message.id]) + const hasToolPart = createMemo(() => + assistantMessages() + ?.flatMap((m) => sync.data.part[m.id]) + .some((p) => p?.type === "tool"), + ) + const working = createMemo(() => !message.summary?.body && !error()) + + // allowing time for the animations to finish + createEffect(() => { + const title = message.summary?.title + setTimeout(() => setTitled(!!title), 10_000) + }) + createEffect(() => { + const summary = message.summary?.body + const complete = !!summary || !!error() + setTimeout(() => setCompleted(complete), 1200) + }) + + return ( + +
+ {/* Title */} +
+
+ + } + > +

+ {message.summary?.title} +

+
+
+
+
+ +
+ {/* Summary */} + +
+
+

+ + Summary + Response + +

+ + {(summary) => ( + *]:fade-up-text": !message.summary?.diffs?.length, + }} + text={summary()} + /> + )} + +
+ + + {(diff) => ( + + + +
+
+ +
+ + + {getDirectory(diff.file)}‎ + + + + {getFilename(diff.file)} + +
+
+
+ + +
+
+
+
+ + + +
+ )} +
+
+
+
+ + + {error()?.data?.message as string} + + + {/* Response */} +
+ + + + + + + +
+
+ + Hide details + Show details + +
+ +
+
+ +
+ + {(assistantMessage) => { + const parts = createMemo(() => sync.data.part[assistantMessage.id]) + return + }} + + + + {error()?.data?.message as string} + + +
+
+
+
+
+
+
+
+ ) + }} +
+
+
+ + +
+
New session
+
+ +
+ {getDirectory(sync.data.path.directory)} + {getFilename(sync.data.path.directory)} +
+
+
+ +
+ Last modified  + + {DateTime.fromMillis(sync.data.project.time.created).toRelative()} + +
+
+
+
+ +
+ { + inputRef = el + }} + /> +
+
+ +
+
+
+ + + + + { + local.layout.review.tab() + session.layout.setActiveTab("review") + }} + /> + +
+
+
All changes
+
+ + + {(diff) => ( + + + +
+
+ +
+ + + {getDirectory(diff.file)}‎ + + + {getFilename(diff.file)} +
+
+
+ + +
+
+
+
+ + + +
+ )} +
+
+
+
+
+
+ + + +
+
+
+
+
All changes
+
+ + + {(diff) => ( + + + +
+
+ +
+ + {getDirectory(diff.file)}‎ + + {getFilename(diff.file)} +
+
+
+ + +
+
+
+
+ + + +
+ )} +
+
+
+
+
+
+ + {(tab) => { + const [file] = createResource( + () => tab, + async (tab) => { + if (tab.startsWith("file://")) { + return local.file.node(tab.replace("file://", "")) + } + return undefined + }, + ) + return ( + + + + {(f) => ( + + )} + + + + ) + }} + + + + + {(draggedFile) => { + const [file] = createResource( + () => draggedFile(), + async (tab) => { + if (tab.startsWith("file://")) { + return local.file.node(tab.replace("file://", "")) + } + return undefined + }, + ) + return ( +
+ {(f) => } +
+ ) + }} +
+
+ + +
+ { + inputRef = el + }} + /> +
+
+ + }> +
    + + {(path) => ( +
  • + +
  • + )} +
    +
+ + + + x} + onOpenChange={(open) => setStore("fileSelectOpen", open)} + onSelect={(x) => (x ? session.layout.openTab("file://" + x) : undefined)} + > + {(i) => ( +
+
+ +
+ + {getDirectory(i)} + + {getFilename(i)} +
+
+
+
+ )} +
+
+ + ) +} diff --git a/packages/desktop/src/sst-env.d.ts b/packages/desktop/src/sst-env.d.ts index 47a8fbec..1b1683a1 100644 --- a/packages/desktop/src/sst-env.d.ts +++ b/packages/desktop/src/sst-env.d.ts @@ -2,9 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /// -interface ImportMetaEnv { - -} +interface ImportMetaEnv {} interface ImportMeta { readonly env: ImportMetaEnv -} \ No newline at end of file +} diff --git a/packages/desktop/sst-env.d.ts b/packages/desktop/sst-env.d.ts index b6a7e906..0397645b 100644 --- a/packages/desktop/sst-env.d.ts +++ b/packages/desktop/sst-env.d.ts @@ -6,4 +6,4 @@ /// import "sst" -export {} \ No newline at end of file +export {} diff --git a/packages/extensions/zed/LICENSE b/packages/extensions/zed/LICENSE new file mode 120000 index 00000000..5853aaea --- /dev/null +++ b/packages/extensions/zed/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml new file mode 100644 index 00000000..df9003af --- /dev/null +++ b/packages/extensions/zed/extension.toml @@ -0,0 +1,36 @@ +id = "opencode" +name = "OpenCode" +description = "The AI coding agent built for the terminal" +version = "1.0.55" +schema_version = 1 +authors = ["Anomaly"] +repository = "https://github.com/sst/opencode" + +[agent_servers.opencode] +name = "OpenCode" +icon = "./icons/opencode.svg" + +[agent_servers.opencode.targets.darwin-aarch64] +archive = "https://github.com/sst/opencode/releases/download/v1.0.55/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.55/opencode-darwin-x64.zip" +cmd = "./opencode" +args = ["acp"] + +[agent_servers.opencode.targets.linux-aarch64] +archive = "https://github.com/sst/opencode/releases/download/v1.0.55/opencode-linux-arm64.zip" +cmd = "./opencode" +args = ["acp"] + +[agent_servers.opencode.targets.linux-x86_64] +archive = "https://github.com/sst/opencode/releases/download/v1.0.55/opencode-linux-x64.zip" +cmd = "./opencode" +args = ["acp"] + +[agent_servers.opencode.targets.windows-x86_64] +archive = "https://github.com/sst/opencode/releases/download/v1.0.55/opencode-windows-x64.zip" +cmd = "./opencode.exe" +args = ["acp"] diff --git a/packages/extensions/zed/icons/opencode.svg b/packages/extensions/zed/icons/opencode.svg new file mode 100644 index 00000000..fc001e49 --- /dev/null +++ b/packages/extensions/zed/icons/opencode.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/function/package.json b/packages/function/package.json index eeb842ca..35944446 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.0.23", + "version": "1.0.55", "$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 01407434..bcd7c265 100644 --- a/packages/function/sst-env.d.ts +++ b/packages/function/sst-env.d.ts @@ -6,99 +6,108 @@ import "sst" declare module "sst" { export interface Resource { - "ADMIN_SECRET": { - "type": "sst.sst.Secret" - "value": string + ADMIN_SECRET: { + type: "sst.sst.Secret" + value: string } - "AUTH_API_URL": { - "type": "sst.sst.Linkable" - "value": string + AUTH_API_URL: { + type: "sst.sst.Linkable" + value: string } - "AWS_SES_ACCESS_KEY_ID": { - "type": "sst.sst.Secret" - "value": string + AWS_SES_ACCESS_KEY_ID: { + type: "sst.sst.Secret" + value: string } - "AWS_SES_SECRET_ACCESS_KEY": { - "type": "sst.sst.Secret" - "value": string + AWS_SES_SECRET_ACCESS_KEY: { + type: "sst.sst.Secret" + value: string } - "Console": { - "type": "sst.cloudflare.SolidStart" - "url": string + CLOUDFLARE_API_TOKEN: { + type: "sst.sst.Secret" + value: string } - "Database": { - "database": string - "host": string - "password": string - "port": number - "type": "sst.sst.Linkable" - "username": string + CLOUDFLARE_DEFAULT_ACCOUNT_ID: { + type: "sst.sst.Secret" + value: string } - "Desktop": { - "type": "sst.cloudflare.StaticSite" - "url": string + Console: { + type: "sst.cloudflare.SolidStart" + url: string } - "EMAILOCTOPUS_API_KEY": { - "type": "sst.sst.Secret" - "value": string + Database: { + database: string + host: string + password: string + port: number + type: "sst.sst.Linkable" + username: string } - "GITHUB_APP_ID": { - "type": "sst.sst.Secret" - "value": string + Desktop: { + type: "sst.cloudflare.StaticSite" + url: string } - "GITHUB_APP_PRIVATE_KEY": { - "type": "sst.sst.Secret" - "value": string + EMAILOCTOPUS_API_KEY: { + type: "sst.sst.Secret" + value: string } - "GITHUB_CLIENT_ID_CONSOLE": { - "type": "sst.sst.Secret" - "value": string + GITHUB_APP_ID: { + type: "sst.sst.Secret" + value: string } - "GITHUB_CLIENT_SECRET_CONSOLE": { - "type": "sst.sst.Secret" - "value": string + GITHUB_APP_PRIVATE_KEY: { + type: "sst.sst.Secret" + value: string } - "GOOGLE_CLIENT_ID": { - "type": "sst.sst.Secret" - "value": string + GITHUB_CLIENT_ID_CONSOLE: { + type: "sst.sst.Secret" + value: string } - "HONEYCOMB_API_KEY": { - "type": "sst.sst.Secret" - "value": string + GITHUB_CLIENT_SECRET_CONSOLE: { + type: "sst.sst.Secret" + value: string } - "STRIPE_SECRET_KEY": { - "type": "sst.sst.Secret" - "value": string + GOOGLE_CLIENT_ID: { + type: "sst.sst.Secret" + value: string } - "STRIPE_WEBHOOK_SECRET": { - "type": "sst.sst.Linkable" - "value": string + HONEYCOMB_API_KEY: { + type: "sst.sst.Secret" + value: string } - "Web": { - "type": "sst.cloudflare.Astro" - "url": string + STRIPE_SECRET_KEY: { + type: "sst.sst.Secret" + value: string } - "ZEN_MODELS1": { - "type": "sst.sst.Secret" - "value": string + STRIPE_WEBHOOK_SECRET: { + type: "sst.sst.Linkable" + value: string } - "ZEN_MODELS2": { - "type": "sst.sst.Secret" - "value": string + Web: { + type: "sst.cloudflare.Astro" + url: string + } + ZEN_MODELS1: { + type: "sst.sst.Secret" + value: string + } + ZEN_MODELS2: { + type: "sst.sst.Secret" + value: string } } } -// cloudflare -import * as cloudflare from "@cloudflare/workers-types"; +// cloudflare +import * as cloudflare from "@cloudflare/workers-types" declare module "sst" { export interface Resource { - "Api": cloudflare.Service - "AuthApi": cloudflare.Service - "AuthStorage": cloudflare.KVNamespace - "Bucket": cloudflare.R2Bucket - "LogProcessor": cloudflare.Service + Api: cloudflare.Service + AuthApi: cloudflare.Service + AuthStorage: cloudflare.KVNamespace + Bucket: cloudflare.R2Bucket + GatewayKv: cloudflare.KVNamespace + LogProcessor: cloudflare.Service } } import "sst" -export {} \ No newline at end of file +export {} diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 3e4f5c02..2cc0cef7 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.23", + "version": "1.0.55", "name": "opencode", "type": "module", "private": true, @@ -43,7 +43,7 @@ "dependencies": { "@actions/core": "1.11.1", "@actions/github": "6.0.1", - "@agentclientprotocol/sdk": "0.4.9", + "@agentclientprotocol/sdk": "0.5.1", "@clack/prompts": "1.0.0-alpha.1", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", @@ -54,11 +54,11 @@ "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", - "@opentui/core": "0.1.33", - "@opentui/solid": "0.1.33", + "@opentui/core": "0.0.0-20251108-0c7899b1", + "@opentui/solid": "0.0.0-20251108-0c7899b1", "@parcel/watcher": "2.5.1", - "@solid-primitives/event-bus": "1.1.2", "@pierre/precision-diffs": "catalog:", + "@solid-primitives/event-bus": "1.1.2", "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", @@ -77,6 +77,7 @@ "partial-json": "0.1.7", "remeda": "catalog:", "solid-js": "catalog:", + "strip-ansi": "7.1.2", "tree-sitter-bash": "0.25.0", "turndown": "7.2.0", "ulid": "catalog:", diff --git a/packages/opencode/parsers-config.ts b/packages/opencode/parsers-config.ts index cfa00454..ef149524 100644 --- a/packages/opencode/parsers-config.ts +++ b/packages/opencode/parsers-config.ts @@ -203,5 +203,14 @@ export default { ], }, }, + { + filetype: "clojure", + wasm: "https://github.com/sogaiu/tree-sitter-clojure/releases/download/v0.0.13/tree-sitter-clojure.wasm", + queries: { + highlights: [ + "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/clojure/highlights.scm", + ], + }, + }, ], } diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index 4ce8bfba..29706c09 100755 --- a/packages/opencode/script/build.ts +++ b/packages/opencode/script/build.ts @@ -41,9 +41,7 @@ for (const [os, arch] of targets) { const opentui = `@opentui/core-${os === "windows" ? "win32" : os}-${arch.replace("-baseline", "")}` await $`mkdir -p ../../node_modules/${opentui}` - await $`npm pack ${opentui}@${pkg.dependencies["@opentui/core"]}`.cwd( - path.join(dir, "../../node_modules"), - ) + await $`npm pack ${opentui}@${pkg.dependencies["@opentui/core"]}`.cwd(path.join(dir, "../../node_modules")) await $`tar -xf ../../node_modules/${opentui.replace("@opentui/", "opentui-")}-*.tgz -C ../../node_modules/${opentui} --strip-components=1` const watcher = `@parcel/watcher-${os === "windows" ? "win32" : os}-${arch.replace("-baseline", "")}${os === "linux" ? "-glibc" : ""}` @@ -51,9 +49,7 @@ for (const [os, arch] of targets) { await $`npm pack ${watcher}`.cwd(path.join(dir, "../../node_modules")).quiet() await $`tar -xf ../../node_modules/${watcher.replace("@parcel/", "parcel-")}-*.tgz -C ../../node_modules/${watcher} --strip-components=1` - const parserWorker = fs.realpathSync( - path.resolve(dir, "./node_modules/@opentui/core/parser.worker.js"), - ) + const parserWorker = fs.realpathSync(path.resolve(dir, "./node_modules/@opentui/core/parser.worker.js")) const workerPath = "./src/cli/cmd/tui/worker.ts" await Bun.build({ diff --git a/packages/opencode/script/publish.ts b/packages/opencode/script/publish.ts index 1bac6cb0..3e989cc6 100755 --- a/packages/opencode/script/publish.ts +++ b/packages/opencode/script/publish.ts @@ -55,18 +55,10 @@ if (!Script.preview) { } // Calculate SHA values - const arm64Sha = await $`sha256sum ./dist/opencode-linux-arm64.zip | cut -d' ' -f1` - .text() - .then((x) => x.trim()) - const x64Sha = await $`sha256sum ./dist/opencode-linux-x64.zip | cut -d' ' -f1` - .text() - .then((x) => x.trim()) - const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1` - .text() - .then((x) => x.trim()) - const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1` - .text() - .then((x) => x.trim()) + const arm64Sha = await $`sha256sum ./dist/opencode-linux-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim()) + const x64Sha = await $`sha256sum ./dist/opencode-linux-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim()) + const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim()) + const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim()) const [pkgver, _subver = ""] = Script.version.split(/(-.*)/, 2) @@ -131,7 +123,7 @@ if (!Script.preview) { "", "package() {", ` cd "opencode-\${pkgver}/packages/opencode"`, - ' install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode"', + ' install -Dm755 $(find dist/*/bin/opencode) "${pkgdir}/usr/bin/opencode"', "}", "", ].join("\n") diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 1eae36e6..ff71b045 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -1,9 +1,12 @@ import { + RequestError, type Agent as ACPAgent, type AgentSideConnection, type AuthenticateRequest, + type AuthMethod, type CancelNotification, type InitializeRequest, + type InitializeResponse, type LoadSessionRequest, type NewSessionRequest, type PermissionOption, @@ -17,294 +20,340 @@ import { } from "@agentclientprotocol/sdk" import { Log } from "../util/log" import { ACPSessionManager } from "./session" -import type { ACPConfig } from "./types" +import type { ACPConfig, ACPSessionState } from "./types" import { Provider } from "../provider/provider" -import { SessionPrompt } from "../session/prompt" import { Installation } from "@/installation" -import { SessionLock } from "@/session/lock" -import { Bus } from "@/bus" import { MessageV2 } from "@/session/message-v2" -import { Storage } from "@/storage/storage" -import { Command } from "@/command" -import { Agent as Agents } from "@/agent/agent" -import { Permission } from "@/permission" -import { SessionCompaction } from "@/session/compaction" -import type { Config } from "@/config/config" +import { Config } from "@/config/config" import { MCP } from "@/mcp" import { Todo } from "@/session/todo" import { z } from "zod" +import { LoadAPIKeyError } from "ai" +import type { OpencodeClient } from "@opencode-ai/sdk" export namespace ACP { const log = Log.create({ service: "acp-agent" }) + export async function init({ sdk }: { sdk: OpencodeClient }) { + const model = await defaultModel({ sdk }) + return { + create: (connection: AgentSideConnection, fullConfig: ACPConfig) => { + if (!fullConfig.defaultModel) { + fullConfig.defaultModel = model + } + return new Agent(connection, fullConfig) + }, + } + } + export class Agent implements ACPAgent { - private sessionManager = new ACPSessionManager() private connection: AgentSideConnection private config: ACPConfig + private sdk: OpencodeClient + private sessionManager - constructor(connection: AgentSideConnection, config: ACPConfig = {}) { + constructor(connection: AgentSideConnection, config: ACPConfig) { this.connection = connection this.config = config - this.setupEventSubscriptions() + this.sdk = config.sdk + this.sessionManager = new ACPSessionManager(this.sdk) } - private setupEventSubscriptions() { + private setupEventSubscriptions(session: ACPSessionState) { + const sessionId = session.id + const directory = session.cwd + const options: PermissionOption[] = [ { optionId: "once", kind: "allow_once", name: "Allow once" }, { optionId: "always", kind: "allow_always", name: "Always allow" }, { optionId: "reject", kind: "reject_once", name: "Reject" }, ] - Bus.subscribe(Permission.Event.Updated, async (event) => { - const acpSession = this.sessionManager.get(event.properties.sessionID) - if (!acpSession) return - try { - const permission = event.properties - const res = await this.connection - .requestPermission({ - sessionId: acpSession.id, - toolCall: { - toolCallId: permission.callID ?? permission.id, - status: "pending", - title: permission.title, - rawInput: permission.metadata, - kind: toToolKind(permission.type), - locations: toLocations(permission.type, permission.metadata), - }, - options, - }) - .catch((error) => { - log.error("failed to request permission from ACP", { - error, - permissionID: permission.id, - sessionID: permission.sessionID, - }) - Permission.respond({ - sessionID: permission.sessionID, - permissionID: permission.id, - response: "reject", - }) - return - }) - if (!res) return - if (res.outcome.outcome !== "selected") { - Permission.respond({ - sessionID: permission.sessionID, - permissionID: permission.id, - response: "reject", - }) - return - } - Permission.respond({ - sessionID: permission.sessionID, - permissionID: permission.id, - response: res.outcome.optionId as "once" | "always" | "reject", - }) - } catch (err) { - if (!(err instanceof Permission.RejectedError)) { - log.error("unexpected error when handling permission", { error: err }) - throw err - } - } - }) - - Bus.subscribe(MessageV2.Event.PartUpdated, async (event) => { - const props = event.properties - const { part } = props - const acpSession = this.sessionManager.get(part.sessionID) - if (!acpSession) return - - const message = await Storage.read([ - "message", - part.sessionID, - part.messageID, - ]).catch(() => undefined) - if (!message || message.role !== "assistant") return - - if (part.type === "tool") { - switch (part.state.status) { - case "pending": - await this.connection - .sessionUpdate({ - sessionId: acpSession.id, - update: { - sessionUpdate: "tool_call", - toolCallId: part.callID, - title: part.tool, - kind: toToolKind(part.tool), - status: "pending", - locations: [], - rawInput: {}, - }, - }) - .catch((err) => { - log.error("failed to send tool pending to ACP", { error: err }) - }) - break - case "running": - await this.connection - .sessionUpdate({ - sessionId: acpSession.id, - update: { - sessionUpdate: "tool_call_update", - toolCallId: part.callID, - status: "in_progress", - locations: toLocations(part.tool, part.state.input), - rawInput: part.state.input, - }, - }) - .catch((err) => { - log.error("failed to send tool in_progress to ACP", { error: err }) - }) - break - case "completed": - const kind = toToolKind(part.tool) - const content: ToolCallContent[] = [ - { - type: "content", - content: { - type: "text", - text: part.state.output, - }, - }, - ] - - if (kind === "edit") { - const input = part.state.input - const filePath = typeof input["filePath"] === "string" ? input["filePath"] : "" - const oldText = typeof input["oldString"] === "string" ? input["oldString"] : "" - const newText = - typeof input["newString"] === "string" - ? input["newString"] - : typeof input["content"] === "string" - ? input["content"] - : "" - content.push({ - type: "diff", - path: filePath, - oldText, - newText, - }) - } - - if (part.tool === "todowrite") { - const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output)) - if (parsedTodos.success) { - await this.connection - .sessionUpdate({ - sessionId: acpSession.id, - update: { - sessionUpdate: "plan", - entries: parsedTodos.data.map((todo) => { - const status: PlanEntry["status"] = - todo.status === "cancelled" - ? "completed" - : (todo.status as PlanEntry["status"]) - return { - priority: "medium", - status, - content: todo.content, - } - }), + this.config.sdk.event.subscribe({ query: { directory } }).then(async (events) => { + for await (const event of events.stream) { + switch (event.type) { + case "permission.updated": + try { + const permission = event.properties + const res = await this.connection + .requestPermission({ + sessionId, + toolCall: { + toolCallId: permission.callID ?? permission.id, + status: "pending", + title: permission.title, + rawInput: permission.metadata, + kind: toToolKind(permission.type), + locations: toLocations(permission.type, permission.metadata), + }, + options, + }) + .catch(async (error) => { + log.error("failed to request permission from ACP", { + error, + permissionID: permission.id, + sessionID: permission.sessionID, + }) + await this.config.sdk.postSessionIdPermissionsPermissionId({ + path: { id: permission.sessionID, permissionID: permission.id }, + body: { + response: "reject", }, + query: { directory }, }) - .catch((err) => { - log.error("failed to send session update for todo", { error: err }) - }) - } else { - log.error("failed to parse todo output", { error: parsedTodos.error }) + return + }) + if (!res) return + if (res.outcome.outcome !== "selected") { + await this.config.sdk.postSessionIdPermissionsPermissionId({ + path: { id: permission.sessionID, permissionID: permission.id }, + body: { + response: "reject", + }, + query: { directory }, + }) + return } + await this.config.sdk.postSessionIdPermissionsPermissionId({ + path: { id: permission.sessionID, permissionID: permission.id }, + body: { + response: res.outcome.optionId as "once" | "always" | "reject", + }, + query: { directory }, + }) + } catch (err) { + log.error("unexpected error when handling permission", { error: err }) + } finally { + break } - await this.connection - .sessionUpdate({ - sessionId: acpSession.id, - update: { - sessionUpdate: "tool_call_update", - toolCallId: part.callID, - status: "completed", - kind, - content, - title: part.state.title, - rawOutput: { - output: part.state.output, - metadata: part.state.metadata, + case "message.part.updated": + log.info("message part updated", { event: event.properties }) + try { + const props = event.properties + const { part } = props + + const message = await this.config.sdk.session + .message({ + throwOnError: true, + path: { + id: part.sessionID, + messageID: part.messageID, }, - }, - }) - .catch((err) => { - log.error("failed to send tool completed to ACP", { error: err }) - }) - break - case "error": - await this.connection - .sessionUpdate({ - sessionId: acpSession.id, - update: { - sessionUpdate: "tool_call_update", - toolCallId: part.callID, - status: "failed", - content: [ - { - type: "content", - content: { - type: "text", - text: part.state.error, + query: { directory }, + }) + .then((x) => x.data) + .catch((err) => { + log.error("unexpected error when fetching message", { error: err }) + return undefined + }) + + if (!message || message.info.role !== "assistant") return + + if (part.type === "tool") { + switch (part.state.status) { + case "pending": + await this.connection + .sessionUpdate({ + sessionId, + update: { + sessionUpdate: "tool_call", + toolCallId: part.callID, + title: part.tool, + kind: toToolKind(part.tool), + status: "pending", + locations: [], + rawInput: {}, + }, + }) + .catch((err) => { + log.error("failed to send tool pending to ACP", { error: err }) + }) + break + case "running": + await this.connection + .sessionUpdate({ + sessionId, + update: { + sessionUpdate: "tool_call_update", + toolCallId: part.callID, + status: "in_progress", + locations: toLocations(part.tool, part.state.input), + rawInput: part.state.input, + }, + }) + .catch((err) => { + log.error("failed to send tool in_progress to ACP", { error: err }) + }) + break + case "completed": + const kind = toToolKind(part.tool) + const content: ToolCallContent[] = [ + { + type: "content", + content: { + type: "text", + text: part.state.output, + }, }, - }, - ], - rawOutput: { - error: part.state.error, - }, - }, - }) - .catch((err) => { - log.error("failed to send tool error to ACP", { error: err }) - }) - break - } - } else if (part.type === "text") { - const delta = props.delta - if (delta && part.synthetic !== true) { - await this.connection - .sessionUpdate({ - sessionId: acpSession.id, - update: { - sessionUpdate: "agent_message_chunk", - content: { - type: "text", - text: delta, - }, - }, - }) - .catch((err) => { - log.error("failed to send text to ACP", { error: err }) - }) - } - } else if (part.type === "reasoning") { - const delta = props.delta - if (delta) { - await this.connection - .sessionUpdate({ - sessionId: acpSession.id, - update: { - sessionUpdate: "agent_thought_chunk", - content: { - type: "text", - text: delta, - }, - }, - }) - .catch((err) => { - log.error("failed to send reasoning to ACP", { error: err }) - }) + ] + + if (kind === "edit") { + const input = part.state.input + const filePath = typeof input["filePath"] === "string" ? input["filePath"] : "" + const oldText = typeof input["oldString"] === "string" ? input["oldString"] : "" + const newText = + typeof input["newString"] === "string" + ? input["newString"] + : typeof input["content"] === "string" + ? input["content"] + : "" + content.push({ + type: "diff", + path: filePath, + oldText, + newText, + }) + } + + if (part.tool === "todowrite") { + const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output)) + if (parsedTodos.success) { + await this.connection + .sessionUpdate({ + sessionId, + update: { + sessionUpdate: "plan", + entries: parsedTodos.data.map((todo) => { + const status: PlanEntry["status"] = + todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"]) + return { + priority: "medium", + status, + content: todo.content, + } + }), + }, + }) + .catch((err) => { + log.error("failed to send session update for todo", { error: err }) + }) + } else { + log.error("failed to parse todo output", { error: parsedTodos.error }) + } + } + + await this.connection + .sessionUpdate({ + sessionId, + update: { + sessionUpdate: "tool_call_update", + toolCallId: part.callID, + status: "completed", + kind, + content, + title: part.state.title, + rawOutput: { + output: part.state.output, + metadata: part.state.metadata, + }, + }, + }) + .catch((err) => { + log.error("failed to send tool completed to ACP", { error: err }) + }) + break + case "error": + await this.connection + .sessionUpdate({ + sessionId, + update: { + sessionUpdate: "tool_call_update", + toolCallId: part.callID, + status: "failed", + content: [ + { + type: "content", + content: { + type: "text", + text: part.state.error, + }, + }, + ], + rawOutput: { + error: part.state.error, + }, + }, + }) + .catch((err) => { + log.error("failed to send tool error to ACP", { error: err }) + }) + break + } + } else if (part.type === "text") { + const delta = props.delta + if (delta && part.synthetic !== true) { + await this.connection + .sessionUpdate({ + sessionId, + update: { + sessionUpdate: "agent_message_chunk", + content: { + type: "text", + text: delta, + }, + }, + }) + .catch((err) => { + log.error("failed to send text to ACP", { error: err }) + }) + } + } else if (part.type === "reasoning") { + const delta = props.delta + if (delta) { + await this.connection + .sessionUpdate({ + sessionId, + update: { + sessionUpdate: "agent_thought_chunk", + content: { + type: "text", + text: delta, + }, + }, + }) + .catch((err) => { + log.error("failed to send reasoning to ACP", { error: err }) + }) + } + } + } finally { + break + } } } }) } - async initialize(params: InitializeRequest) { + async initialize(params: InitializeRequest): Promise { log.info("initialize", { protocolVersion: params.protocolVersion }) + const authMethod: AuthMethod = { + description: "Run `opencode auth login` in the terminal", + name: "Login with opencode", + id: "opencode-login", + } + + // If client supports terminal-auth capability, use that instead. + if (params.clientCapabilities?._meta?.["terminal-auth"] === true) { + authMethod._meta = { + "terminal-auth": { + command: "opencode", + args: ["auth", "login"], + label: "OpenCode Login", + }, + } + } + return { protocolVersion: 1, agentCapabilities: { @@ -318,17 +367,10 @@ export namespace ACP { image: true, }, }, - authMethods: [ - { - description: "Run `opencode auth login` in the terminal", - name: "Login with opencode", - id: "opencode-login", - }, - ], - _meta: { - opencode: { - version: Installation.VERSION, - }, + authMethods: [authMethod], + agentInfo: { + name: "OpenCode", + version: Installation.VERSION, }, } } @@ -338,45 +380,83 @@ export namespace ACP { } async newSession(params: NewSessionRequest) { - const model = await defaultModel(this.config) - const session = await this.sessionManager.create(params.cwd, params.mcpServers, model) + const directory = params.cwd + try { + const model = await defaultModel(this.config, directory) - log.info("creating_session", { mcpServers: params.mcpServers.length }) - const load = await this.loadSession({ - cwd: params.cwd, - mcpServers: params.mcpServers, - sessionId: session.id, - }) + // Store ACP session state + const state = await this.sessionManager.create(params.cwd, params.mcpServers, model) + const sessionId = state.id - return { - sessionId: session.id, - models: load.models, - modes: load.modes, - _meta: {}, + log.info("creating_session", { sessionId, mcpServers: params.mcpServers.length }) + + const load = await this.loadSession({ + cwd: directory, + mcpServers: params.mcpServers, + sessionId, + }) + + this.setupEventSubscriptions(state) + + return { + sessionId, + models: load.models, + modes: load.modes, + _meta: {}, + } + } catch (e) { + const error = MessageV2.fromError(e, { + providerID: this.config.defaultModel?.providerID ?? "unknown", + }) + if (LoadAPIKeyError.isInstance(error)) { + throw RequestError.authRequired() + } + throw e } } async loadSession(params: LoadSessionRequest) { - const model = await defaultModel(this.config) + const directory = params.cwd + const model = await defaultModel(this.config, directory) const sessionId = params.sessionId - const providers = await Provider.list() - const entries = Object.entries(providers).sort((a, b) => { - const nameA = a[1].info.name.toLowerCase() - const nameB = b[1].info.name.toLowerCase() + const providers = await this.sdk.config + .providers({ throwOnError: true, query: { directory } }) + .then((x) => x.data.providers) + const entries = providers.sort((a, b) => { + const nameA = a.name.toLowerCase() + const nameB = b.name.toLowerCase() if (nameA < nameB) return -1 if (nameA > nameB) return 1 return 0 }) - const availableModels = entries.flatMap(([providerID, provider]) => { - const models = Provider.sort(Object.values(provider.info.models)) + const availableModels = entries.flatMap((provider) => { + const models = Provider.sort(Object.values(provider.models)) return models.map((model) => ({ - modelId: `${providerID}/${model.id}`, - name: `${provider.info.name}/${model.name}`, + modelId: `${provider.id}/${model.id}`, + name: `${provider.name}/${model.name}`, })) }) - const availableCommands = (await Command.list()).map((command) => ({ + const agents = await this.config.sdk.app + .agents({ + throwOnError: true, + query: { + directory, + }, + }) + .then((resp) => resp.data) + + const commands = await this.config.sdk.command + .list({ + throwOnError: true, + query: { + directory, + }, + }) + .then((resp) => resp.data) + + const availableCommands = commands.map((command) => ({ name: command.name, description: command.description ?? "", })) @@ -387,17 +467,7 @@ export namespace ACP { description: "compact the session", }) - setTimeout(() => { - this.connection.sessionUpdate({ - sessionId, - update: { - sessionUpdate: "available_commands_update", - availableCommands, - }, - }) - }, 0) - - const availableModes = (await Agents.list()) + const availableModes = agents .filter((agent) => agent.mode !== "subagent") .map((agent) => ({ id: agent.name, @@ -405,8 +475,7 @@ export namespace ACP { description: agent.description, })) - const currentModeId = - availableModes.find((m) => m.name === "build")?.id ?? availableModes[0].id + const currentModeId = availableModes.find((m) => m.name === "build")?.id ?? availableModes[0].id const mcpServers: Record = {} for (const server of params.mcpServers) { @@ -433,10 +502,31 @@ export namespace ACP { await Promise.all( Object.entries(mcpServers).map(async ([key, mcp]) => { - await MCP.add(key, mcp) + await this.sdk.mcp + .add({ + throwOnError: true, + query: { directory }, + body: { + name: key, + config: mcp, + }, + }) + .catch((error) => { + log.error("failed to add mcp server", { name: key, error }) + }) }), ) + setTimeout(() => { + this.connection.sessionUpdate({ + sessionId, + update: { + sessionUpdate: "available_commands_update", + availableCommands, + }, + }) + }, 0) + return { sessionId, models: { @@ -453,12 +543,8 @@ export namespace ACP { async setSessionModel(params: SetSessionModelRequest) { const session = this.sessionManager.get(params.sessionId) - if (!session) { - throw new Error(`Session not found: ${params.sessionId}`) - } - const parsed = Provider.parseModel(params.modelId) - const model = await Provider.getModel(parsed.providerID, parsed.modelID) + const model = Provider.parseModel(params.modelId) this.sessionManager.setModel(session.id, { providerID: model.providerID, @@ -471,31 +557,31 @@ export namespace ACP { } async setSessionMode(params: SetSessionModeRequest): Promise { - const session = this.sessionManager.get(params.sessionId) - if (!session) { - throw new Error(`Session not found: ${params.sessionId}`) - } - await Agents.get(params.modeId).then((agent) => { - if (!agent) throw new Error(`Agent not found: ${params.modeId}`) - }) + this.sessionManager.get(params.sessionId) + await this.config.sdk.app + .agents({ throwOnError: true }) + .then((x) => x.data) + .then((agent) => { + if (!agent) throw new Error(`Agent not found: ${params.modeId}`) + }) this.sessionManager.setMode(params.sessionId, params.modeId) } async prompt(params: PromptRequest) { const sessionID = params.sessionId - const acpSession = this.sessionManager.get(sessionID) - if (!acpSession) { - throw new Error(`Session not found: ${sessionID}`) - } + const session = this.sessionManager.get(sessionID) + const directory = session.cwd - const current = acpSession.model - const model = current ?? (await defaultModel(this.config)) + const current = session.model + const model = current ?? (await defaultModel(this.config, directory)) if (!current) { - this.sessionManager.setModel(acpSession.id, model) + this.sessionManager.setModel(session.id, model) } - const agent = acpSession.modeId ?? "build" + const agent = session.modeId ?? "build" - const parts: SessionPrompt.PromptInput["parts"] = [] + const parts: Array< + { type: "text"; text: string } | { type: "file"; url: string; filename: string; mime: string } + > = [] for (const part of params.prompt) { switch (part.type) { case "text": @@ -509,12 +595,14 @@ export namespace ACP { parts.push({ type: "file", url: `data:${part.mimeType};base64,${part.data}`, + filename: "image", mime: part.mimeType, }) } else if (part.uri && part.uri.startsWith("http:")) { parts.push({ type: "file", url: part.uri, + filename: "image", mime: part.mimeType, }) } @@ -545,7 +633,7 @@ export namespace ACP { const cmd = (() => { const text = parts - .filter((p) => p.type === "text") + .filter((p): p is { type: "text"; text: string } => p.type === "text") .map((p) => p.text) .join("") .trim() @@ -562,36 +650,50 @@ export namespace ACP { } if (!cmd) { - await SessionPrompt.prompt({ - sessionID, - model: { - providerID: model.providerID, - modelID: model.modelID, + await this.sdk.session.prompt({ + path: { id: sessionID }, + body: { + model: { + providerID: model.providerID, + modelID: model.modelID, + }, + parts, + agent, + }, + query: { + directory, }, - parts, - agent, }) return done } - const command = await Command.get(cmd.name) + const command = await this.config.sdk.command + .list({ throwOnError: true, query: { directory } }) + .then((x) => x.data.find((c) => c.name === cmd.name)) if (command) { - await SessionPrompt.command({ - sessionID, - command: command.name, - arguments: cmd.args, - model: model.providerID + "/" + model.modelID, - agent, + await this.sdk.session.command({ + path: { id: sessionID }, + body: { + command: command.name, + arguments: cmd.args, + model: model.providerID + "/" + model.modelID, + agent, + }, + query: { + directory, + }, }) return done } switch (cmd.name) { case "compact": - await SessionCompaction.run({ - sessionID, - providerID: model.providerID, - modelID: model.modelID, + await this.config.sdk.session.summarize({ + path: { id: sessionID }, + throwOnError: true, + query: { + directory, + }, }) break } @@ -600,7 +702,14 @@ export namespace ACP { } async cancel(params: CancelNotification) { - SessionLock.abort(params.sessionId) + const session = this.sessionManager.get(params.sessionId) + await this.config.sdk.session.abort({ + path: { id: params.sessionId }, + throwOnError: true, + query: { + directory: session.cwd, + }, + }) } } @@ -651,17 +760,33 @@ export namespace ACP { } } - async function defaultModel(config: ACPConfig) { + async function defaultModel(config: ACPConfig, cwd?: string) { + const sdk = config.sdk const configured = config.defaultModel if (configured) return configured - return Provider.defaultModel() + + const model = await sdk.config + .get({ throwOnError: true, query: { directory: cwd } }) + .then((resp) => { + const cfg = resp.data + if (!cfg.model) return undefined + const parsed = Provider.parseModel(cfg.model) + return { + providerID: parsed.providerID, + modelID: parsed.modelID, + } + }) + .catch((error) => { + log.error("failed to load user config for default model", { error }) + return undefined + }) + + return model ?? { providerID: "opencode", modelID: "big-pickle" } } function parseUri( uri: string, - ): - | { type: "file"; url: string; filename: string; mime: string } - | { type: "text"; text: string } { + ): { type: "file"; url: string; filename: string; mime: string } | { type: "text"; text: string } { try { if (uri.startsWith("file://")) { const path = uri.slice(7) diff --git a/packages/opencode/src/acp/session.ts b/packages/opencode/src/acp/session.ts index 5d45ee28..63948a8c 100644 --- a/packages/opencode/src/acp/session.ts +++ b/packages/opencode/src/acp/session.ts @@ -1,15 +1,33 @@ -import type { McpServer } from "@agentclientprotocol/sdk" -import { Session } from "../session" -import { Provider } from "../provider/provider" +import { RequestError, type McpServer } from "@agentclientprotocol/sdk" import type { ACPSessionState } from "./types" +import { Log } from "@/util/log" +import type { OpencodeClient } from "@opencode-ai/sdk" + +const log = Log.create({ service: "acp-session-manager" }) export class ACPSessionManager { private sessions = new Map() + private sdk: OpencodeClient + + constructor(sdk: OpencodeClient) { + this.sdk = sdk + } async create(cwd: string, mcpServers: McpServer[], model?: ACPSessionState["model"]): Promise { - const session = await Session.create({ title: `ACP Session ${crypto.randomUUID()}` }) + const session = await this.sdk.session + .create({ + body: { + title: `ACP Session ${crypto.randomUUID()}`, + }, + query: { + directory: cwd, + }, + throwOnError: true, + }) + .then((x) => x.data) + const sessionId = session.id - const resolvedModel = model ?? (await Provider.defaultModel()) + const resolvedModel = model const state: ACPSessionState = { id: sessionId, @@ -18,44 +36,35 @@ export class ACPSessionManager { createdAt: new Date(), model: resolvedModel, } + log.info("creating_session", { state }) this.sessions.set(sessionId, state) return state } - get(sessionId: string) { - return this.sessions.get(sessionId) - } - - async remove(sessionId: string) { - const state = this.sessions.get(sessionId) - if (!state) return - - await Session.remove(sessionId).catch(() => {}) - this.sessions.delete(sessionId) - } - - has(sessionId: string) { - return this.sessions.has(sessionId) + get(sessionId: string): ACPSessionState { + const session = this.sessions.get(sessionId) + if (!session) { + log.error("session not found", { sessionId }) + throw RequestError.invalidParams(JSON.stringify({ error: `Session not found: ${sessionId}` })) + } + return session } getModel(sessionId: string) { - const session = this.sessions.get(sessionId) - if (!session) return + const session = this.get(sessionId) return session.model } setModel(sessionId: string, model: ACPSessionState["model"]) { - const session = this.sessions.get(sessionId) - if (!session) return + const session = this.get(sessionId) session.model = model this.sessions.set(sessionId, session) return session } setMode(sessionId: string, modeId: string) { - const session = this.sessions.get(sessionId) - if (!session) return + const session = this.get(sessionId) session.modeId = modeId this.sessions.set(sessionId, session) return session diff --git a/packages/opencode/src/acp/types.ts b/packages/opencode/src/acp/types.ts index 56308cb7..8507228e 100644 --- a/packages/opencode/src/acp/types.ts +++ b/packages/opencode/src/acp/types.ts @@ -1,11 +1,12 @@ import type { McpServer } from "@agentclientprotocol/sdk" +import type { OpencodeClient } from "@opencode-ai/sdk" export interface ACPSessionState { id: string cwd: string mcpServers: McpServer[] createdAt: Date - model: { + model?: { providerID: string modelID: string } @@ -13,6 +14,7 @@ export interface ACPSessionState { } export interface ACPConfig { + sdk: OpencodeClient defaultModel?: { providerID: string modelID: string diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index a6933708..f1050ae7 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -20,6 +20,8 @@ export namespace Agent { edit: Config.Permission, bash: z.record(z.string(), Config.Permission), webfetch: Config.Permission.optional(), + doom_loop: Config.Permission.optional(), + external_directory: Config.Permission.optional(), }), model: z .object({ @@ -45,6 +47,8 @@ export namespace Agent { "*": "allow", }, webfetch: "allow", + doom_loop: "ask", + external_directory: "ask", } const agentPermission = mergeAgentPermissions(defaultPermission, cfg.permission ?? {}) @@ -244,6 +248,8 @@ function mergeAgentPermissions(basePermission: any, overridePermission: any): Ag edit: merged.edit ?? "allow", webfetch: merged.webfetch ?? "allow", bash: mergedBash ?? { "*": "allow" }, + doom_loop: merged.doom_loop, + external_directory: merged.external_directory, } return result diff --git a/packages/opencode/src/cli/cmd/acp.ts b/packages/opencode/src/cli/cmd/acp.ts index de461e17..7d27f941 100644 --- a/packages/opencode/src/cli/cmd/acp.ts +++ b/packages/opencode/src/cli/cmd/acp.ts @@ -3,6 +3,8 @@ import { bootstrap } from "../bootstrap" import { cmd } from "./cmd" import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk" import { ACP } from "@/acp/agent" +import { Server } from "@/server/server" +import { createOpencodeClient } from "@opencode-ai/sdk" const log = Log.create({ service: "acp-command" }) @@ -17,15 +19,34 @@ export const AcpCommand = cmd({ command: "acp", describe: "Start ACP (Agent Client Protocol) server", builder: (yargs) => { - return yargs.option("cwd", { - describe: "working directory", - type: "string", - default: process.cwd(), - }) + return yargs + .option("cwd", { + describe: "working directory", + type: "string", + default: process.cwd(), + }) + .option("port", { + type: "number", + describe: "port to listen on", + default: 0, + }) + .option("hostname", { + type: "string", + describe: "hostname to listen on", + default: "127.0.0.1", + }) }, - handler: async (opts) => { - if (opts.cwd) process.chdir(opts["cwd"]) + handler: async (args) => { await bootstrap(process.cwd(), async () => { + const server = Server.listen({ + port: args.port, + hostname: args.hostname, + }) + + const sdk = createOpencodeClient({ + baseUrl: `http://${server.hostname}:${server.port}`, + }) + const input = new WritableStream({ write(chunk) { return new Promise((resolve, reject) => { @@ -50,9 +71,10 @@ export const AcpCommand = cmd({ }) const stream = ndJsonStream(input, output) + const agent = await ACP.init({ sdk }) new AgentSideConnection((conn) => { - return new ACP.Agent(conn) + return agent.create(conn, { sdk }) }, stream) log.info("setup connection") diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/auth.ts index aa833e97..ae24fbef 100644 --- a/packages/opencode/src/cli/cmd/auth.ts +++ b/packages/opencode/src/cli/cmd/auth.ts @@ -102,223 +102,223 @@ export const AuthLoginCommand = cmd({ prompts.outro("Done") return } - await ModelsDev.refresh().catch(() => {}) - const providers = await ModelsDev.get() - const priority: Record = { - opencode: 0, - anthropic: 1, - "github-copilot": 2, - openai: 3, - google: 4, - openrouter: 5, - vercel: 6, - } - let provider = await prompts.autocomplete({ - message: "Select provider", - maxItems: 8, - options: [ - ...pipe( - providers, - values(), - sortBy( - (x) => priority[x.id] ?? 99, - (x) => x.name ?? x.id, - ), - map((x) => ({ - label: x.name, - value: x.id, - hint: priority[x.id] <= 1 ? "recommended" : undefined, - })), - ), - { - value: "other", - label: "Other", - }, - ], - }) - - if (prompts.isCancel(provider)) throw new UI.CancelledError() - - const plugin = await Plugin.list().then((x) => x.find((x) => x.auth?.provider === provider)) - if (plugin && plugin.auth) { - let index = 0 - if (plugin.auth.methods.length > 1) { - const method = await prompts.select({ - message: "Login method", - options: [ - ...plugin.auth.methods.map((x, index) => ({ - label: x.label, - value: index.toString(), + await ModelsDev.refresh().catch(() => {}) + const providers = await ModelsDev.get() + const priority: Record = { + opencode: 0, + anthropic: 1, + "github-copilot": 2, + openai: 3, + google: 4, + openrouter: 5, + vercel: 6, + } + let provider = await prompts.autocomplete({ + message: "Select provider", + maxItems: 8, + options: [ + ...pipe( + providers, + values(), + sortBy( + (x) => priority[x.id] ?? 99, + (x) => x.name ?? x.id, + ), + map((x) => ({ + label: x.name, + value: x.id, + hint: priority[x.id] <= 1 ? "recommended" : undefined, })), - ], - }) - if (prompts.isCancel(method)) throw new UI.CancelledError() - index = parseInt(method) - } - const method = plugin.auth.methods[index] + ), + { + value: "other", + label: "Other", + }, + ], + }) - // Handle prompts for all auth types - await new Promise((resolve) => setTimeout(resolve, 10)) - const inputs: Record = {} - if (method.prompts) { - for (const prompt of method.prompts) { - if (prompt.condition && !prompt.condition(inputs)) { - continue - } - if (prompt.type === "select") { - const value = await prompts.select({ - message: prompt.message, - options: prompt.options, - }) - if (prompts.isCancel(value)) throw new UI.CancelledError() - inputs[prompt.key] = value - } else { - const value = await prompts.text({ - message: prompt.message, - placeholder: prompt.placeholder, - validate: prompt.validate ? (v) => prompt.validate!(v ?? "") : undefined, - }) - if (prompts.isCancel(value)) throw new UI.CancelledError() - inputs[prompt.key] = value - } - } - } + if (prompts.isCancel(provider)) throw new UI.CancelledError() - if (method.type === "oauth") { - const authorize = await method.authorize(inputs) - - if (authorize.url) { - prompts.log.info("Go to: " + authorize.url) - } - - if (authorize.method === "auto") { - if (authorize.instructions) { - prompts.log.info(authorize.instructions) - } - const spinner = prompts.spinner() - spinner.start("Waiting for authorization...") - const result = await authorize.callback() - if (result.type === "failed") { - spinner.stop("Failed to authorize", 1) - } - if (result.type === "success") { - const saveProvider = result.provider ?? provider - if ("refresh" in result) { - const { type: _, provider: __, refresh, access, expires, ...extraFields } = result - await Auth.set(saveProvider, { - type: "oauth", - refresh, - access, - expires, - ...extraFields, - }) - } - if ("key" in result) { - await Auth.set(saveProvider, { - type: "api", - key: result.key, - }) - } - spinner.stop("Login successful") - } - } - - if (authorize.method === "code") { - const code = await prompts.text({ - message: "Paste the authorization code here: ", - validate: (x) => (x && x.length > 0 ? undefined : "Required"), + const plugin = await Plugin.list().then((x) => x.find((x) => x.auth?.provider === provider)) + if (plugin && plugin.auth) { + let index = 0 + if (plugin.auth.methods.length > 1) { + const method = await prompts.select({ + message: "Login method", + options: [ + ...plugin.auth.methods.map((x, index) => ({ + label: x.label, + value: index.toString(), + })), + ], }) - if (prompts.isCancel(code)) throw new UI.CancelledError() - const result = await authorize.callback(code) - if (result.type === "failed") { - prompts.log.error("Failed to authorize") - } - if (result.type === "success") { - const saveProvider = result.provider ?? provider - if ("refresh" in result) { - const { type: _, provider: __, refresh, access, expires, ...extraFields } = result - await Auth.set(saveProvider, { - type: "oauth", - refresh, - access, - expires, - ...extraFields, - }) + if (prompts.isCancel(method)) throw new UI.CancelledError() + index = parseInt(method) + } + const method = plugin.auth.methods[index] + + // Handle prompts for all auth types + await new Promise((resolve) => setTimeout(resolve, 10)) + const inputs: Record = {} + if (method.prompts) { + for (const prompt of method.prompts) { + if (prompt.condition && !prompt.condition(inputs)) { + continue } - if ("key" in result) { + if (prompt.type === "select") { + const value = await prompts.select({ + message: prompt.message, + options: prompt.options, + }) + if (prompts.isCancel(value)) throw new UI.CancelledError() + inputs[prompt.key] = value + } else { + const value = await prompts.text({ + message: prompt.message, + placeholder: prompt.placeholder, + validate: prompt.validate ? (v) => prompt.validate!(v ?? "") : undefined, + }) + if (prompts.isCancel(value)) throw new UI.CancelledError() + inputs[prompt.key] = value + } + } + } + + if (method.type === "oauth") { + const authorize = await method.authorize(inputs) + + if (authorize.url) { + prompts.log.info("Go to: " + authorize.url) + } + + if (authorize.method === "auto") { + if (authorize.instructions) { + prompts.log.info(authorize.instructions) + } + const spinner = prompts.spinner() + spinner.start("Waiting for authorization...") + const result = await authorize.callback() + if (result.type === "failed") { + spinner.stop("Failed to authorize", 1) + } + if (result.type === "success") { + const saveProvider = result.provider ?? provider + if ("refresh" in result) { + const { type: _, provider: __, refresh, access, expires, ...extraFields } = result + await Auth.set(saveProvider, { + type: "oauth", + refresh, + access, + expires, + ...extraFields, + }) + } + if ("key" in result) { + await Auth.set(saveProvider, { + type: "api", + key: result.key, + }) + } + spinner.stop("Login successful") + } + } + + if (authorize.method === "code") { + const code = await prompts.text({ + message: "Paste the authorization code here: ", + validate: (x) => (x && x.length > 0 ? undefined : "Required"), + }) + if (prompts.isCancel(code)) throw new UI.CancelledError() + const result = await authorize.callback(code) + if (result.type === "failed") { + prompts.log.error("Failed to authorize") + } + if (result.type === "success") { + const saveProvider = result.provider ?? provider + if ("refresh" in result) { + const { type: _, provider: __, refresh, access, expires, ...extraFields } = result + await Auth.set(saveProvider, { + type: "oauth", + refresh, + access, + expires, + ...extraFields, + }) + } + if ("key" in result) { + await Auth.set(saveProvider, { + type: "api", + key: result.key, + }) + } + prompts.log.success("Login successful") + } + } + + prompts.outro("Done") + return + } + + if (method.type === "api") { + if (method.authorize) { + const result = await method.authorize(inputs) + if (result.type === "failed") { + prompts.log.error("Failed to authorize") + } + if (result.type === "success") { + const saveProvider = result.provider ?? provider await Auth.set(saveProvider, { type: "api", key: result.key, }) + prompts.log.success("Login successful") } - prompts.log.success("Login successful") + prompts.outro("Done") + return } } + } + if (provider === "other") { + provider = await prompts.text({ + message: "Enter provider id", + validate: (x) => (x && x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"), + }) + if (prompts.isCancel(provider)) throw new UI.CancelledError() + provider = provider.replace(/^@ai-sdk\//, "") + if (prompts.isCancel(provider)) throw new UI.CancelledError() + prompts.log.warn( + `This only stores a credential for ${provider} - you will need configure it in opencode.json, check the docs for examples.`, + ) + } + + if (provider === "amazon-bedrock") { + prompts.log.info( + "Amazon bedrock can be configured with standard AWS environment variables like AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE or AWS_ACCESS_KEY_ID", + ) prompts.outro("Done") return } - if (method.type === "api") { - if (method.authorize) { - const result = await method.authorize(inputs) - if (result.type === "failed") { - prompts.log.error("Failed to authorize") - } - if (result.type === "success") { - const saveProvider = result.provider ?? provider - await Auth.set(saveProvider, { - type: "api", - key: result.key, - }) - prompts.log.success("Login successful") - } - prompts.outro("Done") - return - } + if (provider === "opencode") { + prompts.log.info("Create an api key at https://opencode.ai/auth") } - } - if (provider === "other") { - provider = await prompts.text({ - message: "Enter provider id", - validate: (x) => (x && x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"), + if (provider === "vercel") { + prompts.log.info("You can create an api key at https://vercel.link/ai-gateway-token") + } + + const key = await prompts.password({ + message: "Enter your API key", + validate: (x) => (x && x.length > 0 ? undefined : "Required"), + }) + if (prompts.isCancel(key)) throw new UI.CancelledError() + await Auth.set(provider, { + type: "api", + key, }) - if (prompts.isCancel(provider)) throw new UI.CancelledError() - provider = provider.replace(/^@ai-sdk\//, "") - if (prompts.isCancel(provider)) throw new UI.CancelledError() - prompts.log.warn( - `This only stores a credential for ${provider} - you will need configure it in opencode.json, check the docs for examples.`, - ) - } - if (provider === "amazon-bedrock") { - prompts.log.info( - "Amazon bedrock can be configured with standard AWS environment variables like AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE or AWS_ACCESS_KEY_ID", - ) prompts.outro("Done") - return - } - - if (provider === "opencode") { - prompts.log.info("Create an api key at https://opencode.ai/auth") - } - - if (provider === "vercel") { - prompts.log.info("You can create an api key at https://vercel.link/ai-gateway-token") - } - - const key = await prompts.password({ - message: "Enter your API key", - validate: (x) => (x && x.length > 0 ? undefined : "Required"), - }) - if (prompts.isCancel(key)) throw new UI.CancelledError() - await Auth.set(provider, { - type: "api", - key, - }) - - prompts.outro("Done") }, }) }, diff --git a/packages/opencode/src/cli/cmd/debug/ripgrep.ts b/packages/opencode/src/cli/cmd/debug/ripgrep.ts index 66cfba20..4c18bce9 100644 --- a/packages/opencode/src/cli/cmd/debug/ripgrep.ts +++ b/packages/opencode/src/cli/cmd/debug/ripgrep.ts @@ -18,7 +18,7 @@ const TreeCommand = cmd({ }), async handler(args) { await bootstrap(process.cwd(), async () => { - process.stdout.write(await Ripgrep.tree({ cwd: Instance.directory, limit: args.limit }) + EOL) + process.stdout.write((await Ripgrep.tree({ cwd: Instance.directory, limit: args.limit })) + EOL) }) }, }) diff --git a/packages/opencode/src/cli/cmd/export.ts b/packages/opencode/src/cli/cmd/export.ts index 82952f1f..27f46050 100644 --- a/packages/opencode/src/cli/cmd/export.ts +++ b/packages/opencode/src/cli/cmd/export.ts @@ -18,10 +18,13 @@ export const ExportCommand = cmd({ handler: async (args) => { await bootstrap(process.cwd(), async () => { let sessionID = args.sessionID + process.stderr.write(`Exporting session: ${sessionID ?? "latest"}`) if (!sessionID) { UI.empty() - prompts.intro("Export session") + prompts.intro("Export session", { + output: process.stderr, + }) const sessions = [] for await (const session of Session.list()) { @@ -29,8 +32,12 @@ export const ExportCommand = cmd({ } if (sessions.length === 0) { - prompts.log.error("No sessions found") - prompts.outro("Done") + prompts.log.error("No sessions found", { + output: process.stderr, + }) + prompts.outro("Done", { + output: process.stderr, + }) return } @@ -44,6 +51,7 @@ export const ExportCommand = cmd({ value: session.id, hint: `${new Date(session.time.updated).toLocaleString()} • ${session.id.slice(-8)}`, })), + output: process.stderr, }) if (prompts.isCancel(selectedSession)) { @@ -52,12 +60,14 @@ export const ExportCommand = cmd({ sessionID = selectedSession as string - prompts.outro("Exporting session...") + prompts.outro("Exporting session...", { + output: process.stderr, + }) } try { const sessionInfo = await Session.get(sessionID!) - const messages = await Session.messages(sessionID!) + const messages = await Session.messages({ sessionID: sessionID! }) const exportData = { info: sessionInfo, diff --git a/packages/opencode/src/cli/cmd/generate.ts b/packages/opencode/src/cli/cmd/generate.ts index 0cefb253..c29a22a8 100644 --- a/packages/opencode/src/cli/cmd/generate.ts +++ b/packages/opencode/src/cli/cmd/generate.ts @@ -6,7 +6,7 @@ export const GenerateCommand = { handler: async () => { const specs = await Server.openapi() const json = JSON.stringify(specs, null, 2) - + // Wait for stdout to finish writing before process.exit() is called await new Promise((resolve, reject) => { process.stdout.write(json, (err) => { diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts new file mode 100644 index 00000000..1c0c605f --- /dev/null +++ b/packages/opencode/src/cli/cmd/import.ts @@ -0,0 +1,98 @@ +import type { Argv } from "yargs" +import { Session } from "../../session" +import { cmd } from "./cmd" +import { bootstrap } from "../bootstrap" +import { Storage } from "../../storage/storage" +import { Instance } from "../../project/instance" +import { EOL } from "os" + +export const ImportCommand = cmd({ + command: "import ", + describe: "import session data from JSON file or URL", + builder: (yargs: Argv) => { + return yargs.positional("file", { + describe: "path to JSON file or opencode.ai share URL", + type: "string", + demandOption: true, + }) + }, + handler: async (args) => { + await bootstrap(process.cwd(), async () => { + let exportData: + | { + info: Session.Info + messages: Array<{ + info: any + parts: any[] + }> + } + | undefined + + const isUrl = args.file.startsWith("http://") || args.file.startsWith("https://") + + if (isUrl) { + const urlMatch = args.file.match(/https?:\/\/opencode\.ai\/s\/([a-zA-Z0-9_-]+)/) + if (!urlMatch) { + process.stdout.write(`Invalid URL format. Expected: https://opencode.ai/s/`) + process.stdout.write(EOL) + return + } + + const slug = urlMatch[1] + const response = await fetch(`https://api.opencode.ai/share_data?id=${slug}`) + + if (!response.ok) { + process.stdout.write(`Failed to fetch share data: ${response.statusText}`) + process.stdout.write(EOL) + return + } + + const data = await response.json() + + if (!data.info || !data.messages || Object.keys(data.messages).length === 0) { + process.stdout.write(`Share not found: ${slug}`) + process.stdout.write(EOL) + return + } + + exportData = { + info: data.info, + messages: Object.values(data.messages).map((msg: any) => { + const { parts, ...info } = msg + return { + info, + parts, + } + }), + } + } else { + const file = Bun.file(args.file) + exportData = await file.json().catch(() => {}) + if (!exportData) { + process.stdout.write(`File not found: ${args.file}`) + process.stdout.write(EOL) + return + } + } + + if (!exportData) { + process.stdout.write(`Failed to read session data`) + process.stdout.write(EOL) + return + } + + await Storage.write(["session", Instance.project.id, exportData.info.id], exportData.info) + + for (const msg of exportData.messages) { + await Storage.write(["message", exportData.info.id, msg.info.id], msg.info) + + for (const part of msg.parts) { + await Storage.write(["part", msg.info.id, part.id], part) + } + } + + process.stdout.write(`Imported session: ${exportData.info.id}`) + process.stdout.write(EOL) + }) + }, +}) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index a979f8ba..b646f0b1 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -1,21 +1,15 @@ import type { Argv } from "yargs" import path from "path" -import { Bus } from "../../bus" -import { Provider } from "../../provider/provider" -import { Session } from "../../session" import { UI } from "../ui" import { cmd } from "./cmd" import { Flag } from "../../flag/flag" -import { Config } from "../../config/config" import { bootstrap } from "../bootstrap" -import { MessageV2 } from "../../session/message-v2" -import { Identifier } from "../../id/id" -import { Agent } from "../../agent/agent" import { Command } from "../../command" -import { SessionPrompt } from "../../session/prompt" import { EOL } from "os" -import { Permission } from "@/permission" import { select } from "@clack/prompts" +import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk" +import { Server } from "../../server/server" +import { Provider } from "../../provider/provider" const TOOL: Record = { todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD], @@ -84,11 +78,19 @@ export const RunCommand = cmd({ type: "string", describe: "title for the session (uses truncated prompt if no value provided)", }) + .option("attach", { + type: "string", + describe: "attach to a running opencode server (e.g., http://localhost:4096)", + }) + .option("port", { + type: "number", + describe: "port for the local server (defaults to random port if no value provided)", + }) }, handler: async (args) => { let message = args.message.join(" ") - let fileParts: any[] = [] + const fileParts: any[] = [] if (args.file) { const files = Array.isArray(args.file) ? args.file : [args.file] @@ -124,79 +126,8 @@ export const RunCommand = cmd({ process.exit(1) } - await bootstrap(process.cwd(), async () => { - if (args.command) { - const exists = await Command.get(args.command) - if (!exists) { - UI.error(`Command "${args.command}" not found`) - process.exit(1) - } - } - const session = await (async () => { - if (args.continue) { - const it = Session.list() - try { - for await (const s of it) { - if (s.parentID === undefined) { - return s - } - } - return - } finally { - await it.return() - } - } - - if (args.session) return Session.get(args.session) - - const title = (() => { - if (args.title !== undefined) { - if (args.title === "") { - return message.slice(0, 50) + (message.length > 50 ? "..." : "") - } - return args.title - } - return undefined - })() - - return Session.create({ - title, - }) - })() - - if (!session) { - UI.error("Session not found") - process.exit(1) - } - - const cfg = await Config.get() - if (cfg.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share) { - try { - await Session.share(session.id) - UI.println(UI.Style.TEXT_INFO_BOLD + "~ https://opencode.ai/s/" + session.id.slice(-8)) - } catch (error) { - if (error instanceof Error && error.message.includes("disabled")) { - UI.println(UI.Style.TEXT_DANGER_BOLD + "! " + error.message) - } else { - throw error - } - } - } - - const agent = await (async () => { - if (args.agent) return Agent.get(args.agent) - const build = Agent.get("build") - if (build) return build - return Agent.list().then((x) => x[0]) - })() - - const { providerID, modelID } = await (async () => { - if (args.model) return Provider.parseModel(args.model) - if (agent.model) return agent.model - return await Provider.defaultModel() - })() - - function printEvent(color: string, type: string, title: string) { + const execute = async (sdk: OpencodeClient, sessionID: string) => { + const printEvent = (color: string, type: string, title: string) => { UI.println( color + `|`, UI.Style.TEXT_NORMAL + UI.Style.TEXT_DIM + ` ${type.padEnd(7, " ")}`, @@ -205,135 +136,210 @@ export const RunCommand = cmd({ ) } - function outputJsonEvent(type: string, data: any) { + const outputJsonEvent = (type: string, data: any) => { if (args.format === "json") { - const jsonEvent = { - type, - timestamp: Date.now(), - sessionID: session?.id, - ...data, - } - process.stdout.write(JSON.stringify(jsonEvent) + EOL) + process.stdout.write(JSON.stringify({ type, timestamp: Date.now(), sessionID, ...data }) + EOL) return true } return false } - const messageID = Identifier.ascending("message") - - Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => { - if (evt.properties.part.sessionID !== session.id) return - if (evt.properties.part.messageID === messageID) return - const part = evt.properties.part - - if (part.type === "tool" && part.state.status === "completed") { - if (outputJsonEvent("tool_use", { part })) return - const [tool, color] = TOOL[part.tool] ?? [part.tool, UI.Style.TEXT_INFO_BOLD] - const title = - part.state.title || - (Object.keys(part.state.input).length > 0 - ? JSON.stringify(part.state.input) - : "Unknown") - - printEvent(color, tool, title) - - if (part.tool === "bash" && part.state.output && part.state.output.trim()) { - UI.println() - UI.println(part.state.output) - } - } - - if (part.type === "step-start") { - if (outputJsonEvent("step_start", { part })) return - } - - if (part.type === "step-finish") { - if (outputJsonEvent("step_finish", { part })) return - } - - if (part.type === "text") { - const text = part.text - const isPiped = !process.stdout.isTTY - - if (part.time?.end) { - if (outputJsonEvent("text", { part })) return - if (!isPiped) UI.println() - process.stdout.write((isPiped ? text : UI.markdown(text)) + EOL) - if (!isPiped) UI.println() - } - } - }) - + const events = await sdk.event.subscribe() let errorMsg: string | undefined - Bus.subscribe(Session.Event.Error, async (evt) => { - const { sessionID, error } = evt.properties - if (sessionID !== session.id || !error) return - let err = String(error.name) - if ("data" in error && error.data && "message" in error.data) { - err = error.data.message + const eventProcessor = (async () => { + for await (const event of events.stream) { + if (event.type === "message.part.updated") { + const part = event.properties.part + if (part.sessionID !== sessionID) continue + + if (part.type === "tool" && part.state.status === "completed") { + if (outputJsonEvent("tool_use", { part })) continue + const [tool, color] = TOOL[part.tool] ?? [part.tool, UI.Style.TEXT_INFO_BOLD] + const title = + part.state.title || + (Object.keys(part.state.input).length > 0 ? JSON.stringify(part.state.input) : "Unknown") + printEvent(color, tool, title) + if (part.tool === "bash" && part.state.output?.trim()) { + UI.println() + UI.println(part.state.output) + } + } + + if (part.type === "step-start") { + if (outputJsonEvent("step_start", { part })) continue + } + + if (part.type === "step-finish") { + if (outputJsonEvent("step_finish", { part })) continue + } + + if (part.type === "text" && part.time?.end) { + if (outputJsonEvent("text", { part })) continue + const isPiped = !process.stdout.isTTY + if (!isPiped) UI.println() + process.stdout.write((isPiped ? part.text : UI.markdown(part.text)) + EOL) + if (!isPiped) UI.println() + } + } + + if (event.type === "session.error") { + const props = event.properties + if (props.sessionID !== sessionID || !props.error) continue + let err = String(props.error.name) + if ("data" in props.error && props.error.data && "message" in props.error.data) { + err = String(props.error.data.message) + } + errorMsg = errorMsg ? errorMsg + EOL + err : err + if (outputJsonEvent("error", { error: props.error })) continue + UI.error(err) + } + + if (event.type === "session.idle" && event.properties.sessionID === sessionID) { + break + } + + if (event.type === "permission.updated") { + const permission = event.properties + if (permission.sessionID !== sessionID) continue + const result = await select({ + message: `Permission required to run: ${permission.title}`, + options: [ + { value: "once", label: "Allow once" }, + { value: "always", label: "Always allow" }, + { value: "reject", label: "Reject" }, + ], + initialValue: "once", + }).catch(() => "reject") + const response = (result.toString().includes("cancel") ? "reject" : result) as "once" | "always" | "reject" + await sdk.postSessionIdPermissionsPermissionId({ + path: { id: sessionID, permissionID: permission.id }, + body: { response }, + }) + } } - errorMsg = errorMsg ? errorMsg + EOL + err : err + })() - if (outputJsonEvent("error", { error })) return - UI.error(err) - }) - - Bus.subscribe(Permission.Event.Updated, async (evt) => { - const permission = evt.properties - const message = `Permission required to run: ${permission.title}` - - const result = await select({ - message, - options: [ - { value: "once", label: "Allow once" }, - { value: "always", label: "Always allow" }, - { value: "reject", label: "Reject" }, - ], - initialValue: "once", - }).catch(() => "reject") - const response = (result.toString().includes("cancel") ? "reject" : result) as - | "once" - | "always" - | "reject" - - Permission.respond({ - sessionID: session.id, - permissionID: permission.id, - response, - }) - }) - - await (async () => { - if (args.command) { - return await SessionPrompt.command({ - messageID, - sessionID: session.id, - agent: agent.name, - model: providerID + "/" + modelID, + if (args.command) { + await sdk.session.command({ + path: { id: sessionID }, + body: { + agent: args.agent || "build", + model: args.model, command: args.command, arguments: message, - }) - } - return await SessionPrompt.prompt({ - sessionID: session.id, - messageID, - model: { - providerID, - modelID, }, - agent: agent.name, - parts: [ - ...fileParts, - { - id: Identifier.ascending("part"), - type: "text", - text: message, - }, - ], }) - })() + } else { + const modelParam = args.model ? Provider.parseModel(args.model) : undefined + await sdk.session.prompt({ + path: { id: sessionID }, + body: { + agent: args.agent || "build", + model: modelParam, + parts: [...fileParts, { type: "text", text: message }], + }, + }) + } + + await eventProcessor if (errorMsg) process.exit(1) + } + + if (args.attach) { + const sdk = createOpencodeClient({ baseUrl: args.attach }) + + const sessionID = await (async () => { + if (args.continue) { + const result = await sdk.session.list() + return result.data?.find((s) => !s.parentID)?.id + } + if (args.session) return args.session + + const title = + args.title !== undefined + ? args.title === "" + ? message.slice(0, 50) + (message.length > 50 ? "..." : "") + : args.title + : undefined + + const result = await sdk.session.create({ body: title ? { title } : {} }) + return result.data?.id + })() + + if (!sessionID) { + UI.error("Session not found") + process.exit(1) + } + + const cfgResult = await sdk.config.get() + if (cfgResult.data && (cfgResult.data.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share)) { + const shareResult = await sdk.session.share({ path: { id: sessionID } }).catch((error) => { + if (error instanceof Error && error.message.includes("disabled")) { + UI.println(UI.Style.TEXT_DANGER_BOLD + "! " + error.message) + } + return { error } + }) + if (!shareResult.error) { + UI.println(UI.Style.TEXT_INFO_BOLD + "~ https://opencode.ai/s/" + sessionID.slice(-8)) + } + } + + return await execute(sdk, sessionID) + } + + await bootstrap(process.cwd(), async () => { + const server = Server.listen({ port: args.port ?? 0, hostname: "127.0.0.1" }) + const sdk = createOpencodeClient({ baseUrl: `http://${server.hostname}:${server.port}` }) + + if (args.command) { + const exists = await Command.get(args.command) + if (!exists) { + server.stop() + UI.error(`Command "${args.command}" not found`) + process.exit(1) + } + } + + const sessionID = await (async () => { + if (args.continue) { + const result = await sdk.session.list() + return result.data?.find((s) => !s.parentID)?.id + } + if (args.session) return args.session + + const title = + args.title !== undefined + ? args.title === "" + ? message.slice(0, 50) + (message.length > 50 ? "..." : "") + : args.title + : undefined + + const result = await sdk.session.create({ body: title ? { title } : {} }) + return result.data?.id + })() + + if (!sessionID) { + server.stop() + UI.error("Session not found") + process.exit(1) + } + + const cfgResult = await sdk.config.get() + if (cfgResult.data && (cfgResult.data.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share)) { + const shareResult = await sdk.session.share({ path: { id: sessionID } }).catch((error) => { + if (error instanceof Error && error.message.includes("disabled")) { + UI.println(UI.Style.TEXT_DANGER_BOLD + "! " + error.message) + } + return { error } + }) + if (!shareResult.error) { + UI.println(UI.Style.TEXT_INFO_BOLD + "~ https://opencode.ai/s/" + sessionID.slice(-8)) + } + } + + await execute(sdk, sessionID) + server.stop() }) }, }) diff --git a/packages/opencode/src/cli/cmd/stats.ts b/packages/opencode/src/cli/cmd/stats.ts index 5abee45f..58e8397d 100644 --- a/packages/opencode/src/cli/cmd/stats.ts +++ b/packages/opencode/src/cli/cmd/stats.ts @@ -68,9 +68,7 @@ async function getAllSessions(): Promise { if (!project) continue const sessionKeys = await Storage.list(["session", project.id]) - const projectSessions = await Promise.all( - sessionKeys.map((key) => Storage.read(key)), - ) + const projectSessions = await Promise.all(sessionKeys.map((key) => Storage.read(key))) for (const session of projectSessions) { if (session) { @@ -87,16 +85,12 @@ async function aggregateSessionStats(days?: number, projectFilter?: string): Pro const DAYS_IN_SECOND = 24 * 60 * 60 * 1000 const cutoffTime = days ? Date.now() - days * DAYS_IN_SECOND : 0 - let filteredSessions = days - ? sessions.filter((session) => session.time.updated >= cutoffTime) - : sessions + let filteredSessions = days ? sessions.filter((session) => session.time.updated >= cutoffTime) : sessions if (projectFilter !== undefined) { if (projectFilter === "") { const currentProject = await getCurrentProject() - filteredSessions = filteredSessions.filter( - (session) => session.projectID === currentProject.id, - ) + filteredSessions = filteredSessions.filter((session) => session.projectID === currentProject.id) } else { filteredSessions = filteredSessions.filter((session) => session.projectID === projectFilter) } @@ -125,9 +119,7 @@ async function aggregateSessionStats(days?: number, projectFilter?: string): Pro } if (filteredSessions.length > 1000) { - console.log( - `Large dataset detected (${filteredSessions.length} sessions). This may take a while...`, - ) + console.log(`Large dataset detected (${filteredSessions.length} sessions). This may take a while...`) } if (filteredSessions.length === 0) { @@ -142,7 +134,7 @@ async function aggregateSessionStats(days?: number, projectFilter?: string): Pro const batch = filteredSessions.slice(i, i + BATCH_SIZE) const batchPromises = batch.map(async (session) => { - const messages = await Session.messages(session.id) + const messages = await Session.messages({ sessionID: session.id }) let sessionCost = 0 let sessionTokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } @@ -262,8 +254,7 @@ export function displayStats(stats: SessionStats, toolLimit?: number) { const percentage = ((count / totalToolUsage) * 100).toFixed(1) const maxToolLength = 18 - const truncatedTool = - tool.length > maxToolLength ? tool.substring(0, maxToolLength - 2) + ".." : tool + const truncatedTool = tool.length > maxToolLength ? tool.substring(0, maxToolLength - 2) + ".." : tool const toolName = truncatedTool.padEnd(maxToolLength) const content = ` ${toolName} ${bar.padEnd(20)} ${count.toString().padStart(3)} (${percentage.padStart(4)}%)` diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index e57ef8cd..dc4eabfd 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -1,8 +1,8 @@ import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid" import { Clipboard } from "@tui/util/clipboard" import { TextAttributes } from "@opentui/core" -import { RouteProvider, useRoute, type Route } from "@tui/context/route" -import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal } from "solid-js" +import { RouteProvider, useRoute } from "@tui/context/route" +import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch } from "solid-js" import { Installation } from "@/installation" import { Global } from "@/global" import { DialogProvider, useDialog } from "@tui/ui/dialog" @@ -24,10 +24,11 @@ import { PromptHistoryProvider } from "./component/prompt/history" import { DialogAlert } from "./ui/dialog-alert" import { ToastProvider, useToast } from "./ui/toast" import { ExitProvider, useExit } from "./context/exit" -import type { SessionRoute } from "./context/route" import { Session as SessionApi } from "@/session" import { TuiEvent } from "./event" import { KVProvider, useKV } from "./context/kv" +import { Provider } from "@/provider/provider" +import { ArgsProvider, useArgs, type Args } from "./context/args" async function getTerminalBackgroundColor(): Promise<"dark" | "light"> { // can't set raw mode if not a TTY @@ -89,25 +90,10 @@ async function getTerminalBackgroundColor(): Promise<"dark" | "light"> { }) } -export function tui(input: { - url: string - sessionID?: string - model?: string - agent?: string - prompt?: string - onExit?: () => Promise -}) { +export function tui(input: { url: string; args: Args; onExit?: () => Promise }) { // promise to prevent immediate exit return new Promise(async (resolve) => { const mode = await getTerminalBackgroundColor() - - const routeData: Route | undefined = input.sessionID - ? { - type: "session", - sessionID: input.sessionID, - } - : undefined - const onExit = async () => { await input.onExit?.() resolve() @@ -116,40 +102,34 @@ export function tui(input: { render( () => { return ( - ( - - )} - > - - - - - - - - - - - - - - - - - - - - - - - - - + }> + + + + + + + + + + + + + + + + + + + + + + + + + + + ) }, @@ -173,44 +153,51 @@ function App() { const kv = useKV() const command = useCommandDialog() const { event } = useSDK() - const sync = useSync() const toast = useToast() - const [sessionExists, setSessionExists] = createSignal(false) const { theme, mode, setMode } = useTheme() + const sync = useSync() const exit = useExit() - useKeyboard(async (evt) => { - if (!Installation.isLocal()) return - if (evt.meta && evt.name === "t") { - renderer.toggleDebugOverlay() - return - } - - if (evt.meta && evt.name === "d") { - renderer.console.toggle() - return - } - }) - - // Make sure session is valid, otherwise redirect to home - createEffect(async () => { - if (route.data.type === "session") { - const data = route.data as SessionRoute - await sync.session.sync(data.sessionID).catch(() => { - toast.show({ - message: `Session not found: ${data.sessionID}`, - variant: "error", - }) - return route.navigate({ type: "home" }) - }) - setSessionExists(true) - } - }) - createEffect(() => { console.log(JSON.stringify(route.data)) }) + const args = useArgs() + onMount(() => { + batch(() => { + if (args.agent) local.agent.set(args.agent) + if (args.model) { + const { providerID, modelID } = Provider.parseModel(args.model) + if (!providerID || !modelID) + return toast.show({ + variant: "warning", + message: `Invalid model format: ${args.model}`, + duration: 3000, + }) + local.model.set({ providerID, modelID }, { recent: true }) + } + if (args.sessionID) { + route.navigate({ + type: "session", + sessionID: args.sessionID, + }) + } + }) + }) + + createEffect(() => { + if (sync.status !== "complete") return + if (args.continue) { + const match = sync.data.session.at(0)?.id + if (match) { + route.navigate({ + type: "session", + sessionID: match, + }) + } + } + }) + command.register(() => [ { title: "Switch session", @@ -328,6 +315,24 @@ function App() { onSelect: exit, category: "System", }, + { + title: "Toggle debug panel", + category: "System", + value: "app.debug", + onSelect: (dialog) => { + renderer.toggleDebugOverlay() + dialog.clear() + }, + }, + { + title: "Toggle console", + category: "System", + value: "app.fps", + onSelect: (dialog) => { + renderer.console.toggle() + dialog.clear() + }, + }, ]) createEffect(() => { @@ -367,6 +372,27 @@ function App() { } }) + event.on(SessionApi.Event.Error.type, (evt) => { + const error = evt.properties.error + const message = (() => { + if (!error) return "An error occured" + + if (typeof error === "object") { + const data = error.data + if ("message" in data && typeof data.message === "string") { + return data.message + } + } + return String(error) + })() + + toast.show({ + variant: "error", + message, + duration: 5000, + }) + }) + return ( - + @@ -405,12 +431,7 @@ function App() { flexShrink={0} > - + open code{" "} @@ -426,11 +447,7 @@ function App() { tab {""} - + {local.agent.current().name.toUpperCase()} AGENT diff --git a/packages/opencode/src/cli/cmd/tui/attach.ts b/packages/opencode/src/cli/cmd/tui/attach.ts index 38f1b671..7da6507e 100644 --- a/packages/opencode/src/cli/cmd/tui/attach.ts +++ b/packages/opencode/src/cli/cmd/tui/attach.ts @@ -17,6 +17,9 @@ export const AttachCommand = cmd({ }), handler: async (args) => { if (args.dir) process.chdir(args.dir) - await tui(args) + await tui({ + url: args.url, + args: {}, + }) }, }) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx index 04f2f652..9f2d927e 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx @@ -4,12 +4,19 @@ import { useSync } from "@tui/context/sync" import { map, pipe, flatMap, entries, filter, isDeepEqual, sortBy } from "remeda" import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select" import { useDialog } from "@tui/ui/dialog" +import { useTheme } from "../context/theme" + +function Free() { + const { theme } = useTheme() + return Free +} export function DialogModel() { const local = useLocal() const sync = useSync() const dialog = useDialog() const [ref, setRef] = createSignal>() + const { theme } = useTheme() const options = createMemo(() => { return [ @@ -29,6 +36,7 @@ export function DialogModel() { title: model.name ?? item.modelID, description: provider.name, category: "Recent", + footer: model.cost?.input === 0 && provider.id === "opencode" ? : undefined, }, ] }) @@ -51,6 +59,7 @@ export function DialogModel() { title: info.name ?? model, description: provider.name, category: provider.name, + footer: info.cost?.input === 0 && provider.id === "opencode" ? : undefined, })), filter((x) => Boolean(ref()?.filter) || !local.model.recent().find((y) => isDeepEqual(y, x.value))), ), diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 4a720aa1..5e0095a8 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -2,7 +2,7 @@ import { useDialog } from "@tui/ui/dialog" import { DialogSelect } from "@tui/ui/dialog-select" import { useRoute } from "@tui/context/route" import { useSync } from "@tui/context/sync" -import { createMemo, createSignal, onMount } from "solid-js" +import { createEffect, createMemo, createSignal, onMount } from "solid-js" import { Locale } from "@/util/locale" import { Keybind } from "@/util/keybind" import { useTheme } from "../context/theme" @@ -20,6 +20,8 @@ export function DialogSessionList() { const deleteKeybind = "ctrl+d" + const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined)) + const options = createMemo(() => { const today = new Date().toDateString() return sync.data.session @@ -39,6 +41,11 @@ export function DialogSessionList() { footer: Locale.time(x.time.updated), } }) + .slice(0, 150) + }) + + createEffect(() => { + console.log("session count", sync.data.session.length) }) onMount(() => { @@ -49,7 +56,7 @@ export function DialogSessionList() { { setToDelete(undefined) }} diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx index d1ef5ca5..e427e24e 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx @@ -77,10 +77,7 @@ export function DialogStatus() { )} - 0} - fallback={No Formatters} - > + 0} fallback={No Formatters}> {enabledFormatters().length} Formatters diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-theme-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-theme-list.tsx index 60411e56..5240603f 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-theme-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-theme-list.tsx @@ -1,26 +1,24 @@ import { DialogSelect, type DialogSelectRef } from "../ui/dialog-select" -import { THEMES, useTheme } from "../context/theme" +import { useTheme } from "../context/theme" import { useDialog } from "../ui/dialog" import { onCleanup, onMount } from "solid-js" export function DialogThemeList() { const theme = useTheme() - const options = Object.keys(THEMES).map((value) => ({ + const options = Object.keys(theme.all()).map((value) => ({ title: value, - value: value as keyof typeof THEMES, + value: value, })) const dialog = useDialog() let confirmed = false - let ref: DialogSelectRef + let ref: DialogSelectRef const initial = theme.selected onMount(() => { - // highlight the first theme in the list when we open it for UX - theme.set(Object.keys(THEMES)[0] as keyof typeof THEMES) + theme.set(Object.keys(theme.all())[0]) }) onCleanup(() => { - // if we close the dialog without confirming, reset back to the initial theme if (!confirmed) theme.set(initial) }) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index a4c1f33d..68578e70 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -83,12 +83,7 @@ export function Autocomplete(props: { const extmarkStart = store.index const extmarkEnd = extmarkStart + Bun.stringWidth(virtualText) - const styleId = - part.type === "file" - ? props.fileStyleId - : part.type === "agent" - ? props.agentStyleId - : undefined + const styleId = part.type === "file" ? props.fileStyleId : part.type === "agent" ? props.agentStyleId : undefined const extmarkId = input.extmarks.create({ start: extmarkStart, @@ -164,7 +159,6 @@ export function Autocomplete(props: { ) const agents = createMemo(() => { - if (store.index !== 0) return [] const agents = sync.data.agent return agents .filter((agent) => !agent.builtIn && agent.mode !== "primary") @@ -186,9 +180,7 @@ export function Autocomplete(props: { ) }) - const session = createMemo(() => - props.sessionID ? sync.session.get(props.sessionID) : undefined, - ) + const session = createMemo(() => (props.sessionID ? sync.session.get(props.sessionID) : undefined)) const commands = createMemo((): AutocompleteOption[] => { const results: AutocompleteOption[] = [] const s = session() @@ -211,7 +203,6 @@ export function Autocomplete(props: { display: "/undo", description: "undo the last message", onSelect: () => { - hide() command.trigger("session.undo") }, }, @@ -243,6 +234,16 @@ export function Autocomplete(props: { description: "rename session", onSelect: () => command.trigger("session.rename"), }, + { + display: "/copy", + description: "copy session transcript to clipboard", + onSelect: () => command.trigger("session.copy"), + }, + { + display: "/export", + description: "export session transcript to file", + onSelect: () => command.trigger("session.export"), + }, { display: "/timeline", description: "jump to message", @@ -316,14 +317,12 @@ export function Autocomplete(props: { const options = createMemo(() => { const mixed: AutocompleteOption[] = ( - store.visible === "@" - ? [...agents(), ...(files.loading ? files.latest || [] : files())] - : [...commands()] + store.visible === "@" ? [...agents(), ...(files.loading ? files.latest || [] : files())] : [...commands()] ).filter((x) => x.disabled !== true) const currentFilter = filter() if (!currentFilter) return mixed.slice(0, 10) const result = fuzzysort.go(currentFilter, mixed, { - keys: ["display", "description", (obj) => obj.aliases?.join(" ") ?? ""], + keys: [(obj) => obj.display.trimEnd(), "description", (obj) => obj.aliases?.join(" ") ?? ""], limit: 10, }) return result.map((arr) => arr.obj) @@ -365,7 +364,7 @@ export function Autocomplete(props: { function hide() { const text = props.input().plainText - if (store.visible === "/" && !text.endsWith(" ")) { + if (store.visible === "/" && !text.endsWith(" ") && text.startsWith("/")) { const cursor = props.input().logicalCursor props.input().deleteRange(0, 0, cursor.row, cursor.col) } @@ -423,13 +422,8 @@ export function Autocomplete(props: { if (e.name === "@") { const cursorOffset = props.input().cursorOffset const charBeforeCursor = - cursorOffset === 0 - ? undefined - : props.input().getTextRange(cursorOffset - 1, cursorOffset) - const canTrigger = - charBeforeCursor === undefined || - charBeforeCursor === "" || - /\s/.test(charBeforeCursor) + cursorOffset === 0 ? undefined : props.input().getTextRange(cursorOffset - 1, cursorOffset) + const canTrigger = charBeforeCursor === undefined || charBeforeCursor === "" || /\s/.test(charBeforeCursor) if (canTrigger) show("@") } @@ -477,10 +471,7 @@ export function Autocomplete(props: { {option.display} - + {option.description} 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 785eb7e4..4b852e66 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -3,11 +3,11 @@ import { BoxRenderable, TextareaRenderable, MouseEvent, - KeyEvent, PasteEvent, t, dim, fg, + type KeyBinding, } from "@opentui/core" import { createEffect, createMemo, Match, Switch, type JSX, onMount, batch } from "solid-js" import { useLocal } from "@tui/context/local" @@ -28,6 +28,7 @@ import { useExit } from "../../context/exit" import { Clipboard } from "../../util/clipboard" import type { FilePart } from "@opencode-ai/sdk" import { TuiEvent } from "../../event" +import { iife } from "@/util/iife" export type PromptProps = { sessionID?: string @@ -83,7 +84,7 @@ export function Prompt(props: PromptProps) { shift: binding.shift || undefined, action: "submit" as const, })), - ] + ] satisfies KeyBinding[] }) const fileStyleId = syntax().getStyleId("extmark.file")! @@ -156,10 +157,12 @@ export function Prompt(props: PromptProps) { title: "Interrupt session", value: "session.interrupt", keybind: "session_interrupt", + disabled: status() !== "working", category: "Session", - disabled: true, onSelect: (dialog) => { if (!props.sessionID) return + if (autocomplete.visible) return + if (!input.focused) return sdk.client.session.abort({ path: { id: props.sessionID, @@ -197,16 +200,6 @@ export function Prompt(props: PromptProps) { input.focus() }) - local.setInitialPrompt.listen((initialPrompt) => { - batch(() => { - setStore("prompt", { - input: initialPrompt, - parts: [], - }) - input.insertText(initialPrompt) - }) - }) - onMount(() => { promptPartTypeId = input.extmarks.registerType("prompt-part") }) @@ -331,9 +324,7 @@ export function Prompt(props: PromptProps) { // Expand pasted text inline before submitting const allExtmarks = input.extmarks.getAllForTypeId(promptPartTypeId) - const sortedExtmarks = allExtmarks.sort( - (a: { start: number }, b: { start: number }) => b.start - a.start, - ) + const sortedExtmarks = allExtmarks.sort((a: { start: number }, b: { start: number }) => b.start - a.start) for (const extmark of sortedExtmarks) { const partIndex = store.extmarkToPartIndex.get(extmark.id) @@ -361,8 +352,15 @@ export function Prompt(props: PromptProps) { }, }) setStore("mode", "normal") - } else if (inputText.startsWith("/")) { - const [command, ...args] = inputText.split(" ") + } else if ( + inputText.startsWith("/") && + iife(() => { + const command = inputText.split(" ")[0].slice(1) + console.log(command) + return sync.data.command.some((x) => x.name === command) + }) + ) { + let [command, ...args] = inputText.split(" ") sdk.client.session.command({ path: { id: sessionID, @@ -489,28 +487,15 @@ export function Prompt(props: PromptProps) { - + {store.mode === "normal" ? ">" : "!"} - +