diff --git a/.opencode/command/commit.md b/.opencode/command/commit.md
index d8a420b173..e88932a244 100644
--- a/.opencode/command/commit.md
+++ b/.opencode/command/commit.md
@@ -16,15 +16,12 @@ wip:
For anything in the packages/web use the docs: prefix.
-For anything in the packages/app use the ignore: prefix.
-
prefer to explain WHY something was done from an end user perspective instead of
WHAT was done.
do not do generic messages like "improved agent experience" be very specific
about what user facing changes were made
-if there are changes do a git pull --rebase
if there are conflicts DO NOT FIX THEM. notify me and I will fix them
## GIT DIFF
diff --git a/.signpath/policies/opencode/test-signing.yml b/.signpath/policies/opencode/test-signing.yml
new file mode 100644
index 0000000000..683b27adb7
--- /dev/null
+++ b/.signpath/policies/opencode/test-signing.yml
@@ -0,0 +1,5 @@
+github-policies:
+ runners:
+ allowed_groups:
+ - "GitHub Actions"
+ - "blacksmith runners 01kbd5v56sg8tz7rea39b7ygpt"
diff --git a/AGENTS.md b/AGENTS.md
index d51134c0e2..758714d10a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -110,3 +110,4 @@ const table = sqliteTable("session", {
- Avoid mocks as much as possible
- Test actual implementation, do not duplicate logic into tests
+- Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`.
diff --git a/README.ar.md b/README.ar.md
index 4c8ac5fcc3..edac204a28 100644
--- a/README.ar.md
+++ b/README.ar.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS و Linux (موصى به، دائما محدث)
brew install opencode # macOS و Linux (صيغة brew الرسمية، تحديث اقل)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # اي نظام
nix run nixpkgs#opencode # او github:anomalyco/opencode لاحدث فرع dev
```
diff --git a/README.br.md b/README.br.md
index ee5e85fd44..c185603efb 100644
--- a/README.br.md
+++ b/README.br.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS e Linux (recomendado, sempre atualizado)
brew install opencode # macOS e Linux (fórmula oficial do brew, atualiza menos)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # qualquer sistema
nix run nixpkgs#opencode # ou github:anomalyco/opencode para a branch dev mais recente
```
diff --git a/README.bs.md b/README.bs.md
index 56a1e72fb6..d64a69c0d7 100644
--- a/README.bs.md
+++ b/README.bs.md
@@ -51,7 +51,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS i Linux (preporučeno, uvijek ažurno)
brew install opencode # macOS i Linux (zvanična brew formula, rjeđe se ažurira)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # Bilo koji OS
nix run nixpkgs#opencode # ili github:anomalyco/opencode za najnoviji dev branch
```
diff --git a/README.da.md b/README.da.md
index 79928fd944..7f3d5aa5dd 100644
--- a/README.da.md
+++ b/README.da.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS og Linux (anbefalet, altid up to date)
brew install opencode # macOS og Linux (officiel brew formula, opdateres sjældnere)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # alle OS
nix run nixpkgs#opencode # eller github:anomalyco/opencode for nyeste dev-branch
```
diff --git a/README.de.md b/README.de.md
index ccb3ad07dc..2aa78657ca 100644
--- a/README.de.md
+++ b/README.de.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS und Linux (empfohlen, immer aktuell)
brew install opencode # macOS und Linux (offizielle Brew-Formula, seltener aktualisiert)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # jedes Betriebssystem
nix run nixpkgs#opencode # oder github:anomalyco/opencode für den neuesten dev-Branch
```
diff --git a/README.es.md b/README.es.md
index e5a7d8e8dd..2b80427ab2 100644
--- a/README.es.md
+++ b/README.es.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS y Linux (recomendado, siempre al día)
brew install opencode # macOS y Linux (fórmula oficial de brew, se actualiza menos)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # cualquier sistema
nix run nixpkgs#opencode # o github:anomalyco/opencode para la rama dev más reciente
```
diff --git a/README.fr.md b/README.fr.md
index 5436009903..bc3fe9e757 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS et Linux (recommandé, toujours à jour)
brew install opencode # macOS et Linux (formule officielle brew, mise à jour moins fréquente)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # n'importe quel OS
nix run nixpkgs#opencode # ou github:anomalyco/opencode pour la branche dev la plus récente
```
diff --git a/README.it.md b/README.it.md
index cbc8a5f6d2..6da7d51fc6 100644
--- a/README.it.md
+++ b/README.it.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS e Linux (consigliato, sempre aggiornato)
brew install opencode # macOS e Linux (formula brew ufficiale, aggiornata meno spesso)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # Qualsiasi OS
nix run nixpkgs#opencode # oppure github:anomalyco/opencode per l’ultima branch di sviluppo
```
diff --git a/README.ja.md b/README.ja.md
index 8827efae88..7a0bbb08f3 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS と Linux(推奨。常に最新)
brew install opencode # macOS と Linux(公式 brew formula。更新頻度は低め)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # どのOSでも
nix run nixpkgs#opencode # または github:anomalyco/opencode で最新 dev ブランチ
```
diff --git a/README.ko.md b/README.ko.md
index 806dc642c1..1c931c31f3 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS 및 Linux (권장, 항상 최신)
brew install opencode # macOS 및 Linux (공식 brew formula, 업데이트 빈도 낮음)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # 어떤 OS든
nix run nixpkgs#opencode # 또는 github:anomalyco/opencode 로 최신 dev 브랜치
```
diff --git a/README.md b/README.md
index 2cd1e2aa01..bd01fc94e8 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)
brew install opencode # macOS and Linux (official brew formula, updated less)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # Any OS
nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch
```
diff --git a/README.no.md b/README.no.md
index 90b631fef2..092316bae1 100644
--- a/README.no.md
+++ b/README.no.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS og Linux (anbefalt, alltid oppdatert)
brew install opencode # macOS og Linux (offisiell brew-formel, oppdateres sjeldnere)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # alle OS
nix run nixpkgs#opencode # eller github:anomalyco/opencode for nyeste dev-branch
```
diff --git a/README.pl.md b/README.pl.md
index ae653a7fa0..a225d82539 100644
--- a/README.pl.md
+++ b/README.pl.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS i Linux (polecane, zawsze aktualne)
brew install opencode # macOS i Linux (oficjalna formuła brew, rzadziej aktualizowana)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # dowolny system
nix run nixpkgs#opencode # lub github:anomalyco/opencode dla najnowszej gałęzi dev
```
diff --git a/README.ru.md b/README.ru.md
index cf15c6ebce..c13f039d16 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS и Linux (рекомендуем, всегда актуально)
brew install opencode # macOS и Linux (официальная формула brew, обновляется реже)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # любая ОС
nix run nixpkgs#opencode # или github:anomalyco/opencode для самой свежей ветки dev
```
diff --git a/README.th.md b/README.th.md
index 4077abc011..ba2db8a850 100644
--- a/README.th.md
+++ b/README.th.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS และ Linux (แนะนำ อัปเดตเสมอ)
brew install opencode # macOS และ Linux (brew formula อย่างเป็นทางการ อัปเดตน้อยกว่า)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # ระบบปฏิบัติการใดก็ได้
nix run nixpkgs#opencode # หรือ github:anomalyco/opencode สำหรับสาขาพัฒนาล่าสุด
```
diff --git a/README.tr.md b/README.tr.md
index e3055e7a99..635a5782fe 100644
--- a/README.tr.md
+++ b/README.tr.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS ve Linux (önerilir, her zaman güncel)
brew install opencode # macOS ve Linux (resmi brew formülü, daha az güncellenir)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # Tüm işletim sistemleri
nix run nixpkgs#opencode # veya en güncel geliştirme dalı için github:anomalyco/opencode
```
diff --git a/README.zh.md b/README.zh.md
index 6970fe34ef..b2f288f5ba 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS 和 Linux(推荐,始终保持最新)
brew install opencode # macOS 和 Linux(官方 brew formula,更新频率较低)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # 任意系统
nix run nixpkgs#opencode # 或用 github:anomalyco/opencode 获取最新 dev 分支
```
diff --git a/README.zht.md b/README.zht.md
index a045f45490..be4ef053c0 100644
--- a/README.zht.md
+++ b/README.zht.md
@@ -50,7 +50,8 @@ scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS 與 Linux(推薦,始終保持最新)
brew install opencode # macOS 與 Linux(官方 brew formula,更新頻率較低)
-paru -S opencode-bin # Arch Linux
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # 任何作業系統
nix run nixpkgs#opencode # 或使用 github:anomalyco/opencode 以取得最新開發分支
```
diff --git a/bun.lock b/bun.lock
index 3fe8a4ca07..061e36e143 100644
--- a/bun.lock
+++ b/bun.lock
@@ -23,7 +23,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -73,7 +73,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -107,7 +107,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -115,7 +115,7 @@
"@opencode-ai/console-resource": "workspace:*",
"@planetscale/database": "1.19.0",
"aws4fetch": "1.0.20",
- "drizzle-orm": "0.41.0",
+ "drizzle-orm": "catalog:",
"postgres": "3.4.7",
"stripe": "18.0.0",
"ulid": "catalog:",
@@ -127,14 +127,14 @@
"@types/bun": "1.3.0",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
- "drizzle-kit": "0.30.5",
+ "drizzle-kit": "catalog:",
"mysql2": "3.14.4",
"typescript": "catalog:",
},
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -158,7 +158,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -182,7 +182,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -215,7 +215,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -244,7 +244,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -260,7 +260,7 @@
},
"packages/opencode": {
"name": "opencode",
- "version": "1.1.64",
+ "version": "1.2.1",
"bin": {
"opencode": "./bin/opencode",
},
@@ -268,12 +268,12 @@
"@actions/core": "1.11.1",
"@actions/github": "6.0.1",
"@agentclientprotocol/sdk": "0.14.1",
- "@ai-sdk/amazon-bedrock": "3.0.74",
- "@ai-sdk/anthropic": "2.0.58",
+ "@ai-sdk/amazon-bedrock": "3.0.79",
+ "@ai-sdk/anthropic": "2.0.62",
"@ai-sdk/azure": "2.0.91",
"@ai-sdk/cerebras": "1.0.36",
"@ai-sdk/cohere": "2.0.22",
- "@ai-sdk/deepinfra": "1.0.33",
+ "@ai-sdk/deepinfra": "1.0.36",
"@ai-sdk/gateway": "2.0.30",
"@ai-sdk/google": "2.0.52",
"@ai-sdk/google-vertex": "3.0.98",
@@ -317,6 +317,7 @@
"clipboardy": "4.0.0",
"decimal.js": "10.5.0",
"diff": "catalog:",
+ "drizzle-orm": "1.0.0-beta.12-a5629fb",
"fuzzysort": "3.1.0",
"gray-matter": "4.0.3",
"hono": "catalog:",
@@ -358,6 +359,8 @@
"@types/turndown": "5.0.5",
"@types/yargs": "17.0.33",
"@typescript/native-preview": "catalog:",
+ "drizzle-kit": "1.0.0-beta.12-a5629fb",
+ "drizzle-orm": "1.0.0-beta.12-a5629fb",
"typescript": "catalog:",
"vscode-languageserver-types": "3.17.5",
"why-is-node-running": "3.2.2",
@@ -366,7 +369,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -386,7 +389,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
- "version": "1.1.64",
+ "version": "1.2.1",
"devDependencies": {
"@hey-api/openapi-ts": "0.90.10",
"@tsconfig/node22": "catalog:",
@@ -397,7 +400,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -410,7 +413,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -452,7 +455,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"zod": "catalog:",
},
@@ -463,7 +466,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -530,6 +533,8 @@
"ai": "5.0.124",
"diff": "8.0.2",
"dompurify": "3.3.1",
+ "drizzle-kit": "1.0.0-beta.12-a5629fb",
+ "drizzle-orm": "1.0.0-beta.12-a5629fb",
"fuzzysort": "3.1.0",
"hono": "4.10.7",
"hono-openapi": "1.1.2",
@@ -565,7 +570,7 @@
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.14.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-b6r3PS3Nly+Wyw9U+0nOr47bV8tfS476EgyEMhoKvJCZLbgqoDFN7DJwkxL88RR0aiOqOYV1ZnESHqb+RmdH8w=="],
- "@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.74", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.58", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-q83HE3FBb/HPIvjXsehrHOgCuGHPorSMFt6BYnzIYZy8gNnSqV1OWX4oXVsCAuYPPMtYW/KMK35hmoIFV8QKoQ=="],
+ "@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.79", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.62", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-GfAQUb1GEmdTjLu5Ud1d5sieNHDpwoQdb4S14KmJlA5RsGREUZ1tfSKngFaiClxFtL0xPSZjePhTMV6Z65A7/g=="],
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
@@ -575,15 +580,15 @@
"@ai-sdk/cohere": ["@ai-sdk/cohere@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yJ9kP5cEDJwo8qpITq5TQFD8YNfNtW+HbyvWwrKMbFzmiMvIZuk95HIaFXE7PCTuZsqMA05yYu+qX/vQ3rNKjA=="],
- "@ai-sdk/deepgram": ["@ai-sdk/deepgram@1.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-lqmINr+1Jy2yGXxnQB6IrC2xMtUY5uK96pyKfqTj1kLlXGatKnJfXF7WTkOGgQrFqIYqpjDz+sPVR3n0KUEUtA=="],
+ "@ai-sdk/deepgram": ["@ai-sdk/deepgram@1.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yQ5izccuO7+WDtitbJsqH7qX7BqVVonUbPZBxQypF3zqBXbCI3/3CH+0XbsWRVRWFN8/rmCAbgHg8DXjaqVQsw=="],
- "@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hn2y8Q+2iZgGNVJyzPsH8EECECryFMVmxBJrBvBWoi8xcJPRyt0fZP5dOSLyGg3q0oxmPS9M0Eq0NNlKot/bYQ=="],
+ "@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.36", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.33", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LndvRktEgY2IFu4peDJMEXcjhHEEFtM0upLx/J64kCpFHCifalXpK4PPSX3PVndnn0bJzvamO5+fc0z2ooqBZw=="],
- "@ai-sdk/deepseek": ["@ai-sdk/deepseek@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-NiKjvqXI/96e/7SjZGgQH141PBqggsF7fNbjGTv4RgVWayMXp9mj0Ou2NjAUGwwxJwj/qseY0gXiDCYaHWFBkw=="],
+ "@ai-sdk/deepseek": ["@ai-sdk/deepseek@1.0.34", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-H+5UGOGGZB5tYpX+3fcWxoPPDzRTEH1w6z+yD7053PmKZfHcxSJWv9HwLEyEkAv3ef1E7MIyG5EB+HmkclQ+KQ=="],
- "@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@1.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4d5EKu0OW7Gf5WFpGo4ixn0iWEwA+GpteqUjEznWGmi7qdLE5zdkbRik5B1HrDDiw5P90yO51xBex/Fp50JcVA=="],
+ "@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@1.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B9uz4+KEB5RkphL9d9XL03iA24g3f0VAeklNlq7StY7L8Mo2sBx3Bg8Udzv7G3xJmT41GuzR5pR0FkKUTju0Rg=="],
- "@ai-sdk/fireworks": ["@ai-sdk/fireworks@1.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-WWOz5Kj+5fVe94h7WeReqjUOVtAquDE2kM575FUc8CsVxH2tRfA5cLa8nu3bknSezsKt3i67YM6mvCRxiXCkWA=="],
+ "@ai-sdk/fireworks": ["@ai-sdk/fireworks@1.0.34", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.33", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-JvRdp8bMokbmp/mFz0qHPAhvAZT+vR+c9o4lTkENkDcbRBcNYUN05sSWCuwiVDdz9T+8GW7goAec6fXJBzjIFw=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5Nrkj8B4MzkkOfjjA+Cs5pamkbkK4lI11bx80QV7TFcen/hWA8wEC+UVzwuM5H2zpekoNMjvl6GonHnR62XIZw=="],
@@ -623,7 +628,7 @@
"@astrojs/cloudflare": ["@astrojs/cloudflare@12.6.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "@astrojs/underscore-redirects": "1.0.0", "@cloudflare/workers-types": "^4.20250507.0", "tinyglobby": "^0.2.13", "vite": "^6.3.5", "wrangler": "^4.14.1" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-xhJptF5tU2k5eo70nIMyL1Udma0CqmUEnGSlGyFflLqSY82CRQI6nWZ/xZt0ZvmXuErUjIx0YYQNfZsz5CNjLQ=="],
- "@astrojs/compiler": ["@astrojs/compiler@2.13.0", "", {}, "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw=="],
+ "@astrojs/compiler": ["@astrojs/compiler@2.13.1", "", {}, "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg=="],
"@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.1", "", {}, "sha512-7dwEVigz9vUWDw3nRwLQ/yH/xYovlUA0ZD86xoeKEBmkz9O6iELG1yri67PgAPW6VLL/xInA4t7H0CK6VmtkKQ=="],
@@ -727,6 +732,8 @@
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="],
+ "@azure-rest/core-client": ["@azure-rest/core-client@2.5.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-EHaOXW0RYDKS5CFffnixdyRPak5ytiCtU7uXDcP/uiY+A6jFRwNGzzJBiznkCzvi5EYpY+YWinieqHb0oY916A=="],
+
"@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],
"@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="],
@@ -735,7 +742,7 @@
"@azure/core-http": ["@azure/core-http@3.0.5", "", { "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.3.0", "@azure/core-tracing": "1.0.0-preview.13", "@azure/core-util": "^1.1.1", "@azure/logger": "^1.0.0", "@types/node-fetch": "^2.5.0", "@types/tunnel": "^0.0.3", "form-data": "^4.0.0", "node-fetch": "^2.6.7", "process": "^0.11.10", "tslib": "^2.2.0", "tunnel": "^0.0.6", "uuid": "^8.3.0", "xml2js": "^0.5.0" } }, "sha512-T8r2q/c3DxNu6mEJfPuJtptUVqwchxzjj32gKcnMi06rdiVONS9rar7kT9T2Am+XvER7uOzpsP79WsqNbdgdWg=="],
- "@azure/core-http-compat": ["@azure/core-http-compat@2.3.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-client": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0" } }, "sha512-az9BkXND3/d5VgdRRQVkiJb2gOmDU8Qcq4GvjtBmDICNiQ9udFmDk4ZpSB5Qq1OmtDJGlQAfBaS4palFsazQ5g=="],
+ "@azure/core-http-compat": ["@azure/core-http-compat@2.3.2", "", { "dependencies": { "@azure/abort-controller": "^2.1.2" }, "peerDependencies": { "@azure/core-client": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0" } }, "sha512-Tf6ltdKzOJEgxZeWLCjMxrxbodB/ZeCbzzA1A2qHbhzAjzjHoBVSUeSl/baT/oHAxhc4qdqVaDKnc2+iE932gw=="],
"@azure/core-lro": ["@azure/core-lro@2.7.2", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.2.0", "@azure/logger": "^1.0.0", "tslib": "^2.6.2" } }, "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw=="],
@@ -749,19 +756,31 @@
"@azure/core-xml": ["@azure/core-xml@1.5.0", "", { "dependencies": { "fast-xml-parser": "^5.0.7", "tslib": "^2.8.1" } }, "sha512-D/sdlJBMJfx7gqoj66PKVmhDDaU6TKA49ptcolxdas29X7AfvLTmfAGLjAcIMBK7UZ2o4lygHIqVckOlQU3xWw=="],
+ "@azure/identity": ["@azure/identity@4.13.0", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.2", "@azure/core-rest-pipeline": "^1.17.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^4.2.0", "@azure/msal-node": "^3.5.0", "open": "^10.1.0", "tslib": "^2.2.0" } }, "sha512-uWC0fssc+hs1TGGVkkghiaFkkS7NkTxfnCH+Hdg+yTehTpMcehpok4PgUKKdyCH+9ldu6FhiHRv84Ntqj1vVcw=="],
+
+ "@azure/keyvault-common": ["@azure/keyvault-common@2.0.0", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.3.0", "@azure/core-client": "^1.5.0", "@azure/core-rest-pipeline": "^1.8.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.10.0", "@azure/logger": "^1.1.4", "tslib": "^2.2.0" } }, "sha512-wRLVaroQtOqfg60cxkzUkGKrKMsCP6uYXAOomOIysSMyt1/YM0eUn9LqieAWM8DLcU4+07Fio2YGpPeqUbpP9w=="],
+
+ "@azure/keyvault-keys": ["@azure/keyvault-keys@4.10.0", "", { "dependencies": { "@azure-rest/core-client": "^2.3.3", "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.7.2", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.0", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/keyvault-common": "^2.0.0", "@azure/logger": "^1.1.4", "tslib": "^2.8.1" } }, "sha512-eDT7iXoBTRZ2n3fLiftuGJFD+yjkiB1GNqzU2KbY1TLYeXeSPVTVgn2eJ5vmRTZ11978jy2Kg2wI7xa9Tyr8ag=="],
+
"@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="],
- "@azure/storage-blob": ["@azure/storage-blob@12.30.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.3", "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.2.0", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/core-xml": "^1.4.5", "@azure/logger": "^1.1.4", "@azure/storage-common": "^12.2.0", "events": "^3.0.0", "tslib": "^2.8.1" } }, "sha512-peDCR8blSqhsAKDbpSP/o55S4sheNwSrblvCaHUZ5xUI73XA7ieUGGwrONgD/Fng0EoDe1VOa3fAQ7+WGB3Ocg=="],
+ "@azure/msal-browser": ["@azure/msal-browser@4.28.2", "", { "dependencies": { "@azure/msal-common": "15.14.2" } }, "sha512-6vYUMvs6kJxJgxaCmHn/F8VxjLHNh7i9wzfwPGf8kyBJ8Gg2yvBXx175Uev8LdrD1F5C4o7qHa2CC4IrhGE1XQ=="],
- "@azure/storage-common": ["@azure/storage-common@12.2.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-http-compat": "^2.2.0", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.1.4", "events": "^3.3.0", "tslib": "^2.8.1" } }, "sha512-YZLxiJ3vBAAnFbG3TFuAMUlxZRexjQX5JDQxOkFGb6e2TpoxH3xyHI6idsMe/QrWtj41U/KoqBxlayzhS+LlwA=="],
+ "@azure/msal-common": ["@azure/msal-common@15.14.2", "", {}, "sha512-n8RBJEUmd5QotoqbZfd+eGBkzuFI1KX6jw2b3WcpSyGjwmzoeI/Jb99opIBPHpb8y312NB+B6+FGi2ZVSR8yfA=="],
- "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
+ "@azure/msal-node": ["@azure/msal-node@3.8.7", "", { "dependencies": { "@azure/msal-common": "15.14.2", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-a+Xnrae+uwLnlw68bplS1X4kuJ9F/7K6afuMFyRkNIskhjgDezl5Fhrx+1pmAlDmC0VaaAxjRQMp1OmcqVwkIg=="],
- "@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="],
+ "@azure/storage-blob": ["@azure/storage-blob@12.31.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.3", "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.2.0", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/core-xml": "^1.4.5", "@azure/logger": "^1.1.4", "@azure/storage-common": "^12.3.0", "events": "^3.0.0", "tslib": "^2.8.1" } }, "sha512-DBgNv10aCSxopt92DkTDD0o9xScXeBqPKGmR50FPZQaEcH4JLQ+GEOGEDv19V5BMkB7kxr+m4h6il/cCDPvmHg=="],
+
+ "@azure/storage-common": ["@azure/storage-common@12.3.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-http-compat": "^2.2.0", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.1.4", "events": "^3.3.0", "tslib": "^2.8.1" } }, "sha512-/OFHhy86aG5Pe8dP5tsp+BuJ25JOAl9yaMU3WZbkeoiFMHFtJ7tu5ili7qEdBXNW9G5lDB19trwyI6V49F/8iQ=="],
+
+ "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
+
+ "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
"@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="],
- "@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="],
+ "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
"@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="],
@@ -793,7 +812,7 @@
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
- "@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
+ "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
@@ -813,9 +832,9 @@
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
- "@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="],
+ "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
- "@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
+ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.11.0", "", {}, "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ=="],
@@ -855,7 +874,7 @@
"@dot/log": ["@dot/log@0.1.5", "", { "dependencies": { "chalk": "^4.1.2", "loglevelnext": "^6.0.0", "p-defer": "^3.0.0" } }, "sha512-ECraEVJWv2f2mWK93lYiefUkphStVlKD6yKDzisuoEmxuLKrxO9iGetHK2DoEAkj7sxjE886n0OUVVCUx0YPNg=="],
- "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
+ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="],
"@emmetio/abbreviation": ["@emmetio/abbreviation@2.3.3", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="],
@@ -881,10 +900,6 @@
"@emotion/memoize": ["@emotion/memoize@0.7.4", "", {}, "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="],
- "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
-
- "@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.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
@@ -1037,15 +1052,15 @@
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
- "@internationalized/date": ["@internationalized/date@3.10.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-oJrXtQiAXLvT9clCf1K4kxp3eKsQhIaZqxEyowkBcsvZDdZkbWrVmnGknxs5flTD0VGsxrxKgBCZty1EzoiMzA=="],
+ "@internationalized/date": ["@internationalized/date@3.11.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q=="],
"@internationalized/number": ["@internationalized/number@3.6.5", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g=="],
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
- "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
+ "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.1", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ=="],
- "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
+ "@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="],
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
@@ -1117,6 +1132,10 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+ "@js-joda/core": ["@js-joda/core@5.7.0", "", {}, "sha512-WBu4ULVVxySLLzK1Ppq+OdfP+adRS4ntmDQT915rzDJ++i95gc2jZkM5B6LWEAwN3lGXpfie3yPABozdD3K3Vg=="],
+
+ "@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="],
+
"@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="],
"@jsx-email/all": ["@jsx-email/all@2.2.3", "", { "dependencies": { "@jsx-email/body": "1.0.2", "@jsx-email/button": "1.0.4", "@jsx-email/column": "1.0.3", "@jsx-email/container": "1.0.2", "@jsx-email/font": "1.0.3", "@jsx-email/head": "1.0.2", "@jsx-email/heading": "1.0.2", "@jsx-email/hr": "1.0.2", "@jsx-email/html": "1.0.2", "@jsx-email/img": "1.0.2", "@jsx-email/link": "1.0.2", "@jsx-email/markdown": "2.0.4", "@jsx-email/preview": "1.0.2", "@jsx-email/render": "1.1.1", "@jsx-email/row": "1.0.2", "@jsx-email/section": "1.0.2", "@jsx-email/tailwind": "2.4.4", "@jsx-email/text": "1.0.2" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-OBvLe/hVSQc0LlMSTJnkjFoqs3bmxcC4zpy/5pT5agPCSKMvAKQjzmsc2xJ2wO73jSpRV1K/g38GmvdCfrhSoQ=="],
@@ -1407,8 +1426,6 @@
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
- "@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
-
"@pierre/diffs": ["@pierre/diffs@1.1.0-beta.13", "", { "dependencies": { "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-D35rxDu5V7XHX5aVGU6PF12GhscL+I+9QYgxK/i3h0d2XSirAxDdVNm49aYwlOhgmdvL0NbS1IHxPswVB5yJvw=="],
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
@@ -1501,55 +1518,55 @@
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
- "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.0", "", { "os": "android", "cpu": "arm" }, "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA=="],
+ "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
- "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.0", "", { "os": "android", "cpu": "arm64" }, "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg=="],
+ "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
- "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg=="],
+ "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
- "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA=="],
+ "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
- "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g=="],
+ "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
- "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA=="],
+ "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
- "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.0", "", { "os": "linux", "cpu": "arm" }, "sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q=="],
+ "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
- "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA=="],
+ "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
- "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw=="],
+ "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
- "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw=="],
+ "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
- "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.0", "", { "os": "linux", "cpu": "none" }, "sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q=="],
+ "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
- "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.0", "", { "os": "linux", "cpu": "none" }, "sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ=="],
+ "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
- "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ=="],
+ "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
- "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA=="],
+ "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
- "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.0", "", { "os": "linux", "cpu": "none" }, "sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ=="],
+ "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
- "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.0", "", { "os": "linux", "cpu": "none" }, "sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ=="],
+ "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
- "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg=="],
+ "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
- "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.0", "", { "os": "linux", "cpu": "x64" }, "sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A=="],
+ "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
- "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.0", "", { "os": "linux", "cpu": "x64" }, "sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw=="],
+ "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
- "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw=="],
+ "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
- "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.0", "", { "os": "none", "cpu": "arm64" }, "sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA=="],
+ "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
- "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ=="],
+ "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
- "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA=="],
+ "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
- "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.0", "", { "os": "win32", "cpu": "x64" }, "sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA=="],
+ "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
- "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ=="],
+ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
@@ -1579,7 +1596,7 @@
"@slack/socket-mode": ["@slack/socket-mode@1.3.6", "", { "dependencies": { "@slack/logger": "^3.0.0", "@slack/web-api": "^6.12.1", "@types/node": ">=12.0.0", "@types/ws": "^7.4.7", "eventemitter3": "^5", "finity": "^0.5.4", "ws": "^7.5.3" } }, "sha512-G+im7OP7jVqHhiNSdHgv2VVrnN5U7KY845/5EZimZkrD4ZmtV0P3BiWkgeJhPtdLuM7C7i6+M6h6Bh+S4OOalA=="],
- "@slack/types": ["@slack/types@2.19.0", "", {}, "sha512-7+QZ38HGcNh/b/7MpvPG6jnw7mliV6UmrquJLqgdxkzJgQEYUcEztvFWRU49z0x4vthF0ixL5lTK601AXrS8IA=="],
+ "@slack/types": ["@slack/types@2.20.0", "", {}, "sha512-PVF6P6nxzDMrzPC8fSCsnwaI+kF8YfEpxf3MqXmdyjyWTYsZQURpkK7WWUWvP5QpH55pB7zyYL9Qem/xSgc5VA=="],
"@slack/web-api": ["@slack/web-api@6.13.0", "", { "dependencies": { "@slack/logger": "^3.0.0", "@slack/types": "^2.11.0", "@types/is-stream": "^1.1.0", "@types/node": ">=12.0.0", "axios": "^1.7.4", "eventemitter3": "^3.1.0", "form-data": "^2.5.0", "is-electron": "2.2.2", "is-stream": "^1.1.0", "p-queue": "^6.6.1", "p-retry": "^4.0.0" } }, "sha512-dv65crIgdh9ZYHrevLU6XFHTQwTyDmNqEqzuIrV+Vqe/vgiG6w37oex5ePDU1RGm2IJ90H8iOvHFvzdEO/vB+g=="],
@@ -1591,7 +1608,7 @@
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ=="],
- "@smithy/core": ["@smithy/core@3.22.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-6vjCHD6vaY8KubeNw2Fg3EK0KLGQYdldG4fYgQmA0xSW0dJ8G2xFhSOdrlUakWVoP5JuWHtFODg3PNd/DN3FDA=="],
+ "@smithy/core": ["@smithy/core@3.23.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Yq4UPVoQICM9zHnByLmG8632t2M0+yap4T7ANVw482J0W7HW0pOuxwVmeOwzJqX2Q89fkXz0Vybz55Wj2Xzrsg=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw=="],
@@ -1621,9 +1638,9 @@
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A=="],
- "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.12", "", { "dependencies": { "@smithy/core": "^3.22.0", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-9JMKHVJtW9RysTNjcBZQHDwB0p3iTP6B1IfQV4m+uCevkVd/VuLgwfqk5cnI4RHcp4cPwoIvxQqN4B1sxeHo8Q=="],
+ "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.14", "", { "dependencies": { "@smithy/core": "^3.23.0", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FUFNE5KVeaY6U/GL0nzAAHkaCHzXLZcY1EhtQnsAqhD8Du13oPKtMB9/0WK4/LK6a/T5OZ24wPoSShff5iI6Ag=="],
- "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.29", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-bmTn75a4tmKRkC5w61yYQLb3DmxNzB8qSVu9SbTYqW6GAL0WXO2bDZuMAn/GJSbOdHEdjZvWxe+9Kk015bw6Cg=="],
+ "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.31", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-RXBzLpMkIrxBPe4C8OmEOHvS8aH9RUuCOH++Acb5jZDEblxDjyg6un72X9IcbrGTJoiUwmI7hLypNfuDACypbg=="],
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ=="],
@@ -1631,7 +1648,7 @@
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.8", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg=="],
- "@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.8", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg=="],
+ "@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.10", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA=="],
"@smithy/property-provider": ["@smithy/property-provider@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w=="],
@@ -1647,7 +1664,7 @@
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.8", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg=="],
- "@smithy/smithy-client": ["@smithy/smithy-client@4.11.1", "", { "dependencies": { "@smithy/core": "^3.22.0", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" } }, "sha512-SERgNg5Z1U+jfR6/2xPYjSEHY1t3pyTHC/Ma3YQl6qWtmiL42bvNId3W/oMUWIwu7ekL2FMPdqAmwbQegM7HeQ=="],
+ "@smithy/smithy-client": ["@smithy/smithy-client@4.11.3", "", { "dependencies": { "@smithy/core": "^3.23.0", "@smithy/middleware-endpoint": "^4.4.14", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" } }, "sha512-Q7kY5sDau8OoE6Y9zJoRGgje8P4/UY0WzH8R2ok0PDh+iJ+ZnEKowhjEqYafVcubkbYxQVaqwm3iufktzhprGg=="],
"@smithy/types": ["@smithy/types@4.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw=="],
@@ -1663,9 +1680,9 @@
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
- "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.28", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-/9zcatsCao9h6g18p/9vH9NIi5PSqhCkxQ/tb7pMgRFnqYp9XUOyOlGPDMHzr8n5ih6yYgwJEY2MLEobUgi47w=="],
+ "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.30", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-cMni0uVU27zxOiU8TuC8pQLC1pYeZ/xEMxvchSK/ILwleRd1ugobOcIRr5vXtcRqKd4aBLWlpeBoDPJJ91LQng=="],
- "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.31", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-JTvoApUXA5kbpceI2vuqQzRjeTbLpx1eoa5R/YEZbTgtxvIB7AQZxFJ0SEyfCpgPCyVV9IT7we+ytSeIB3CyWA=="],
+ "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.33", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-LEb2aq5F4oZUSzWBG7S53d4UytZSkOEJPXcBq/xbG2/TmK9EW5naUZ8lKu1BEyWMzdHIzEVN16M3k8oxDq+DJA=="],
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw=="],
@@ -1675,7 +1692,7 @@
"@smithy/util-retry": ["@smithy/util-retry@4.2.8", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg=="],
- "@smithy/util-stream": ["@smithy/util-stream@4.5.10", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g=="],
+ "@smithy/util-stream": ["@smithy/util-stream@4.5.12", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.10", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg=="],
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
@@ -1781,39 +1798,39 @@
"@tanstack/server-functions-plugin": ["@tanstack/server-functions-plugin@1.134.5", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.27.7", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.27.7", "@babel/types": "^7.27.7", "@tanstack/directive-functions-plugin": "1.134.5", "babel-dead-code-elimination": "^1.0.9", "tiny-invariant": "^1.3.3" } }, "sha512-2sWxq70T+dOEUlE3sHlXjEPhaFZfdPYlWTSkHchWXrFGw2YOAa+hzD6L9wHMjGDQezYd03ue8tQlHG+9Jzbzgw=="],
- "@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="],
+ "@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
- "@tauri-apps/cli": ["@tauri-apps/cli@2.9.6", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.9.6", "@tauri-apps/cli-darwin-x64": "2.9.6", "@tauri-apps/cli-linux-arm-gnueabihf": "2.9.6", "@tauri-apps/cli-linux-arm64-gnu": "2.9.6", "@tauri-apps/cli-linux-arm64-musl": "2.9.6", "@tauri-apps/cli-linux-riscv64-gnu": "2.9.6", "@tauri-apps/cli-linux-x64-gnu": "2.9.6", "@tauri-apps/cli-linux-x64-musl": "2.9.6", "@tauri-apps/cli-win32-arm64-msvc": "2.9.6", "@tauri-apps/cli-win32-ia32-msvc": "2.9.6", "@tauri-apps/cli-win32-x64-msvc": "2.9.6" }, "bin": { "tauri": "tauri.js" } }, "sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw=="],
+ "@tauri-apps/cli": ["@tauri-apps/cli@2.10.0", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.0", "@tauri-apps/cli-darwin-x64": "2.10.0", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", "@tauri-apps/cli-linux-arm64-musl": "2.10.0", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", "@tauri-apps/cli-linux-x64-gnu": "2.10.0", "@tauri-apps/cli-linux-x64-musl": "2.10.0", "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", "@tauri-apps/cli-win32-x64-msvc": "2.10.0" }, "bin": { "tauri": "tauri.js" } }, "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q=="],
- "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.9.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gf5no6N9FCk1qMrti4lfwP77JHP5haASZgVbBgpZG7BUepB3fhiLCXGUK8LvuOjP36HivXewjg72LTnPDScnQQ=="],
+ "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.10.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA=="],
- "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.9.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-oWh74WmqbERwwrwcueJyY6HYhgCksUc6NT7WKeXyrlY/FPmNgdyQAgcLuTSkhRFuQ6zh4Np1HZpOqCTpeZBDcw=="],
+ "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.10.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q=="],
- "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.9.6", "", { "os": "linux", "cpu": "arm" }, "sha512-/zde3bFroFsNXOHN204DC2qUxAcAanUjVXXSdEGmhwMUZeAQalNj5cz2Qli2elsRjKN/hVbZOJj0gQ5zaYUjSg=="],
+ "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.10.0", "", { "os": "linux", "cpu": "arm" }, "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g=="],
- "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.9.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-pvbljdhp9VOo4RnID5ywSxgBs7qiylTPlK56cTk7InR3kYSTJKYMqv/4Q/4rGo/mG8cVppesKIeBMH42fw6wjg=="],
+ "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA=="],
- "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.9.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-02TKUndpodXBCR0oP//6dZWGYcc22Upf2eP27NvC6z0DIqvkBBFziQUcvi2n6SrwTRL0yGgQjkm9K5NIn8s6jw=="],
+ "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA=="],
- "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.9.6", "", { "os": "linux", "cpu": "none" }, "sha512-fmp1hnulbqzl1GkXl4aTX9fV+ubHw2LqlLH1PE3BxZ11EQk+l/TmiEongjnxF0ie4kV8DQfDNJ1KGiIdWe1GvQ=="],
+ "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.10.0", "", { "os": "linux", "cpu": "none" }, "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw=="],
- "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.9.6", "", { "os": "linux", "cpu": "x64" }, "sha512-vY0le8ad2KaV1PJr+jCd8fUF9VOjwwQP/uBuTJvhvKTloEwxYA/kAjKK9OpIslGA9m/zcnSo74czI6bBrm2sYA=="],
+ "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng=="],
- "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.9.6", "", { "os": "linux", "cpu": "x64" }, "sha512-TOEuB8YCFZTWVDzsO2yW0+zGcoMiPPwcUgdnW1ODnmgfwccpnihDRoks+ABT1e3fHb1ol8QQWsHSCovb3o2ENQ=="],
+ "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q=="],
- "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.9.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-ujmDGMRc4qRLAnj8nNG26Rlz9klJ0I0jmZs2BPpmNNf0gM/rcVHhqbEkAaHPTBVIrtUdf7bGvQAD2pyIiUrBHQ=="],
+ "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.10.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g=="],
- "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.9.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-S4pT0yAJgFX8QRCyKA1iKjZ9Q/oPjCZf66A/VlG5Yw54Nnr88J1uBpmenINbXxzyhduWrIXBaUbEY1K80ZbpMg=="],
+ "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.10.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A=="],
- "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.9.6", "", { "os": "win32", "cpu": "x64" }, "sha512-ldWuWSSkWbKOPjQMJoYVj9wLHcOniv7diyI5UAJ4XsBdtaFB0pKHQsqw/ItUma0VXGC7vB4E9fZjivmxur60aw=="],
+ "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.0", "", { "os": "win32", "cpu": "x64" }, "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ=="],
"@tauri-apps/plugin-clipboard-manager": ["@tauri-apps/plugin-clipboard-manager@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ=="],
- "@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.6", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-UUOSt0U5juK20uhO2MoHZX/IPblkrhUh+VPtIeu3RwtzI0R9Em3Auzfg/PwcZ9Pv8mLne3cQ4p9CFXD6WxqCZA=="],
+ "@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.7", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-K0FQlLM6BoV7Ws2xfkh+Tnwi5VZVdkI4Vw/3AGLSf0Xvu2y86AMBzd9w/SpzKhw9ai2B6ES8di/OoGDCExkOzg=="],
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.6.0", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg=="],
- "@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.6", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-KhCK3TDNDF4vdz75/j+KNQipYKf+295Visa8r32QcXScg0+D3JwShcCM6D+FN8WuDF24X3KSiAB8QtRxW6jKRA=="],
+ "@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.7", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-+F2lEH/c9b0zSsOXKq+5hZNcd9F4IIKCK1T17RqMwpCmVnx2aoqY8yIBccCd25HTYUb3j6NPVbRax/m00hKG8A=="],
"@tauri-apps/plugin-notification": ["@tauri-apps/plugin-notification@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg=="],
@@ -1823,14 +1840,16 @@
"@tauri-apps/plugin-process": ["@tauri-apps/plugin-process@2.3.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA=="],
- "@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.4", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ktsRWf8wHLD17aZEyqE8c5x98eNAuTizR1FSX475zQ4TxaiJnhwksLygQz+AGwckJL5bfEP13nWrlTNQJUpKpA=="],
+ "@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.5", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg=="],
"@tauri-apps/plugin-store": ["@tauri-apps/plugin-store@2.4.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-0ClHS50Oq9HEvLPhNzTNFxbWVOqoAp3dRvtewQBeqfIQ0z5m3JRnOISIn2ZVPCrQC0MyGyhTS9DWhHjpigQE7A=="],
- "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.9.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg=="],
+ "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.10.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-ljN8jPlnT0aSn8ecYhuBib84alxfMx6Hc8vJSKMJyzGbTPFZAC44T2I1QNFZssgWKrAlofvJqCC6Rr472JWfkQ=="],
"@tauri-apps/plugin-window-state": ["@tauri-apps/plugin-window-state@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw=="],
+ "@tediousjs/connection-string": ["@tediousjs/connection-string@0.5.0", "", {}, "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ=="],
+
"@thisbeyond/solid-dnd": ["@thisbeyond/solid-dnd@0.7.5", "", { "peerDependencies": { "solid-js": "^1.5" } }, "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw=="],
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
@@ -1899,6 +1918,8 @@
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
+ "@types/mssql": ["@types/mssql@9.1.9", "", { "dependencies": { "@types/node": "*", "tarn": "^3.0.1", "tedious": "*" } }, "sha512-P0nCgw6vzY23UxZMnbI4N7fnLGANt4LI4yvxze1paPj+LuN28cFv5EI+QidP8udnId/BKhkcRhm/BleNsjK65A=="],
+
"@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="],
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
@@ -1915,13 +1936,15 @@
"@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="],
+ "@types/readable-stream": ["@types/readable-stream@4.0.23", "", { "dependencies": { "@types/node": "*" } }, "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig=="],
+
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
"@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="],
"@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=="],
@@ -1961,7 +1984,7 @@
"@typescript/vfs": ["@typescript/vfs@1.6.2", "", { "dependencies": { "debug": "^4.1.1" }, "peerDependencies": { "typescript": "*" } }, "sha512-hoBwJwcbKHmvd2QVebiytN1aELvpk9B74B4L1mFm/XT1Q/VOYAWl2vQ9AWRFtQq8zmz6enTpfTV8WRc4ATjW/g=="],
- "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.2", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg=="],
+ "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.3", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
@@ -2089,11 +2112,11 @@
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
- "autoprefixer": ["autoprefixer@10.4.23", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001760", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA=="],
+ "autoprefixer": ["autoprefixer@10.4.24", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001766", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
- "avvio": ["avvio@9.1.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw=="],
+ "avvio": ["avvio@9.2.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ=="],
"await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="],
@@ -2103,11 +2126,11 @@
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
- "axios": ["axios@1.13.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg=="],
+ "axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
- "b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="],
+ "b4a": ["b4a@1.7.4", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug=="],
"babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="],
@@ -2119,7 +2142,7 @@
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
- "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+ "balanced-match": ["balanced-match@4.0.2", "", { "dependencies": { "jackspeak": "^4.2.3" } }, "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg=="],
"bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="],
@@ -2141,6 +2164,8 @@
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
+ "bl": ["bl@6.1.6", "", { "dependencies": { "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^4.2.0" } }, "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg=="],
+
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
"blob-to-buffer": ["blob-to-buffer@1.2.9", "", {}, "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA=="],
@@ -2155,11 +2180,11 @@
"bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="],
- "bowser": ["bowser@2.13.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="],
+ "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
"boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="],
- "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+ "brace-expansion": ["brace-expansion@5.0.2", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
@@ -2211,7 +2236,7 @@
"camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
- "caniuse-lite": ["caniuse-lite@1.0.30001766", "", {}, "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA=="],
+ "caniuse-lite": ["caniuse-lite@1.0.30001769", "", {}, "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg=="],
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
@@ -2285,7 +2310,7 @@
"condense-newlines": ["condense-newlines@0.2.1", "", { "dependencies": { "extend-shallow": "^2.0.1", "is-whitespace": "^0.3.0", "kind-of": "^3.0.2" } }, "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg=="],
- "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
+ "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="],
"config-chain": ["config-chain@1.1.13", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="],
@@ -2347,7 +2372,7 @@
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
- "default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="],
+ "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
"default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
@@ -2411,11 +2436,11 @@
"dot-prop": ["dot-prop@8.0.2", "", { "dependencies": { "type-fest": "^3.8.0" } }, "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ=="],
- "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
+ "dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
- "drizzle-kit": ["drizzle-kit@0.30.5", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0", "gel": "^2.0.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-l6dMSE100u7sDaTbLczibrQZjA35jLsHNqIV+jmhNVO3O8jzM6kywMOmV9uOz9ZVSCMPQhAZEFjL/qDPVrqpUA=="],
+ "drizzle-kit": ["drizzle-kit@1.0.0-beta.12-a5629fb", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "tsx": "^4.20.6" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-l+p4QOMvPGYBYEE9NBlU7diu+NSlxuOUwi0I7i01Uj1PpfU0NxhPzaks/9q1MDw4FAPP8vdD0dOhoqosKtRWWQ=="],
- "drizzle-orm": ["drizzle-orm@0.41.0", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-7A4ZxhHk9gdlXmTdPj/lREtP+3u8KvZ4yEN6MYVxBzZGex5Wtdc+CWSbu7btgF6TB0N+MNPrvW7RKBbxJchs/Q=="],
+ "drizzle-orm": ["drizzle-orm@1.0.0-beta.12-a5629fb", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-wyOAgr9Cy9oEN6z5S0JGhfipLKbRRJtQKgbDO9SXGR9swMBbGNIlXkeMqPRrqYQ8k70mh+7ZJ/eVmJ2F7zR3Vg=="],
"dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="],
@@ -2429,7 +2454,7 @@
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
- "electron-to-chromium": ["electron-to-chromium@1.5.282", "", {}, "sha512-FCPkJtpst28UmFzd903iU7PdeVTfY0KAeJy+Lk0GLZRwgwYHn/irRcaCbQQOmr5Vytc/7rcavsYLvTM8RiHYhQ=="],
+ "electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
"emmet": ["emmet@2.4.11", "", { "dependencies": { "@emmetio/abbreviation": "^2.3.3", "@emmetio/css-abbreviation": "^2.1.8" } }, "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ=="],
@@ -2443,11 +2468,9 @@
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
- "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="],
+ "enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
- "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
-
- "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
+ "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="],
@@ -2479,8 +2502,6 @@
"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=="],
- "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
-
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
@@ -2547,7 +2568,7 @@
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
- "fast-json-stringify": ["fast-json-stringify@6.2.0", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-Eaf/KNIDwHkzfyeQFNfLXJnQ7cl1XQI3+zRqmPlvtkMigbXnAcasTrvJQmquBSxKfFGeRA6PFog8t+hFmpDoWw=="],
+ "fast-json-stringify": ["fast-json-stringify@6.3.0", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA=="],
"fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
@@ -2625,8 +2646,6 @@
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
- "gel": ["gel@2.2.0", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ=="],
-
"generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="],
"generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
@@ -2649,7 +2668,7 @@
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
- "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
+ "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
"ghostty-web": ["ghostty-web@0.4.0", "", {}, "sha512-0puDBik2qapbD/QQBW9o5ZHfXnZBqZWx/ctBiVtKZ6ZLds4NYb+wZuw1cRLXZk9zYovIQ908z3rvFhexAvc5Hg=="],
@@ -2687,7 +2706,7 @@
"h3": ["h3@2.0.1-rc.4", "", { "dependencies": { "rou3": "^0.7.8", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-vZq8pEUp6THsXKXrUXX44eOqfChic2wVQ1GlSzQCBr7DeFBkfIZAo2WyNND4GSv54TAa0E4LYIK73WSPdgKUgw=="],
- "happy-dom": ["happy-dom@20.4.0", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^4.5.0", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-RDeQm3dT9n0A5f/TszjUmNCLEuPnMGv3Tv4BmNINebz/h17PA6LMBcxJ5FrcqltNBMh9jA/8ufgDdBYUdBt+eg=="],
+ "happy-dom": ["happy-dom@20.6.1", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^6.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-+0vhESXXhFwkdjZnJ5DlmJIfUYGgIEEjzIjB+aKJbFuqlvvKyOi+XkI1fYbgYR9QCxG5T08koxsQ6HrQfa5gCQ=="],
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
@@ -2905,7 +2924,7 @@
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
- "isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
@@ -2913,7 +2932,7 @@
"iterate-value": ["iterate-value@1.0.2", "", { "dependencies": { "es-get-iterator": "^1.0.2", "iterate-iterator": "^1.0.1" } }, "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ=="],
- "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="],
+ "jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="],
"jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="],
@@ -2931,10 +2950,14 @@
"js-cookie": ["js-cookie@3.0.5", "", {}, "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="],
+ "js-md4": ["js-md4@0.3.2", "", {}, "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA=="],
+
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
+ "jsbi": ["jsbi@4.3.2", "", {}, "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="],
+
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
@@ -3037,7 +3060,7 @@
"lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
- "lru.min": ["lru.min@1.1.3", "", {}, "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q=="],
+ "lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="],
"lru_map": ["lru_map@0.4.1", "", {}, "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg=="],
@@ -3211,6 +3234,8 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+ "mssql": ["mssql@11.0.1", "", { "dependencies": { "@tediousjs/connection-string": "^0.5.0", "commander": "^11.0.0", "debug": "^4.3.3", "rfdc": "^1.3.0", "tarn": "^3.0.2", "tedious": "^18.2.1" }, "bin": { "mssql": "bin/mssql" } }, "sha512-KlGNsugoT90enKlR8/G36H0kTxPthDhmtNUCwEHvgRza5Cjpjoj+P2X6eMpFUDN7pFrJZsKadL4x990G8RBE1w=="],
+
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
"multicast-dns": ["multicast-dns@7.2.5", "", { "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" }, "bin": { "multicast-dns": "cli.js" } }, "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg=="],
@@ -3227,6 +3252,8 @@
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+ "native-duplexpair": ["native-duplexpair@1.0.0", "", {}, "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA=="],
+
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
@@ -3263,7 +3290,7 @@
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
- "nypm": ["nypm@0.6.4", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw=="],
+ "nypm": ["nypm@0.6.5", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
@@ -3395,7 +3422,7 @@
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
- "pino": ["pino@10.3.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA=="],
+ "pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="],
"pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="],
@@ -3413,7 +3440,7 @@
"pkg-up": ["pkg-up@3.1.0", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA=="],
- "planck": ["planck@1.4.2", "", { "peerDependencies": { "stage-js": "^1.0.0-alpha.12" } }, "sha512-mNbhnV3g8X2rwGxzcesjmN8BDA6qfXgQxXVMkWau9MCRlQY0RLNEkyHlVp6yFy/X6qrzAXyNONCnZ1cGDLrNew=="],
+ "planck": ["planck@1.4.3", "", { "peerDependencies": { "stage-js": "^1.0.0-alpha.12" } }, "sha512-B+lHKhRSeg7vZOfEyEzyQVu7nx8JHcX3QgnAcHXrPW0j04XYKX5eXSiUrxH2Z5QR8OoqvjD6zKIaPMdMYAd0uA=="],
"playwright": ["playwright@1.57.0", "", { "dependencies": { "playwright-core": "1.57.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw=="],
@@ -3469,7 +3496,7 @@
"punycode": ["punycode@1.3.2", "", {}, "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="],
- "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="],
+ "qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="],
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
@@ -3603,7 +3630,7 @@
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
- "rollup": ["rollup@4.57.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.0", "@rollup/rollup-android-arm64": "4.57.0", "@rollup/rollup-darwin-arm64": "4.57.0", "@rollup/rollup-darwin-x64": "4.57.0", "@rollup/rollup-freebsd-arm64": "4.57.0", "@rollup/rollup-freebsd-x64": "4.57.0", "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", "@rollup/rollup-linux-arm-musleabihf": "4.57.0", "@rollup/rollup-linux-arm64-gnu": "4.57.0", "@rollup/rollup-linux-arm64-musl": "4.57.0", "@rollup/rollup-linux-loong64-gnu": "4.57.0", "@rollup/rollup-linux-loong64-musl": "4.57.0", "@rollup/rollup-linux-ppc64-gnu": "4.57.0", "@rollup/rollup-linux-ppc64-musl": "4.57.0", "@rollup/rollup-linux-riscv64-gnu": "4.57.0", "@rollup/rollup-linux-riscv64-musl": "4.57.0", "@rollup/rollup-linux-s390x-gnu": "4.57.0", "@rollup/rollup-linux-x64-gnu": "4.57.0", "@rollup/rollup-linux-x64-musl": "4.57.0", "@rollup/rollup-openbsd-x64": "4.57.0", "@rollup/rollup-openharmony-arm64": "4.57.0", "@rollup/rollup-win32-arm64-msvc": "4.57.0", "@rollup/rollup-win32-ia32-msvc": "4.57.0", "@rollup/rollup-win32-x64-gnu": "4.57.0", "@rollup/rollup-win32-x64-msvc": "4.57.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA=="],
+ "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
"rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
@@ -3639,7 +3666,7 @@
"selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
- "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
+ "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
@@ -3667,8 +3694,6 @@
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
- "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
-
"shiki": ["shiki@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/engine-javascript": "3.20.0", "@shikijs/engine-oniguruma": "3.20.0", "@shikijs/langs": "3.20.0", "@shikijs/themes": "3.20.0", "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg=="],
"shikiji": ["shikiji@0.6.13", "", { "dependencies": { "hast-util-to-html": "^9.0.0" } }, "sha512-4T7X39csvhT0p7GDnq9vysWddf2b6BeioiN3Ymhnt3xcy9tXmDcnsEFVxX18Z4YcQgEE/w48dLJ4pPPUcG9KkA=="],
@@ -3715,7 +3740,7 @@
"solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="],
- "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="],
+ "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
@@ -3727,7 +3752,7 @@
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
- "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
+ "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
"sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="],
@@ -3755,7 +3780,7 @@
"stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
- "stage-js": ["stage-js@1.0.0-alpha.18", "", {}, "sha512-Mh+pbkfxA6NXlDrcutP8vp1Zg04pDRcC8D39UXKZzEcQeBPOZ4SRUSkIsF26aoODUZ4CSQRY7shXc1Avb0wZKA=="],
+ "stage-js": ["stage-js@1.0.1", "", {}, "sha512-cz14aPp/wY0s3bkb/B93BPP5ZAEhgBbRmAT3CCDqert8eCAqIpQ0RB2zpK8Ksxf+Pisl5oTzvPHtL4CVzzeHcw=="],
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
@@ -3819,11 +3844,15 @@
"tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
+ "tarn": ["tarn@3.0.2", "", {}, "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="],
+
+ "tedious": ["tedious@19.2.1", "", { "dependencies": { "@azure/core-auth": "^1.7.2", "@azure/identity": "^4.2.1", "@azure/keyvault-keys": "^4.4.0", "@js-joda/core": "^5.6.5", "@types/node": ">=18", "bl": "^6.1.4", "iconv-lite": "^0.7.0", "js-md4": "^0.3.2", "native-duplexpair": "^1.0.0", "sprintf-js": "^1.1.3" } }, "sha512-pk1Q16Yl62iocuQB+RWbg6rFUFkIyzqOFQ6NfysCltRvQqKwfurgj8v/f2X+CKvDhSL4IJ0cCOfCHDg9PWEEYA=="],
+
"terracotta": ["terracotta@1.1.0", "", { "dependencies": { "solid-use": "^0.9.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-kfQciWUBUBgYkXu7gh3CK3FAJng/iqZslAaY08C+k1Hdx17aVEpcFFb/WPaysxAfcupNH3y53s/pc53xxZauww=="],
"terser": ["terser@5.46.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-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg=="],
- "text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="],
+ "text-decoder": ["text-decoder@1.2.4", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-mzlffA3tBNhziEHPK5L5InZg1d/ElNIpJhnhbDRNUtem/edZcJ5zg5FgwKKKOyklxk+6Jt+TrSu83musmvrDlg=="],
"thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
@@ -3881,6 +3910,8 @@
"tsscmp": ["tsscmp@1.0.6", "", {}, "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="],
+ "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
+
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
"turbo": ["turbo@2.5.6", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.6", "turbo-darwin-arm64": "2.5.6", "turbo-linux-64": "2.5.6", "turbo-linux-arm64": "2.5.6", "turbo-windows-64": "2.5.6", "turbo-windows-arm64": "2.5.6" }, "bin": { "turbo": "bin/turbo" } }, "sha512-gxToHmi9oTBNB05UjUsrWf0OyN5ZXtD0apOarC1KIx232Vp3WimRNy3810QzeNSgyD5rsaIDXlxlbnOzlouo+w=="],
@@ -3929,7 +3960,7 @@
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
- "undici": ["undici@7.19.2", "", {}, "sha512-4VQSpGEGsWzk0VYxyB/wVX/Q7qf9t5znLRgs0dzszr9w9Fej/8RVNQ+S20vdXSAyra/bJ7ZQfGv6ZMj7UEbzSg=="],
+ "undici": ["undici@7.21.0", "", {}, "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
@@ -4061,7 +4092,7 @@
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
- "which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
@@ -4151,9 +4182,9 @@
"@actions/http-client/undici": ["undici@6.23.0", "", {}, "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g=="],
- "@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.58", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CkNW5L1Arv8gPtPlEmKd+yf/SG9ucJf0XQdpMG8OiYEtEMc2smuCA+tyCp8zI7IBVg/FE7nUfFHntQFaOjRwJQ=="],
+ "@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.62", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-I3RhaOEMnWlWnrvjNBOYvUb19Dwf2nw01IruZrVJRDi688886e11wnd5DxrBZLd2V29Gizo3vpOPnnExsA+wTA=="],
- "@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.8", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw=="],
+ "@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="],
"@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
@@ -4163,9 +4194,19 @@
"@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="],
- "@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="],
+ "@ai-sdk/deepgram/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="],
- "@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="],
+ "@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="],
+
+ "@ai-sdk/deepinfra/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="],
+
+ "@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="],
+
+ "@ai-sdk/elevenlabs/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="],
+
+ "@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="],
+
+ "@ai-sdk/fireworks/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="],
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.58", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CkNW5L1Arv8gPtPlEmKd+yf/SG9ucJf0XQdpMG8OiYEtEMc2smuCA+tyCp8zI7IBVg/FE7nUfFHntQFaOjRwJQ=="],
@@ -4233,7 +4274,9 @@
"@azure/core-http/xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
- "@azure/core-xml/fast-xml-parser": ["fast-xml-parser@5.3.3", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-2O3dkPAAC6JavuMm8+4+pgTk+5hoAs+CjZ+sWcQLkX9+/tHRuTkQh/Oaifr8qDmZ8iEHb771Ea6G8CdwkrgvYA=="],
+ "@azure/core-xml/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="],
+
+ "@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@@ -4249,22 +4292,18 @@
"@dot/log/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
- "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
-
"@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="],
- "@gitlab/gitlab-ai-provider/openai": ["openai@6.17.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-NHRpPEUPzAvFOAFs9+9pC6+HCw/iWsYsKCMPXH5Kw7BpMxqd8g/A07/1o7Gx2TWtCnzevVRyKMRFqyiHyAlqcA=="],
+ "@gitlab/gitlab-ai-provider/openai": ["openai@6.21.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-26dQFi76dB8IiN/WKGQOV+yKKTTlRCxQjoi2WLt0kMcH8pvxVyvfdBDkld5GTl7W1qvBpwVOtFcsqktj3fBRpA=="],
"@gitlab/gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@hey-api/openapi-ts/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
+ "@hey-api/openapi-ts/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
+
"@hono/zod-validator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
- "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
-
- "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
-
"@jimp/plugin-blit/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@jimp/plugin-circle/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
@@ -4447,21 +4486,23 @@
"@tanstack/directive-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
+ "@tanstack/router-utils/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
+
"@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
+ "@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
+
"@vscode/emmet-helper/jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="],
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
- "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.58", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CkNW5L1Arv8gPtPlEmKd+yf/SG9ucJf0XQdpMG8OiYEtEMc2smuCA+tyCp8zI7IBVg/FE7nUfFHntQFaOjRwJQ=="],
+ "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.62", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-I3RhaOEMnWlWnrvjNBOYvUb19Dwf2nw01IruZrVJRDi688886e11wnd5DxrBZLd2V29Gizo3vpOPnnExsA+wTA=="],
"ai-gateway-provider/@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.90", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.56", "@ai-sdk/google": "2.0.46", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-C9MLe1KZGg1ZbupV2osygHtL5qngyCDA6ATatunyfTbIe8TXKG8HGni/3O6ifbnI5qxTidIn150Ox7eIFZVMYg=="],
"ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
- "ai-gateway-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="],
-
- "ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/xai@2.0.56", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FGlqwWc3tAYqDHE8r8hQGQLcMiPUwgz90oU2QygUH930OWtCLapFkSu114DgVaIN/qoM1DUX+inv0Ee74Fgp5g=="],
+ "ai-gateway-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="],
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -4487,6 +4528,8 @@
"babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
+ "bl/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
+
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
@@ -4501,12 +4544,10 @@
"condense-newlines/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="],
- "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+ "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"dot-prop/type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="],
- "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=="],
-
"editorconfig/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
"editorconfig/minimatch": ["minimatch@9.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="],
@@ -4535,7 +4576,7 @@
"gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
- "glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
+ "glob/minimatch": ["minimatch@10.2.0", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w=="],
"globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
@@ -4545,6 +4586,10 @@
"html-minifier-terser/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
+ "html-minifier-terser/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+
+ "htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+
"js-beautify/glob": ["glob@10.5.0", "", { "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-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
@@ -4567,15 +4612,19 @@
"miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="],
+ "mssql/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
+
+ "mssql/tedious": ["tedious@18.6.2", "", { "dependencies": { "@azure/core-auth": "^1.7.2", "@azure/identity": "^4.2.1", "@azure/keyvault-keys": "^4.4.0", "@js-joda/core": "^5.6.1", "@types/node": ">=18", "bl": "^6.0.11", "iconv-lite": "^0.6.3", "js-md4": "^0.3.2", "native-duplexpair": "^1.0.0", "sprintf-js": "^1.1.3" } }, "sha512-g7jC56o3MzLkE3lHkaFe2ZdOVFBahq5bsB60/M4NYUbocw/MCrS89IOEQUFr+ba6pb8ZHczZ/VqCyYeYq0xBAg=="],
+
"nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="],
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
- "nypm/citty": ["citty@0.2.0", "", {}, "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA=="],
+ "nypm/citty": ["citty@0.2.1", "", {}, "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg=="],
"nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
- "opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.58", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CkNW5L1Arv8gPtPlEmKd+yf/SG9ucJf0XQdpMG8OiYEtEMc2smuCA+tyCp8zI7IBVg/FE7nUfFHntQFaOjRwJQ=="],
+ "opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.62", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-I3RhaOEMnWlWnrvjNBOYvUb19Dwf2nw01IruZrVJRDi688886e11wnd5DxrBZLd2V29Gizo3vpOPnnExsA+wTA=="],
"opencode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
@@ -4599,9 +4648,7 @@
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
- "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
-
- "path-scurry/lru-cache": ["lru-cache@11.2.5", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="],
+ "path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
"pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
@@ -4609,6 +4656,8 @@
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
+ "postcss-css-variables/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
"postcss-load-config/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
@@ -4661,6 +4710,8 @@
"tree-sitter-bash/node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="],
+ "tsx/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
+
"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=="],
"tw-to-css/tailwindcss": ["tailwindcss@3.3.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.18.2", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w=="],
@@ -4747,54 +4798,6 @@
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
-
- "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
-
- "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
-
- "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
-
"@jsx-email/cli/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
"@jsx-email/cli/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="],
@@ -4853,6 +4856,8 @@
"@jsx-email/cli/vite/rollup": ["rollup@3.29.5", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w=="],
+ "@jsx-email/doiuse-email/htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+
"@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
@@ -4995,6 +5000,8 @@
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+ "ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="],
+
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ=="],
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@2.0.46", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8PK6u4sGE/kXebd7ZkTp+0aya4kNqzoqpS5m7cHY2NfTK6fhPc6GNvE+MZIZIoHQTp5ed86wGBdeBPpFaaUtyg=="],
@@ -5003,6 +5010,8 @@
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="],
+ "ai-gateway-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="],
+
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -5017,7 +5026,7 @@
"astro/unstorage/h3": ["h3@1.15.5", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg=="],
- "astro/unstorage/lru-cache": ["lru-cache@11.2.5", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="],
+ "astro/unstorage/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
"astro/unstorage/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=="],
@@ -5027,57 +5036,13 @@
"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/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
- "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
-
- "drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
-
- "drizzle-kit/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="],
-
- "drizzle-kit/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="],
-
- "drizzle-kit/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="],
-
- "drizzle-kit/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="],
-
- "drizzle-kit/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="],
-
- "drizzle-kit/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="],
-
- "drizzle-kit/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="],
-
- "drizzle-kit/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="],
-
- "drizzle-kit/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="],
-
- "drizzle-kit/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="],
-
- "drizzle-kit/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="],
-
- "drizzle-kit/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="],
-
- "drizzle-kit/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="],
-
- "drizzle-kit/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="],
-
- "drizzle-kit/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="],
-
- "drizzle-kit/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="],
-
- "drizzle-kit/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="],
-
- "drizzle-kit/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="],
-
- "drizzle-kit/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="],
-
- "drizzle-kit/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="],
-
- "drizzle-kit/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="],
-
- "drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="],
+ "editorconfig/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"esbuild-plugin-copy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
@@ -5099,6 +5064,10 @@
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
+ "mssql/tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+
+ "opencode/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="],
+
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
@@ -5113,6 +5082,8 @@
"readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+ "readdir-glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
"rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
@@ -5123,6 +5094,58 @@
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
+
+ "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
+
+ "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
+
+ "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
+
+ "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
+
+ "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
+
+ "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
+
+ "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
+
+ "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
+
+ "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
+
+ "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
+
+ "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
+
+ "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
+
+ "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
+
+ "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
+
+ "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
+
+ "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
+
+ "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
+
+ "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
+
+ "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
+
+ "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
+
+ "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
+
+ "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
+
+ "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
+
+ "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
+
+ "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
+
"tw-to-css/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"tw-to-css/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
@@ -5295,6 +5318,10 @@
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "archiver-utils/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
+
+ "archiver-utils/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
"archiver-utils/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"astro/unstorage/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
@@ -5303,12 +5330,22 @@
"astro/unstorage/h3/crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="],
+ "babel-plugin-module-resolver/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
"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=="],
+ "editorconfig/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
"esbuild-plugin-copy/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+ "gray-matter/js-yaml/argparse/sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
+
+ "js-beautify/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
+
+ "js-beautify/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
"js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
@@ -5337,6 +5374,12 @@
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
+ "readdir-glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "rimraf/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
+
+ "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
"rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
@@ -5355,12 +5398,44 @@
"@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=="],
+ "archiver-utils/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
+
+ "archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
+
+ "archiver-utils/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "babel-plugin-module-resolver/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "js-beautify/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
+
+ "js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
+
+ "js-beautify/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
"opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
+ "rimraf/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
+
+ "rimraf/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
+
+ "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
"tw-to-css/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+
+ "archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
+
+ "archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
+
+ "js-beautify/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
+
+ "js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
+
+ "rimraf/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
+
+ "rimraf/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
}
}
diff --git a/nix/hashes.json b/nix/hashes.json
index 70d7378493..ef0b63897d 100644
--- a/nix/hashes.json
+++ b/nix/hashes.json
@@ -1,8 +1,8 @@
{
"nodeModules": {
- "x86_64-linux": "sha256-XIf7b6yALzH1/MkGGrsmq2DeXIC9vgD9a7D/dxhi6iU=",
- "aarch64-linux": "sha256-mKDCs6QhIelWc3E17zOufaSDTovtjO/Xyh3JtlWl01s=",
- "aarch64-darwin": "sha256-wC7bbbIyZ62uMxTr9FElTbEBMrfz0S/ndqwZZ3V9EOA=",
- "x86_64-darwin": "sha256-/7Nn65m5Zhvzz0TKsG9nWd2v5WDHQNi3UzCfuAR8SLo="
+ "x86_64-linux": "sha256-hVf8rBEqy3q4xexOqyKDtKmlMydl1hFoDV0JiEvmfgs=",
+ "aarch64-linux": "sha256-4m3UZllEmfJXB70cOgIoyWRIYMXxGzzenyOfF3kEQKk=",
+ "aarch64-darwin": "sha256-27xGR9+FVnC0rsUIyepk2tCP1eEUmGvqWUGAZ+rk7IQ=",
+ "x86_64-darwin": "sha256-+At7bHSeg6QJu6yGawyvzt53Tu/fddDg6Ms+xhaMLhY="
}
}
diff --git a/package.json b/package.json
index c396905d45..c4408e264b 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,8 @@
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
"dompurify": "3.3.1",
+ "drizzle-kit": "1.0.0-beta.12-a5629fb",
+ "drizzle-orm": "1.0.0-beta.12-a5629fb",
"ai": "5.0.124",
"hono": "4.10.7",
"hono-openapi": "1.1.2",
diff --git a/packages/app/e2e/prompt/prompt-async.spec.ts b/packages/app/e2e/prompt/prompt-async.spec.ts
new file mode 100644
index 0000000000..ce9b1a7a3b
--- /dev/null
+++ b/packages/app/e2e/prompt/prompt-async.spec.ts
@@ -0,0 +1,43 @@
+import { test, expect } from "../fixtures"
+import { promptSelector } from "../selectors"
+import { sessionIDFromUrl } from "../actions"
+
+// Regression test for Issue #12453: the synchronous POST /message endpoint holds
+// the connection open while the agent works, causing "Failed to fetch" over
+// VPN/Tailscale. The fix switches to POST /prompt_async which returns immediately.
+test("prompt succeeds when sync message endpoint is unreachable", async ({ page, sdk, gotoSession }) => {
+ test.setTimeout(120_000)
+
+ // Simulate Tailscale/VPN killing the long-lived sync connection
+ await page.route("**/session/*/message", (route) => route.abort("connectionfailed"))
+
+ await gotoSession()
+
+ const token = `E2E_ASYNC_${Date.now()}`
+ await page.locator(promptSelector).click()
+ await page.keyboard.type(`Reply with exactly: ${token}`)
+ await page.keyboard.press("Enter")
+
+ await expect(page).toHaveURL(/\/session\/[^/?#]+/, { timeout: 30_000 })
+ const sessionID = sessionIDFromUrl(page.url())!
+
+ try {
+ // Agent response arrives via SSE despite sync endpoint being dead
+ await expect
+ .poll(
+ async () => {
+ const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
+ return messages
+ .filter((m) => m.info.role === "assistant")
+ .flatMap((m) => m.parts)
+ .filter((p) => p.type === "text")
+ .map((p) => p.text)
+ .join("\n")
+ },
+ { timeout: 90_000 },
+ )
+ .toContain(token)
+ } finally {
+ await sdk.session.delete({ sessionID }).catch(() => undefined)
+ }
+})
diff --git a/packages/app/package.json b/packages/app/package.json
index ebd1a4b35b..e8c24c08ba 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
- "version": "1.1.64",
+ "version": "1.2.1",
"description": "",
"type": "module",
"exports": {
diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx
index 3032a795f8..1121c2e955 100644
--- a/packages/app/src/app.tsx
+++ b/packages/app/src/app.tsx
@@ -1,5 +1,5 @@
import "@/index.css"
-import { ErrorBoundary, Suspense, lazy, type JSX, type ParentProps } from "solid-js"
+import { ErrorBoundary, Show, Suspense, lazy, type JSX, type ParentProps } from "solid-js"
import { Router, Route, Navigate } from "@solidjs/router"
import { MetaProvider } from "@solidjs/meta"
import { Font } from "@opencode-ai/ui/font"
@@ -156,8 +156,11 @@ export function AppBaseProviders(props: ParentProps) {
function ServerKey(props: ParentProps) {
const server = useServer()
- if (!server.url) return null
- return props.children
+ return (
+
+ {props.children}
+
+ )
}
export function AppInterface(props: { defaultUrl?: string; children?: JSX.Element; isSidecar?: boolean }) {
diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts
index 49d75a95ec..9a1fba5d5c 100644
--- a/packages/app/src/components/prompt-input/submit.ts
+++ b/packages/app/src/components/prompt-input/submit.ts
@@ -385,7 +385,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
const send = async () => {
const ok = await waitForWorktree()
if (!ok) return
- await client.session.prompt({
+ await client.session.promptAsync({
sessionID: session.id,
agent,
model,
diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx
index b85b9a536a..f81a2ec440 100644
--- a/packages/app/src/components/session/session-header.tsx
+++ b/packages/app/src/components/session/session-header.tsx
@@ -552,7 +552,7 @@ export function SessionHeader() {
-
+
{
let serializeAddon: SerializeAddon
let fitAddon: FitAddon
let handleResize: () => void
+ let fitFrame: number | undefined
+ let sizeTimer: ReturnType | undefined
+ let pendingSize: { cols: number; rows: number } | undefined
+ let lastSize: { cols: number; rows: number } | undefined
let disposed = false
const cleanups: VoidFunction[] = []
const start =
@@ -209,6 +213,43 @@ export const Terminal = (props: TerminalProps) => {
const [terminalColors, setTerminalColors] = createSignal(getTerminalColors())
+ const scheduleFit = () => {
+ if (disposed) return
+ if (!fitAddon) return
+ if (fitFrame !== undefined) return
+
+ fitFrame = requestAnimationFrame(() => {
+ fitFrame = undefined
+ if (disposed) return
+ fitAddon.fit()
+ })
+ }
+
+ const scheduleSize = (cols: number, rows: number) => {
+ if (disposed) return
+ if (lastSize?.cols === cols && lastSize?.rows === rows) return
+
+ pendingSize = { cols, rows }
+
+ if (!lastSize) {
+ lastSize = pendingSize
+ void pushSize(cols, rows)
+ return
+ }
+
+ if (sizeTimer !== undefined) return
+ sizeTimer = setTimeout(() => {
+ sizeTimer = undefined
+ const next = pendingSize
+ if (!next) return
+ pendingSize = undefined
+ if (disposed) return
+ if (lastSize?.cols === next.cols && lastSize?.rows === next.rows) return
+ lastSize = next
+ void pushSize(next.cols, next.rows)
+ }, 100)
+ }
+
createEffect(() => {
const colors = getTerminalColors()
setTerminalColors(colors)
@@ -220,6 +261,16 @@ export const Terminal = (props: TerminalProps) => {
const font = monoFontFamily(settings.appearance.font())
if (!term) return
setOptionIfSupported(term, "fontFamily", font)
+ scheduleFit()
+ })
+
+ let zoom = platform.webviewZoom?.()
+ createEffect(() => {
+ const next = platform.webviewZoom?.()
+ if (next === undefined) return
+ if (next === zoom) return
+ zoom = next
+ scheduleFit()
})
const focusTerminal = () => {
@@ -263,25 +314,6 @@ export const Terminal = (props: TerminalProps) => {
const once = { value: false }
- const url = new URL(sdk.url + `/pty/${local.pty.id}/connect`)
- url.searchParams.set("directory", sdk.directory)
- url.searchParams.set("cursor", String(start !== undefined ? start : local.pty.buffer ? -1 : 0))
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:"
- if (window.__OPENCODE__?.serverPassword) {
- url.username = "opencode"
- url.password = window.__OPENCODE__?.serverPassword
- }
- const socket = new WebSocket(url)
- socket.binaryType = "arraybuffer"
- cleanups.push(() => {
- if (socket.readyState !== WebSocket.CLOSED && socket.readyState !== WebSocket.CLOSING) socket.close()
- })
- if (disposed) {
- cleanup()
- return
- }
- ws = socket
-
const restore = typeof local.pty.buffer === "string" ? local.pty.buffer : ""
const restoreSize =
restore &&
@@ -344,39 +376,16 @@ export const Terminal = (props: TerminalProps) => {
focusTerminal()
- const startResize = () => {
- fit.observeResize()
- handleResize = () => fit.fit()
- window.addEventListener("resize", handleResize)
- cleanups.push(() => window.removeEventListener("resize", handleResize))
+ if (typeof document !== "undefined" && document.fonts) {
+ document.fonts.ready.then(scheduleFit)
}
- if (restore && restoreSize) {
- t.write(restore, () => {
- fit.fit()
- if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
- startResize()
- })
- } else {
- fit.fit()
- if (restore) {
- t.write(restore, () => {
- if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
- })
- }
- startResize()
- }
-
- const onResize = t.onResize(async (size) => {
- if (socket.readyState === WebSocket.OPEN) {
- await pushSize(size.cols, size.rows)
- }
+ const onResize = t.onResize((size) => {
+ scheduleSize(size.cols, size.rows)
})
cleanups.push(() => disposeIfDisposable(onResize))
const onData = t.onData((data) => {
- if (socket.readyState === WebSocket.OPEN) {
- socket.send(data)
- }
+ if (ws?.readyState === WebSocket.OPEN) ws.send(data)
})
cleanups.push(() => disposeIfDisposable(onData))
const onKey = t.onKey((key) => {
@@ -385,17 +394,64 @@ export const Terminal = (props: TerminalProps) => {
}
})
cleanups.push(() => disposeIfDisposable(onKey))
+
+ const startResize = () => {
+ fit.observeResize()
+ handleResize = scheduleFit
+ window.addEventListener("resize", handleResize)
+ cleanups.push(() => window.removeEventListener("resize", handleResize))
+ }
+
+ if (restore && restoreSize) {
+ t.write(restore, () => {
+ fit.fit()
+ scheduleSize(t.cols, t.rows)
+ if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
+ startResize()
+ })
+ } else {
+ fit.fit()
+ scheduleSize(t.cols, t.rows)
+ if (restore) {
+ t.write(restore, () => {
+ if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
+ })
+ }
+ startResize()
+ }
+
// t.onScroll((ydisp) => {
// console.log("Scroll position:", ydisp)
// })
+ const url = new URL(sdk.url + `/pty/${local.pty.id}/connect`)
+ url.searchParams.set("directory", sdk.directory)
+ url.searchParams.set("cursor", String(start !== undefined ? start : local.pty.buffer ? -1 : 0))
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:"
+ if (window.__OPENCODE__?.serverPassword) {
+ url.username = "opencode"
+ url.password = window.__OPENCODE__?.serverPassword
+ }
+ const socket = new WebSocket(url)
+ socket.binaryType = "arraybuffer"
+ ws = socket
+ cleanups.push(() => {
+ if (socket.readyState !== WebSocket.CLOSED && socket.readyState !== WebSocket.CLOSING) socket.close()
+ })
+ if (disposed) {
+ cleanup()
+ return
+ }
+
const handleOpen = () => {
local.onConnect?.()
- void pushSize(t.cols, t.rows)
+ scheduleSize(t.cols, t.rows)
}
socket.addEventListener("open", handleOpen)
cleanups.push(() => socket.removeEventListener("open", handleOpen))
+ if (socket.readyState === WebSocket.OPEN) handleOpen()
+
const decoder = new TextDecoder()
const handleMessage = (event: MessageEvent) => {
@@ -462,6 +518,8 @@ export const Terminal = (props: TerminalProps) => {
onCleanup(() => {
disposed = true
+ if (fitFrame !== undefined) cancelAnimationFrame(fitFrame)
+ if (sizeTimer !== undefined) clearTimeout(sizeTimer)
output?.flush()
persistTerminal({ term, addon: serializeAddon, cursor, pty: local.pty, onCleanup: props.onCleanup })
cleanup()
@@ -477,7 +535,7 @@ export const Terminal = (props: TerminalProps) => {
classList={{
...(local.classList ?? {}),
"select-text": true,
- "size-full px-6 py-3 font-mono": true,
+ "size-full px-6 py-3 font-mono relative overflow-hidden": true,
[local.class ?? ""]: !!local.class,
}}
{...others}
diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts
index fa1a43d479..66fcac66d5 100644
--- a/packages/app/src/context/global-sync/event-reducer.ts
+++ b/packages/app/src/context/global-sync/event-reducer.ts
@@ -231,6 +231,24 @@ export function applyDirectoryEvent(input: {
}
break
}
+ case "message.part.delta": {
+ const props = event.properties as { messageID: string; partID: string; field: string; delta: string }
+ const parts = input.store.part[props.messageID]
+ if (!parts) break
+ const result = Binary.search(parts, props.partID, (p) => p.id)
+ if (!result.found) break
+ input.setStore(
+ "part",
+ props.messageID,
+ produce((draft) => {
+ const part = draft[result.index]
+ const field = props.field as keyof typeof part
+ const existing = part[field] as string | undefined
+ ;(part[field] as string) = (existing ?? "") + props.delta
+ }),
+ )
+ break
+ }
case "vcs.branch.updated": {
const props = event.properties as { branch: string }
if (input.store.vcs?.branch === props.branch) break
diff --git a/packages/app/src/context/language.tsx b/packages/app/src/context/language.tsx
index a5d894e62e..b21ec6d3cc 100644
--- a/packages/app/src/context/language.tsx
+++ b/packages/app/src/context/language.tsx
@@ -57,6 +57,10 @@ export type Locale =
type RawDictionary = typeof en & typeof uiEn
type Dictionary = i18n.Flatten
+function cookie(locale: Locale) {
+ return `oc_locale=${encodeURIComponent(locale)}; Path=/; Max-Age=31536000; SameSite=Lax`
+}
+
const LOCALES: readonly Locale[] = [
"en",
"zh",
@@ -199,6 +203,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
createEffect(() => {
if (typeof document !== "object") return
document.documentElement.lang = locale()
+ document.cookie = cookie(locale())
})
return {
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index 101f3312c3..5ce6202eef 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -1758,7 +1758,7 @@ export default function Page() {
state()?.content?.content ?? "")
- const cacheKey = createMemo(() => checksum(contents()))
+ const cacheKey = createMemo(() => sampledChecksum(contents()))
const isImage = createMemo(() => {
const c = state()?.content
return c?.encoding === "base64" && c?.mimeType?.startsWith("image/") && c?.mimeType !== "image/svg+xml"
@@ -163,11 +163,20 @@ export function FileTabContent(props: {
return
}
+ const estimateTop = (range: SelectedLineRange) => {
+ const line = Math.max(range.start, range.end)
+ const height = 24
+ const offset = 2
+ return Math.max(0, (line - 1) * height + offset)
+ }
+
+ const large = contents().length > 500_000
+
const next: Record = {}
for (const comment of fileComments()) {
const marker = findMarker(root, comment.selection)
- if (!marker) continue
- next[comment.id] = markerTop(el, marker)
+ if (marker) next[comment.id] = markerTop(el, marker)
+ else if (large) next[comment.id] = estimateTop(comment.selection)
}
const removed = Object.keys(note.positions).filter((id) => next[id] === undefined)
@@ -194,12 +203,12 @@ export function FileTabContent(props: {
}
const marker = findMarker(root, range)
- if (!marker) {
- setNote("draftTop", undefined)
+ if (marker) {
+ setNote("draftTop", markerTop(el, marker))
return
}
- setNote("draftTop", markerTop(el, marker))
+ setNote("draftTop", large ? estimateTop(range) : undefined)
}
const scheduleComments = () => {
diff --git a/packages/console/app/package.json b/packages/console/app/package.json
index c5556a4431..a6267c73ef 100644
--- a/packages/console/app/package.json
+++ b/packages/console/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
- "version": "1.1.64",
+ "version": "1.2.1",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts
index d2bcaa851b..a8e275ba9a 100644
--- a/packages/console/app/src/routes/zen/util/handler.ts
+++ b/packages/console/app/src/routes/zen/util/handler.ts
@@ -110,6 +110,7 @@ export async function handler(
providerInfo.modifyBody({
...createBodyConverter(opts.format, providerInfo.format)(body),
model: providerInfo.model,
+ ...(providerInfo.payloadModifier ?? {}),
}),
)
logger.debug("REQUEST URL: " + reqUrl)
@@ -274,8 +275,8 @@ export async function handler(
part = part.trim()
usageParser.parse(part)
- if (providerInfo.bodyModifier) {
- for (const [k, v] of Object.entries(providerInfo.bodyModifier)) {
+ if (providerInfo.responseModifier) {
+ for (const [k, v] of Object.entries(providerInfo.responseModifier)) {
part = part.replace(k, v)
}
c.enqueue(encoder.encode(part + "\n\n"))
@@ -285,7 +286,7 @@ export async function handler(
}
}
- if (!providerInfo.bodyModifier && providerInfo.format === opts.format) {
+ if (!providerInfo.responseModifier && providerInfo.format === opts.format) {
c.enqueue(value)
}
diff --git a/packages/console/core/package.json b/packages/console/core/package.json
index 498270b952..85f9c200a7 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.1.64",
+ "version": "1.2.1",
"private": true,
"type": "module",
"license": "MIT",
@@ -12,7 +12,7 @@
"@opencode-ai/console-resource": "workspace:*",
"@planetscale/database": "1.19.0",
"aws4fetch": "1.0.20",
- "drizzle-orm": "0.41.0",
+ "drizzle-orm": "catalog:",
"postgres": "3.4.7",
"stripe": "18.0.0",
"ulid": "catalog:",
@@ -44,7 +44,7 @@
"@tsconfig/node22": "22.0.2",
"@types/bun": "1.3.0",
"@types/node": "catalog:",
- "drizzle-kit": "0.30.5",
+ "drizzle-kit": "catalog:",
"mysql2": "3.14.4",
"typescript": "catalog:",
"@typescript/native-preview": "catalog:"
diff --git a/packages/console/core/src/drizzle/index.ts b/packages/console/core/src/drizzle/index.ts
index f0f065de4a..d3a4b63bf3 100644
--- a/packages/console/core/src/drizzle/index.ts
+++ b/packages/console/core/src/drizzle/index.ts
@@ -4,7 +4,6 @@ export * from "drizzle-orm"
import { Client } from "@planetscale/database"
import { MySqlTransaction, type MySqlTransactionConfig } from "drizzle-orm/mysql-core"
-import type { ExtractTablesWithRelations } from "drizzle-orm"
import type { PlanetScalePreparedQueryHKT, PlanetscaleQueryResultHKT } from "drizzle-orm/planetscale-serverless"
import { Context } from "../context"
import { memo } from "../util/memo"
@@ -14,7 +13,7 @@ export namespace Database {
PlanetscaleQueryResultHKT,
PlanetScalePreparedQueryHKT,
Record,
- ExtractTablesWithRelations>
+ any
>
const client = memo(() => {
@@ -23,7 +22,7 @@ export namespace Database {
username: Resource.Database.username,
password: Resource.Database.password,
})
- const db = drizzle(result, {})
+ const db = drizzle({ client: result })
return db
})
diff --git a/packages/console/core/src/model.ts b/packages/console/core/src/model.ts
index 9a2908e32e..da26778449 100644
--- a/packages/console/core/src/model.ts
+++ b/packages/console/core/src/model.ts
@@ -62,12 +62,13 @@ export namespace ZenData {
apiKey: z.string(),
format: FormatSchema.optional(),
headerMappings: z.record(z.string(), z.string()).optional(),
+ payloadModifier: z.record(z.string(), z.any()).optional(),
family: z.string().optional(),
})
const ProviderFamilySchema = z.object({
headers: z.record(z.string(), z.string()).optional(),
- bodyModifier: z.record(z.string(), z.string()).optional(),
+ responseModifier: z.record(z.string(), z.string()).optional(),
})
const ModelsSchema = z.object({
diff --git a/packages/console/function/package.json b/packages/console/function/package.json
index d2117dffb2..02aa6f76ef 100644
--- a/packages/console/function/package.json
+++ b/packages/console/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
- "version": "1.1.64",
+ "version": "1.2.1",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json
index f632ab92fe..115d365f7e 100644
--- a/packages/console/mail/package.json
+++ b/packages/console/mail/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
- "version": "1.1.64",
+ "version": "1.2.1",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index da89d36a88..858dbf5c4d 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
- "version": "1.1.64",
+ "version": "1.2.1",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/desktop/src-tauri/build.rs b/packages/desktop/src-tauri/build.rs
index 85c91f55a6..d860e1e6a7 100644
--- a/packages/desktop/src-tauri/build.rs
+++ b/packages/desktop/src-tauri/build.rs
@@ -1,10 +1,3 @@
fn main() {
- if let Ok(git_ref) = std::env::var("GITHUB_REF") {
- let branch = git_ref.strip_prefix("refs/heads/").unwrap_or(&git_ref);
- if branch == "beta" {
- println!("cargo:rustc-env=OPENCODE_SQLITE=1");
- }
- }
-
tauri_build::build()
}
diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs
index 85ea21d38c..aa605a9239 100644
--- a/packages/desktop/src-tauri/src/lib.rs
+++ b/packages/desktop/src-tauri/src/lib.rs
@@ -566,7 +566,7 @@ async fn initialize(app: AppHandle) {
// come from any invocation of the sidecar CLI. The progress is captured by a stdout stream interceptor.
// Then in the loading task, we wait for sqlite migration to complete before
// starting our health check against the server, otherwise long migrations could result in a timeout.
- let needs_sqlite_migration = option_env!("OPENCODE_SQLITE").is_some() && !sqlite_file_exists();
+ let needs_sqlite_migration = !sqlite_file_exists();
let sqlite_done = needs_sqlite_migration.then(|| {
tracing::info!(
path = %opencode_db_path().expect("failed to get db path").display(),
diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json
index 31b62e12b1..7aa3bb90d1 100644
--- a/packages/enterprise/package.json
+++ b/packages/enterprise/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
- "version": "1.1.64",
+ "version": "1.2.1",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml
index 22aca32bae..fdb66d69bf 100644
--- a/packages/extensions/zed/extension.toml
+++ b/packages/extensions/zed/extension.toml
@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The open source coding agent."
-version = "1.1.64"
+version = "1.2.1"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/anomalyco/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.64/opencode-darwin-arm64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.1/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.64/opencode-darwin-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.1/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.64/opencode-linux-arm64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.1/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.64/opencode-linux-x64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.1/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.64/opencode-windows-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.1/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
diff --git a/packages/function/package.json b/packages/function/package.json
index ae9a6d7b3c..242ce353f3 100644
--- a/packages/function/package.json
+++ b/packages/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
- "version": "1.1.64",
+ "version": "1.2.1",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
diff --git a/packages/opencode/AGENTS.md b/packages/opencode/AGENTS.md
index a68fd7f3e3..dcfc336d65 100644
--- a/packages/opencode/AGENTS.md
+++ b/packages/opencode/AGENTS.md
@@ -1,27 +1,10 @@
-# opencode agent guidelines
+# opencode database guide
-## Build/Test Commands
+## Database
-- **Install**: `bun install`
-- **Run**: `bun run --conditions=browser ./src/index.ts`
-- **Typecheck**: `bun run typecheck` (npm run typecheck)
-- **Test**: `bun test` (runs all tests)
-- **Single test**: `bun test test/tool/tool.test.ts` (specific test file)
-
-## Code Style
-
-- **Runtime**: Bun with TypeScript ESM modules
-- **Imports**: Use relative imports for local modules, named imports preferred
-- **Types**: Zod schemas for validation, TypeScript interfaces for structure
-- **Naming**: camelCase for variables/functions, PascalCase for classes/namespaces
-- **Error handling**: Use Result patterns, avoid throwing exceptions in tools
-- **File structure**: Namespace-based organization (e.g., `Tool.define()`, `Session.create()`)
-
-## Architecture
-
-- **Tools**: Implement `Tool.Info` interface with `execute()` method
-- **Context**: Pass `sessionID` in tool context, use `App.provide()` for DI
-- **Validation**: All inputs validated with Zod schemas
-- **Logging**: Use `Log.create({ service: "name" })` pattern
-- **Storage**: Use `Storage` namespace for persistence
-- **API Client**: The TypeScript TUI (built with SolidJS + OpenTUI) communicates with the OpenCode server using `@opencode-ai/sdk`. When adding/modifying server endpoints in `packages/opencode/src/server/server.ts`, run `./script/generate.ts` to regenerate the SDK and related files.
+- **Schema**: Drizzle schema lives in `src/**/*.sql.ts`.
+- **Naming**: tables and columns use snake*case; join columns are `_id`; indexes are `*\_idx`.
+- **Migrations**: generated by Drizzle Kit using `drizzle.config.ts` (schema: `./src/**/*.sql.ts`, output: `./migration`).
+- **Command**: `bun run db generate --name `.
+- **Output**: creates `migration/_/migration.sql` and `snapshot.json`.
+- **Tests**: migration tests should read the per-folder layout (no `_journal.json`).
diff --git a/packages/opencode/bunfig.toml b/packages/opencode/bunfig.toml
index c4617527d0..c3b7270764 100644
--- a/packages/opencode/bunfig.toml
+++ b/packages/opencode/bunfig.toml
@@ -2,4 +2,6 @@ preload = ["@opentui/solid/preload"]
[test]
preload = ["./test/preload.ts"]
-timeout = 30000 # 30 seconds - allow time for package installation
+# timeout is not actually parsed from bunfig.toml (see src/bunfig.zig in oven-sh/bun)
+# using --timeout in package.json scripts instead
+# https://github.com/oven-sh/bun/issues/7789
diff --git a/packages/opencode/drizzle.config.ts b/packages/opencode/drizzle.config.ts
new file mode 100644
index 0000000000..1b4fd556e9
--- /dev/null
+++ b/packages/opencode/drizzle.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from "drizzle-kit"
+
+export default defineConfig({
+ dialect: "sqlite",
+ schema: "./src/**/*.sql.ts",
+ out: "./migration",
+ dbCredentials: {
+ url: "/home/thdxr/.local/share/opencode/opencode.db",
+ },
+})
diff --git a/packages/opencode/migration/20260127222353_familiar_lady_ursula/migration.sql b/packages/opencode/migration/20260127222353_familiar_lady_ursula/migration.sql
new file mode 100644
index 0000000000..775c1a1173
--- /dev/null
+++ b/packages/opencode/migration/20260127222353_familiar_lady_ursula/migration.sql
@@ -0,0 +1,90 @@
+CREATE TABLE `project` (
+ `id` text PRIMARY KEY,
+ `worktree` text NOT NULL,
+ `vcs` text,
+ `name` text,
+ `icon_url` text,
+ `icon_color` text,
+ `time_created` integer NOT NULL,
+ `time_updated` integer NOT NULL,
+ `time_initialized` integer,
+ `sandboxes` text NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE `message` (
+ `id` text PRIMARY KEY,
+ `session_id` text NOT NULL,
+ `time_created` integer NOT NULL,
+ `time_updated` integer NOT NULL,
+ `data` text NOT NULL,
+ CONSTRAINT `fk_message_session_id_session_id_fk` FOREIGN KEY (`session_id`) REFERENCES `session`(`id`) ON DELETE CASCADE
+);
+--> statement-breakpoint
+CREATE TABLE `part` (
+ `id` text PRIMARY KEY,
+ `message_id` text NOT NULL,
+ `session_id` text NOT NULL,
+ `time_created` integer NOT NULL,
+ `time_updated` integer NOT NULL,
+ `data` text NOT NULL,
+ CONSTRAINT `fk_part_message_id_message_id_fk` FOREIGN KEY (`message_id`) REFERENCES `message`(`id`) ON DELETE CASCADE
+);
+--> statement-breakpoint
+CREATE TABLE `permission` (
+ `project_id` text PRIMARY KEY,
+ `time_created` integer NOT NULL,
+ `time_updated` integer NOT NULL,
+ `data` text NOT NULL,
+ CONSTRAINT `fk_permission_project_id_project_id_fk` FOREIGN KEY (`project_id`) REFERENCES `project`(`id`) ON DELETE CASCADE
+);
+--> statement-breakpoint
+CREATE TABLE `session` (
+ `id` text PRIMARY KEY,
+ `project_id` text NOT NULL,
+ `parent_id` text,
+ `slug` text NOT NULL,
+ `directory` text NOT NULL,
+ `title` text NOT NULL,
+ `version` text NOT NULL,
+ `share_url` text,
+ `summary_additions` integer,
+ `summary_deletions` integer,
+ `summary_files` integer,
+ `summary_diffs` text,
+ `revert` text,
+ `permission` text,
+ `time_created` integer NOT NULL,
+ `time_updated` integer NOT NULL,
+ `time_compacting` integer,
+ `time_archived` integer,
+ CONSTRAINT `fk_session_project_id_project_id_fk` FOREIGN KEY (`project_id`) REFERENCES `project`(`id`) ON DELETE CASCADE
+);
+--> statement-breakpoint
+CREATE TABLE `todo` (
+ `session_id` text NOT NULL,
+ `content` text NOT NULL,
+ `status` text NOT NULL,
+ `priority` text NOT NULL,
+ `position` integer NOT NULL,
+ `time_created` integer NOT NULL,
+ `time_updated` integer NOT NULL,
+ CONSTRAINT `todo_pk` PRIMARY KEY(`session_id`, `position`),
+ CONSTRAINT `fk_todo_session_id_session_id_fk` FOREIGN KEY (`session_id`) REFERENCES `session`(`id`) ON DELETE CASCADE
+);
+--> statement-breakpoint
+CREATE TABLE `session_share` (
+ `session_id` text PRIMARY KEY,
+ `id` text NOT NULL,
+ `secret` text NOT NULL,
+ `url` text NOT NULL,
+ `time_created` integer NOT NULL,
+ `time_updated` integer NOT NULL,
+ CONSTRAINT `fk_session_share_session_id_session_id_fk` FOREIGN KEY (`session_id`) REFERENCES `session`(`id`) ON DELETE CASCADE
+);
+--> statement-breakpoint
+CREATE INDEX `message_session_idx` ON `message` (`session_id`);--> statement-breakpoint
+CREATE INDEX `part_message_idx` ON `part` (`message_id`);--> statement-breakpoint
+CREATE INDEX `part_session_idx` ON `part` (`session_id`);--> statement-breakpoint
+CREATE INDEX `session_project_idx` ON `session` (`project_id`);--> statement-breakpoint
+CREATE INDEX `session_parent_idx` ON `session` (`parent_id`);--> statement-breakpoint
+CREATE INDEX `todo_session_idx` ON `todo` (`session_id`);
\ No newline at end of file
diff --git a/packages/opencode/migration/20260127222353_familiar_lady_ursula/snapshot.json b/packages/opencode/migration/20260127222353_familiar_lady_ursula/snapshot.json
new file mode 100644
index 0000000000..ff76ee209a
--- /dev/null
+++ b/packages/opencode/migration/20260127222353_familiar_lady_ursula/snapshot.json
@@ -0,0 +1,796 @@
+{
+ "version": "7",
+ "dialect": "sqlite",
+ "id": "068758ed-a97a-46f6-8a59-6c639ae7c20c",
+ "prevIds": ["00000000-0000-0000-0000-000000000000"],
+ "ddl": [
+ {
+ "name": "project",
+ "entityType": "tables"
+ },
+ {
+ "name": "message",
+ "entityType": "tables"
+ },
+ {
+ "name": "part",
+ "entityType": "tables"
+ },
+ {
+ "name": "permission",
+ "entityType": "tables"
+ },
+ {
+ "name": "session",
+ "entityType": "tables"
+ },
+ {
+ "name": "todo",
+ "entityType": "tables"
+ },
+ {
+ "name": "session_share",
+ "entityType": "tables"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "worktree",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "vcs",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "name",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "icon_url",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "icon_color",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_initialized",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "sandboxes",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "data",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "message_id",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "data",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "project_id",
+ "entityType": "columns",
+ "table": "permission"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "permission"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "permission"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "data",
+ "entityType": "columns",
+ "table": "permission"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "project_id",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "parent_id",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "slug",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "directory",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "title",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "version",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "share_url",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "summary_additions",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "summary_deletions",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "summary_files",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "summary_diffs",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "revert",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "permission",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_compacting",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_archived",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "content",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "status",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "priority",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "position",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "secret",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "url",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "columns": ["session_id"],
+ "tableTo": "session",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_message_session_id_session_id_fk",
+ "entityType": "fks",
+ "table": "message"
+ },
+ {
+ "columns": ["message_id"],
+ "tableTo": "message",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_part_message_id_message_id_fk",
+ "entityType": "fks",
+ "table": "part"
+ },
+ {
+ "columns": ["project_id"],
+ "tableTo": "project",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_permission_project_id_project_id_fk",
+ "entityType": "fks",
+ "table": "permission"
+ },
+ {
+ "columns": ["project_id"],
+ "tableTo": "project",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_session_project_id_project_id_fk",
+ "entityType": "fks",
+ "table": "session"
+ },
+ {
+ "columns": ["session_id"],
+ "tableTo": "session",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_todo_session_id_session_id_fk",
+ "entityType": "fks",
+ "table": "todo"
+ },
+ {
+ "columns": ["session_id"],
+ "tableTo": "session",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_session_share_session_id_session_id_fk",
+ "entityType": "fks",
+ "table": "session_share"
+ },
+ {
+ "columns": ["session_id", "position"],
+ "nameExplicit": false,
+ "name": "todo_pk",
+ "entityType": "pks",
+ "table": "todo"
+ },
+ {
+ "columns": ["id"],
+ "nameExplicit": false,
+ "name": "project_pk",
+ "table": "project",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["id"],
+ "nameExplicit": false,
+ "name": "message_pk",
+ "table": "message",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["id"],
+ "nameExplicit": false,
+ "name": "part_pk",
+ "table": "part",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["project_id"],
+ "nameExplicit": false,
+ "name": "permission_pk",
+ "table": "permission",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["id"],
+ "nameExplicit": false,
+ "name": "session_pk",
+ "table": "session",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["session_id"],
+ "nameExplicit": false,
+ "name": "session_share_pk",
+ "table": "session_share",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ {
+ "value": "session_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "message_session_idx",
+ "entityType": "indexes",
+ "table": "message"
+ },
+ {
+ "columns": [
+ {
+ "value": "message_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "part_message_idx",
+ "entityType": "indexes",
+ "table": "part"
+ },
+ {
+ "columns": [
+ {
+ "value": "session_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "part_session_idx",
+ "entityType": "indexes",
+ "table": "part"
+ },
+ {
+ "columns": [
+ {
+ "value": "project_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "session_project_idx",
+ "entityType": "indexes",
+ "table": "session"
+ },
+ {
+ "columns": [
+ {
+ "value": "parent_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "session_parent_idx",
+ "entityType": "indexes",
+ "table": "session"
+ },
+ {
+ "columns": [
+ {
+ "value": "session_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "todo_session_idx",
+ "entityType": "indexes",
+ "table": "todo"
+ }
+ ],
+ "renames": []
+}
diff --git a/packages/opencode/migration/20260211171708_add_project_commands/migration.sql b/packages/opencode/migration/20260211171708_add_project_commands/migration.sql
new file mode 100644
index 0000000000..b63f147a0b
--- /dev/null
+++ b/packages/opencode/migration/20260211171708_add_project_commands/migration.sql
@@ -0,0 +1 @@
+ALTER TABLE `project` ADD `commands` text;
\ No newline at end of file
diff --git a/packages/opencode/migration/20260211171708_add_project_commands/snapshot.json b/packages/opencode/migration/20260211171708_add_project_commands/snapshot.json
new file mode 100644
index 0000000000..1182cc32de
--- /dev/null
+++ b/packages/opencode/migration/20260211171708_add_project_commands/snapshot.json
@@ -0,0 +1,806 @@
+{
+ "version": "7",
+ "dialect": "sqlite",
+ "id": "8bc2d11d-97fa-4ba8-8bfa-6c5956c49aeb",
+ "prevIds": ["068758ed-a97a-46f6-8a59-6c639ae7c20c"],
+ "ddl": [
+ {
+ "name": "project",
+ "entityType": "tables"
+ },
+ {
+ "name": "message",
+ "entityType": "tables"
+ },
+ {
+ "name": "part",
+ "entityType": "tables"
+ },
+ {
+ "name": "permission",
+ "entityType": "tables"
+ },
+ {
+ "name": "session",
+ "entityType": "tables"
+ },
+ {
+ "name": "todo",
+ "entityType": "tables"
+ },
+ {
+ "name": "session_share",
+ "entityType": "tables"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "worktree",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "vcs",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "name",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "icon_url",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "icon_color",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_initialized",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "sandboxes",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "commands",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "data",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "message_id",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "data",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "project_id",
+ "entityType": "columns",
+ "table": "permission"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "permission"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "permission"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "data",
+ "entityType": "columns",
+ "table": "permission"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "project_id",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "parent_id",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "slug",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "directory",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "title",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "version",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "share_url",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "summary_additions",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "summary_deletions",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "summary_files",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "summary_diffs",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "revert",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "permission",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_compacting",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_archived",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "content",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "status",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "priority",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "position",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "secret",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "url",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "columns": ["session_id"],
+ "tableTo": "session",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_message_session_id_session_id_fk",
+ "entityType": "fks",
+ "table": "message"
+ },
+ {
+ "columns": ["message_id"],
+ "tableTo": "message",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_part_message_id_message_id_fk",
+ "entityType": "fks",
+ "table": "part"
+ },
+ {
+ "columns": ["project_id"],
+ "tableTo": "project",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_permission_project_id_project_id_fk",
+ "entityType": "fks",
+ "table": "permission"
+ },
+ {
+ "columns": ["project_id"],
+ "tableTo": "project",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_session_project_id_project_id_fk",
+ "entityType": "fks",
+ "table": "session"
+ },
+ {
+ "columns": ["session_id"],
+ "tableTo": "session",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_todo_session_id_session_id_fk",
+ "entityType": "fks",
+ "table": "todo"
+ },
+ {
+ "columns": ["session_id"],
+ "tableTo": "session",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_session_share_session_id_session_id_fk",
+ "entityType": "fks",
+ "table": "session_share"
+ },
+ {
+ "columns": ["session_id", "position"],
+ "nameExplicit": false,
+ "name": "todo_pk",
+ "entityType": "pks",
+ "table": "todo"
+ },
+ {
+ "columns": ["id"],
+ "nameExplicit": false,
+ "name": "project_pk",
+ "table": "project",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["id"],
+ "nameExplicit": false,
+ "name": "message_pk",
+ "table": "message",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["id"],
+ "nameExplicit": false,
+ "name": "part_pk",
+ "table": "part",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["project_id"],
+ "nameExplicit": false,
+ "name": "permission_pk",
+ "table": "permission",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["id"],
+ "nameExplicit": false,
+ "name": "session_pk",
+ "table": "session",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["session_id"],
+ "nameExplicit": false,
+ "name": "session_share_pk",
+ "table": "session_share",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ {
+ "value": "session_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "message_session_idx",
+ "entityType": "indexes",
+ "table": "message"
+ },
+ {
+ "columns": [
+ {
+ "value": "message_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "part_message_idx",
+ "entityType": "indexes",
+ "table": "part"
+ },
+ {
+ "columns": [
+ {
+ "value": "session_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "part_session_idx",
+ "entityType": "indexes",
+ "table": "part"
+ },
+ {
+ "columns": [
+ {
+ "value": "project_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "session_project_idx",
+ "entityType": "indexes",
+ "table": "session"
+ },
+ {
+ "columns": [
+ {
+ "value": "parent_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "session_parent_idx",
+ "entityType": "indexes",
+ "table": "session"
+ },
+ {
+ "columns": [
+ {
+ "value": "session_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "todo_session_idx",
+ "entityType": "indexes",
+ "table": "todo"
+ }
+ ],
+ "renames": []
+}
diff --git a/packages/opencode/migration/20260213144116_wakeful_the_professor/migration.sql b/packages/opencode/migration/20260213144116_wakeful_the_professor/migration.sql
new file mode 100644
index 0000000000..3085fe280f
--- /dev/null
+++ b/packages/opencode/migration/20260213144116_wakeful_the_professor/migration.sql
@@ -0,0 +1,11 @@
+CREATE TABLE `control_account` (
+ `email` text NOT NULL,
+ `url` text NOT NULL,
+ `access_token` text NOT NULL,
+ `refresh_token` text NOT NULL,
+ `token_expiry` integer,
+ `active` integer NOT NULL,
+ `time_created` integer NOT NULL,
+ `time_updated` integer NOT NULL,
+ CONSTRAINT `control_account_pk` PRIMARY KEY(`email`, `url`)
+);
diff --git a/packages/opencode/migration/20260213144116_wakeful_the_professor/snapshot.json b/packages/opencode/migration/20260213144116_wakeful_the_professor/snapshot.json
new file mode 100644
index 0000000000..05c00a10cf
--- /dev/null
+++ b/packages/opencode/migration/20260213144116_wakeful_the_professor/snapshot.json
@@ -0,0 +1,897 @@
+{
+ "version": "7",
+ "dialect": "sqlite",
+ "id": "d2736e43-700f-4e9e-8151-9f2f0d967bc8",
+ "prevIds": ["8bc2d11d-97fa-4ba8-8bfa-6c5956c49aeb"],
+ "ddl": [
+ {
+ "name": "control_account",
+ "entityType": "tables"
+ },
+ {
+ "name": "project",
+ "entityType": "tables"
+ },
+ {
+ "name": "message",
+ "entityType": "tables"
+ },
+ {
+ "name": "part",
+ "entityType": "tables"
+ },
+ {
+ "name": "permission",
+ "entityType": "tables"
+ },
+ {
+ "name": "session",
+ "entityType": "tables"
+ },
+ {
+ "name": "todo",
+ "entityType": "tables"
+ },
+ {
+ "name": "session_share",
+ "entityType": "tables"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "email",
+ "entityType": "columns",
+ "table": "control_account"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "url",
+ "entityType": "columns",
+ "table": "control_account"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "access_token",
+ "entityType": "columns",
+ "table": "control_account"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "refresh_token",
+ "entityType": "columns",
+ "table": "control_account"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "token_expiry",
+ "entityType": "columns",
+ "table": "control_account"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "active",
+ "entityType": "columns",
+ "table": "control_account"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "control_account"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "control_account"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "worktree",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "vcs",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "name",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "icon_url",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "icon_color",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_initialized",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "sandboxes",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "commands",
+ "entityType": "columns",
+ "table": "project"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "data",
+ "entityType": "columns",
+ "table": "message"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "message_id",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "data",
+ "entityType": "columns",
+ "table": "part"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "project_id",
+ "entityType": "columns",
+ "table": "permission"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "permission"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "permission"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "data",
+ "entityType": "columns",
+ "table": "permission"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "project_id",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "parent_id",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "slug",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "directory",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "title",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "version",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "share_url",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "summary_additions",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "summary_deletions",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "summary_files",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "summary_diffs",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "revert",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "permission",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_compacting",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "integer",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_archived",
+ "entityType": "columns",
+ "table": "session"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "content",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "status",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "priority",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "position",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "todo"
+ },
+ {
+ "type": "text",
+ "notNull": false,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "secret",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "url",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "type": "integer",
+ "notNull": true,
+ "autoincrement": false,
+ "default": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "session_share"
+ },
+ {
+ "columns": ["session_id"],
+ "tableTo": "session",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_message_session_id_session_id_fk",
+ "entityType": "fks",
+ "table": "message"
+ },
+ {
+ "columns": ["message_id"],
+ "tableTo": "message",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_part_message_id_message_id_fk",
+ "entityType": "fks",
+ "table": "part"
+ },
+ {
+ "columns": ["project_id"],
+ "tableTo": "project",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_permission_project_id_project_id_fk",
+ "entityType": "fks",
+ "table": "permission"
+ },
+ {
+ "columns": ["project_id"],
+ "tableTo": "project",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_session_project_id_project_id_fk",
+ "entityType": "fks",
+ "table": "session"
+ },
+ {
+ "columns": ["session_id"],
+ "tableTo": "session",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_todo_session_id_session_id_fk",
+ "entityType": "fks",
+ "table": "todo"
+ },
+ {
+ "columns": ["session_id"],
+ "tableTo": "session",
+ "columnsTo": ["id"],
+ "onUpdate": "NO ACTION",
+ "onDelete": "CASCADE",
+ "nameExplicit": false,
+ "name": "fk_session_share_session_id_session_id_fk",
+ "entityType": "fks",
+ "table": "session_share"
+ },
+ {
+ "columns": ["email", "url"],
+ "nameExplicit": false,
+ "name": "control_account_pk",
+ "entityType": "pks",
+ "table": "control_account"
+ },
+ {
+ "columns": ["session_id", "position"],
+ "nameExplicit": false,
+ "name": "todo_pk",
+ "entityType": "pks",
+ "table": "todo"
+ },
+ {
+ "columns": ["id"],
+ "nameExplicit": false,
+ "name": "project_pk",
+ "table": "project",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["id"],
+ "nameExplicit": false,
+ "name": "message_pk",
+ "table": "message",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["id"],
+ "nameExplicit": false,
+ "name": "part_pk",
+ "table": "part",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["project_id"],
+ "nameExplicit": false,
+ "name": "permission_pk",
+ "table": "permission",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["id"],
+ "nameExplicit": false,
+ "name": "session_pk",
+ "table": "session",
+ "entityType": "pks"
+ },
+ {
+ "columns": ["session_id"],
+ "nameExplicit": false,
+ "name": "session_share_pk",
+ "table": "session_share",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ {
+ "value": "session_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "message_session_idx",
+ "entityType": "indexes",
+ "table": "message"
+ },
+ {
+ "columns": [
+ {
+ "value": "message_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "part_message_idx",
+ "entityType": "indexes",
+ "table": "part"
+ },
+ {
+ "columns": [
+ {
+ "value": "session_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "part_session_idx",
+ "entityType": "indexes",
+ "table": "part"
+ },
+ {
+ "columns": [
+ {
+ "value": "project_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "session_project_idx",
+ "entityType": "indexes",
+ "table": "session"
+ },
+ {
+ "columns": [
+ {
+ "value": "parent_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "session_parent_idx",
+ "entityType": "indexes",
+ "table": "session"
+ },
+ {
+ "columns": [
+ {
+ "value": "session_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "where": null,
+ "origin": "manual",
+ "name": "todo_session_idx",
+ "entityType": "indexes",
+ "table": "todo"
+ }
+ ],
+ "renames": []
+}
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index f58a3d2fe9..bf372379bd 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -1,13 +1,13 @@
{
"$schema": "https://json.schemastore.org/package.json",
- "version": "1.1.64",
+ "version": "1.2.1",
"name": "opencode",
"type": "module",
"license": "MIT",
"private": true,
"scripts": {
"typecheck": "tsgo --noEmit",
- "test": "bun test",
+ "test": "bun test --timeout 30000",
"build": "bun run script/build.ts",
"dev": "bun run --conditions=browser ./src/index.ts",
"random": "echo 'Random script updated at $(date)' && echo 'Change queued successfully' && echo 'Another change made' && echo 'Yet another change' && echo 'One more change' && echo 'Final change' && echo 'Another final change' && echo 'Yet another final change'",
@@ -15,7 +15,8 @@
"lint": "echo 'Running lint checks...' && bun test --coverage",
"format": "echo 'Formatting code...' && bun run --prettier --write src/**/*.ts",
"docs": "echo 'Generating documentation...' && find src -name '*.ts' -exec echo 'Processing: {}' \\;",
- "deploy": "echo 'Deploying application...' && bun run build && echo 'Deployment completed successfully'"
+ "deploy": "echo 'Deploying application...' && bun run build && echo 'Deployment completed successfully'",
+ "db": "bun drizzle-kit"
},
"bin": {
"opencode": "./bin/opencode"
@@ -42,6 +43,8 @@
"@types/turndown": "5.0.5",
"@types/yargs": "17.0.33",
"@typescript/native-preview": "catalog:",
+ "drizzle-kit": "1.0.0-beta.12-a5629fb",
+ "drizzle-orm": "1.0.0-beta.12-a5629fb",
"typescript": "catalog:",
"vscode-languageserver-types": "3.17.5",
"why-is-node-running": "3.2.2",
@@ -51,12 +54,12 @@
"@actions/core": "1.11.1",
"@actions/github": "6.0.1",
"@agentclientprotocol/sdk": "0.14.1",
- "@ai-sdk/amazon-bedrock": "3.0.74",
- "@ai-sdk/anthropic": "2.0.58",
+ "@ai-sdk/amazon-bedrock": "3.0.79",
+ "@ai-sdk/anthropic": "2.0.62",
"@ai-sdk/azure": "2.0.91",
"@ai-sdk/cerebras": "1.0.36",
"@ai-sdk/cohere": "2.0.22",
- "@ai-sdk/deepinfra": "1.0.33",
+ "@ai-sdk/deepinfra": "1.0.36",
"@ai-sdk/gateway": "2.0.30",
"@ai-sdk/google": "2.0.52",
"@ai-sdk/google-vertex": "3.0.98",
@@ -100,6 +103,7 @@
"clipboardy": "4.0.0",
"decimal.js": "10.5.0",
"diff": "catalog:",
+ "drizzle-orm": "1.0.0-beta.12-a5629fb",
"fuzzysort": "3.1.0",
"gray-matter": "4.0.3",
"hono": "catalog:",
@@ -122,5 +126,8 @@
"yargs": "18.0.0",
"zod": "catalog:",
"zod-to-json-schema": "3.24.5"
+ },
+ "overrides": {
+ "drizzle-orm": "1.0.0-beta.12-a5629fb"
}
}
diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts
index f0b3fa828a..ddb4769912 100755
--- a/packages/opencode/script/build.ts
+++ b/packages/opencode/script/build.ts
@@ -25,6 +25,32 @@ await Bun.write(
)
console.log("Generated models-snapshot.ts")
+// Load migrations from migration directories
+const migrationDirs = (await fs.promises.readdir(path.join(dir, "migration"), { withFileTypes: true }))
+ .filter((entry) => entry.isDirectory() && /^\d{4}\d{2}\d{2}\d{2}\d{2}\d{2}/.test(entry.name))
+ .map((entry) => entry.name)
+ .sort()
+
+const migrations = await Promise.all(
+ migrationDirs.map(async (name) => {
+ const file = path.join(dir, "migration", name, "migration.sql")
+ const sql = await Bun.file(file).text()
+ const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name)
+ const timestamp = match
+ ? Date.UTC(
+ Number(match[1]),
+ Number(match[2]) - 1,
+ Number(match[3]),
+ Number(match[4]),
+ Number(match[5]),
+ Number(match[6]),
+ )
+ : 0
+ return { sql, timestamp }
+ }),
+)
+console.log(`Loaded ${migrations.length} migrations`)
+
const singleFlag = process.argv.includes("--single")
const baselineFlag = process.argv.includes("--baseline")
const skipInstall = process.argv.includes("--skip-install")
@@ -156,6 +182,7 @@ for (const item of targets) {
entrypoints: ["./src/index.ts", parserWorker, workerPath],
define: {
OPENCODE_VERSION: `'${Script.version}'`,
+ OPENCODE_MIGRATIONS: JSON.stringify(migrations),
OTUI_TREE_SITTER_WORKER_PATH: bunfsRoot + workerRelativePath,
OPENCODE_WORKER_PATH: workerPath,
OPENCODE_CHANNEL: `'${Script.channel}'`,
diff --git a/packages/opencode/script/check-migrations.ts b/packages/opencode/script/check-migrations.ts
new file mode 100644
index 0000000000..f5eaf79323
--- /dev/null
+++ b/packages/opencode/script/check-migrations.ts
@@ -0,0 +1,16 @@
+#!/usr/bin/env bun
+
+import { $ } from "bun"
+
+// drizzle-kit check compares schema to migrations, exits non-zero if drift
+const result = await $`bun drizzle-kit check`.quiet().nothrow()
+
+if (result.exitCode !== 0) {
+ console.error("Schema has changes not captured in migrations!")
+ console.error("Run: bun drizzle-kit generate")
+ console.error("")
+ console.error(result.stderr.toString())
+ process.exit(1)
+}
+
+console.log("Migrations are up to date")
diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts
index ae6f6fcc29..9512406b3d 100644
--- a/packages/opencode/src/acp/agent.ts
+++ b/packages/opencode/src/acp/agent.ts
@@ -435,46 +435,68 @@ export namespace ACP {
return
}
}
+ return
+ }
- if (part.type === "text") {
- const delta = props.delta
- if (delta && part.ignored !== true) {
- await this.connection
- .sessionUpdate({
- sessionId,
- update: {
- sessionUpdate: "agent_message_chunk",
- content: {
- type: "text",
- text: delta,
- },
+ case "message.part.delta": {
+ const props = event.properties
+ const session = this.sessionManager.tryGet(props.sessionID)
+ if (!session) return
+ const sessionId = session.id
+
+ const message = await this.sdk.session
+ .message(
+ {
+ sessionID: props.sessionID,
+ messageID: props.messageID,
+ directory: session.cwd,
+ },
+ { throwOnError: true },
+ )
+ .then((x) => x.data)
+ .catch((error) => {
+ log.error("unexpected error when fetching message", { error })
+ return undefined
+ })
+
+ if (!message || message.info.role !== "assistant") return
+
+ const part = message.parts.find((p) => p.id === props.partID)
+ if (!part) return
+
+ if (part.type === "text" && props.field === "text" && part.ignored !== true) {
+ await this.connection
+ .sessionUpdate({
+ sessionId,
+ update: {
+ sessionUpdate: "agent_message_chunk",
+ content: {
+ type: "text",
+ text: props.delta,
},
- })
- .catch((error) => {
- log.error("failed to send text to ACP", { error })
- })
- }
+ },
+ })
+ .catch((error) => {
+ log.error("failed to send text delta to ACP", { error })
+ })
return
}
- 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,
- },
+ if (part.type === "reasoning" && props.field === "text") {
+ await this.connection
+ .sessionUpdate({
+ sessionId,
+ update: {
+ sessionUpdate: "agent_thought_chunk",
+ content: {
+ type: "text",
+ text: props.delta,
},
- })
- .catch((error) => {
- log.error("failed to send reasoning to ACP", { error })
- })
- }
+ },
+ })
+ .catch((error) => {
+ log.error("failed to send reasoning delta to ACP", { error })
+ })
}
return
}
diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts
index 37419f4e23..fd45a09b73 100644
--- a/packages/opencode/src/cli/cmd/import.ts
+++ b/packages/opencode/src/cli/cmd/import.ts
@@ -3,7 +3,8 @@ import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2"
import { Session } from "../../session"
import { cmd } from "./cmd"
import { bootstrap } from "../bootstrap"
-import { Storage } from "../../storage/storage"
+import { Database } from "../../storage/db"
+import { SessionTable, MessageTable, PartTable } from "../../session/session.sql"
import { Instance } from "../../project/instance"
import { ShareNext } from "../../share/share-next"
import { EOL } from "os"
@@ -130,13 +131,35 @@ export const ImportCommand = cmd({
return
}
- await Storage.write(["session", Instance.project.id, exportData.info.id], exportData.info)
+ Database.use((db) => db.insert(SessionTable).values(Session.toRow(exportData.info)).onConflictDoNothing().run())
for (const msg of exportData.messages) {
- await Storage.write(["message", exportData.info.id, msg.info.id], msg.info)
+ Database.use((db) =>
+ db
+ .insert(MessageTable)
+ .values({
+ id: msg.info.id,
+ session_id: exportData.info.id,
+ time_created: msg.info.time?.created ?? Date.now(),
+ data: msg.info,
+ })
+ .onConflictDoNothing()
+ .run(),
+ )
for (const part of msg.parts) {
- await Storage.write(["part", msg.info.id, part.id], part)
+ Database.use((db) =>
+ db
+ .insert(PartTable)
+ .values({
+ id: part.id,
+ message_id: msg.info.id,
+ session_id: exportData.info.id,
+ data: part,
+ })
+ .onConflictDoNothing()
+ .run(),
+ )
}
}
diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts
index 163a5820d9..55cf9a2a0a 100644
--- a/packages/opencode/src/cli/cmd/run.ts
+++ b/packages/opencode/src/cli/cmd/run.ts
@@ -274,6 +274,10 @@ export const RunCommand = cmd({
type: "string",
describe: "attach to a running opencode server (e.g., http://localhost:4096)",
})
+ .option("dir", {
+ type: "string",
+ describe: "directory to run in, path on remote server if attaching",
+ })
.option("port", {
type: "number",
describe: "port for the local server (defaults to random port if no value provided)",
@@ -293,6 +297,18 @@ export const RunCommand = cmd({
.map((arg) => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg))
.join(" ")
+ const directory = (() => {
+ if (!args.dir) return undefined
+ if (args.attach) return args.dir
+ try {
+ process.chdir(args.dir)
+ return process.cwd()
+ } catch {
+ UI.error("Failed to change directory to " + args.dir)
+ process.exit(1)
+ }
+ })()
+
const files: { type: "file"; url: string; filename: string; mime: string }[] = []
if (args.file) {
const list = Array.isArray(args.file) ? args.file : [args.file]
@@ -390,20 +406,24 @@ export const RunCommand = cmd({
async function execute(sdk: OpencodeClient) {
function tool(part: ToolPart) {
- if (part.tool === "bash") return bash(props(part))
- if (part.tool === "glob") return glob(props(part))
- if (part.tool === "grep") return grep(props(part))
- if (part.tool === "list") return list(props(part))
- if (part.tool === "read") return read(props(part))
- if (part.tool === "write") return write(props(part))
- if (part.tool === "webfetch") return webfetch(props(part))
- if (part.tool === "edit") return edit(props(part))
- if (part.tool === "codesearch") return codesearch(props(part))
- if (part.tool === "websearch") return websearch(props(part))
- if (part.tool === "task") return task(props(part))
- if (part.tool === "todowrite") return todo(props(part))
- if (part.tool === "skill") return skill(props(part))
- return fallback(part)
+ try {
+ if (part.tool === "bash") return bash(props(part))
+ if (part.tool === "glob") return glob(props(part))
+ if (part.tool === "grep") return grep(props(part))
+ if (part.tool === "list") return list(props(part))
+ if (part.tool === "read") return read(props(part))
+ if (part.tool === "write") return write(props(part))
+ if (part.tool === "webfetch") return webfetch(props(part))
+ if (part.tool === "edit") return edit(props(part))
+ if (part.tool === "codesearch") return codesearch(props(part))
+ if (part.tool === "websearch") return websearch(props(part))
+ if (part.tool === "task") return task(props(part))
+ if (part.tool === "todowrite") return todo(props(part))
+ if (part.tool === "skill") return skill(props(part))
+ return fallback(part)
+ } catch {
+ return fallback(part)
+ }
}
function emit(type: string, data: Record) {
@@ -582,7 +602,7 @@ export const RunCommand = cmd({
}
if (args.attach) {
- const sdk = createOpencodeClient({ baseUrl: args.attach })
+ const sdk = createOpencodeClient({ baseUrl: args.attach, directory })
return await execute(sdk)
}
diff --git a/packages/opencode/src/cli/cmd/stats.ts b/packages/opencode/src/cli/cmd/stats.ts
index 9239bb90a6..04c1fe2ebc 100644
--- a/packages/opencode/src/cli/cmd/stats.ts
+++ b/packages/opencode/src/cli/cmd/stats.ts
@@ -2,7 +2,8 @@ import type { Argv } from "yargs"
import { cmd } from "./cmd"
import { Session } from "../../session"
import { bootstrap } from "../bootstrap"
-import { Storage } from "../../storage/storage"
+import { Database } from "../../storage/db"
+import { SessionTable } from "../../session/session.sql"
import { Project } from "../../project/project"
import { Instance } from "../../project/instance"
@@ -87,25 +88,8 @@ async function getCurrentProject(): Promise {
}
async function getAllSessions(): Promise {
- const sessions: Session.Info[] = []
-
- const projectKeys = await Storage.list(["project"])
- const projects = await Promise.all(projectKeys.map((key) => Storage.read(key)))
-
- for (const project of projects) {
- if (!project) continue
-
- const sessionKeys = await Storage.list(["session", project.id])
- const projectSessions = await Promise.all(sessionKeys.map((key) => Storage.read(key)))
-
- for (const session of projectSessions) {
- if (session) {
- sessions.push(session)
- }
- }
- }
-
- return sessions
+ const rows = Database.use((db) => db.select().from(SessionTable).all())
+ return rows.map((row) => Session.fromRow(row))
}
export async function aggregateSessionStats(days?: number, projectFilter?: string): Promise {
diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx
index eb8ed2d9bb..269ed7ae0b 100644
--- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx
@@ -299,6 +299,24 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
break
}
+ case "message.part.delta": {
+ const parts = store.part[event.properties.messageID]
+ if (!parts) break
+ const result = Binary.search(parts, event.properties.partID, (p) => p.id)
+ if (!result.found) break
+ setStore(
+ "part",
+ event.properties.messageID,
+ produce((draft) => {
+ const part = draft[result.index]
+ const field = event.properties.field as keyof typeof part
+ const existing = part[field] as string | undefined
+ ;(part[field] as string) = (existing ?? "") + event.properties.delta
+ }),
+ )
+ break
+ }
+
case "message.part.removed": {
const parts = store.part[event.properties.messageID]
const result = Binary.search(parts, event.properties.partID, (p) => p.id)
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
index b843bda1c9..e83b9abe98 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -2042,8 +2042,8 @@ function ApplyPatch(props: ToolProps) {
-
- apply_patch
+
+ Patch
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index f4d7a840fe..261731b8b0 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -31,6 +31,7 @@ import { Event } from "../server/event"
import { PackageRegistry } from "@/bun/registry"
import { proxied } from "@/util/proxied"
import { iife } from "@/util/iife"
+import { Control } from "@/control"
export namespace Config {
const ModelId = z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" })
@@ -53,7 +54,7 @@ export namespace Config {
const managedConfigDir = process.env.OPENCODE_TEST_MANAGED_CONFIG_DIR || getManagedConfigDir()
// Custom merge function that concatenates array fields instead of replacing them
- function mergeConfigConcatArrays(target: Info, source: Info): Info {
+ function merge(target: Info, source: Info): Info {
const merged = mergeDeep(target, source)
if (target.plugin && source.plugin) {
merged.plugin = Array.from(new Set([...target.plugin, ...source.plugin]))
@@ -88,20 +89,21 @@ export namespace Config {
const remoteConfig = wellknown.config ?? {}
// Add $schema to prevent load() from trying to write back to a non-existent file
if (!remoteConfig.$schema) remoteConfig.$schema = "https://opencode.ai/config.json"
- result = mergeConfigConcatArrays(
- result,
- await load(JSON.stringify(remoteConfig), `${key}/.well-known/opencode`),
- )
+ result = merge(result, await load(JSON.stringify(remoteConfig), `${key}/.well-known/opencode`))
log.debug("loaded remote config from well-known", { url: key })
}
}
+ const token = await Control.token()
+ if (token) {
+ }
+
// Global user config overrides remote config.
- result = mergeConfigConcatArrays(result, await global())
+ result = merge(result, await global())
// Custom config path overrides global config.
if (Flag.OPENCODE_CONFIG) {
- result = mergeConfigConcatArrays(result, await loadFile(Flag.OPENCODE_CONFIG))
+ result = merge(result, await loadFile(Flag.OPENCODE_CONFIG))
log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
}
@@ -110,7 +112,7 @@ export namespace Config {
for (const file of ["opencode.jsonc", "opencode.json"]) {
const found = await Filesystem.findUp(file, Instance.directory, Instance.worktree)
for (const resolved of found.toReversed()) {
- result = mergeConfigConcatArrays(result, await loadFile(resolved))
+ result = merge(result, await loadFile(resolved))
}
}
}
@@ -153,7 +155,7 @@ export namespace Config {
if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
for (const file of ["opencode.jsonc", "opencode.json"]) {
log.debug(`loading config from ${path.join(dir, file)}`)
- result = mergeConfigConcatArrays(result, await loadFile(path.join(dir, file)))
+ result = merge(result, await loadFile(path.join(dir, file)))
// to satisfy the type checker
result.agent ??= {}
result.mode ??= {}
@@ -175,14 +177,8 @@ export namespace Config {
}
// Inline config content overrides all non-managed config sources.
- // Route through load() to enable {env:} and {file:} token substitution.
- // Use a path within Instance.directory so relative {file:} paths resolve correctly.
- // The filename "OPENCODE_CONFIG_CONTENT" appears in error messages for clarity.
if (Flag.OPENCODE_CONFIG_CONTENT) {
- result = mergeConfigConcatArrays(
- result,
- await load(Flag.OPENCODE_CONFIG_CONTENT, path.join(Instance.directory, "OPENCODE_CONFIG_CONTENT")),
- )
+ result = merge(result, JSON.parse(Flag.OPENCODE_CONFIG_CONTENT))
log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT")
}
@@ -192,7 +188,7 @@ export namespace Config {
// This way it only loads config file and not skills/plugins/commands
if (existsSync(managedConfigDir)) {
for (const file of ["opencode.jsonc", "opencode.json"]) {
- result = mergeConfigConcatArrays(result, await loadFile(path.join(managedConfigDir, file)))
+ result = merge(result, await loadFile(path.join(managedConfigDir, file)))
}
}
diff --git a/packages/opencode/src/control/control.sql.ts b/packages/opencode/src/control/control.sql.ts
new file mode 100644
index 0000000000..7b805c1627
--- /dev/null
+++ b/packages/opencode/src/control/control.sql.ts
@@ -0,0 +1,22 @@
+import { sqliteTable, text, integer, primaryKey, uniqueIndex } from "drizzle-orm/sqlite-core"
+import { eq } from "drizzle-orm"
+import { Timestamps } from "@/storage/schema.sql"
+
+export const ControlAccountTable = sqliteTable(
+ "control_account",
+ {
+ email: text().notNull(),
+ url: text().notNull(),
+ access_token: text().notNull(),
+ refresh_token: text().notNull(),
+ token_expiry: integer(),
+ active: integer({ mode: "boolean" })
+ .notNull()
+ .$default(() => false),
+ ...Timestamps,
+ },
+ (table) => [
+ primaryKey({ columns: [table.email, table.url] }),
+ // uniqueIndex("control_account_active_idx").on(table.email).where(eq(table.active, true)),
+ ],
+)
diff --git a/packages/opencode/src/control/index.ts b/packages/opencode/src/control/index.ts
new file mode 100644
index 0000000000..f712e88281
--- /dev/null
+++ b/packages/opencode/src/control/index.ts
@@ -0,0 +1,67 @@
+import { eq, and } from "drizzle-orm"
+import { Database } from "@/storage/db"
+import { ControlAccountTable } from "./control.sql"
+import z from "zod"
+
+export * from "./control.sql"
+
+export namespace Control {
+ export const Account = z.object({
+ email: z.string(),
+ url: z.string(),
+ })
+ export type Account = z.infer
+
+ function fromRow(row: (typeof ControlAccountTable)["$inferSelect"]): Account {
+ return {
+ email: row.email,
+ url: row.url,
+ }
+ }
+
+ export function account(): Account | undefined {
+ const row = Database.use((db) =>
+ db.select().from(ControlAccountTable).where(eq(ControlAccountTable.active, true)).get(),
+ )
+ return row ? fromRow(row) : undefined
+ }
+
+ export async function token(): Promise {
+ const row = Database.use((db) =>
+ db.select().from(ControlAccountTable).where(eq(ControlAccountTable.active, true)).get(),
+ )
+ if (!row) return undefined
+ if (row.token_expiry && row.token_expiry > Date.now()) return row.access_token
+
+ const res = await fetch(`${row.url}/oauth/token`, {
+ method: "POST",
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
+ body: new URLSearchParams({
+ grant_type: "refresh_token",
+ refresh_token: row.refresh_token,
+ }).toString(),
+ })
+
+ if (!res.ok) return
+
+ const json = (await res.json()) as {
+ access_token: string
+ refresh_token?: string
+ expires_in?: number
+ }
+
+ Database.use((db) =>
+ db
+ .update(ControlAccountTable)
+ .set({
+ access_token: json.access_token,
+ refresh_token: json.refresh_token ?? row.refresh_token,
+ token_expiry: json.expires_in ? Date.now() + json.expires_in * 1000 : undefined,
+ })
+ .where(and(eq(ControlAccountTable.email, row.email), eq(ControlAccountTable.url, row.url)))
+ .run(),
+ )
+
+ return json.access_token
+ }
+}
diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts
index 641cb3325b..dfcb88bc51 100644
--- a/packages/opencode/src/flag/flag.ts
+++ b/packages/opencode/src/flag/flag.ts
@@ -8,7 +8,7 @@ export namespace Flag {
export const OPENCODE_GIT_BASH_PATH = process.env["OPENCODE_GIT_BASH_PATH"]
export const OPENCODE_CONFIG = process.env["OPENCODE_CONFIG"]
export declare const OPENCODE_CONFIG_DIR: string | undefined
- export declare const OPENCODE_CONFIG_CONTENT: string | undefined
+ export const OPENCODE_CONFIG_CONTENT = process.env["OPENCODE_CONFIG_CONTENT"]
export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE")
export const OPENCODE_DISABLE_PRUNE = truthy("OPENCODE_DISABLE_PRUNE")
export const OPENCODE_DISABLE_TERMINAL_TITLE = truthy("OPENCODE_DISABLE_TERMINAL_TITLE")
@@ -94,14 +94,3 @@ Object.defineProperty(Flag, "OPENCODE_CLIENT", {
enumerable: true,
configurable: false,
})
-
-// Dynamic getter for OPENCODE_CONFIG_CONTENT
-// This must be evaluated at access time, not module load time,
-// because external tooling may set this env var at runtime
-Object.defineProperty(Flag, "OPENCODE_CONFIG_CONTENT", {
- get() {
- return process.env["OPENCODE_CONFIG_CONTENT"]
- },
- enumerable: true,
- configurable: false,
-})
diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts
index 6dc5e99e91..420ead5555 100644
--- a/packages/opencode/src/index.ts
+++ b/packages/opencode/src/index.ts
@@ -26,6 +26,10 @@ import { EOL } from "os"
import { WebCommand } from "./cli/cmd/web"
import { PrCommand } from "./cli/cmd/pr"
import { SessionCommand } from "./cli/cmd/session"
+import path from "path"
+import { Global } from "./global"
+import { JsonMigration } from "./storage/json-migration"
+import { Database } from "./storage/db"
process.on("unhandledRejection", (e) => {
Log.Default.error("rejection", {
@@ -74,6 +78,43 @@ const cli = yargs(hideBin(process.argv))
version: Installation.VERSION,
args: process.argv.slice(2),
})
+
+ const marker = path.join(Global.Path.data, "opencode.db")
+ if (!(await Bun.file(marker).exists())) {
+ console.log("Performing one time database migration, may take a few minutes...")
+ const tty = process.stdout.isTTY
+ const width = 36
+ const orange = "\x1b[38;5;214m"
+ const muted = "\x1b[0;2m"
+ const reset = "\x1b[0m"
+ let last = -1
+ if (tty) process.stdout.write("\x1b[?25l")
+ try {
+ await JsonMigration.run(Database.Client().$client, {
+ progress: (event) => {
+ const percent = Math.floor((event.current / event.total) * 100)
+ if (percent === last && event.current !== event.total) return
+ last = percent
+ if (tty) {
+ const fill = Math.round((percent / 100) * width)
+ const bar = `${"■".repeat(fill)}${"・".repeat(width - fill)}`
+ process.stdout.write(
+ `\r${orange}${bar} ${percent.toString().padStart(3)}%${reset} ${muted}${event.label.padEnd(12)} ${event.current}/${event.total}${reset}`,
+ )
+ if (event.current === event.total) process.stdout.write("\n")
+ } else {
+ console.log(`sqlite-migration:${percent}`)
+ }
+ },
+ })
+ } finally {
+ if (tty) process.stdout.write("\x1b[?25h")
+ else {
+ console.log(`sqlite-migration:done`)
+ }
+ }
+ console.log("Database migration complete.")
+ }
})
.usage("\n" + UI.logo())
.completion("completion", "generate shell completion script")
diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts
index 2481f104ed..1e1df62a3c 100644
--- a/packages/opencode/src/permission/next.ts
+++ b/packages/opencode/src/permission/next.ts
@@ -3,7 +3,8 @@ import { BusEvent } from "@/bus/bus-event"
import { Config } from "@/config/config"
import { Identifier } from "@/id/id"
import { Instance } from "@/project/instance"
-import { Storage } from "@/storage/storage"
+import { Database, eq } from "@/storage/db"
+import { PermissionTable } from "@/session/session.sql"
import { fn } from "@/util/fn"
import { Log } from "@/util/log"
import { Wildcard } from "@/util/wildcard"
@@ -105,9 +106,12 @@ export namespace PermissionNext {
),
}
- const state = Instance.state(async () => {
+ const state = Instance.state(() => {
const projectID = Instance.project.id
- const stored = await Storage.read(["permission", projectID]).catch(() => [] as Ruleset)
+ const row = Database.use((db) =>
+ db.select().from(PermissionTable).where(eq(PermissionTable.project_id, projectID)).get(),
+ )
+ const stored = row?.data ?? ([] as Ruleset)
const pending: Record<
string,
@@ -222,7 +226,8 @@ export namespace PermissionNext {
// TODO: we don't save the permission ruleset to disk yet until there's
// UI to manage it
- // await Storage.write(["permission", Instance.project.id], s.approved)
+ // db().insert(PermissionTable).values({ projectID: Instance.project.id, data: s.approved })
+ // .onConflictDoUpdate({ target: PermissionTable.projectID, set: { data: s.approved } }).run()
return
}
},
@@ -275,6 +280,7 @@ export namespace PermissionNext {
}
export async function list() {
- return state().then((x) => Object.values(x.pending).map((x) => x.info))
+ const s = await state()
+ return Object.values(s.pending).map((x) => x.info)
}
}
diff --git a/packages/opencode/src/project/bootstrap.ts b/packages/opencode/src/project/bootstrap.ts
index efdcaba990..a2be3733f8 100644
--- a/packages/opencode/src/project/bootstrap.ts
+++ b/packages/opencode/src/project/bootstrap.ts
@@ -1,5 +1,4 @@
import { Plugin } from "../plugin"
-import { Share } from "../share/share"
import { Format } from "../format"
import { LSP } from "../lsp"
import { FileWatcher } from "../file/watcher"
@@ -17,7 +16,6 @@ import { Truncate } from "../tool/truncation"
export async function InstanceBootstrap() {
Log.Default.info("bootstrapping", { directory: Instance.directory })
await Plugin.init()
- Share.init()
ShareNext.init()
Format.init()
await LSP.init()
diff --git a/packages/opencode/src/project/project.sql.ts b/packages/opencode/src/project/project.sql.ts
new file mode 100644
index 0000000000..12373244f5
--- /dev/null
+++ b/packages/opencode/src/project/project.sql.ts
@@ -0,0 +1,15 @@
+import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"
+import { Timestamps } from "@/storage/schema.sql"
+
+export const ProjectTable = sqliteTable("project", {
+ id: text().primaryKey(),
+ worktree: text().notNull(),
+ vcs: text(),
+ name: text(),
+ icon_url: text(),
+ icon_color: text(),
+ ...Timestamps,
+ time_initialized: integer(),
+ sandboxes: text({ mode: "json" }).notNull().$type(),
+ commands: text({ mode: "json" }).$type<{ start?: string }>(),
+})
diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts
index c79a62c6c9..8fa0f6c6f0 100644
--- a/packages/opencode/src/project/project.ts
+++ b/packages/opencode/src/project/project.ts
@@ -1,11 +1,11 @@
import z from "zod"
-import fs from "fs/promises"
import { Filesystem } from "../util/filesystem"
import path from "path"
-import { Storage } from "../storage/storage"
+import { Database, eq } from "../storage/db"
+import { ProjectTable } from "./project.sql"
+import { SessionTable } from "../session/session.sql"
import { Log } from "../util/log"
import { Flag } from "@/flag/flag"
-import { Session } from "../session"
import { work } from "../util/queue"
import { fn } from "@opencode-ai/util/fn"
import { BusEvent } from "@/bus/bus-event"
@@ -50,10 +50,33 @@ export namespace Project {
Updated: BusEvent.define("project.updated", Info),
}
+ type Row = typeof ProjectTable.$inferSelect
+
+ export function fromRow(row: Row): Info {
+ const icon =
+ row.icon_url || row.icon_color
+ ? { url: row.icon_url ?? undefined, color: row.icon_color ?? undefined }
+ : undefined
+ return {
+ id: row.id,
+ worktree: row.worktree,
+ vcs: row.vcs ? Info.shape.vcs.parse(row.vcs) : undefined,
+ name: row.name ?? undefined,
+ icon,
+ time: {
+ created: row.time_created,
+ updated: row.time_updated,
+ initialized: row.time_initialized ?? undefined,
+ },
+ sandboxes: row.sandboxes,
+ commands: row.commands ?? undefined,
+ }
+ }
+
export async function fromDirectory(directory: string) {
log.info("fromDirectory", { directory })
- const { id, sandbox, worktree, vcs } = await iife(async () => {
+ const data = await iife(async () => {
const matches = Filesystem.up({ targets: [".git"], start: directory })
const dotgit = await matches.next().then((x) => x.value)
await matches.return()
@@ -169,47 +192,73 @@ export namespace Project {
}
})
- let existing = await Storage.read(["project", id]).catch(() => undefined)
- if (!existing) {
- existing = {
- id,
- worktree,
- vcs: vcs as Info["vcs"],
+ const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, data.id)).get())
+ const existing = await iife(async () => {
+ if (row) return fromRow(row)
+ const fresh: Info = {
+ id: data.id,
+ worktree: data.worktree,
+ vcs: data.vcs as Info["vcs"],
sandboxes: [],
time: {
created: Date.now(),
updated: Date.now(),
},
}
- if (id !== "global") {
- await migrateFromGlobal(id, worktree)
+ if (data.id !== "global") {
+ await migrateFromGlobal(data.id, data.worktree)
}
- }
-
- // migrate old projects before sandboxes
- if (!existing.sandboxes) existing.sandboxes = []
+ return fresh
+ })
if (Flag.OPENCODE_EXPERIMENTAL_ICON_DISCOVERY) discover(existing)
const result: Info = {
...existing,
- worktree,
- vcs: vcs as Info["vcs"],
+ worktree: data.worktree,
+ vcs: data.vcs as Info["vcs"],
time: {
...existing.time,
updated: Date.now(),
},
}
- if (sandbox !== result.worktree && !result.sandboxes.includes(sandbox)) result.sandboxes.push(sandbox)
+ if (data.sandbox !== result.worktree && !result.sandboxes.includes(data.sandbox))
+ result.sandboxes.push(data.sandbox)
result.sandboxes = result.sandboxes.filter((x) => existsSync(x))
- await Storage.write(["project", id], result)
+ const insert = {
+ id: result.id,
+ worktree: result.worktree,
+ vcs: result.vcs ?? null,
+ name: result.name,
+ icon_url: result.icon?.url,
+ icon_color: result.icon?.color,
+ time_created: result.time.created,
+ time_updated: result.time.updated,
+ time_initialized: result.time.initialized,
+ sandboxes: result.sandboxes,
+ commands: result.commands,
+ }
+ const updateSet = {
+ worktree: result.worktree,
+ vcs: result.vcs ?? null,
+ name: result.name,
+ icon_url: result.icon?.url,
+ icon_color: result.icon?.color,
+ time_updated: result.time.updated,
+ time_initialized: result.time.initialized,
+ sandboxes: result.sandboxes,
+ commands: result.commands,
+ }
+ Database.use((db) =>
+ db.insert(ProjectTable).values(insert).onConflictDoUpdate({ target: ProjectTable.id, set: updateSet }).run(),
+ )
GlobalBus.emit("event", {
payload: {
type: Event.Updated.type,
properties: result,
},
})
- return { project: result, sandbox }
+ return { project: result, sandbox: data.sandbox }
}
export async function discover(input: Info) {
@@ -242,43 +291,54 @@ export namespace Project {
return
}
- async function migrateFromGlobal(newProjectID: string, worktree: string) {
- const globalProject = await Storage.read(["project", "global"]).catch(() => undefined)
- if (!globalProject) return
+ async function migrateFromGlobal(id: string, worktree: string) {
+ const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, "global")).get())
+ if (!row) return
- const globalSessions = await Storage.list(["session", "global"]).catch(() => [])
- if (globalSessions.length === 0) return
+ const sessions = Database.use((db) =>
+ db.select().from(SessionTable).where(eq(SessionTable.project_id, "global")).all(),
+ )
+ if (sessions.length === 0) return
- log.info("migrating sessions from global", { newProjectID, worktree, count: globalSessions.length })
+ log.info("migrating sessions from global", { newProjectID: id, worktree, count: sessions.length })
- await work(10, globalSessions, async (key) => {
- const sessionID = key[key.length - 1]
- const session = await Storage.read(key).catch(() => undefined)
- if (!session) return
- if (session.directory && session.directory !== worktree) return
+ await work(10, sessions, async (row) => {
+ // Skip sessions that belong to a different directory
+ if (row.directory && row.directory !== worktree) return
- session.projectID = newProjectID
- log.info("migrating session", { sessionID, from: "global", to: newProjectID })
- await Storage.write(["session", newProjectID, sessionID], session)
- await Storage.remove(key)
+ log.info("migrating session", { sessionID: row.id, from: "global", to: id })
+ Database.use((db) => db.update(SessionTable).set({ project_id: id }).where(eq(SessionTable.id, row.id)).run())
}).catch((error) => {
- log.error("failed to migrate sessions from global to project", { error, projectId: newProjectID })
+ log.error("failed to migrate sessions from global to project", { error, projectId: id })
})
}
- export async function setInitialized(projectID: string) {
- await Storage.update(["project", projectID], (draft) => {
- draft.time.initialized = Date.now()
- })
+ export function setInitialized(id: string) {
+ Database.use((db) =>
+ db
+ .update(ProjectTable)
+ .set({
+ time_initialized: Date.now(),
+ })
+ .where(eq(ProjectTable.id, id))
+ .run(),
+ )
}
- export async function list() {
- const keys = await Storage.list(["project"])
- const projects = await Promise.all(keys.map((x) => Storage.read(x)))
- return projects.map((project) => ({
- ...project,
- sandboxes: project.sandboxes?.filter((x) => existsSync(x)),
- }))
+ export function list() {
+ return Database.use((db) =>
+ db
+ .select()
+ .from(ProjectTable)
+ .all()
+ .map((row) => fromRow(row)),
+ )
+ }
+
+ export function get(id: string): Info | undefined {
+ const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get())
+ if (!row) return undefined
+ return fromRow(row)
}
export const update = fn(
@@ -289,77 +349,90 @@ export namespace Project {
commands: Info.shape.commands.optional(),
}),
async (input) => {
- const result = await Storage.update(["project", input.projectID], (draft) => {
- if (input.name !== undefined) draft.name = input.name
- if (input.icon !== undefined) {
- draft.icon = {
- ...draft.icon,
- }
- if (input.icon.url !== undefined) draft.icon.url = input.icon.url
- if (input.icon.override !== undefined) draft.icon.override = input.icon.override || undefined
- if (input.icon.color !== undefined) draft.icon.color = input.icon.color
- }
-
- if (input.commands?.start !== undefined) {
- const start = input.commands.start || undefined
- draft.commands = {
- ...(draft.commands ?? {}),
- }
- draft.commands.start = start
- if (!draft.commands.start) draft.commands = undefined
- }
-
- draft.time.updated = Date.now()
- })
+ const result = Database.use((db) =>
+ db
+ .update(ProjectTable)
+ .set({
+ name: input.name,
+ icon_url: input.icon?.url,
+ icon_color: input.icon?.color,
+ commands: input.commands,
+ time_updated: Date.now(),
+ })
+ .where(eq(ProjectTable.id, input.projectID))
+ .returning()
+ .get(),
+ )
+ if (!result) throw new Error(`Project not found: ${input.projectID}`)
+ const data = fromRow(result)
GlobalBus.emit("event", {
payload: {
type: Event.Updated.type,
- properties: result,
+ properties: data,
},
})
- return result
+ return data
},
)
- export async function sandboxes(projectID: string) {
- const project = await Storage.read(["project", projectID]).catch(() => undefined)
- if (!project?.sandboxes) return []
+ export async function sandboxes(id: string) {
+ const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get())
+ if (!row) return []
+ const data = fromRow(row)
const valid: string[] = []
- for (const dir of project.sandboxes) {
- const stat = await fs.stat(dir).catch(() => undefined)
+ for (const dir of data.sandboxes) {
+ const stat = await Bun.file(dir)
+ .stat()
+ .catch(() => undefined)
if (stat?.isDirectory()) valid.push(dir)
}
return valid
}
- export async function addSandbox(projectID: string, directory: string) {
- const result = await Storage.update(["project", projectID], (draft) => {
- const sandboxes = draft.sandboxes ?? []
- if (!sandboxes.includes(directory)) sandboxes.push(directory)
- draft.sandboxes = sandboxes
- draft.time.updated = Date.now()
- })
+ export async function addSandbox(id: string, directory: string) {
+ const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get())
+ if (!row) throw new Error(`Project not found: ${id}`)
+ const sandboxes = [...row.sandboxes]
+ if (!sandboxes.includes(directory)) sandboxes.push(directory)
+ const result = Database.use((db) =>
+ db
+ .update(ProjectTable)
+ .set({ sandboxes, time_updated: Date.now() })
+ .where(eq(ProjectTable.id, id))
+ .returning()
+ .get(),
+ )
+ if (!result) throw new Error(`Project not found: ${id}`)
+ const data = fromRow(result)
GlobalBus.emit("event", {
payload: {
type: Event.Updated.type,
- properties: result,
+ properties: data,
},
})
- return result
+ return data
}
- export async function removeSandbox(projectID: string, directory: string) {
- const result = await Storage.update(["project", projectID], (draft) => {
- const sandboxes = draft.sandboxes ?? []
- draft.sandboxes = sandboxes.filter((sandbox) => sandbox !== directory)
- draft.time.updated = Date.now()
- })
+ export async function removeSandbox(id: string, directory: string) {
+ const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get())
+ if (!row) throw new Error(`Project not found: ${id}`)
+ const sandboxes = row.sandboxes.filter((s) => s !== directory)
+ const result = Database.use((db) =>
+ db
+ .update(ProjectTable)
+ .set({ sandboxes, time_updated: Date.now() })
+ .where(eq(ProjectTable.id, id))
+ .returning()
+ .get(),
+ )
+ if (!result) throw new Error(`Project not found: ${id}`)
+ const data = fromRow(result)
GlobalBus.emit("event", {
payload: {
type: Event.Updated.type,
- properties: result,
+ properties: data,
},
})
- return result
+ return data
}
}
diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts
index 876a26fce7..8091f731f0 100644
--- a/packages/opencode/src/provider/transform.ts
+++ b/packages/opencode/src/provider/transform.ts
@@ -458,6 +458,22 @@ export namespace ProviderTransform {
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/anthropic
case "@ai-sdk/google-vertex/anthropic":
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex#anthropic-provider
+
+ if (model.api.id.includes("opus-4-6") || model.api.id.includes("opus-4.6")) {
+ const efforts = ["low", "medium", "high", "max"]
+ return Object.fromEntries(
+ efforts.map((effort) => [
+ effort,
+ {
+ thinking: {
+ type: "adaptive",
+ },
+ effort,
+ },
+ ]),
+ )
+ }
+
return {
high: {
thinking: {
@@ -475,6 +491,20 @@ export namespace ProviderTransform {
case "@ai-sdk/amazon-bedrock":
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/amazon-bedrock
+ if (model.api.id.includes("opus-4-6") || model.api.id.includes("opus-4.6")) {
+ const efforts = ["low", "medium", "high", "max"]
+ return Object.fromEntries(
+ efforts.map((effort) => [
+ effort,
+ {
+ reasoningConfig: {
+ type: "adaptive",
+ maxReasoningEffort: effort,
+ },
+ },
+ ]),
+ )
+ }
// For Anthropic models on Bedrock, use reasoningConfig with budgetTokens
if (model.api.id.includes("anthropic")) {
return {
diff --git a/packages/opencode/src/server/error.ts b/packages/opencode/src/server/error.ts
index 26e2dfcb12..cc5fa96187 100644
--- a/packages/opencode/src/server/error.ts
+++ b/packages/opencode/src/server/error.ts
@@ -1,6 +1,6 @@
import { resolver } from "hono-openapi"
import z from "zod"
-import { Storage } from "../storage/storage"
+import { NotFoundError } from "../storage/db"
export const ERRORS = {
400: {
@@ -25,7 +25,7 @@ export const ERRORS = {
description: "Not found",
content: {
"application/json": {
- schema: resolver(Storage.NotFoundError.Schema),
+ schema: resolver(NotFoundError.Schema),
},
},
},
diff --git a/packages/opencode/src/server/routes/pty.ts b/packages/opencode/src/server/routes/pty.ts
index 10bf51cb99..21156190dc 100644
--- a/packages/opencode/src/server/routes/pty.ts
+++ b/packages/opencode/src/server/routes/pty.ts
@@ -3,7 +3,7 @@ import { describeRoute, validator, resolver } from "hono-openapi"
import { upgradeWebSocket } from "hono/bun"
import z from "zod"
import { Pty } from "@/pty"
-import { Storage } from "../../storage/storage"
+import { NotFoundError } from "../../storage/db"
import { errors } from "../error"
import { lazy } from "../../util/lazy"
@@ -76,7 +76,7 @@ export const PtyRoutes = lazy(() =>
async (c) => {
const info = Pty.get(c.req.valid("param").ptyID)
if (!info) {
- throw new Storage.NotFoundError({ message: "Session not found" })
+ throw new NotFoundError({ message: "Session not found" })
}
return c.json(info)
},
diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts
index 82e6f3121b..2cf5473f22 100644
--- a/packages/opencode/src/server/routes/session.ts
+++ b/packages/opencode/src/server/routes/session.ts
@@ -276,18 +276,15 @@ export const SessionRoutes = lazy(() =>
const sessionID = c.req.valid("param").sessionID
const updates = c.req.valid("json")
- const updatedSession = await Session.update(
- sessionID,
- (session) => {
- if (updates.title !== undefined) {
- session.title = updates.title
- }
- if (updates.time?.archived !== undefined) session.time.archived = updates.time.archived
- },
- { touch: false },
- )
+ let session = await Session.get(sessionID)
+ if (updates.title !== undefined) {
+ session = await Session.setTitle({ sessionID, title: updates.title })
+ }
+ if (updates.time?.archived !== undefined) {
+ session = await Session.setArchived({ sessionID, time: updates.time.archived })
+ }
- return c.json(updatedSession)
+ return c.json(session)
},
)
.post(
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index 9fb5206551..c1896a8d13 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -31,7 +31,7 @@ import { ExperimentalRoutes } from "./routes/experimental"
import { ProviderRoutes } from "./routes/provider"
import { lazy } from "../util/lazy"
import { InstanceBootstrap } from "../project/bootstrap"
-import { Storage } from "../storage/storage"
+import { NotFoundError } from "../storage/db"
import type { ContentfulStatusCode } from "hono/utils/http-status"
import { websocket } from "hono/bun"
import { HTTPException } from "hono/http-exception"
@@ -65,7 +65,7 @@ export namespace Server {
})
if (err instanceof NamedError) {
let status: ContentfulStatusCode
- if (err instanceof Storage.NotFoundError) status = 404
+ if (err instanceof NotFoundError) status = 404
else if (err instanceof Provider.ModelNotFoundError) status = 400
else if (err.name.startsWith("Worktree")) status = 400
else status = 500
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index b0ffaaf70d..38007a0a7f 100644
--- a/packages/opencode/src/session/index.ts
+++ b/packages/opencode/src/session/index.ts
@@ -10,7 +10,9 @@ import { Flag } from "../flag/flag"
import { Identifier } from "../id/id"
import { Installation } from "../installation"
-import { Storage } from "../storage/storage"
+import { Database, NotFoundError, eq, and, or, like } from "../storage/db"
+import { SessionTable, MessageTable, PartTable } from "./session.sql"
+import { Storage } from "@/storage/storage"
import { Log } from "../util/log"
import { MessageV2 } from "./message-v2"
import { Instance } from "../project/instance"
@@ -41,6 +43,64 @@ export namespace Session {
).test(title)
}
+ type SessionRow = typeof SessionTable.$inferSelect
+
+ export function fromRow(row: SessionRow): Info {
+ const summary =
+ row.summary_additions !== null || row.summary_deletions !== null || row.summary_files !== null
+ ? {
+ additions: row.summary_additions ?? 0,
+ deletions: row.summary_deletions ?? 0,
+ files: row.summary_files ?? 0,
+ diffs: row.summary_diffs ?? undefined,
+ }
+ : undefined
+ const share = row.share_url ? { url: row.share_url } : undefined
+ const revert = row.revert ?? undefined
+ return {
+ id: row.id,
+ slug: row.slug,
+ projectID: row.project_id,
+ directory: row.directory,
+ parentID: row.parent_id ?? undefined,
+ title: row.title,
+ version: row.version,
+ summary,
+ share,
+ revert,
+ permission: row.permission ?? undefined,
+ time: {
+ created: row.time_created,
+ updated: row.time_updated,
+ compacting: row.time_compacting ?? undefined,
+ archived: row.time_archived ?? undefined,
+ },
+ }
+ }
+
+ export function toRow(info: Info) {
+ return {
+ id: info.id,
+ project_id: info.projectID,
+ parent_id: info.parentID,
+ slug: info.slug,
+ directory: info.directory,
+ title: info.title,
+ version: info.version,
+ share_url: info.share?.url,
+ summary_additions: info.summary?.additions,
+ summary_deletions: info.summary?.deletions,
+ summary_files: info.summary?.files,
+ summary_diffs: info.summary?.diffs,
+ revert: info.revert ?? null,
+ permission: info.permission,
+ time_created: info.time.created,
+ time_updated: info.time.updated,
+ time_compacting: info.time.compacting,
+ time_archived: info.time.archived,
+ }
+ }
+
function getForkedTitle(title: string): string {
const match = title.match(/^(.+) \(fork #(\d+)\)$/)
if (match) {
@@ -94,16 +154,6 @@ export namespace Session {
})
export type Info = z.output
- export const ShareInfo = z
- .object({
- secret: z.string(),
- url: z.string(),
- })
- .meta({
- ref: "SessionShare",
- })
- export type ShareInfo = z.output
-
export const Event = {
Created: BusEvent.define(
"session.created",
@@ -200,8 +250,17 @@ export namespace Session {
)
export const touch = fn(Identifier.schema("session"), async (sessionID) => {
- await update(sessionID, (draft) => {
- draft.time.updated = Date.now()
+ const now = Date.now()
+ Database.use((db) => {
+ const row = db
+ .update(SessionTable)
+ .set({ time_updated: now })
+ .where(eq(SessionTable.id, sessionID))
+ .returning()
+ .get()
+ if (!row) throw new NotFoundError({ message: `Session not found: ${sessionID}` })
+ const info = fromRow(row)
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
})
})
@@ -227,21 +286,19 @@ export namespace Session {
},
}
log.info("created", result)
- await Storage.write(["session", Instance.project.id, result.id], result)
- Bus.publish(Event.Created, {
- info: result,
+ Database.use((db) => {
+ db.insert(SessionTable).values(toRow(result)).run()
+ Database.effect(() =>
+ Bus.publish(Event.Created, {
+ info: result,
+ }),
+ )
})
const cfg = await Config.get()
if (!result.parentID && (Flag.OPENCODE_AUTO_SHARE || cfg.share === "auto"))
- share(result.id)
- .then((share) => {
- update(result.id, (draft) => {
- draft.share = share
- })
- })
- .catch(() => {
- // Silently ignore sharing errors during session creation
- })
+ share(result.id).catch(() => {
+ // Silently ignore sharing errors during session creation
+ })
Bus.publish(Event.Updated, {
info: result,
})
@@ -256,12 +313,9 @@ export namespace Session {
}
export const get = fn(Identifier.schema("session"), async (id) => {
- const read = await Storage.read(["session", Instance.project.id, id])
- return read as Info
- })
-
- export const getShare = fn(Identifier.schema("session"), async (id) => {
- return Storage.read(["share", id])
+ const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get())
+ if (!row) throw new NotFoundError({ message: `Session not found: ${id}` })
+ return fromRow(row)
})
export const share = fn(Identifier.schema("session"), async (id) => {
@@ -271,15 +325,12 @@ export namespace Session {
}
const { ShareNext } = await import("@/share/share-next")
const share = await ShareNext.create(id)
- await update(
- id,
- (draft) => {
- draft.share = {
- url: share.url,
- }
- },
- { touch: false },
- )
+ Database.use((db) => {
+ const row = db.update(SessionTable).set({ share_url: share.url }).where(eq(SessionTable.id, id)).returning().get()
+ if (!row) throw new NotFoundError({ message: `Session not found: ${id}` })
+ const info = fromRow(row)
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
+ })
return share
})
@@ -287,32 +338,155 @@ export namespace Session {
// Use ShareNext to remove the share (same as share function uses ShareNext to create)
const { ShareNext } = await import("@/share/share-next")
await ShareNext.remove(id)
- await update(
- id,
- (draft) => {
- draft.share = undefined
- },
- { touch: false },
- )
+ Database.use((db) => {
+ const row = db.update(SessionTable).set({ share_url: null }).where(eq(SessionTable.id, id)).returning().get()
+ if (!row) throw new NotFoundError({ message: `Session not found: ${id}` })
+ const info = fromRow(row)
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
+ })
})
- export async function update(id: string, editor: (session: Info) => void, options?: { touch?: boolean }) {
- const project = Instance.project
- const result = await Storage.update(["session", project.id, id], (draft) => {
- editor(draft)
- if (options?.touch !== false) {
- draft.time.updated = Date.now()
- }
+ export const setTitle = fn(
+ z.object({
+ sessionID: Identifier.schema("session"),
+ title: z.string(),
+ }),
+ async (input) => {
+ return Database.use((db) => {
+ const row = db
+ .update(SessionTable)
+ .set({ title: input.title })
+ .where(eq(SessionTable.id, input.sessionID))
+ .returning()
+ .get()
+ if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` })
+ const info = fromRow(row)
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
+ return info
+ })
+ },
+ )
+
+ export const setArchived = fn(
+ z.object({
+ sessionID: Identifier.schema("session"),
+ time: z.number().optional(),
+ }),
+ async (input) => {
+ return Database.use((db) => {
+ const row = db
+ .update(SessionTable)
+ .set({ time_archived: input.time })
+ .where(eq(SessionTable.id, input.sessionID))
+ .returning()
+ .get()
+ if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` })
+ const info = fromRow(row)
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
+ return info
+ })
+ },
+ )
+
+ export const setPermission = fn(
+ z.object({
+ sessionID: Identifier.schema("session"),
+ permission: PermissionNext.Ruleset,
+ }),
+ async (input) => {
+ return Database.use((db) => {
+ const row = db
+ .update(SessionTable)
+ .set({ permission: input.permission, time_updated: Date.now() })
+ .where(eq(SessionTable.id, input.sessionID))
+ .returning()
+ .get()
+ if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` })
+ const info = fromRow(row)
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
+ return info
+ })
+ },
+ )
+
+ export const setRevert = fn(
+ z.object({
+ sessionID: Identifier.schema("session"),
+ revert: Info.shape.revert,
+ summary: Info.shape.summary,
+ }),
+ async (input) => {
+ return Database.use((db) => {
+ const row = db
+ .update(SessionTable)
+ .set({
+ revert: input.revert ?? null,
+ summary_additions: input.summary?.additions,
+ summary_deletions: input.summary?.deletions,
+ summary_files: input.summary?.files,
+ time_updated: Date.now(),
+ })
+ .where(eq(SessionTable.id, input.sessionID))
+ .returning()
+ .get()
+ if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` })
+ const info = fromRow(row)
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
+ return info
+ })
+ },
+ )
+
+ export const clearRevert = fn(Identifier.schema("session"), async (sessionID) => {
+ return Database.use((db) => {
+ const row = db
+ .update(SessionTable)
+ .set({
+ revert: null,
+ time_updated: Date.now(),
+ })
+ .where(eq(SessionTable.id, sessionID))
+ .returning()
+ .get()
+ if (!row) throw new NotFoundError({ message: `Session not found: ${sessionID}` })
+ const info = fromRow(row)
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
+ return info
})
- Bus.publish(Event.Updated, {
- info: result,
- })
- return result
- }
+ })
+
+ export const setSummary = fn(
+ z.object({
+ sessionID: Identifier.schema("session"),
+ summary: Info.shape.summary,
+ }),
+ async (input) => {
+ return Database.use((db) => {
+ const row = db
+ .update(SessionTable)
+ .set({
+ summary_additions: input.summary?.additions,
+ summary_deletions: input.summary?.deletions,
+ summary_files: input.summary?.files,
+ time_updated: Date.now(),
+ })
+ .where(eq(SessionTable.id, input.sessionID))
+ .returning()
+ .get()
+ if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` })
+ const info = fromRow(row)
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
+ return info
+ })
+ },
+ )
export const diff = fn(Identifier.schema("session"), async (sessionID) => {
- const diffs = await Storage.read(["session_diff", sessionID])
- return diffs ?? []
+ try {
+ return await Storage.read(["session_diff", sessionID])
+ } catch {
+ return []
+ }
})
export const messages = fn(
@@ -331,25 +505,37 @@ export namespace Session {
},
)
- export async function* list() {
+ export function* list() {
const project = Instance.project
- for (const item of await Storage.list(["session", project.id])) {
- const session = await Storage.read(item).catch(() => undefined)
- if (!session) continue
- yield session
+ // const rel = path.relative(Instance.worktree, Instance.directory)
+ // const suffix = path.sep + rel
+ const rows = Database.use((db) =>
+ db
+ .select()
+ .from(SessionTable)
+ .where(
+ and(
+ eq(SessionTable.project_id, project.id),
+ // or(eq(SessionTable.directory, Instance.directory), like(SessionTable.directory, `%${suffix}`)),
+ ),
+ )
+ .all(),
+ )
+ for (const row of rows) {
+ yield fromRow(row)
}
}
export const children = fn(Identifier.schema("session"), async (parentID) => {
const project = Instance.project
- const result = [] as Session.Info[]
- for (const item of await Storage.list(["session", project.id])) {
- const session = await Storage.read(item).catch(() => undefined)
- if (!session) continue
- if (session.parentID !== parentID) continue
- result.push(session)
- }
- return result
+ const rows = Database.use((db) =>
+ db
+ .select()
+ .from(SessionTable)
+ .where(and(eq(SessionTable.project_id, project.id), eq(SessionTable.parent_id, parentID)))
+ .all(),
+ )
+ return rows.map(fromRow)
})
export const remove = fn(Identifier.schema("session"), async (sessionID) => {
@@ -360,15 +546,14 @@ export namespace Session {
await remove(child.id)
}
await unshare(sessionID).catch(() => {})
- for (const msg of await Storage.list(["message", sessionID])) {
- for (const part of await Storage.list(["part", msg.at(-1)!])) {
- await Storage.remove(part)
- }
- await Storage.remove(msg)
- }
- await Storage.remove(["session", project.id, sessionID])
- Bus.publish(Event.Deleted, {
- info: session,
+ // CASCADE delete handles messages and parts automatically
+ Database.use((db) => {
+ db.delete(SessionTable).where(eq(SessionTable.id, sessionID)).run()
+ Database.effect(() =>
+ Bus.publish(Event.Deleted, {
+ info: session,
+ }),
+ )
})
} catch (e) {
log.error(e)
@@ -376,9 +561,23 @@ export namespace Session {
})
export const updateMessage = fn(MessageV2.Info, async (msg) => {
- await Storage.write(["message", msg.sessionID, msg.id], msg)
- Bus.publish(MessageV2.Event.Updated, {
- info: msg,
+ const time_created = msg.role === "user" ? msg.time.created : msg.time.created
+ const { id, sessionID, ...data } = msg
+ Database.use((db) => {
+ db.insert(MessageTable)
+ .values({
+ id,
+ session_id: sessionID,
+ time_created,
+ data,
+ })
+ .onConflictDoUpdate({ target: MessageTable.id, set: { data } })
+ .run()
+ Database.effect(() =>
+ Bus.publish(MessageV2.Event.Updated, {
+ info: msg,
+ }),
+ )
})
return msg
})
@@ -389,10 +588,15 @@ export namespace Session {
messageID: Identifier.schema("message"),
}),
async (input) => {
- await Storage.remove(["message", input.sessionID, input.messageID])
- Bus.publish(MessageV2.Event.Removed, {
- sessionID: input.sessionID,
- messageID: input.messageID,
+ // CASCADE delete handles parts automatically
+ Database.use((db) => {
+ db.delete(MessageTable).where(eq(MessageTable.id, input.messageID)).run()
+ Database.effect(() =>
+ Bus.publish(MessageV2.Event.Removed, {
+ sessionID: input.sessionID,
+ messageID: input.messageID,
+ }),
+ )
})
return input.messageID
},
@@ -405,39 +609,58 @@ export namespace Session {
partID: Identifier.schema("part"),
}),
async (input) => {
- await Storage.remove(["part", input.messageID, input.partID])
- Bus.publish(MessageV2.Event.PartRemoved, {
- sessionID: input.sessionID,
- messageID: input.messageID,
- partID: input.partID,
+ Database.use((db) => {
+ db.delete(PartTable).where(eq(PartTable.id, input.partID)).run()
+ Database.effect(() =>
+ Bus.publish(MessageV2.Event.PartRemoved, {
+ sessionID: input.sessionID,
+ messageID: input.messageID,
+ partID: input.partID,
+ }),
+ )
})
return input.partID
},
)
- const UpdatePartInput = z.union([
- MessageV2.Part,
- z.object({
- part: MessageV2.TextPart,
- delta: z.string(),
- }),
- z.object({
- part: MessageV2.ReasoningPart,
- delta: z.string(),
- }),
- ])
+ const UpdatePartInput = MessageV2.Part
- export const updatePart = fn(UpdatePartInput, async (input) => {
- const part = "delta" in input ? input.part : input
- const delta = "delta" in input ? input.delta : undefined
- await Storage.write(["part", part.messageID, part.id], part)
- Bus.publish(MessageV2.Event.PartUpdated, {
- part,
- delta,
+ export const updatePart = fn(UpdatePartInput, async (part) => {
+ const { id, messageID, sessionID, ...data } = part
+ const time = Date.now()
+ Database.use((db) => {
+ db.insert(PartTable)
+ .values({
+ id,
+ message_id: messageID,
+ session_id: sessionID,
+ time_created: time,
+ data,
+ })
+ .onConflictDoUpdate({ target: PartTable.id, set: { data } })
+ .run()
+ Database.effect(() =>
+ Bus.publish(MessageV2.Event.PartUpdated, {
+ part,
+ }),
+ )
})
return part
})
+ export const updatePartDelta = fn(
+ z.object({
+ sessionID: z.string(),
+ messageID: z.string(),
+ partID: z.string(),
+ field: z.string(),
+ delta: z.string(),
+ }),
+ async (input) => {
+ Bus.publish(MessageV2.Event.PartDelta, input)
+ },
+ )
+
export const getUsage = fn(
z.object({
model: z.custom(),
diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts
index 70763548c6..178751a222 100644
--- a/packages/opencode/src/session/message-v2.ts
+++ b/packages/opencode/src/session/message-v2.ts
@@ -6,6 +6,10 @@ import { Identifier } from "../id/id"
import { LSP } from "../lsp"
import { Snapshot } from "@/snapshot"
import { fn } from "@/util/fn"
+import { Database, eq, desc, inArray } from "@/storage/db"
+import { MessageTable, PartTable } from "./session.sql"
+import { ProviderTransform } from "@/provider/transform"
+import { STATUS_CODES } from "http"
import { Storage } from "@/storage/storage"
import { ProviderError } from "@/provider/error"
import { iife } from "@/util/iife"
@@ -456,7 +460,16 @@ export namespace MessageV2 {
"message.part.updated",
z.object({
part: Part,
- delta: z.string().optional(),
+ }),
+ ),
+ PartDelta: BusEvent.define(
+ "message.part.delta",
+ z.object({
+ sessionID: z.string(),
+ messageID: z.string(),
+ partID: z.string(),
+ field: z.string(),
+ delta: z.string(),
}),
),
PartRemoved: BusEvent.define(
@@ -701,23 +714,65 @@ export namespace MessageV2 {
}
export const stream = fn(Identifier.schema("session"), async function* (sessionID) {
- const list = await Array.fromAsync(await Storage.list(["message", sessionID]))
- for (let i = list.length - 1; i >= 0; i--) {
- yield await get({
- sessionID,
- messageID: list[i][2],
- })
+ const size = 50
+ let offset = 0
+ while (true) {
+ const rows = Database.use((db) =>
+ db
+ .select()
+ .from(MessageTable)
+ .where(eq(MessageTable.session_id, sessionID))
+ .orderBy(desc(MessageTable.time_created))
+ .limit(size)
+ .offset(offset)
+ .all(),
+ )
+ if (rows.length === 0) break
+
+ const ids = rows.map((row) => row.id)
+ const partsByMessage = new Map()
+ if (ids.length > 0) {
+ const partRows = Database.use((db) =>
+ db
+ .select()
+ .from(PartTable)
+ .where(inArray(PartTable.message_id, ids))
+ .orderBy(PartTable.message_id, PartTable.id)
+ .all(),
+ )
+ for (const row of partRows) {
+ const part = {
+ ...row.data,
+ id: row.id,
+ sessionID: row.session_id,
+ messageID: row.message_id,
+ } as MessageV2.Part
+ const list = partsByMessage.get(row.message_id)
+ if (list) list.push(part)
+ else partsByMessage.set(row.message_id, [part])
+ }
+ }
+
+ for (const row of rows) {
+ const info = { ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info
+ yield {
+ info,
+ parts: partsByMessage.get(row.id) ?? [],
+ }
+ }
+
+ offset += rows.length
+ if (rows.length < size) break
}
})
- export const parts = fn(Identifier.schema("message"), async (messageID) => {
- const result = [] as MessageV2.Part[]
- for (const item of await Storage.list(["part", messageID])) {
- const read = await Storage.read(item)
- result.push(read)
- }
- result.sort((a, b) => (a.id > b.id ? 1 : -1))
- return result
+ export const parts = fn(Identifier.schema("message"), async (message_id) => {
+ const rows = Database.use((db) =>
+ db.select().from(PartTable).where(eq(PartTable.message_id, message_id)).orderBy(PartTable.id).all(),
+ )
+ return rows.map(
+ (row) => ({ ...row.data, id: row.id, sessionID: row.session_id, messageID: row.message_id }) as MessageV2.Part,
+ )
})
export const get = fn(
@@ -726,8 +781,11 @@ export namespace MessageV2 {
messageID: Identifier.schema("message"),
}),
async (input): Promise => {
+ const row = Database.use((db) => db.select().from(MessageTable).where(eq(MessageTable.id, input.messageID)).get())
+ if (!row) throw new Error(`Message not found: ${input.messageID}`)
+ const info = { ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info
return {
- info: await Storage.read(["message", input.sessionID, input.messageID]),
+ info,
parts: await parts(input.messageID),
}
},
diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts
index 8e7472e2f1..e7532d2007 100644
--- a/packages/opencode/src/session/processor.ts
+++ b/packages/opencode/src/session/processor.ts
@@ -63,17 +63,19 @@ export namespace SessionProcessor {
if (value.id in reasoningMap) {
continue
}
- reasoningMap[value.id] = {
+ const reasoningPart = {
id: Identifier.ascending("part"),
messageID: input.assistantMessage.id,
sessionID: input.assistantMessage.sessionID,
- type: "reasoning",
+ type: "reasoning" as const,
text: "",
time: {
start: Date.now(),
},
metadata: value.providerMetadata,
}
+ reasoningMap[value.id] = reasoningPart
+ await Session.updatePart(reasoningPart)
break
case "reasoning-delta":
@@ -81,7 +83,13 @@ export namespace SessionProcessor {
const part = reasoningMap[value.id]
part.text += value.text
if (value.providerMetadata) part.metadata = value.providerMetadata
- if (part.text) await Session.updatePart({ part, delta: value.text })
+ await Session.updatePartDelta({
+ sessionID: part.sessionID,
+ messageID: part.messageID,
+ partID: part.id,
+ field: "text",
+ delta: value.text,
+ })
}
break
@@ -288,17 +296,20 @@ export namespace SessionProcessor {
},
metadata: value.providerMetadata,
}
+ await Session.updatePart(currentText)
break
case "text-delta":
if (currentText) {
currentText.text += value.text
if (value.providerMetadata) currentText.metadata = value.providerMetadata
- if (currentText.text)
- await Session.updatePart({
- part: currentText,
- delta: value.text,
- })
+ await Session.updatePartDelta({
+ sessionID: currentText.sessionID,
+ messageID: currentText.messageID,
+ partID: currentText.id,
+ field: "text",
+ delta: value.text,
+ })
}
break
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 99d44cd850..f705f209aa 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -26,7 +26,6 @@ import { ToolRegistry } from "../tool/registry"
import { MCP } from "../mcp"
import { LSP } from "../lsp"
import { ReadTool } from "../tool/read"
-import { ListTool } from "../tool/ls"
import { FileTime } from "../file/time"
import { Flag } from "../flag/flag"
import { ulid } from "ulid"
@@ -175,9 +174,7 @@ export namespace SessionPrompt {
}
if (permissions.length > 0) {
session.permission = permissions
- await Session.update(session.id, (draft) => {
- draft.permission = permissions
- })
+ await Session.setPermission({ sessionID: session.id, permission: permissions })
}
if (input.noReply === true) {
@@ -1198,7 +1195,7 @@ export namespace SessionPrompt {
}
if (part.mime === "application/x-directory") {
- const args = { path: filepath }
+ const args = { filePath: filepath }
const listCtx: Tool.Context = {
sessionID: input.sessionID,
abort: new AbortController().signal,
@@ -1209,7 +1206,7 @@ export namespace SessionPrompt {
metadata: async () => {},
ask: async () => {},
}
- const result = await ListTool.init().then((t) => t.execute(args, listCtx))
+ const result = await ReadTool.init().then((t) => t.execute(args, listCtx))
return [
{
id: Identifier.ascending("part"),
@@ -1217,7 +1214,7 @@ export namespace SessionPrompt {
sessionID: input.sessionID,
type: "text",
synthetic: true,
- text: `Called the list tool with the following input: ${JSON.stringify(args)}`,
+ text: `Called the Read tool with the following input: ${JSON.stringify(args)}`,
},
{
id: Identifier.ascending("part"),
@@ -1947,21 +1944,16 @@ NOTE: At any point in time through this workflow you should feel free to ask the
],
})
const text = await result.text.catch((err) => log.error("failed to generate title", { error: err }))
- if (text)
- return Session.update(
- input.session.id,
- (draft) => {
- const cleaned = text
- .replace(/[\s\S]*?<\/think>\s*/g, "")
- .split("\n")
- .map((line) => line.trim())
- .find((line) => line.length > 0)
- if (!cleaned) return
+ if (text) {
+ const cleaned = text
+ .replace(/[\s\S]*?<\/think>\s*/g, "")
+ .split("\n")
+ .map((line) => line.trim())
+ .find((line) => line.length > 0)
+ if (!cleaned) return
- const title = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned
- draft.title = title
- },
- { touch: false },
- )
+ const title = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned
+ return Session.setTitle({ sessionID: input.session.id, title })
+ }
}
}
diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts
index 7afe44e2ce..ef9c7e2aac 100644
--- a/packages/opencode/src/session/revert.ts
+++ b/packages/opencode/src/session/revert.ts
@@ -4,8 +4,9 @@ import { Snapshot } from "../snapshot"
import { MessageV2 } from "./message-v2"
import { Session } from "."
import { Log } from "../util/log"
-import { splitWhen } from "remeda"
-import { Storage } from "../storage/storage"
+import { Database, eq } from "../storage/db"
+import { MessageTable, PartTable } from "./session.sql"
+import { Storage } from "@/storage/storage"
import { Bus } from "../bus"
import { SessionPrompt } from "./prompt"
import { SessionSummary } from "./summary"
@@ -65,13 +66,14 @@ export namespace SessionRevert {
sessionID: input.sessionID,
diff: diffs,
})
- return Session.update(input.sessionID, (draft) => {
- draft.revert = revert
- draft.summary = {
+ return Session.setRevert({
+ sessionID: input.sessionID,
+ revert,
+ summary: {
additions: diffs.reduce((sum, x) => sum + x.additions, 0),
deletions: diffs.reduce((sum, x) => sum + x.deletions, 0),
files: diffs.length,
- }
+ },
})
}
return session
@@ -83,39 +85,54 @@ export namespace SessionRevert {
const session = await Session.get(input.sessionID)
if (!session.revert) return session
if (session.revert.snapshot) await Snapshot.restore(session.revert.snapshot)
- const next = await Session.update(input.sessionID, (draft) => {
- draft.revert = undefined
- })
- return next
+ return Session.clearRevert(input.sessionID)
}
export async function cleanup(session: Session.Info) {
if (!session.revert) return
const sessionID = session.id
- let msgs = await Session.messages({ sessionID })
+ const msgs = await Session.messages({ sessionID })
const messageID = session.revert.messageID
- const [preserve, remove] = splitWhen(msgs, (x) => x.info.id === messageID)
- msgs = preserve
+ const preserve = [] as MessageV2.WithParts[]
+ const remove = [] as MessageV2.WithParts[]
+ let target: MessageV2.WithParts | undefined
+ for (const msg of msgs) {
+ if (msg.info.id < messageID) {
+ preserve.push(msg)
+ continue
+ }
+ if (msg.info.id > messageID) {
+ remove.push(msg)
+ continue
+ }
+ if (session.revert.partID) {
+ preserve.push(msg)
+ target = msg
+ continue
+ }
+ remove.push(msg)
+ }
for (const msg of remove) {
- await Storage.remove(["message", sessionID, msg.info.id])
+ Database.use((db) => db.delete(MessageTable).where(eq(MessageTable.id, msg.info.id)).run())
await Bus.publish(MessageV2.Event.Removed, { sessionID: sessionID, messageID: msg.info.id })
}
- const last = preserve.at(-1)
- if (session.revert.partID && last) {
+ if (session.revert.partID && target) {
const partID = session.revert.partID
- const [preserveParts, removeParts] = splitWhen(last.parts, (x) => x.id === partID)
- last.parts = preserveParts
- for (const part of removeParts) {
- await Storage.remove(["part", last.info.id, part.id])
- await Bus.publish(MessageV2.Event.PartRemoved, {
- sessionID: sessionID,
- messageID: last.info.id,
- partID: part.id,
- })
+ const removeStart = target.parts.findIndex((part) => part.id === partID)
+ if (removeStart >= 0) {
+ const preserveParts = target.parts.slice(0, removeStart)
+ const removeParts = target.parts.slice(removeStart)
+ target.parts = preserveParts
+ for (const part of removeParts) {
+ Database.use((db) => db.delete(PartTable).where(eq(PartTable.id, part.id)).run())
+ await Bus.publish(MessageV2.Event.PartRemoved, {
+ sessionID: sessionID,
+ messageID: target.info.id,
+ partID: part.id,
+ })
+ }
}
}
- await Session.update(sessionID, (draft) => {
- draft.revert = undefined
- })
+ await Session.clearRevert(sessionID)
}
}
diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts
new file mode 100644
index 0000000000..9c5c72c4c5
--- /dev/null
+++ b/packages/opencode/src/session/session.sql.ts
@@ -0,0 +1,88 @@
+import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core"
+import { ProjectTable } from "../project/project.sql"
+import type { MessageV2 } from "./message-v2"
+import type { Snapshot } from "@/snapshot"
+import type { PermissionNext } from "@/permission/next"
+import { Timestamps } from "@/storage/schema.sql"
+
+type PartData = Omit
+type InfoData = Omit
+
+export const SessionTable = sqliteTable(
+ "session",
+ {
+ id: text().primaryKey(),
+ project_id: text()
+ .notNull()
+ .references(() => ProjectTable.id, { onDelete: "cascade" }),
+ parent_id: text(),
+ slug: text().notNull(),
+ directory: text().notNull(),
+ title: text().notNull(),
+ version: text().notNull(),
+ share_url: text(),
+ summary_additions: integer(),
+ summary_deletions: integer(),
+ summary_files: integer(),
+ summary_diffs: text({ mode: "json" }).$type(),
+ revert: text({ mode: "json" }).$type<{ messageID: string; partID?: string; snapshot?: string; diff?: string }>(),
+ permission: text({ mode: "json" }).$type(),
+ ...Timestamps,
+ time_compacting: integer(),
+ time_archived: integer(),
+ },
+ (table) => [index("session_project_idx").on(table.project_id), index("session_parent_idx").on(table.parent_id)],
+)
+
+export const MessageTable = sqliteTable(
+ "message",
+ {
+ id: text().primaryKey(),
+ session_id: text()
+ .notNull()
+ .references(() => SessionTable.id, { onDelete: "cascade" }),
+ ...Timestamps,
+ data: text({ mode: "json" }).notNull().$type(),
+ },
+ (table) => [index("message_session_idx").on(table.session_id)],
+)
+
+export const PartTable = sqliteTable(
+ "part",
+ {
+ id: text().primaryKey(),
+ message_id: text()
+ .notNull()
+ .references(() => MessageTable.id, { onDelete: "cascade" }),
+ session_id: text().notNull(),
+ ...Timestamps,
+ data: text({ mode: "json" }).notNull().$type(),
+ },
+ (table) => [index("part_message_idx").on(table.message_id), index("part_session_idx").on(table.session_id)],
+)
+
+export const TodoTable = sqliteTable(
+ "todo",
+ {
+ session_id: text()
+ .notNull()
+ .references(() => SessionTable.id, { onDelete: "cascade" }),
+ content: text().notNull(),
+ status: text().notNull(),
+ priority: text().notNull(),
+ position: integer().notNull(),
+ ...Timestamps,
+ },
+ (table) => [
+ primaryKey({ columns: [table.session_id, table.position] }),
+ index("todo_session_idx").on(table.session_id),
+ ],
+)
+
+export const PermissionTable = sqliteTable("permission", {
+ project_id: text()
+ .primaryKey()
+ .references(() => ProjectTable.id, { onDelete: "cascade" }),
+ ...Timestamps,
+ data: text({ mode: "json" }).notNull().$type(),
+})
diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts
index b92fc9979f..c3e14ddd69 100644
--- a/packages/opencode/src/session/summary.ts
+++ b/packages/opencode/src/session/summary.ts
@@ -90,12 +90,13 @@ export namespace SessionSummary {
async function summarizeSession(input: { sessionID: string; messages: MessageV2.WithParts[] }) {
const diffs = await computeDiff({ messages: input.messages })
- await Session.update(input.sessionID, (draft) => {
- draft.summary = {
+ await Session.setSummary({
+ sessionID: input.sessionID,
+ summary: {
additions: diffs.reduce((sum, x) => sum + x.additions, 0),
deletions: diffs.reduce((sum, x) => sum + x.deletions, 0),
files: diffs.length,
- }
+ },
})
await Storage.write(["session_diff", input.sessionID], diffs)
Bus.publish(Session.Event.Diff, {
diff --git a/packages/opencode/src/session/todo.ts b/packages/opencode/src/session/todo.ts
index aa7df7e981..ec2bcdda3c 100644
--- a/packages/opencode/src/session/todo.ts
+++ b/packages/opencode/src/session/todo.ts
@@ -1,7 +1,8 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import z from "zod"
-import { Storage } from "../storage/storage"
+import { Database, eq, asc } from "../storage/db"
+import { TodoTable } from "./session.sql"
export namespace Todo {
export const Info = z
@@ -9,7 +10,6 @@ export namespace Todo {
content: z.string().describe("Brief description of the task"),
status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
priority: z.string().describe("Priority level of the task: high, medium, low"),
- id: z.string().describe("Unique identifier for the todo item"),
})
.meta({ ref: "Todo" })
export type Info = z.infer
@@ -24,14 +24,33 @@ export namespace Todo {
),
}
- export async function update(input: { sessionID: string; todos: Info[] }) {
- await Storage.write(["todo", input.sessionID], input.todos)
+ export function update(input: { sessionID: string; todos: Info[] }) {
+ Database.transaction((db) => {
+ db.delete(TodoTable).where(eq(TodoTable.session_id, input.sessionID)).run()
+ if (input.todos.length === 0) return
+ db.insert(TodoTable)
+ .values(
+ input.todos.map((todo, position) => ({
+ session_id: input.sessionID,
+ content: todo.content,
+ status: todo.status,
+ priority: todo.priority,
+ position,
+ })),
+ )
+ .run()
+ })
Bus.publish(Event.Updated, input)
}
- export async function get(sessionID: string) {
- return Storage.read(["todo", sessionID])
- .then((x) => x || [])
- .catch(() => [])
+ export function get(sessionID: string) {
+ const rows = Database.use((db) =>
+ db.select().from(TodoTable).where(eq(TodoTable.session_id, sessionID)).orderBy(asc(TodoTable.position)).all(),
+ )
+ return rows.map((row) => ({
+ content: row.content,
+ status: row.status,
+ priority: row.priority,
+ }))
}
}
diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts
index a3a229d1a1..c36616b7ef 100644
--- a/packages/opencode/src/share/share-next.ts
+++ b/packages/opencode/src/share/share-next.ts
@@ -4,7 +4,8 @@ import { ulid } from "ulid"
import { Provider } from "@/provider/provider"
import { Session } from "@/session"
import { MessageV2 } from "@/session/message-v2"
-import { Storage } from "@/storage/storage"
+import { Database, eq } from "@/storage/db"
+import { SessionShareTable } from "./share.sql"
import { Log } from "@/util/log"
import type * as SDK from "@opencode-ai/sdk/v2"
@@ -77,17 +78,26 @@ export namespace ShareNext {
})
.then((x) => x.json())
.then((x) => x as { id: string; url: string; secret: string })
- await Storage.write(["session_share", sessionID], result)
+ Database.use((db) =>
+ db
+ .insert(SessionShareTable)
+ .values({ session_id: sessionID, id: result.id, secret: result.secret, url: result.url })
+ .onConflictDoUpdate({
+ target: SessionShareTable.session_id,
+ set: { id: result.id, secret: result.secret, url: result.url },
+ })
+ .run(),
+ )
fullSync(sessionID)
return result
}
function get(sessionID: string) {
- return Storage.read<{
- id: string
- secret: string
- url: string
- }>(["session_share", sessionID])
+ const row = Database.use((db) =>
+ db.select().from(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).get(),
+ )
+ if (!row) return
+ return { id: row.id, secret: row.secret, url: row.url }
}
type Data =
@@ -132,7 +142,7 @@ export namespace ShareNext {
const queued = queue.get(sessionID)
if (!queued) return
queue.delete(sessionID)
- const share = await get(sessionID).catch(() => undefined)
+ const share = get(sessionID)
if (!share) return
await fetch(`${await url()}/api/share/${share.id}/sync`, {
@@ -152,7 +162,7 @@ export namespace ShareNext {
export async function remove(sessionID: string) {
if (disabled) return
log.info("removing share", { sessionID })
- const share = await get(sessionID)
+ const share = get(sessionID)
if (!share) return
await fetch(`${await url()}/api/share/${share.id}`, {
method: "DELETE",
@@ -163,7 +173,7 @@ export namespace ShareNext {
secret: share.secret,
}),
})
- await Storage.remove(["session_share", sessionID])
+ Database.use((db) => db.delete(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).run())
}
async function fullSync(sessionID: string) {
diff --git a/packages/opencode/src/share/share.sql.ts b/packages/opencode/src/share/share.sql.ts
new file mode 100644
index 0000000000..268d41a6f6
--- /dev/null
+++ b/packages/opencode/src/share/share.sql.ts
@@ -0,0 +1,13 @@
+import { sqliteTable, text } from "drizzle-orm/sqlite-core"
+import { SessionTable } from "../session/session.sql"
+import { Timestamps } from "@/storage/schema.sql"
+
+export const SessionShareTable = sqliteTable("session_share", {
+ session_id: text()
+ .primaryKey()
+ .references(() => SessionTable.id, { onDelete: "cascade" }),
+ id: text().notNull(),
+ secret: text().notNull(),
+ url: text().notNull(),
+ ...Timestamps,
+})
diff --git a/packages/opencode/src/share/share.ts b/packages/opencode/src/share/share.ts
deleted file mode 100644
index f7bf4b3fa5..0000000000
--- a/packages/opencode/src/share/share.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { Bus } from "../bus"
-import { Installation } from "../installation"
-import { Session } from "../session"
-import { MessageV2 } from "../session/message-v2"
-import { Log } from "../util/log"
-
-export namespace Share {
- const log = Log.create({ service: "share" })
-
- let queue: Promise = Promise.resolve()
- const pending = new Map()
-
- export async function sync(key: string, content: any) {
- if (disabled) return
- const [root, ...splits] = key.split("/")
- if (root !== "session") return
- const [sub, sessionID] = splits
- if (sub === "share") return
- const share = await Session.getShare(sessionID).catch(() => {})
- if (!share) return
- const { secret } = share
- pending.set(key, content)
- queue = queue
- .then(async () => {
- const content = pending.get(key)
- if (content === undefined) return
- pending.delete(key)
-
- return fetch(`${URL}/share_sync`, {
- method: "POST",
- body: JSON.stringify({
- sessionID: sessionID,
- secret,
- key: key,
- content,
- }),
- })
- })
- .then((x) => {
- if (x) {
- log.info("synced", {
- key: key,
- status: x.status,
- })
- }
- })
- }
-
- export function init() {
- Bus.subscribe(Session.Event.Updated, async (evt) => {
- await sync("session/info/" + evt.properties.info.id, evt.properties.info)
- })
- Bus.subscribe(MessageV2.Event.Updated, async (evt) => {
- await sync("session/message/" + evt.properties.info.sessionID + "/" + evt.properties.info.id, evt.properties.info)
- })
- Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => {
- await sync(
- "session/part/" +
- evt.properties.part.sessionID +
- "/" +
- evt.properties.part.messageID +
- "/" +
- evt.properties.part.id,
- evt.properties.part,
- )
- })
- }
-
- export const URL =
- process.env["OPENCODE_API"] ??
- (Installation.isPreview() || Installation.isLocal() ? "https://api.dev.opencode.ai" : "https://api.opencode.ai")
-
- const disabled = process.env["OPENCODE_DISABLE_SHARE"] === "true" || process.env["OPENCODE_DISABLE_SHARE"] === "1"
-
- export async function create(sessionID: string) {
- if (disabled) return { url: "", secret: "" }
- return fetch(`${URL}/share_create`, {
- method: "POST",
- body: JSON.stringify({ sessionID: sessionID }),
- })
- .then((x) => x.json())
- .then((x) => x as { url: string; secret: string })
- }
-
- export async function remove(sessionID: string, secret: string) {
- if (disabled) return {}
- return fetch(`${URL}/share_delete`, {
- method: "POST",
- body: JSON.stringify({ sessionID, secret }),
- }).then((x) => x.json())
- }
-}
diff --git a/packages/opencode/src/sql.d.ts b/packages/opencode/src/sql.d.ts
new file mode 100644
index 0000000000..28cb1e2649
--- /dev/null
+++ b/packages/opencode/src/sql.d.ts
@@ -0,0 +1,4 @@
+declare module "*.sql" {
+ const content: string
+ export default content
+}
diff --git a/packages/opencode/src/storage/db.ts b/packages/opencode/src/storage/db.ts
new file mode 100644
index 0000000000..50aa76384e
--- /dev/null
+++ b/packages/opencode/src/storage/db.ts
@@ -0,0 +1,140 @@
+import { Database as BunDatabase } from "bun:sqlite"
+import { drizzle, type SQLiteBunDatabase } from "drizzle-orm/bun-sqlite"
+import { migrate } from "drizzle-orm/bun-sqlite/migrator"
+import { type SQLiteTransaction } from "drizzle-orm/sqlite-core"
+export * from "drizzle-orm"
+import { Context } from "../util/context"
+import { lazy } from "../util/lazy"
+import { Global } from "../global"
+import { Log } from "../util/log"
+import { NamedError } from "@opencode-ai/util/error"
+import z from "zod"
+import path from "path"
+import { readFileSync, readdirSync } from "fs"
+import * as schema from "./schema"
+
+declare const OPENCODE_MIGRATIONS: { sql: string; timestamp: number }[] | undefined
+
+export const NotFoundError = NamedError.create(
+ "NotFoundError",
+ z.object({
+ message: z.string(),
+ }),
+)
+
+const log = Log.create({ service: "db" })
+
+export namespace Database {
+ type Schema = typeof schema
+ export type Transaction = SQLiteTransaction<"sync", void, Schema>
+
+ type Client = SQLiteBunDatabase
+
+ type Journal = { sql: string; timestamp: number }[]
+
+ function time(tag: string) {
+ const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(tag)
+ if (!match) return 0
+ return Date.UTC(
+ Number(match[1]),
+ Number(match[2]) - 1,
+ Number(match[3]),
+ Number(match[4]),
+ Number(match[5]),
+ Number(match[6]),
+ )
+ }
+
+ function migrations(dir: string): Journal {
+ const dirs = readdirSync(dir, { withFileTypes: true })
+ .filter((entry) => entry.isDirectory())
+ .map((entry) => entry.name)
+
+ const sql = dirs
+ .map((name) => {
+ const file = path.join(dir, name, "migration.sql")
+ if (!Bun.file(file).size) return
+ return {
+ sql: readFileSync(file, "utf-8"),
+ timestamp: time(name),
+ }
+ })
+ .filter(Boolean) as Journal
+
+ return sql.sort((a, b) => a.timestamp - b.timestamp)
+ }
+
+ export const Client = lazy(() => {
+ log.info("opening database", { path: path.join(Global.Path.data, "opencode.db") })
+
+ const sqlite = new BunDatabase(path.join(Global.Path.data, "opencode.db"), { create: true })
+
+ sqlite.run("PRAGMA journal_mode = WAL")
+ sqlite.run("PRAGMA synchronous = NORMAL")
+ sqlite.run("PRAGMA busy_timeout = 5000")
+ sqlite.run("PRAGMA cache_size = -64000")
+ sqlite.run("PRAGMA foreign_keys = ON")
+
+ const db = drizzle({ client: sqlite, schema })
+
+ // Apply schema migrations
+ const entries =
+ typeof OPENCODE_MIGRATIONS !== "undefined"
+ ? OPENCODE_MIGRATIONS
+ : migrations(path.join(import.meta.dirname, "../../migration"))
+ if (entries.length > 0) {
+ log.info("applying migrations", {
+ count: entries.length,
+ mode: typeof OPENCODE_MIGRATIONS !== "undefined" ? "bundled" : "dev",
+ })
+ migrate(db, entries)
+ }
+
+ return db
+ })
+
+ export type TxOrDb = Transaction | Client
+
+ const ctx = Context.create<{
+ tx: TxOrDb
+ effects: (() => void | Promise)[]
+ }>("database")
+
+ export function use(callback: (trx: TxOrDb) => T): T {
+ try {
+ return callback(ctx.use().tx)
+ } catch (err) {
+ if (err instanceof Context.NotFound) {
+ const effects: (() => void | Promise)[] = []
+ const result = ctx.provide({ effects, tx: Client() }, () => callback(Client()))
+ for (const effect of effects) effect()
+ return result
+ }
+ throw err
+ }
+ }
+
+ export function effect(fn: () => any | Promise) {
+ try {
+ ctx.use().effects.push(fn)
+ } catch {
+ fn()
+ }
+ }
+
+ export function transaction(callback: (tx: TxOrDb) => T): T {
+ try {
+ return callback(ctx.use().tx)
+ } catch (err) {
+ if (err instanceof Context.NotFound) {
+ const effects: (() => void | Promise)[] = []
+ const result = Client().transaction((tx) => {
+ return ctx.provide({ tx, effects }, () => callback(tx))
+ })
+ for (const effect of effects) effect()
+ return result
+ }
+ throw err
+ }
+ }
+}
diff --git a/packages/opencode/src/storage/json-migration.ts b/packages/opencode/src/storage/json-migration.ts
new file mode 100644
index 0000000000..89d561188c
--- /dev/null
+++ b/packages/opencode/src/storage/json-migration.ts
@@ -0,0 +1,437 @@
+import { Database } from "bun:sqlite"
+import { drizzle } from "drizzle-orm/bun-sqlite"
+import { Global } from "../global"
+import { Log } from "../util/log"
+import { ProjectTable } from "../project/project.sql"
+import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../session/session.sql"
+import { SessionShareTable } from "../share/share.sql"
+import path from "path"
+import { existsSync } from "fs"
+
+export namespace JsonMigration {
+ const log = Log.create({ service: "json-migration" })
+
+ export type Progress = {
+ current: number
+ total: number
+ label: string
+ }
+
+ type Options = {
+ progress?: (event: Progress) => void
+ }
+
+ export async function run(sqlite: Database, options?: Options) {
+ const storageDir = path.join(Global.Path.data, "storage")
+
+ if (!existsSync(storageDir)) {
+ log.info("storage directory does not exist, skipping migration")
+ return {
+ projects: 0,
+ sessions: 0,
+ messages: 0,
+ parts: 0,
+ todos: 0,
+ permissions: 0,
+ shares: 0,
+ errors: [] as string[],
+ }
+ }
+
+ log.info("starting json to sqlite migration", { storageDir })
+ const start = performance.now()
+
+ const db = drizzle({ client: sqlite })
+
+ // Optimize SQLite for bulk inserts
+ sqlite.exec("PRAGMA journal_mode = WAL")
+ sqlite.exec("PRAGMA synchronous = OFF")
+ sqlite.exec("PRAGMA cache_size = 10000")
+ sqlite.exec("PRAGMA temp_store = MEMORY")
+ const stats = {
+ projects: 0,
+ sessions: 0,
+ messages: 0,
+ parts: 0,
+ todos: 0,
+ permissions: 0,
+ shares: 0,
+ errors: [] as string[],
+ }
+ const orphans = {
+ sessions: 0,
+ todos: 0,
+ permissions: 0,
+ shares: 0,
+ }
+ const errs = stats.errors
+
+ const batchSize = 1000
+ const now = Date.now()
+
+ async function list(pattern: string) {
+ const items: string[] = []
+ const scan = new Bun.Glob(pattern)
+ for await (const file of scan.scan({ cwd: storageDir, absolute: true })) {
+ items.push(file)
+ }
+ return items
+ }
+
+ async function read(files: string[], start: number, end: number) {
+ const count = end - start
+ const tasks = new Array(count)
+ for (let i = 0; i < count; i++) {
+ tasks[i] = Bun.file(files[start + i]).json()
+ }
+ const results = await Promise.allSettled(tasks)
+ const items = new Array(count)
+ for (let i = 0; i < results.length; i++) {
+ const result = results[i]
+ if (result.status === "fulfilled") {
+ items[i] = result.value
+ continue
+ }
+ errs.push(`failed to read ${files[start + i]}: ${result.reason}`)
+ }
+ return items
+ }
+
+ function insert(values: any[], table: any, label: string) {
+ if (values.length === 0) return 0
+ try {
+ db.insert(table).values(values).onConflictDoNothing().run()
+ return values.length
+ } catch (e) {
+ errs.push(`failed to migrate ${label} batch: ${e}`)
+ return 0
+ }
+ }
+
+ // Pre-scan all files upfront to avoid repeated glob operations
+ log.info("scanning files...")
+ const [projectFiles, sessionFiles, messageFiles, partFiles, todoFiles, permFiles, shareFiles] = await Promise.all([
+ list("project/*.json"),
+ list("session/*/*.json"),
+ list("message/*/*.json"),
+ list("part/*/*.json"),
+ list("todo/*.json"),
+ list("permission/*.json"),
+ list("session_share/*.json"),
+ ])
+
+ log.info("file scan complete", {
+ projects: projectFiles.length,
+ sessions: sessionFiles.length,
+ messages: messageFiles.length,
+ parts: partFiles.length,
+ todos: todoFiles.length,
+ permissions: permFiles.length,
+ shares: shareFiles.length,
+ })
+
+ const total = Math.max(
+ 1,
+ projectFiles.length +
+ sessionFiles.length +
+ messageFiles.length +
+ partFiles.length +
+ todoFiles.length +
+ permFiles.length +
+ shareFiles.length,
+ )
+ const progress = options?.progress
+ let current = 0
+ const step = (label: string, count: number) => {
+ current = Math.min(total, current + count)
+ progress?.({ current, total, label })
+ }
+
+ progress?.({ current, total, label: "starting" })
+
+ sqlite.exec("BEGIN TRANSACTION")
+
+ // Migrate projects first (no FK deps)
+ const projectIds = new Set()
+ const projectValues = [] as any[]
+ for (let i = 0; i < projectFiles.length; i += batchSize) {
+ const end = Math.min(i + batchSize, projectFiles.length)
+ const batch = await read(projectFiles, i, end)
+ projectValues.length = 0
+ for (let j = 0; j < batch.length; j++) {
+ const data = batch[j]
+ if (!data) continue
+ if (!data?.id) {
+ errs.push(`project missing id: ${projectFiles[i + j]}`)
+ continue
+ }
+ projectIds.add(data.id)
+ projectValues.push({
+ id: data.id,
+ worktree: data.worktree ?? "/",
+ vcs: data.vcs,
+ name: data.name ?? undefined,
+ icon_url: data.icon?.url,
+ icon_color: data.icon?.color,
+ time_created: data.time?.created ?? now,
+ time_updated: data.time?.updated ?? now,
+ time_initialized: data.time?.initialized,
+ sandboxes: data.sandboxes ?? [],
+ commands: data.commands,
+ })
+ }
+ stats.projects += insert(projectValues, ProjectTable, "project")
+ step("projects", end - i)
+ }
+ log.info("migrated projects", { count: stats.projects, duration: Math.round(performance.now() - start) })
+
+ // Migrate sessions (depends on projects)
+ const sessionIds = new Set()
+ const sessionValues = [] as any[]
+ for (let i = 0; i < sessionFiles.length; i += batchSize) {
+ const end = Math.min(i + batchSize, sessionFiles.length)
+ const batch = await read(sessionFiles, i, end)
+ sessionValues.length = 0
+ for (let j = 0; j < batch.length; j++) {
+ const data = batch[j]
+ if (!data) continue
+ if (!data?.id || !data?.projectID) {
+ errs.push(`session missing id or projectID: ${sessionFiles[i + j]}`)
+ continue
+ }
+ if (!projectIds.has(data.projectID)) {
+ orphans.sessions++
+ continue
+ }
+ sessionIds.add(data.id)
+ sessionValues.push({
+ id: data.id,
+ project_id: data.projectID,
+ parent_id: data.parentID ?? null,
+ slug: data.slug ?? "",
+ directory: data.directory ?? "",
+ title: data.title ?? "",
+ version: data.version ?? "",
+ share_url: data.share?.url ?? null,
+ summary_additions: data.summary?.additions ?? null,
+ summary_deletions: data.summary?.deletions ?? null,
+ summary_files: data.summary?.files ?? null,
+ summary_diffs: data.summary?.diffs ?? null,
+ revert: data.revert ?? null,
+ permission: data.permission ?? null,
+ time_created: data.time?.created ?? now,
+ time_updated: data.time?.updated ?? now,
+ time_compacting: data.time?.compacting ?? null,
+ time_archived: data.time?.archived ?? null,
+ })
+ }
+ stats.sessions += insert(sessionValues, SessionTable, "session")
+ step("sessions", end - i)
+ }
+ log.info("migrated sessions", { count: stats.sessions })
+ if (orphans.sessions > 0) {
+ log.warn("skipped orphaned sessions", { count: orphans.sessions })
+ }
+
+ // Migrate messages using pre-scanned file map
+ const allMessageFiles = [] as string[]
+ const allMessageSessions = [] as string[]
+ const messageSessions = new Map()
+ for (const file of messageFiles) {
+ const sessionID = path.basename(path.dirname(file))
+ if (!sessionIds.has(sessionID)) continue
+ allMessageFiles.push(file)
+ allMessageSessions.push(sessionID)
+ }
+
+ for (let i = 0; i < allMessageFiles.length; i += batchSize) {
+ const end = Math.min(i + batchSize, allMessageFiles.length)
+ const batch = await read(allMessageFiles, i, end)
+ const values = new Array(batch.length)
+ let count = 0
+ for (let j = 0; j < batch.length; j++) {
+ const data = batch[j]
+ if (!data) continue
+ const file = allMessageFiles[i + j]
+ const id = data.id ?? path.basename(file, ".json")
+ if (!id) {
+ errs.push(`message missing id: ${file}`)
+ continue
+ }
+ const sessionID = allMessageSessions[i + j]
+ messageSessions.set(id, sessionID)
+ const rest = data
+ delete rest.id
+ delete rest.sessionID
+ values[count++] = {
+ id,
+ session_id: sessionID,
+ time_created: data.time?.created ?? now,
+ time_updated: data.time?.updated ?? now,
+ data: rest,
+ }
+ }
+ values.length = count
+ stats.messages += insert(values, MessageTable, "message")
+ step("messages", end - i)
+ }
+ log.info("migrated messages", { count: stats.messages })
+
+ // Migrate parts using pre-scanned file map
+ for (let i = 0; i < partFiles.length; i += batchSize) {
+ const end = Math.min(i + batchSize, partFiles.length)
+ const batch = await read(partFiles, i, end)
+ const values = new Array(batch.length)
+ let count = 0
+ for (let j = 0; j < batch.length; j++) {
+ const data = batch[j]
+ if (!data) continue
+ const file = partFiles[i + j]
+ const id = data.id ?? path.basename(file, ".json")
+ const messageID = data.messageID ?? path.basename(path.dirname(file))
+ if (!id || !messageID) {
+ errs.push(`part missing id/messageID/sessionID: ${file}`)
+ continue
+ }
+ const sessionID = messageSessions.get(messageID)
+ if (!sessionID) {
+ errs.push(`part missing message session: ${file}`)
+ continue
+ }
+ if (!sessionIds.has(sessionID)) continue
+ const rest = data
+ delete rest.id
+ delete rest.messageID
+ delete rest.sessionID
+ values[count++] = {
+ id,
+ message_id: messageID,
+ session_id: sessionID,
+ time_created: data.time?.created ?? now,
+ time_updated: data.time?.updated ?? now,
+ data: rest,
+ }
+ }
+ values.length = count
+ stats.parts += insert(values, PartTable, "part")
+ step("parts", end - i)
+ }
+ log.info("migrated parts", { count: stats.parts })
+
+ // Migrate todos
+ const todoSessions = todoFiles.map((file) => path.basename(file, ".json"))
+ for (let i = 0; i < todoFiles.length; i += batchSize) {
+ const end = Math.min(i + batchSize, todoFiles.length)
+ const batch = await read(todoFiles, i, end)
+ const values = [] as any[]
+ for (let j = 0; j < batch.length; j++) {
+ const data = batch[j]
+ if (!data) continue
+ const sessionID = todoSessions[i + j]
+ if (!sessionIds.has(sessionID)) {
+ orphans.todos++
+ continue
+ }
+ if (!Array.isArray(data)) {
+ errs.push(`todo not an array: ${todoFiles[i + j]}`)
+ continue
+ }
+ for (let position = 0; position < data.length; position++) {
+ const todo = data[position]
+ if (!todo?.content || !todo?.status || !todo?.priority) continue
+ values.push({
+ session_id: sessionID,
+ content: todo.content,
+ status: todo.status,
+ priority: todo.priority,
+ position,
+ time_created: now,
+ time_updated: now,
+ })
+ }
+ }
+ stats.todos += insert(values, TodoTable, "todo")
+ step("todos", end - i)
+ }
+ log.info("migrated todos", { count: stats.todos })
+ if (orphans.todos > 0) {
+ log.warn("skipped orphaned todos", { count: orphans.todos })
+ }
+
+ // Migrate permissions
+ const permProjects = permFiles.map((file) => path.basename(file, ".json"))
+ const permValues = [] as any[]
+ for (let i = 0; i < permFiles.length; i += batchSize) {
+ const end = Math.min(i + batchSize, permFiles.length)
+ const batch = await read(permFiles, i, end)
+ permValues.length = 0
+ for (let j = 0; j < batch.length; j++) {
+ const data = batch[j]
+ if (!data) continue
+ const projectID = permProjects[i + j]
+ if (!projectIds.has(projectID)) {
+ orphans.permissions++
+ continue
+ }
+ permValues.push({ project_id: projectID, data })
+ }
+ stats.permissions += insert(permValues, PermissionTable, "permission")
+ step("permissions", end - i)
+ }
+ log.info("migrated permissions", { count: stats.permissions })
+ if (orphans.permissions > 0) {
+ log.warn("skipped orphaned permissions", { count: orphans.permissions })
+ }
+
+ // Migrate session shares
+ const shareSessions = shareFiles.map((file) => path.basename(file, ".json"))
+ const shareValues = [] as any[]
+ for (let i = 0; i < shareFiles.length; i += batchSize) {
+ const end = Math.min(i + batchSize, shareFiles.length)
+ const batch = await read(shareFiles, i, end)
+ shareValues.length = 0
+ for (let j = 0; j < batch.length; j++) {
+ const data = batch[j]
+ if (!data) continue
+ const sessionID = shareSessions[i + j]
+ if (!sessionIds.has(sessionID)) {
+ orphans.shares++
+ continue
+ }
+ if (!data?.id || !data?.secret || !data?.url) {
+ errs.push(`session_share missing id/secret/url: ${shareFiles[i + j]}`)
+ continue
+ }
+ shareValues.push({ session_id: sessionID, id: data.id, secret: data.secret, url: data.url })
+ }
+ stats.shares += insert(shareValues, SessionShareTable, "session_share")
+ step("shares", end - i)
+ }
+ log.info("migrated session shares", { count: stats.shares })
+ if (orphans.shares > 0) {
+ log.warn("skipped orphaned session shares", { count: orphans.shares })
+ }
+
+ sqlite.exec("COMMIT")
+
+ log.info("json migration complete", {
+ projects: stats.projects,
+ sessions: stats.sessions,
+ messages: stats.messages,
+ parts: stats.parts,
+ todos: stats.todos,
+ permissions: stats.permissions,
+ shares: stats.shares,
+ errorCount: stats.errors.length,
+ duration: Math.round(performance.now() - start),
+ })
+
+ if (stats.errors.length > 0) {
+ log.warn("migration errors", { errors: stats.errors.slice(0, 20) })
+ }
+
+ progress?.({ current: total, total, label: "complete" })
+
+ return stats
+ }
+}
diff --git a/packages/opencode/src/storage/schema.sql.ts b/packages/opencode/src/storage/schema.sql.ts
new file mode 100644
index 0000000000..ead3518dee
--- /dev/null
+++ b/packages/opencode/src/storage/schema.sql.ts
@@ -0,0 +1,10 @@
+import { integer } from "drizzle-orm/sqlite-core"
+
+export const Timestamps = {
+ time_created: integer()
+ .notNull()
+ .$default(() => Date.now()),
+ time_updated: integer()
+ .notNull()
+ .$onUpdate(() => Date.now()),
+}
diff --git a/packages/opencode/src/storage/schema.ts b/packages/opencode/src/storage/schema.ts
new file mode 100644
index 0000000000..7961b0e380
--- /dev/null
+++ b/packages/opencode/src/storage/schema.ts
@@ -0,0 +1,4 @@
+export { ControlAccountTable } from "../control/control.sql"
+export { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../session/session.sql"
+export { SessionShareTable } from "../share/share.sql"
+export { ProjectTable } from "../project/project.sql"
diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts
index 5ed5a879b4..9a06cb5993 100644
--- a/packages/opencode/src/tool/registry.ts
+++ b/packages/opencode/src/tool/registry.ts
@@ -149,9 +149,17 @@ export namespace ToolRegistry {
})
.map(async (t) => {
using _ = log.time(t.id)
+ const tool = await t.init({ agent })
+ const output = {
+ description: tool.description,
+ parameters: tool.parameters,
+ }
+ await Plugin.trigger("tool.definition", { toolID: t.id }, output)
return {
id: t.id,
- ...(await t.init({ agent })),
+ ...tool,
+ description: output.description,
+ parameters: output.parameters,
}
}),
)
diff --git a/packages/opencode/src/tool/websearch.ts b/packages/opencode/src/tool/websearch.ts
index beedd9c7cb..bf16428dfb 100644
--- a/packages/opencode/src/tool/websearch.ts
+++ b/packages/opencode/src/tool/websearch.ts
@@ -40,7 +40,7 @@ interface McpSearchResponse {
export const WebSearchTool = Tool.define("websearch", async () => {
return {
get description() {
- return DESCRIPTION.replace("{{date}}", new Date().toISOString().slice(0, 10))
+ return DESCRIPTION.replace("{{year}}", new Date().getFullYear().toString())
},
parameters: z.object({
query: z.string().describe("Websearch query"),
diff --git a/packages/opencode/src/tool/websearch.txt b/packages/opencode/src/tool/websearch.txt
index 657ac74586..551c0f3b59 100644
--- a/packages/opencode/src/tool/websearch.txt
+++ b/packages/opencode/src/tool/websearch.txt
@@ -10,5 +10,5 @@ Usage notes:
- Configurable context length for optimal LLM integration
- Domain filtering and advanced search options available
-Today's date is {{date}}. You MUST use this year when searching for recent information or current events
-- Example: If today is 2025-07-15 and the user asks for "latest AI news", search for "AI news 2025", NOT "AI news 2024"
+The current year is {{year}}. You MUST use this year when searching for recent information or current events
+- Example: If the current year is 2026 and the user asks for "latest AI news", search for "AI news 2026", NOT "AI news 2025"
diff --git a/packages/opencode/src/util/lazy.ts b/packages/opencode/src/util/lazy.ts
index 0cc6d8d5c4..55643dc6a7 100644
--- a/packages/opencode/src/util/lazy.ts
+++ b/packages/opencode/src/util/lazy.ts
@@ -4,9 +4,14 @@ export function lazy(fn: () => T) {
const result = (): T => {
if (loaded) return value as T
- loaded = true
- value = fn()
- return value as T
+ try {
+ value = fn()
+ loaded = true
+ return value as T
+ } catch (e) {
+ // Don't mark as loaded if initialization failed
+ throw e
+ }
}
result.reset = () => {
diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts
index 85d7f6d0e8..d85a0843fb 100644
--- a/packages/opencode/src/worktree/index.ts
+++ b/packages/opencode/src/worktree/index.ts
@@ -7,7 +7,8 @@ import { Global } from "../global"
import { Instance } from "../project/instance"
import { InstanceBootstrap } from "../project/bootstrap"
import { Project } from "../project/project"
-import { Storage } from "../storage/storage"
+import { Database, eq } from "../storage/db"
+import { ProjectTable } from "../project/project.sql"
import { fn } from "../util/fn"
import { Log } from "../util/log"
import { BusEvent } from "@/bus/bus-event"
@@ -307,7 +308,8 @@ export namespace Worktree {
}
async function runStartScripts(directory: string, input: { projectID: string; extra?: string }) {
- const project = await Storage.read(["project", input.projectID]).catch(() => undefined)
+ const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, input.projectID)).get())
+ const project = row ? Project.fromRow(row) : undefined
const startup = project?.commands?.start?.trim() ?? ""
const ok = await runStartScript(directory, startup, "project")
if (!ok) return false
diff --git a/packages/opencode/test/acp/event-subscription.test.ts b/packages/opencode/test/acp/event-subscription.test.ts
index 8e139ff597..1145a1357d 100644
--- a/packages/opencode/test/acp/event-subscription.test.ts
+++ b/packages/opencode/test/acp/event-subscription.test.ts
@@ -122,12 +122,20 @@ function createFakeAgent() {
messages: async () => {
return { data: [] }
},
- message: async () => {
+ message: async (params?: any) => {
+ // Return a message with parts that can be looked up by partID
return {
data: {
info: {
role: "assistant",
},
+ parts: [
+ {
+ id: params?.messageID ? `${params.messageID}_part` : "part_1",
+ type: "text",
+ text: "",
+ },
+ ],
},
}
},
@@ -193,7 +201,7 @@ function createFakeAgent() {
}
describe("acp.agent event subscription", () => {
- test("routes message.part.updated by the event sessionID (no cross-session pollution)", async () => {
+ test("routes message.part.delta by the event sessionID (no cross-session pollution)", async () => {
await using tmp = await tmpdir()
await Instance.provide({
directory: tmp.path,
@@ -207,14 +215,12 @@ describe("acp.agent event subscription", () => {
controller.push({
directory: cwd,
payload: {
- type: "message.part.updated",
+ type: "message.part.delta",
properties: {
- part: {
- sessionID: sessionB,
- messageID: "msg_1",
- type: "text",
- synthetic: false,
- },
+ sessionID: sessionB,
+ messageID: "msg_1",
+ partID: "msg_1_part",
+ field: "text",
delta: "hello",
},
},
@@ -230,7 +236,7 @@ describe("acp.agent event subscription", () => {
})
})
- test("keeps concurrent sessions isolated when message.part.updated events are interleaved", async () => {
+ test("keeps concurrent sessions isolated when message.part.delta events are interleaved", async () => {
await using tmp = await tmpdir()
await Instance.provide({
directory: tmp.path,
@@ -248,14 +254,12 @@ describe("acp.agent event subscription", () => {
controller.push({
directory: cwd,
payload: {
- type: "message.part.updated",
+ type: "message.part.delta",
properties: {
- part: {
- sessionID: sessionId,
- messageID,
- type: "text",
- synthetic: false,
- },
+ sessionID: sessionId,
+ messageID,
+ partID: `${messageID}_part`,
+ field: "text",
delta,
},
},
@@ -402,14 +406,12 @@ describe("acp.agent event subscription", () => {
controller.push({
directory: cwd,
payload: {
- type: "message.part.updated",
+ type: "message.part.delta",
properties: {
- part: {
- sessionID: sessionB,
- messageID: "msg_b",
- type: "text",
- synthetic: false,
- },
+ sessionID: sessionB,
+ messageID: "msg_b",
+ partID: "msg_b_part",
+ field: "text",
delta: "session_b_message",
},
},
diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts
index 331e05d5a7..91b87f6498 100644
--- a/packages/opencode/test/config/config.test.ts
+++ b/packages/opencode/test/config/config.test.ts
@@ -1800,68 +1800,3 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
}
})
})
-
-// OPENCODE_CONFIG_CONTENT should support {env:} and {file:} token substitution
-// just like file-based config sources do.
-describe("OPENCODE_CONFIG_CONTENT token substitution", () => {
- test("substitutes {env:} tokens in OPENCODE_CONFIG_CONTENT", async () => {
- const originalEnv = process.env["OPENCODE_CONFIG_CONTENT"]
- const originalTestVar = process.env["TEST_CONFIG_VAR"]
- process.env["TEST_CONFIG_VAR"] = "test_api_key_12345"
- process.env["OPENCODE_CONFIG_CONTENT"] = JSON.stringify({
- $schema: "https://opencode.ai/config.json",
- theme: "{env:TEST_CONFIG_VAR}",
- })
-
- try {
- await using tmp = await tmpdir()
- await Instance.provide({
- directory: tmp.path,
- fn: async () => {
- const config = await Config.get()
- expect(config.theme).toBe("test_api_key_12345")
- },
- })
- } finally {
- if (originalEnv !== undefined) {
- process.env["OPENCODE_CONFIG_CONTENT"] = originalEnv
- } else {
- delete process.env["OPENCODE_CONFIG_CONTENT"]
- }
- if (originalTestVar !== undefined) {
- process.env["TEST_CONFIG_VAR"] = originalTestVar
- } else {
- delete process.env["TEST_CONFIG_VAR"]
- }
- }
- })
-
- test("substitutes {file:} tokens in OPENCODE_CONFIG_CONTENT", async () => {
- const originalEnv = process.env["OPENCODE_CONFIG_CONTENT"]
-
- try {
- await using tmp = await tmpdir({
- init: async (dir) => {
- await Bun.write(path.join(dir, "api_key.txt"), "secret_key_from_file")
- process.env["OPENCODE_CONFIG_CONTENT"] = JSON.stringify({
- $schema: "https://opencode.ai/config.json",
- theme: "{file:./api_key.txt}",
- })
- },
- })
- await Instance.provide({
- directory: tmp.path,
- fn: async () => {
- const config = await Config.get()
- expect(config.theme).toBe("secret_key_from_file")
- },
- })
- } finally {
- if (originalEnv !== undefined) {
- process.env["OPENCODE_CONFIG_CONTENT"] = originalEnv
- } else {
- delete process.env["OPENCODE_CONFIG_CONTENT"]
- }
- }
- })
-})
diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts
index 29f1efa401..add3332048 100644
--- a/packages/opencode/test/permission/next.test.ts
+++ b/packages/opencode/test/permission/next.test.ts
@@ -2,7 +2,6 @@ import { test, expect } from "bun:test"
import os from "os"
import { PermissionNext } from "../../src/permission/next"
import { Instance } from "../../src/project/instance"
-import { Storage } from "../../src/storage/storage"
import { tmpdir } from "../fixture/fixture"
// fromConfig tests
diff --git a/packages/opencode/test/preload.ts b/packages/opencode/test/preload.ts
index f0d0272aff..dee7045707 100644
--- a/packages/opencode/test/preload.ts
+++ b/packages/opencode/test/preload.ts
@@ -6,11 +6,19 @@ import fs from "fs/promises"
import fsSync from "fs"
import { afterAll } from "bun:test"
+// Set XDG env vars FIRST, before any src/ imports
const dir = path.join(os.tmpdir(), "opencode-test-data-" + process.pid)
await fs.mkdir(dir, { recursive: true })
afterAll(() => {
fsSync.rmSync(dir, { recursive: true, force: true })
})
+
+process.env["XDG_DATA_HOME"] = path.join(dir, "share")
+process.env["XDG_CACHE_HOME"] = path.join(dir, "cache")
+process.env["XDG_CONFIG_HOME"] = path.join(dir, "config")
+process.env["XDG_STATE_HOME"] = path.join(dir, "state")
+process.env["OPENCODE_MODELS_PATH"] = path.join(import.meta.dir, "tool", "fixtures", "models-api.json")
+
// Set test home directory to isolate tests from user's actual home directory
// This prevents tests from picking up real user configs/skills from ~/.claude/skills
const testHome = path.join(dir, "home")
@@ -21,12 +29,6 @@ process.env["OPENCODE_TEST_HOME"] = testHome
const testManagedConfigDir = path.join(dir, "managed")
process.env["OPENCODE_TEST_MANAGED_CONFIG_DIR"] = testManagedConfigDir
-process.env["XDG_DATA_HOME"] = path.join(dir, "share")
-process.env["XDG_CACHE_HOME"] = path.join(dir, "cache")
-process.env["XDG_CONFIG_HOME"] = path.join(dir, "config")
-process.env["XDG_STATE_HOME"] = path.join(dir, "state")
-process.env["OPENCODE_MODELS_PATH"] = path.join(import.meta.dir, "tool", "fixtures", "models-api.json")
-
// Write the cache version file to prevent global/index.ts from clearing the cache
const cacheDir = path.join(dir, "cache", "opencode")
await fs.mkdir(cacheDir, { recursive: true })
diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts
index 581c63b567..19f9821c42 100644
--- a/packages/opencode/test/project/project.test.ts
+++ b/packages/opencode/test/project/project.test.ts
@@ -1,10 +1,10 @@
import { describe, expect, mock, test } from "bun:test"
-import type { Project as ProjectNS } from "../../src/project/project"
+import { Project } from "../../src/project/project"
import { Log } from "../../src/util/log"
-import { Storage } from "../../src/storage/storage"
import { $ } from "bun"
import path from "path"
import { tmpdir } from "../fixture/fixture"
+import { GlobalBus } from "../../src/bus/global"
Log.init({ print: false })
@@ -152,38 +152,51 @@ describe("Project.fromDirectory with worktrees", () => {
const p = await loadProject()
await using tmp = await tmpdir({ git: true })
- const worktreePath = path.join(tmp.path, "..", "worktree-test")
- await $`git worktree add ${worktreePath} -b test-branch`.cwd(tmp.path).quiet()
+ const worktreePath = path.join(tmp.path, "..", path.basename(tmp.path) + "-worktree")
+ try {
+ await $`git worktree add ${worktreePath} -b test-branch-${Date.now()}`.cwd(tmp.path).quiet()
- const { project, sandbox } = await p.fromDirectory(worktreePath)
+ const { project, sandbox } = await p.fromDirectory(worktreePath)
- expect(project.worktree).toBe(tmp.path)
- expect(sandbox).toBe(worktreePath)
- expect(project.sandboxes).toContain(worktreePath)
- expect(project.sandboxes).not.toContain(tmp.path)
-
- await $`git worktree remove ${worktreePath}`.cwd(tmp.path).quiet()
+ expect(project.worktree).toBe(tmp.path)
+ expect(sandbox).toBe(worktreePath)
+ expect(project.sandboxes).toContain(worktreePath)
+ expect(project.sandboxes).not.toContain(tmp.path)
+ } finally {
+ await $`git worktree remove ${worktreePath}`
+ .cwd(tmp.path)
+ .quiet()
+ .catch(() => {})
+ }
})
test("should accumulate multiple worktrees in sandboxes", async () => {
const p = await loadProject()
await using tmp = await tmpdir({ git: true })
- const worktree1 = path.join(tmp.path, "..", "worktree-1")
- const worktree2 = path.join(tmp.path, "..", "worktree-2")
- await $`git worktree add ${worktree1} -b branch-1`.cwd(tmp.path).quiet()
- await $`git worktree add ${worktree2} -b branch-2`.cwd(tmp.path).quiet()
+ const worktree1 = path.join(tmp.path, "..", path.basename(tmp.path) + "-wt1")
+ const worktree2 = path.join(tmp.path, "..", path.basename(tmp.path) + "-wt2")
+ try {
+ await $`git worktree add ${worktree1} -b branch-${Date.now()}`.cwd(tmp.path).quiet()
+ await $`git worktree add ${worktree2} -b branch-${Date.now() + 1}`.cwd(tmp.path).quiet()
- await p.fromDirectory(worktree1)
- const { project } = await p.fromDirectory(worktree2)
+ await p.fromDirectory(worktree1)
+ const { project } = await p.fromDirectory(worktree2)
- expect(project.worktree).toBe(tmp.path)
- expect(project.sandboxes).toContain(worktree1)
- expect(project.sandboxes).toContain(worktree2)
- expect(project.sandboxes).not.toContain(tmp.path)
-
- await $`git worktree remove ${worktree1}`.cwd(tmp.path).quiet()
- await $`git worktree remove ${worktree2}`.cwd(tmp.path).quiet()
+ expect(project.worktree).toBe(tmp.path)
+ expect(project.sandboxes).toContain(worktree1)
+ expect(project.sandboxes).toContain(worktree2)
+ expect(project.sandboxes).not.toContain(tmp.path)
+ } finally {
+ await $`git worktree remove ${worktree1}`
+ .cwd(tmp.path)
+ .quiet()
+ .catch(() => {})
+ await $`git worktree remove ${worktree2}`
+ .cwd(tmp.path)
+ .quiet()
+ .catch(() => {})
+ }
})
})
@@ -198,11 +211,12 @@ describe("Project.discover", () => {
await p.discover(project)
- const updated = await Storage.read(["project", project.id])
- expect(updated.icon).toBeDefined()
- expect(updated.icon?.url).toStartWith("data:")
- expect(updated.icon?.url).toContain("base64")
- expect(updated.icon?.color).toBeUndefined()
+ const updated = Project.get(project.id)
+ expect(updated).toBeDefined()
+ expect(updated!.icon).toBeDefined()
+ expect(updated!.icon?.url).toStartWith("data:")
+ expect(updated!.icon?.url).toContain("base64")
+ expect(updated!.icon?.color).toBeUndefined()
})
test("should not discover non-image files", async () => {
@@ -214,7 +228,120 @@ describe("Project.discover", () => {
await p.discover(project)
- const updated = await Storage.read(["project", project.id])
- expect(updated.icon).toBeUndefined()
+ const updated = Project.get(project.id)
+ expect(updated).toBeDefined()
+ expect(updated!.icon).toBeUndefined()
+ })
+})
+
+describe("Project.update", () => {
+ test("should update name", async () => {
+ await using tmp = await tmpdir({ git: true })
+ const { project } = await Project.fromDirectory(tmp.path)
+
+ const updated = await Project.update({
+ projectID: project.id,
+ name: "New Project Name",
+ })
+
+ expect(updated.name).toBe("New Project Name")
+
+ const fromDb = Project.get(project.id)
+ expect(fromDb?.name).toBe("New Project Name")
+ })
+
+ test("should update icon url", async () => {
+ await using tmp = await tmpdir({ git: true })
+ const { project } = await Project.fromDirectory(tmp.path)
+
+ const updated = await Project.update({
+ projectID: project.id,
+ icon: { url: "https://example.com/icon.png" },
+ })
+
+ expect(updated.icon?.url).toBe("https://example.com/icon.png")
+
+ const fromDb = Project.get(project.id)
+ expect(fromDb?.icon?.url).toBe("https://example.com/icon.png")
+ })
+
+ test("should update icon color", async () => {
+ await using tmp = await tmpdir({ git: true })
+ const { project } = await Project.fromDirectory(tmp.path)
+
+ const updated = await Project.update({
+ projectID: project.id,
+ icon: { color: "#ff0000" },
+ })
+
+ expect(updated.icon?.color).toBe("#ff0000")
+
+ const fromDb = Project.get(project.id)
+ expect(fromDb?.icon?.color).toBe("#ff0000")
+ })
+
+ test("should update commands", async () => {
+ await using tmp = await tmpdir({ git: true })
+ const { project } = await Project.fromDirectory(tmp.path)
+
+ const updated = await Project.update({
+ projectID: project.id,
+ commands: { start: "npm run dev" },
+ })
+
+ expect(updated.commands?.start).toBe("npm run dev")
+
+ const fromDb = Project.get(project.id)
+ expect(fromDb?.commands?.start).toBe("npm run dev")
+ })
+
+ test("should throw error when project not found", async () => {
+ await using tmp = await tmpdir({ git: true })
+
+ await expect(
+ Project.update({
+ projectID: "nonexistent-project-id",
+ name: "Should Fail",
+ }),
+ ).rejects.toThrow("Project not found: nonexistent-project-id")
+ })
+
+ test("should emit GlobalBus event on update", async () => {
+ await using tmp = await tmpdir({ git: true })
+ const { project } = await Project.fromDirectory(tmp.path)
+
+ let eventFired = false
+ let eventPayload: any = null
+
+ GlobalBus.on("event", (data) => {
+ eventFired = true
+ eventPayload = data
+ })
+
+ await Project.update({
+ projectID: project.id,
+ name: "Updated Name",
+ })
+
+ expect(eventFired).toBe(true)
+ expect(eventPayload.payload.type).toBe("project.updated")
+ expect(eventPayload.payload.properties.name).toBe("Updated Name")
+ })
+
+ test("should update multiple fields at once", async () => {
+ await using tmp = await tmpdir({ git: true })
+ const { project } = await Project.fromDirectory(tmp.path)
+
+ const updated = await Project.update({
+ projectID: project.id,
+ name: "Multi Update",
+ icon: { url: "https://example.com/favicon.ico", color: "#00ff00" },
+ commands: { start: "make start" },
+ })
+
+ expect(updated.name).toBe("Multi Update")
+ expect(updated.icon?.url).toBe("https://example.com/favicon.ico")
+ expect(updated.icon?.color).toBe("#00ff00")
+ expect(updated.commands?.start).toBe("make start")
})
})
diff --git a/packages/opencode/test/storage/json-migration.test.ts b/packages/opencode/test/storage/json-migration.test.ts
new file mode 100644
index 0000000000..ff05d6d059
--- /dev/null
+++ b/packages/opencode/test/storage/json-migration.test.ts
@@ -0,0 +1,687 @@
+import { describe, test, expect, beforeEach, afterEach } from "bun:test"
+import { Database } from "bun:sqlite"
+import { drizzle } from "drizzle-orm/bun-sqlite"
+import { migrate } from "drizzle-orm/bun-sqlite/migrator"
+import path from "path"
+import fs from "fs/promises"
+import { readFileSync, readdirSync } from "fs"
+import { JsonMigration } from "../../src/storage/json-migration"
+import { Global } from "../../src/global"
+import { ProjectTable } from "../../src/project/project.sql"
+import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../../src/session/session.sql"
+import { SessionShareTable } from "../../src/share/share.sql"
+
+// Test fixtures
+const fixtures = {
+ project: {
+ id: "proj_test123abc",
+ name: "Test Project",
+ worktree: "/test/path",
+ vcs: "git" as const,
+ sandboxes: [],
+ },
+ session: {
+ id: "ses_test456def",
+ projectID: "proj_test123abc",
+ slug: "test-session",
+ directory: "/test/path",
+ title: "Test Session",
+ version: "1.0.0",
+ time: { created: 1700000000000, updated: 1700000001000 },
+ },
+ message: {
+ id: "msg_test789ghi",
+ sessionID: "ses_test456def",
+ role: "user" as const,
+ agent: "default",
+ model: { providerID: "openai", modelID: "gpt-4" },
+ time: { created: 1700000000000 },
+ },
+ part: {
+ id: "prt_testabc123",
+ messageID: "msg_test789ghi",
+ sessionID: "ses_test456def",
+ type: "text" as const,
+ text: "Hello, world!",
+ },
+}
+
+// Helper to create test storage directory structure
+async function setupStorageDir() {
+ const storageDir = path.join(Global.Path.data, "storage")
+ await fs.rm(storageDir, { recursive: true, force: true })
+ await fs.mkdir(path.join(storageDir, "project"), { recursive: true })
+ await fs.mkdir(path.join(storageDir, "session", "proj_test123abc"), { recursive: true })
+ await fs.mkdir(path.join(storageDir, "message", "ses_test456def"), { recursive: true })
+ await fs.mkdir(path.join(storageDir, "part", "msg_test789ghi"), { recursive: true })
+ await fs.mkdir(path.join(storageDir, "session_diff"), { recursive: true })
+ await fs.mkdir(path.join(storageDir, "todo"), { recursive: true })
+ await fs.mkdir(path.join(storageDir, "permission"), { recursive: true })
+ await fs.mkdir(path.join(storageDir, "session_share"), { recursive: true })
+ // Create legacy marker to indicate JSON storage exists
+ await Bun.write(path.join(storageDir, "migration"), "1")
+ return storageDir
+}
+
+async function writeProject(storageDir: string, project: Record) {
+ await Bun.write(path.join(storageDir, "project", `${project.id}.json`), JSON.stringify(project))
+}
+
+async function writeSession(storageDir: string, projectID: string, session: Record) {
+ await Bun.write(path.join(storageDir, "session", projectID, `${session.id}.json`), JSON.stringify(session))
+}
+
+// Helper to create in-memory test database with schema
+function createTestDb() {
+ const sqlite = new Database(":memory:")
+ sqlite.exec("PRAGMA foreign_keys = ON")
+
+ // Apply schema migrations using drizzle migrate
+ const dir = path.join(import.meta.dirname, "../../migration")
+ const entries = readdirSync(dir, { withFileTypes: true })
+ const migrations = entries
+ .filter((entry) => entry.isDirectory())
+ .map((entry) => ({
+ sql: readFileSync(path.join(dir, entry.name, "migration.sql"), "utf-8"),
+ timestamp: Number(entry.name.split("_")[0]),
+ }))
+ .sort((a, b) => a.timestamp - b.timestamp)
+ migrate(drizzle({ client: sqlite }), migrations)
+
+ return sqlite
+}
+
+describe("JSON to SQLite migration", () => {
+ let storageDir: string
+ let sqlite: Database
+
+ beforeEach(async () => {
+ storageDir = await setupStorageDir()
+ sqlite = createTestDb()
+ })
+
+ afterEach(async () => {
+ sqlite.close()
+ await fs.rm(storageDir, { recursive: true, force: true })
+ })
+
+ test("migrates project", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/test/path",
+ vcs: "git",
+ name: "Test Project",
+ time: { created: 1700000000000, updated: 1700000001000 },
+ sandboxes: ["/test/sandbox"],
+ })
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.projects).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ const projects = db.select().from(ProjectTable).all()
+ expect(projects.length).toBe(1)
+ expect(projects[0].id).toBe("proj_test123abc")
+ expect(projects[0].worktree).toBe("/test/path")
+ expect(projects[0].name).toBe("Test Project")
+ expect(projects[0].sandboxes).toEqual(["/test/sandbox"])
+ })
+
+ test("migrates project with commands", async () => {
+ await writeProject(storageDir, {
+ id: "proj_with_commands",
+ worktree: "/test/path",
+ vcs: "git",
+ name: "Project With Commands",
+ time: { created: 1700000000000, updated: 1700000001000 },
+ sandboxes: ["/test/sandbox"],
+ commands: { start: "npm run dev" },
+ })
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.projects).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ const projects = db.select().from(ProjectTable).all()
+ expect(projects.length).toBe(1)
+ expect(projects[0].id).toBe("proj_with_commands")
+ expect(projects[0].commands).toEqual({ start: "npm run dev" })
+ })
+
+ test("migrates project without commands field", async () => {
+ await writeProject(storageDir, {
+ id: "proj_no_commands",
+ worktree: "/test/path",
+ vcs: "git",
+ name: "Project Without Commands",
+ time: { created: 1700000000000, updated: 1700000001000 },
+ sandboxes: [],
+ })
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.projects).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ const projects = db.select().from(ProjectTable).all()
+ expect(projects.length).toBe(1)
+ expect(projects[0].id).toBe("proj_no_commands")
+ expect(projects[0].commands).toBeNull()
+ })
+
+ test("migrates session with individual columns", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/test/path",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+
+ await writeSession(storageDir, "proj_test123abc", {
+ id: "ses_test456def",
+ projectID: "proj_test123abc",
+ slug: "test-session",
+ directory: "/test/dir",
+ title: "Test Session Title",
+ version: "1.0.0",
+ time: { created: 1700000000000, updated: 1700000001000 },
+ summary: { additions: 10, deletions: 5, files: 3 },
+ share: { url: "https://example.com/share" },
+ })
+
+ await JsonMigration.run(sqlite)
+
+ const db = drizzle({ client: sqlite })
+ const sessions = db.select().from(SessionTable).all()
+ expect(sessions.length).toBe(1)
+ expect(sessions[0].id).toBe("ses_test456def")
+ expect(sessions[0].project_id).toBe("proj_test123abc")
+ expect(sessions[0].slug).toBe("test-session")
+ expect(sessions[0].title).toBe("Test Session Title")
+ expect(sessions[0].summary_additions).toBe(10)
+ expect(sessions[0].summary_deletions).toBe(5)
+ expect(sessions[0].share_url).toBe("https://example.com/share")
+ })
+
+ test("migrates messages and parts", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+ await writeSession(storageDir, "proj_test123abc", { ...fixtures.session })
+ await Bun.write(
+ path.join(storageDir, "message", "ses_test456def", "msg_test789ghi.json"),
+ JSON.stringify({ ...fixtures.message }),
+ )
+ await Bun.write(
+ path.join(storageDir, "part", "msg_test789ghi", "prt_testabc123.json"),
+ JSON.stringify({ ...fixtures.part }),
+ )
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.messages).toBe(1)
+ expect(stats?.parts).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ const messages = db.select().from(MessageTable).all()
+ expect(messages.length).toBe(1)
+ expect(messages[0].id).toBe("msg_test789ghi")
+
+ const parts = db.select().from(PartTable).all()
+ expect(parts.length).toBe(1)
+ expect(parts[0].id).toBe("prt_testabc123")
+ })
+
+ test("migrates legacy parts without ids in body", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+ await writeSession(storageDir, "proj_test123abc", { ...fixtures.session })
+ await Bun.write(
+ path.join(storageDir, "message", "ses_test456def", "msg_test789ghi.json"),
+ JSON.stringify({
+ role: "user",
+ agent: "default",
+ model: { providerID: "openai", modelID: "gpt-4" },
+ time: { created: 1700000000000 },
+ }),
+ )
+ await Bun.write(
+ path.join(storageDir, "part", "msg_test789ghi", "prt_testabc123.json"),
+ JSON.stringify({
+ type: "text",
+ text: "Hello, world!",
+ }),
+ )
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.messages).toBe(1)
+ expect(stats?.parts).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ const messages = db.select().from(MessageTable).all()
+ expect(messages.length).toBe(1)
+ expect(messages[0].id).toBe("msg_test789ghi")
+ expect(messages[0].session_id).toBe("ses_test456def")
+ expect(messages[0].data).not.toHaveProperty("id")
+ expect(messages[0].data).not.toHaveProperty("sessionID")
+
+ const parts = db.select().from(PartTable).all()
+ expect(parts.length).toBe(1)
+ expect(parts[0].id).toBe("prt_testabc123")
+ expect(parts[0].message_id).toBe("msg_test789ghi")
+ expect(parts[0].session_id).toBe("ses_test456def")
+ expect(parts[0].data).not.toHaveProperty("id")
+ expect(parts[0].data).not.toHaveProperty("messageID")
+ expect(parts[0].data).not.toHaveProperty("sessionID")
+ })
+
+ test("skips orphaned sessions (no parent project)", async () => {
+ await Bun.write(
+ path.join(storageDir, "session", "proj_test123abc", "ses_orphan.json"),
+ JSON.stringify({
+ id: "ses_orphan",
+ projectID: "proj_nonexistent",
+ slug: "orphan",
+ directory: "/",
+ title: "Orphan",
+ version: "1.0.0",
+ time: { created: Date.now(), updated: Date.now() },
+ }),
+ )
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.sessions).toBe(0)
+ })
+
+ test("is idempotent (running twice doesn't duplicate)", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+
+ await JsonMigration.run(sqlite)
+ await JsonMigration.run(sqlite)
+
+ const db = drizzle({ client: sqlite })
+ const projects = db.select().from(ProjectTable).all()
+ expect(projects.length).toBe(1) // Still only 1 due to onConflictDoNothing
+ })
+
+ test("migrates todos", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+ await writeSession(storageDir, "proj_test123abc", { ...fixtures.session })
+
+ // Create todo file (named by sessionID, contains array of todos)
+ await Bun.write(
+ path.join(storageDir, "todo", "ses_test456def.json"),
+ JSON.stringify([
+ {
+ id: "todo_1",
+ content: "First todo",
+ status: "pending",
+ priority: "high",
+ },
+ {
+ id: "todo_2",
+ content: "Second todo",
+ status: "completed",
+ priority: "medium",
+ },
+ ]),
+ )
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.todos).toBe(2)
+
+ const db = drizzle({ client: sqlite })
+ const todos = db.select().from(TodoTable).orderBy(TodoTable.position).all()
+ expect(todos.length).toBe(2)
+ expect(todos[0].content).toBe("First todo")
+ expect(todos[0].status).toBe("pending")
+ expect(todos[0].priority).toBe("high")
+ expect(todos[0].position).toBe(0)
+ expect(todos[1].content).toBe("Second todo")
+ expect(todos[1].position).toBe(1)
+ })
+
+ test("todos are ordered by position", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+ await writeSession(storageDir, "proj_test123abc", { ...fixtures.session })
+
+ await Bun.write(
+ path.join(storageDir, "todo", "ses_test456def.json"),
+ JSON.stringify([
+ { content: "Third", status: "pending", priority: "low" },
+ { content: "First", status: "pending", priority: "high" },
+ { content: "Second", status: "in_progress", priority: "medium" },
+ ]),
+ )
+
+ await JsonMigration.run(sqlite)
+
+ const db = drizzle({ client: sqlite })
+ const todos = db.select().from(TodoTable).orderBy(TodoTable.position).all()
+
+ expect(todos.length).toBe(3)
+ expect(todos[0].content).toBe("Third")
+ expect(todos[0].position).toBe(0)
+ expect(todos[1].content).toBe("First")
+ expect(todos[1].position).toBe(1)
+ expect(todos[2].content).toBe("Second")
+ expect(todos[2].position).toBe(2)
+ })
+
+ test("migrates permissions", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+
+ // Create permission file (named by projectID, contains array of rules)
+ const permissionData = [
+ { permission: "file.read", pattern: "/test/file1.ts", action: "allow" as const },
+ { permission: "file.write", pattern: "/test/file2.ts", action: "ask" as const },
+ { permission: "command.run", pattern: "npm install", action: "deny" as const },
+ ]
+ await Bun.write(path.join(storageDir, "permission", "proj_test123abc.json"), JSON.stringify(permissionData))
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.permissions).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ const permissions = db.select().from(PermissionTable).all()
+ expect(permissions.length).toBe(1)
+ expect(permissions[0].project_id).toBe("proj_test123abc")
+ expect(permissions[0].data).toEqual(permissionData)
+ })
+
+ test("migrates session shares", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+ await writeSession(storageDir, "proj_test123abc", { ...fixtures.session })
+
+ // Create session share file (named by sessionID)
+ await Bun.write(
+ path.join(storageDir, "session_share", "ses_test456def.json"),
+ JSON.stringify({
+ id: "share_123",
+ secret: "supersecretkey",
+ url: "https://share.example.com/ses_test456def",
+ }),
+ )
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats?.shares).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ const shares = db.select().from(SessionShareTable).all()
+ expect(shares.length).toBe(1)
+ expect(shares[0].session_id).toBe("ses_test456def")
+ expect(shares[0].id).toBe("share_123")
+ expect(shares[0].secret).toBe("supersecretkey")
+ expect(shares[0].url).toBe("https://share.example.com/ses_test456def")
+ })
+
+ test("returns empty stats when storage directory does not exist", async () => {
+ await fs.rm(storageDir, { recursive: true, force: true })
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats.projects).toBe(0)
+ expect(stats.sessions).toBe(0)
+ expect(stats.messages).toBe(0)
+ expect(stats.parts).toBe(0)
+ expect(stats.todos).toBe(0)
+ expect(stats.permissions).toBe(0)
+ expect(stats.shares).toBe(0)
+ expect(stats.errors).toEqual([])
+ })
+
+ test("continues when a JSON file is unreadable and records an error", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+ await Bun.write(path.join(storageDir, "project", "broken.json"), "{ invalid json")
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats.projects).toBe(1)
+ expect(stats.errors.some((x) => x.includes("failed to read") && x.includes("broken.json"))).toBe(true)
+
+ const db = drizzle({ client: sqlite })
+ const projects = db.select().from(ProjectTable).all()
+ expect(projects.length).toBe(1)
+ expect(projects[0].id).toBe("proj_test123abc")
+ })
+
+ test("skips invalid todo entries while preserving source positions", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+ await writeSession(storageDir, "proj_test123abc", { ...fixtures.session })
+
+ await Bun.write(
+ path.join(storageDir, "todo", "ses_test456def.json"),
+ JSON.stringify([
+ { content: "keep-0", status: "pending", priority: "high" },
+ { content: "drop-1", priority: "low" },
+ { content: "keep-2", status: "completed", priority: "medium" },
+ ]),
+ )
+
+ const stats = await JsonMigration.run(sqlite)
+ expect(stats.todos).toBe(2)
+
+ const db = drizzle({ client: sqlite })
+ const todos = db.select().from(TodoTable).orderBy(TodoTable.position).all()
+ expect(todos.length).toBe(2)
+ expect(todos[0].content).toBe("keep-0")
+ expect(todos[0].position).toBe(0)
+ expect(todos[1].content).toBe("keep-2")
+ expect(todos[1].position).toBe(2)
+ })
+
+ test("skips orphaned todos, permissions, and shares", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/",
+ time: { created: Date.now(), updated: Date.now() },
+ sandboxes: [],
+ })
+ await writeSession(storageDir, "proj_test123abc", { ...fixtures.session })
+
+ await Bun.write(
+ path.join(storageDir, "todo", "ses_test456def.json"),
+ JSON.stringify([{ content: "valid", status: "pending", priority: "high" }]),
+ )
+ await Bun.write(
+ path.join(storageDir, "todo", "ses_missing.json"),
+ JSON.stringify([{ content: "orphan", status: "pending", priority: "high" }]),
+ )
+
+ await Bun.write(
+ path.join(storageDir, "permission", "proj_test123abc.json"),
+ JSON.stringify([{ permission: "file.read" }]),
+ )
+ await Bun.write(
+ path.join(storageDir, "permission", "proj_missing.json"),
+ JSON.stringify([{ permission: "file.write" }]),
+ )
+
+ await Bun.write(
+ path.join(storageDir, "session_share", "ses_test456def.json"),
+ JSON.stringify({ id: "share_ok", secret: "secret", url: "https://ok.example.com" }),
+ )
+ await Bun.write(
+ path.join(storageDir, "session_share", "ses_missing.json"),
+ JSON.stringify({ id: "share_missing", secret: "secret", url: "https://missing.example.com" }),
+ )
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats.todos).toBe(1)
+ expect(stats.permissions).toBe(1)
+ expect(stats.shares).toBe(1)
+
+ const db = drizzle({ client: sqlite })
+ expect(db.select().from(TodoTable).all().length).toBe(1)
+ expect(db.select().from(PermissionTable).all().length).toBe(1)
+ expect(db.select().from(SessionShareTable).all().length).toBe(1)
+ })
+
+ test("handles mixed corruption and partial validity in one migration run", async () => {
+ await writeProject(storageDir, {
+ id: "proj_test123abc",
+ worktree: "/ok",
+ time: { created: 1700000000000, updated: 1700000001000 },
+ sandboxes: [],
+ })
+ await Bun.write(
+ path.join(storageDir, "project", "proj_missing_id.json"),
+ JSON.stringify({ worktree: "/bad", sandboxes: [] }),
+ )
+ await Bun.write(path.join(storageDir, "project", "proj_broken.json"), "{ nope")
+
+ await writeSession(storageDir, "proj_test123abc", {
+ id: "ses_test456def",
+ projectID: "proj_test123abc",
+ slug: "ok",
+ directory: "/ok",
+ title: "Ok",
+ version: "1",
+ time: { created: 1700000000000, updated: 1700000001000 },
+ })
+ await Bun.write(
+ path.join(storageDir, "session", "proj_test123abc", "ses_missing_project.json"),
+ JSON.stringify({
+ id: "ses_missing_project",
+ slug: "bad",
+ directory: "/bad",
+ title: "Bad",
+ version: "1",
+ }),
+ )
+ await Bun.write(
+ path.join(storageDir, "session", "proj_test123abc", "ses_orphan.json"),
+ JSON.stringify({
+ id: "ses_orphan",
+ projectID: "proj_missing",
+ slug: "orphan",
+ directory: "/bad",
+ title: "Orphan",
+ version: "1",
+ }),
+ )
+
+ await Bun.write(
+ path.join(storageDir, "message", "ses_test456def", "msg_ok.json"),
+ JSON.stringify({ role: "user", time: { created: 1700000000000 } }),
+ )
+ await Bun.write(path.join(storageDir, "message", "ses_test456def", "msg_broken.json"), "{ nope")
+ await Bun.write(
+ path.join(storageDir, "message", "ses_missing", "msg_orphan.json"),
+ JSON.stringify({ role: "user", time: { created: 1700000000000 } }),
+ )
+
+ await Bun.write(
+ path.join(storageDir, "part", "msg_ok", "part_ok.json"),
+ JSON.stringify({ type: "text", text: "ok" }),
+ )
+ await Bun.write(
+ path.join(storageDir, "part", "msg_missing", "part_missing_message.json"),
+ JSON.stringify({ type: "text", text: "bad" }),
+ )
+ await Bun.write(path.join(storageDir, "part", "msg_ok", "part_broken.json"), "{ nope")
+
+ await Bun.write(
+ path.join(storageDir, "todo", "ses_test456def.json"),
+ JSON.stringify([
+ { content: "ok", status: "pending", priority: "high" },
+ { content: "skip", status: "pending" },
+ ]),
+ )
+ await Bun.write(
+ path.join(storageDir, "todo", "ses_missing.json"),
+ JSON.stringify([{ content: "orphan", status: "pending", priority: "high" }]),
+ )
+ await Bun.write(path.join(storageDir, "todo", "ses_broken.json"), "{ nope")
+
+ await Bun.write(
+ path.join(storageDir, "permission", "proj_test123abc.json"),
+ JSON.stringify([{ permission: "file.read" }]),
+ )
+ await Bun.write(
+ path.join(storageDir, "permission", "proj_missing.json"),
+ JSON.stringify([{ permission: "file.write" }]),
+ )
+ await Bun.write(path.join(storageDir, "permission", "proj_broken.json"), "{ nope")
+
+ await Bun.write(
+ path.join(storageDir, "session_share", "ses_test456def.json"),
+ JSON.stringify({ id: "share_ok", secret: "secret", url: "https://ok.example.com" }),
+ )
+ await Bun.write(
+ path.join(storageDir, "session_share", "ses_missing.json"),
+ JSON.stringify({ id: "share_orphan", secret: "secret", url: "https://missing.example.com" }),
+ )
+ await Bun.write(path.join(storageDir, "session_share", "ses_broken.json"), "{ nope")
+
+ const stats = await JsonMigration.run(sqlite)
+
+ expect(stats.projects).toBe(1)
+ expect(stats.sessions).toBe(1)
+ expect(stats.messages).toBe(1)
+ expect(stats.parts).toBe(1)
+ expect(stats.todos).toBe(1)
+ expect(stats.permissions).toBe(1)
+ expect(stats.shares).toBe(1)
+ expect(stats.errors.length).toBeGreaterThanOrEqual(6)
+
+ const db = drizzle({ client: sqlite })
+ expect(db.select().from(ProjectTable).all().length).toBe(1)
+ expect(db.select().from(SessionTable).all().length).toBe(1)
+ expect(db.select().from(MessageTable).all().length).toBe(1)
+ expect(db.select().from(PartTable).all().length).toBe(1)
+ expect(db.select().from(TodoTable).all().length).toBe(1)
+ expect(db.select().from(PermissionTable).all().length).toBe(1)
+ expect(db.select().from(SessionShareTable).all().length).toBe(1)
+ })
+})
diff --git a/packages/plugin/package.json b/packages/plugin/package.json
index d88d5a7ba3..481404969c 100644
--- a/packages/plugin/package.json
+++ b/packages/plugin/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
- "version": "1.1.64",
+ "version": "1.2.1",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts
index 664f2c9673..bd4ba53049 100644
--- a/packages/plugin/src/index.ts
+++ b/packages/plugin/src/index.ts
@@ -224,4 +224,8 @@ export interface Hooks {
input: { sessionID: string; messageID: string; partID: string },
output: { text: string },
) => Promise
+ /**
+ * Modify tool definitions (description and parameters) sent to LLM
+ */
+ "tool.definition"?: (input: { toolID: string }, output: { description: string; parameters: any }) => Promise
}
diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json
index 13d0b549ba..e141774821 100644
--- a/packages/sdk/js/package.json
+++ b/packages/sdk/js/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
- "version": "1.1.64",
+ "version": "1.2.1",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts
index b22b7e9af4..efb7e202e1 100644
--- a/packages/sdk/js/src/v2/gen/types.gen.ts
+++ b/packages/sdk/js/src/v2/gen/types.gen.ts
@@ -525,7 +525,17 @@ export type EventMessagePartUpdated = {
type: "message.part.updated"
properties: {
part: Part
- delta?: string
+ }
+}
+
+export type EventMessagePartDelta = {
+ type: "message.part.delta"
+ properties: {
+ sessionID: string
+ messageID: string
+ partID: string
+ field: string
+ delta: string
}
}
@@ -695,10 +705,6 @@ export type Todo = {
* Priority level of the task: high, medium, low
*/
priority: string
- /**
- * Unique identifier for the todo item
- */
- id: string
}
export type EventTodoUpdated = {
@@ -948,6 +954,7 @@ export type Event =
| EventMessageUpdated
| EventMessageRemoved
| EventMessagePartUpdated
+ | EventMessagePartDelta
| EventMessagePartRemoved
| EventPermissionAsked
| EventPermissionReplied
diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json
index 70596431bb..85a1af9d70 100644
--- a/packages/sdk/openapi.json
+++ b/packages/sdk/openapi.json
@@ -7338,12 +7338,40 @@
"properties": {
"part": {
"$ref": "#/components/schemas/Part"
+ }
+ },
+ "required": ["part"]
+ }
+ },
+ "required": ["type", "properties"]
+ },
+ "Event.message.part.delta": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "message.part.delta"
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "type": "string"
+ },
+ "messageID": {
+ "type": "string"
+ },
+ "partID": {
+ "type": "string"
+ },
+ "field": {
+ "type": "string"
},
"delta": {
"type": "string"
}
},
- "required": ["part"]
+ "required": ["sessionID", "messageID", "partID", "field", "delta"]
}
},
"required": ["type", "properties"]
@@ -7757,13 +7785,9 @@
"priority": {
"description": "Priority level of the task: high, medium, low",
"type": "string"
- },
- "id": {
- "description": "Unique identifier for the todo item",
- "type": "string"
}
},
- "required": ["content", "status", "priority", "id"]
+ "required": ["content", "status", "priority"]
},
"Event.todo.updated": {
"type": "object",
@@ -8434,6 +8458,9 @@
{
"$ref": "#/components/schemas/Event.message.part.updated"
},
+ {
+ "$ref": "#/components/schemas/Event.message.part.delta"
+ },
{
"$ref": "#/components/schemas/Event.message.part.removed"
},
diff --git a/packages/slack/package.json b/packages/slack/package.json
index 7f0eaff83f..d9234899d9 100644
--- a/packages/slack/package.json
+++ b/packages/slack/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
- "version": "1.1.64",
+ "version": "1.2.1",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 6d20e3dfdc..a0e5c2af25 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
- "version": "1.1.64",
+ "version": "1.2.1",
"type": "module",
"license": "MIT",
"exports": {
diff --git a/packages/ui/src/components/code.tsx b/packages/ui/src/components/code.tsx
index abe0d7ca9e..837cc53376 100644
--- a/packages/ui/src/components/code.tsx
+++ b/packages/ui/src/components/code.tsx
@@ -1,10 +1,27 @@
-import { type FileContents, File, FileOptions, LineAnnotation, type SelectedLineRange } from "@pierre/diffs"
+import {
+ DEFAULT_VIRTUAL_FILE_METRICS,
+ type FileContents,
+ File,
+ FileOptions,
+ LineAnnotation,
+ type SelectedLineRange,
+ type VirtualFileMetrics,
+ VirtualizedFile,
+ Virtualizer,
+} from "@pierre/diffs"
import { ComponentProps, createEffect, createMemo, createSignal, onCleanup, onMount, Show, splitProps } from "solid-js"
import { Portal } from "solid-js/web"
import { createDefaultOptions, styleVariables } from "../pierre"
import { getWorkerPool } from "../pierre/worker"
import { Icon } from "./icon"
+const VIRTUALIZE_BYTES = 500_000
+const codeMetrics = {
+ ...DEFAULT_VIRTUAL_FILE_METRICS,
+ lineHeight: 24,
+ fileGap: 0,
+} satisfies Partial
+
type SelectionSide = "additions" | "deletions"
export type CodeProps = FileOptions & {
@@ -160,16 +177,28 @@ export function Code(props: CodeProps) {
const [findPos, setFindPos] = createSignal<{ top: number; right: number }>({ top: 8, right: 8 })
- const file = createMemo(
- () =>
- new File(
- {
- ...createDefaultOptions("unified"),
- ...others,
- },
- getWorkerPool("unified"),
- ),
- )
+ let instance: File | VirtualizedFile | undefined
+ let virtualizer: Virtualizer | undefined
+ let virtualRoot: Document | HTMLElement | undefined
+
+ const bytes = createMemo(() => {
+ const value = local.file.contents as unknown
+ if (typeof value === "string") return value.length
+ if (Array.isArray(value)) {
+ return value.reduce(
+ (acc, part) => acc + (typeof part === "string" ? part.length + 1 : String(part).length + 1),
+ 0,
+ )
+ }
+ if (value == null) return 0
+ return String(value).length
+ })
+ const virtual = createMemo(() => bytes() > VIRTUALIZE_BYTES)
+
+ const options = createMemo(() => ({
+ ...createDefaultOptions("unified"),
+ ...others,
+ }))
const getRoot = () => {
const host = container.querySelector("diffs-container")
@@ -577,6 +606,14 @@ export function Code(props: CodeProps) {
}
const applySelection = (range: SelectedLineRange | null) => {
+ const current = instance
+ if (!current) return false
+
+ if (virtual()) {
+ current.setSelectedLines(range)
+ return true
+ }
+
const root = getRoot()
if (!root) return false
@@ -584,7 +621,7 @@ export function Code(props: CodeProps) {
if (root.querySelectorAll("[data-line]").length < lines) return false
if (!range) {
- file().setSelectedLines(null)
+ current.setSelectedLines(null)
return true
}
@@ -592,12 +629,12 @@ export function Code(props: CodeProps) {
const end = Math.max(range.start, range.end)
if (start < 1 || end > lines) {
- file().setSelectedLines(null)
+ current.setSelectedLines(null)
return true
}
if (!root.querySelector(`[data-line="${start}"]`) || !root.querySelector(`[data-line="${end}"]`)) {
- file().setSelectedLines(null)
+ current.setSelectedLines(null)
return true
}
@@ -608,7 +645,7 @@ export function Code(props: CodeProps) {
return { start: range.start, end: range.end }
})()
- file().setSelectedLines(normalized)
+ current.setSelectedLines(normalized)
return true
}
@@ -619,9 +656,12 @@ export function Code(props: CodeProps) {
const token = renderToken
- const lines = lineCount()
+ const lines = virtual() ? undefined : lineCount()
- const isReady = (root: ShadowRoot) => root.querySelectorAll("[data-line]").length >= lines
+ const isReady = (root: ShadowRoot) =>
+ virtual()
+ ? root.querySelector("[data-line]") != null
+ : root.querySelectorAll("[data-line]").length >= (lines ?? 0)
const notify = () => {
if (token !== renderToken) return
@@ -844,20 +884,41 @@ export function Code(props: CodeProps) {
}
createEffect(() => {
- const current = file()
+ const opts = options()
+ const workerPool = getWorkerPool("unified")
+ const isVirtual = virtual()
- onCleanup(() => {
- current.cleanUp()
- })
- })
-
- createEffect(() => {
observer?.disconnect()
observer = undefined
+ instance?.cleanUp()
+ instance = undefined
+
+ if (!isVirtual && virtualizer) {
+ virtualizer.cleanUp()
+ virtualizer = undefined
+ virtualRoot = undefined
+ }
+
+ const v = (() => {
+ if (!isVirtual) return
+ if (typeof document === "undefined") return
+
+ const root = getScrollParent(wrapper) ?? document
+ if (virtualizer && virtualRoot === root) return virtualizer
+
+ virtualizer?.cleanUp()
+ virtualizer = new Virtualizer()
+ virtualRoot = root
+ virtualizer.setup(root, root instanceof Document ? undefined : wrapper)
+ return virtualizer
+ })()
+
+ instance = isVirtual && v ? new VirtualizedFile(opts, v, codeMetrics, workerPool) : new File(opts, workerPool)
+
container.innerHTML = ""
const value = text()
- file().render({
+ instance.render({
file: typeof local.file.contents === "string" ? local.file : { ...local.file, contents: value },
lineAnnotations: local.annotations,
containerWrapper: container,
@@ -910,6 +971,13 @@ export function Code(props: CodeProps) {
onCleanup(() => {
observer?.disconnect()
+ instance?.cleanUp()
+ instance = undefined
+
+ virtualizer?.cleanUp()
+ virtualizer = undefined
+ virtualRoot = undefined
+
clearOverlayScroll()
clearOverlay()
if (findCurrent === host) {
diff --git a/packages/ui/src/components/diff.tsx b/packages/ui/src/components/diff.tsx
index 0966db75e0..0002232b01 100644
--- a/packages/ui/src/components/diff.tsx
+++ b/packages/ui/src/components/diff.tsx
@@ -1,5 +1,5 @@
-import { checksum } from "@opencode-ai/util/encode"
-import { FileDiff, type SelectedLineRange, VirtualizedFileDiff } from "@pierre/diffs"
+import { sampledChecksum } from "@opencode-ai/util/encode"
+import { FileDiff, type FileDiffOptions, type SelectedLineRange, VirtualizedFileDiff } from "@pierre/diffs"
import { createMediaQuery } from "@solid-primitives/media"
import { createEffect, createMemo, createSignal, onCleanup, splitProps } from "solid-js"
import { createDefaultOptions, type DiffProps, styleVariables } from "../pierre"
@@ -78,14 +78,29 @@ export function Diff(props: DiffProps) {
const mobile = createMediaQuery("(max-width: 640px)")
- const options = createMemo(() => {
- const opts = {
+ const large = createMemo(() => {
+ const before = typeof local.before?.contents === "string" ? local.before.contents : ""
+ const after = typeof local.after?.contents === "string" ? local.after.contents : ""
+ return Math.max(before.length, after.length) > 500_000
+ })
+
+ const largeOptions = {
+ lineDiffType: "none",
+ maxLineDiffLength: 0,
+ tokenizeMaxLineLength: 1,
+ } satisfies Pick, "lineDiffType" | "maxLineDiffLength" | "tokenizeMaxLineLength">
+
+ const options = createMemo>(() => {
+ const base = {
...createDefaultOptions(props.diffStyle),
...others,
}
- if (!mobile()) return opts
+
+ const perf = large() ? { ...base, ...largeOptions } : base
+ if (!mobile()) return perf
+
return {
- ...opts,
+ ...perf,
disableLineNumbers: true,
}
})
@@ -528,12 +543,17 @@ export function Diff(props: DiffProps) {
createEffect(() => {
const opts = options()
- const workerPool = getWorkerPool(props.diffStyle)
+ const workerPool = large() ? getWorkerPool("unified") : getWorkerPool(props.diffStyle)
const virtualizer = getVirtualizer()
const annotations = local.annotations
const beforeContents = typeof local.before?.contents === "string" ? local.before.contents : ""
const afterContents = typeof local.after?.contents === "string" ? local.after.contents : ""
+ const cacheKey = (contents: string) => {
+ if (!large()) return sampledChecksum(contents, contents.length)
+ return sampledChecksum(contents)
+ }
+
instance?.cleanUp()
instance = virtualizer
? new VirtualizedFileDiff(opts, virtualizer, virtualMetrics, workerPool)
@@ -545,12 +565,12 @@ export function Diff(props: DiffProps) {
oldFile: {
...local.before,
contents: beforeContents,
- cacheKey: checksum(beforeContents),
+ cacheKey: cacheKey(beforeContents),
},
newFile: {
...local.after,
contents: afterContents,
- cacheKey: checksum(afterContents),
+ cacheKey: cacheKey(afterContents),
},
lineAnnotations: annotations,
containerWrapper: container,
diff --git a/packages/ui/src/components/markdown.css b/packages/ui/src/components/markdown.css
index 68ae93bda4..27c8f238d5 100644
--- a/packages/ui/src/components/markdown.css
+++ b/packages/ui/src/components/markdown.css
@@ -209,3 +209,8 @@
display: block;
}
}
+
+[data-component="markdown"] a.external-link:hover > code {
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
diff --git a/packages/ui/src/components/markdown.tsx b/packages/ui/src/components/markdown.tsx
index 608db818f5..4c3d562841 100644
--- a/packages/ui/src/components/markdown.tsx
+++ b/packages/ui/src/components/markdown.tsx
@@ -49,6 +49,19 @@ type CopyLabels = {
copied: string
}
+const urlPattern = /^https?:\/\/[^\s<>()`"']+$/
+
+function codeUrl(text: string) {
+ const href = text.trim().replace(/[),.;!?]+$/, "")
+ if (!urlPattern.test(href)) return
+ try {
+ const url = new URL(href)
+ return url.toString()
+ } catch {
+ return
+ }
+}
+
function createIcon(path: string, slot: string) {
const icon = document.createElement("div")
icon.setAttribute("data-component", "icon")
@@ -110,9 +123,39 @@ function setupCodeCopy(root: HTMLDivElement, labels: CopyLabels) {
wrapper.appendChild(createCopyButton(labels))
}
+ const markCodeLinks = () => {
+ const codeNodes = Array.from(root.querySelectorAll(":not(pre) > code"))
+ for (const code of codeNodes) {
+ const href = codeUrl(code.textContent ?? "")
+ const parentLink =
+ code.parentElement instanceof HTMLAnchorElement && code.parentElement.classList.contains("external-link")
+ ? code.parentElement
+ : null
+
+ if (!href) {
+ if (parentLink) parentLink.replaceWith(code)
+ continue
+ }
+
+ if (parentLink) {
+ parentLink.href = href
+ continue
+ }
+
+ const link = document.createElement("a")
+ link.href = href
+ link.className = "external-link"
+ link.target = "_blank"
+ link.rel = "noopener noreferrer"
+ code.parentNode?.replaceChild(link, code)
+ link.appendChild(code)
+ }
+ }
+
const handleClick = async (event: MouseEvent) => {
const target = event.target
if (!(target instanceof Element)) return
+
const button = target.closest('[data-slot="markdown-copy-button"]')
if (!(button instanceof HTMLButtonElement)) return
const code = button.closest('[data-component="markdown-code"]')?.querySelector("code")
@@ -132,6 +175,7 @@ function setupCodeCopy(root: HTMLDivElement, labels: CopyLabels) {
for (const block of blocks) {
ensureWrapper(block)
}
+ markCodeLinks()
const buttons = Array.from(root.querySelectorAll('[data-slot="markdown-copy-button"]'))
for (const button of buttons) {
diff --git a/packages/ui/src/components/session-review.css b/packages/ui/src/components/session-review.css
index 30bfe3b712..46473b75e5 100644
--- a/packages/ui/src/components/session-review.css
+++ b/packages/ui/src/components/session-review.css
@@ -222,4 +222,30 @@
--line-comment-popover-z: 30;
--line-comment-open-z: 6;
}
+
+ [data-slot="session-review-large-diff"] {
+ padding: 12px;
+ background: var(--background-stronger);
+ }
+
+ [data-slot="session-review-large-diff-title"] {
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-medium);
+ color: var(--text-strong);
+ margin-bottom: 4px;
+ }
+
+ [data-slot="session-review-large-diff-meta"] {
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ color: var(--text-weak);
+ word-break: break-word;
+ }
+
+ [data-slot="session-review-large-diff-actions"] {
+ display: flex;
+ gap: 8px;
+ margin-top: 10px;
+ }
}
diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx
index fe2475548e..5f1e6b1aba 100644
--- a/packages/ui/src/components/session-review.tsx
+++ b/packages/ui/src/components/session-review.tsx
@@ -17,6 +17,26 @@ import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
import { type SelectedLineRange } from "@pierre/diffs"
import { Dynamic } from "solid-js/web"
+const MAX_DIFF_LINES = 20_000
+const MAX_DIFF_BYTES = 2_000_000
+
+function linesOver(text: string, max: number) {
+ let lines = 1
+ for (let i = 0; i < text.length; i++) {
+ if (text.charCodeAt(i) !== 10) continue
+ lines++
+ if (lines > max) return true
+ }
+ return lines > max
+}
+
+function formatBytes(bytes: number) {
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 B"
+ if (bytes < 1024) return `${bytes} B`
+ if (bytes < 1024 * 1024) return `${Math.round((bytes / 1024) * 10) / 10} KB`
+ return `${Math.round((bytes / (1024 * 1024)) * 10) / 10} MB`
+}
+
export type SessionReviewDiffStyle = "unified" | "split"
export type SessionReviewComment = {
@@ -326,12 +346,28 @@ export const SessionReview = (props: SessionReviewProps) => {
{(diff) => {
let wrapper: HTMLDivElement | undefined
+ const expanded = createMemo(() => open().includes(diff.file))
+ const [force, setForce] = createSignal(false)
+
const comments = createMemo(() => (props.comments ?? []).filter((c) => c.file === diff.file))
const commentedLines = createMemo(() => comments().map((c) => c.selection))
const beforeText = () => (typeof diff.before === "string" ? diff.before : "")
const afterText = () => (typeof diff.after === "string" ? diff.after : "")
+ const tooLarge = createMemo(() => {
+ if (!expanded()) return false
+ if (force()) return false
+ if (isImageFile(diff.file)) return false
+
+ const before = beforeText()
+ const after = afterText()
+
+ if (before.length > MAX_DIFF_BYTES || after.length > MAX_DIFF_BYTES) return true
+ if (linesOver(before, MAX_DIFF_LINES) || linesOver(after, MAX_DIFF_LINES)) return true
+ return false
+ })
+
const isAdded = () => diff.status === "added" || (beforeText().length === 0 && afterText().length > 0)
const isDeleted = () =>
diff.status === "deleted" || (afterText().length === 0 && beforeText().length > 0)
@@ -571,94 +607,114 @@ export const SessionReview = (props: SessionReviewProps) => {
scheduleAnchors()
}}
>
-
-
-
-
-
-
-
-
-
- {i18n.t("ui.sessionReview.change.removed")}
-
-
-
-
-
-
- {imageStatus() === "loading" ? "Loading..." : "Image"}
-
-
-
-
- {
- props.onDiffRendered?.()
- scheduleAnchors()
- }}
- enableLineSelection={props.onLineComment != null}
- onLineSelected={handleLineSelected}
- onLineSelectionEnd={handleLineSelectionEnd}
- selectedLines={selectedLines()}
- commentedLines={commentedLines()}
- before={{
- name: diff.file!,
- contents: typeof diff.before === "string" ? diff.before : "",
- }}
- after={{
- name: diff.file!,
- contents: typeof diff.after === "string" ? diff.after : "",
- }}
- />
-
-
-
-
- {(comment) => (
-
-
-
- {(range) => (
-
- setCommenting(null)}
- onSubmit={(comment) => {
- props.onLineComment?.({
- file: diff.file,
- selection: range(),
- comment,
- preview: selectionPreview(diff, range()),
- })
- setCommenting(null)
+
+
+
+
+
+
+
+
+
+
+ {i18n.t("ui.sessionReview.change.removed")}
+
+
+
+
+
+
+ {imageStatus() === "loading"
+ ? i18n.t("ui.sessionReview.image.loading")
+ : i18n.t("ui.sessionReview.image.placeholder")}
+
+
+
+
+
+
+ {i18n.t("ui.sessionReview.largeDiff.title")}
+
+
+ Limit: {MAX_DIFF_LINES.toLocaleString()} lines / {formatBytes(MAX_DIFF_BYTES)}.
+ Current: {formatBytes(Math.max(beforeText().length, afterText().length))}.
+
+
+ setForce(true)}>
+ {i18n.t("ui.sessionReview.largeDiff.renderAnyway")}
+
+
+
+
+
+ {
+ props.onDiffRendered?.()
+ scheduleAnchors()
+ }}
+ enableLineSelection={props.onLineComment != null}
+ onLineSelected={handleLineSelected}
+ onLineSelectionEnd={handleLineSelectionEnd}
+ selectedLines={selectedLines()}
+ commentedLines={commentedLines()}
+ before={{
+ name: diff.file!,
+ contents: typeof diff.before === "string" ? diff.before : "",
+ }}
+ after={{
+ name: diff.file!,
+ contents: typeof diff.after === "string" ? diff.after : "",
}}
/>
-
- )}
+
+
+
+
+ {(comment) => (
+
+
+
+ {(range) => (
+
+ setCommenting(null)}
+ onSubmit={(comment) => {
+ props.onLineComment?.({
+ file: diff.file,
+ selection: range(),
+ comment,
+ preview: selectionPreview(diff, range()),
+ })
+ setCommenting(null)
+ }}
+ />
+
+ )}
+
diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts
index 7ee17e2e01..9a6c8dcbd0 100644
--- a/packages/ui/src/i18n/ar.ts
+++ b/packages/ui/src/i18n/ar.ts
@@ -8,6 +8,11 @@ export const dict = {
"ui.sessionReview.change.added": "مضاف",
"ui.sessionReview.change.removed": "محذوف",
"ui.sessionReview.change.modified": "معدل",
+ "ui.sessionReview.image.loading": "جار التحميل...",
+ "ui.sessionReview.image.placeholder": "صورة",
+ "ui.sessionReview.largeDiff.title": "Diff كبير جدا لعرضه",
+ "ui.sessionReview.largeDiff.meta": "الحد: {{lines}} سطر / {{limit}}. الحالي: {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "اعرض على أي حال",
"ui.lineComment.label.prefix": "تعليق على ",
"ui.lineComment.label.suffix": "",
diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts
index 6d7449d845..148b0ae174 100644
--- a/packages/ui/src/i18n/br.ts
+++ b/packages/ui/src/i18n/br.ts
@@ -8,6 +8,11 @@ export const dict = {
"ui.sessionReview.change.added": "Adicionado",
"ui.sessionReview.change.removed": "Removido",
"ui.sessionReview.change.modified": "Modificado",
+ "ui.sessionReview.image.loading": "Carregando...",
+ "ui.sessionReview.image.placeholder": "Imagem",
+ "ui.sessionReview.largeDiff.title": "Diff grande demais para renderizar",
+ "ui.sessionReview.largeDiff.meta": "Limite: {{lines}} linhas / {{limit}}. Atual: {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "Renderizar mesmo assim",
"ui.lineComment.label.prefix": "Comentar em ",
"ui.lineComment.label.suffix": "",
diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts
index 24e4c12068..7614af087f 100644
--- a/packages/ui/src/i18n/bs.ts
+++ b/packages/ui/src/i18n/bs.ts
@@ -12,6 +12,11 @@ export const dict = {
"ui.sessionReview.change.added": "Dodano",
"ui.sessionReview.change.removed": "Uklonjeno",
"ui.sessionReview.change.modified": "Izmijenjeno",
+ "ui.sessionReview.image.loading": "Učitavanje...",
+ "ui.sessionReview.image.placeholder": "Slika",
+ "ui.sessionReview.largeDiff.title": "Diff je prevelik za prikaz",
+ "ui.sessionReview.largeDiff.meta": "Limit: {{lines}} linija / {{limit}}. Trenutno: {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "Prikaži svejedno",
"ui.lineComment.label.prefix": "Komentar na ",
"ui.lineComment.label.suffix": "",
diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts
index 218f3b26a4..2f49a94344 100644
--- a/packages/ui/src/i18n/da.ts
+++ b/packages/ui/src/i18n/da.ts
@@ -9,6 +9,11 @@ export const dict = {
"ui.sessionReview.change.added": "Tilføjet",
"ui.sessionReview.change.removed": "Fjernet",
"ui.sessionReview.change.modified": "Ændret",
+ "ui.sessionReview.image.loading": "Indlæser...",
+ "ui.sessionReview.image.placeholder": "Billede",
+ "ui.sessionReview.largeDiff.title": "Diff er for stor til at blive vist",
+ "ui.sessionReview.largeDiff.meta": "Grænse: {{lines}} linjer / {{limit}}. Nuværende: {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "Vis alligevel",
"ui.lineComment.label.prefix": "Kommenter på ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "Kommenterer på ",
diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts
index 921a12c996..44090b7bdb 100644
--- a/packages/ui/src/i18n/de.ts
+++ b/packages/ui/src/i18n/de.ts
@@ -13,6 +13,11 @@ export const dict = {
"ui.sessionReview.change.added": "Hinzugefügt",
"ui.sessionReview.change.removed": "Entfernt",
"ui.sessionReview.change.modified": "Geändert",
+ "ui.sessionReview.image.loading": "Wird geladen...",
+ "ui.sessionReview.image.placeholder": "Bild",
+ "ui.sessionReview.largeDiff.title": "Diff zu groß zum Rendern",
+ "ui.sessionReview.largeDiff.meta": "Limit: {{lines}} Zeilen / {{limit}}. Aktuell: {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "Trotzdem rendern",
"ui.lineComment.label.prefix": "Kommentar zu ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "Kommentiere ",
diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts
index 631bc660a6..9b6ab0bd6d 100644
--- a/packages/ui/src/i18n/en.ts
+++ b/packages/ui/src/i18n/en.ts
@@ -8,6 +8,11 @@ export const dict = {
"ui.sessionReview.change.added": "Added",
"ui.sessionReview.change.removed": "Removed",
"ui.sessionReview.change.modified": "Modified",
+ "ui.sessionReview.image.loading": "Loading...",
+ "ui.sessionReview.image.placeholder": "Image",
+ "ui.sessionReview.largeDiff.title": "Diff too large to render",
+ "ui.sessionReview.largeDiff.meta": "Limit: {{lines}} lines / {{limit}}. Current: {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "Render anyway",
"ui.lineComment.label.prefix": "Comment on ",
"ui.lineComment.label.suffix": "",
diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts
index 4fd921b606..c2f8ac3b9d 100644
--- a/packages/ui/src/i18n/es.ts
+++ b/packages/ui/src/i18n/es.ts
@@ -8,6 +8,11 @@ export const dict = {
"ui.sessionReview.change.added": "Añadido",
"ui.sessionReview.change.removed": "Eliminado",
"ui.sessionReview.change.modified": "Modificado",
+ "ui.sessionReview.image.loading": "Cargando...",
+ "ui.sessionReview.image.placeholder": "Imagen",
+ "ui.sessionReview.largeDiff.title": "Diff demasiado grande para renderizar",
+ "ui.sessionReview.largeDiff.meta": "Límite: {{lines}} líneas / {{limit}}. Actual: {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "Renderizar de todos modos",
"ui.lineComment.label.prefix": "Comentar en ",
"ui.lineComment.label.suffix": "",
diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts
index 537d01bba9..679d56fa76 100644
--- a/packages/ui/src/i18n/fr.ts
+++ b/packages/ui/src/i18n/fr.ts
@@ -8,6 +8,11 @@ export const dict = {
"ui.sessionReview.change.added": "Ajouté",
"ui.sessionReview.change.removed": "Supprimé",
"ui.sessionReview.change.modified": "Modifié",
+ "ui.sessionReview.image.loading": "Chargement...",
+ "ui.sessionReview.image.placeholder": "Image",
+ "ui.sessionReview.largeDiff.title": "Diff trop volumineux pour être affiché",
+ "ui.sessionReview.largeDiff.meta": "Limite : {{lines}} lignes / {{limit}}. Actuel : {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "Afficher quand même",
"ui.lineComment.label.prefix": "Commenter sur ",
"ui.lineComment.label.suffix": "",
diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts
index 6086070bdb..bf85807d00 100644
--- a/packages/ui/src/i18n/ja.ts
+++ b/packages/ui/src/i18n/ja.ts
@@ -9,6 +9,11 @@ export const dict = {
"ui.sessionReview.change.added": "追加",
"ui.sessionReview.change.removed": "削除",
"ui.sessionReview.change.modified": "変更",
+ "ui.sessionReview.image.loading": "読み込み中...",
+ "ui.sessionReview.image.placeholder": "画像",
+ "ui.sessionReview.largeDiff.title": "差分が大きすぎて表示できません",
+ "ui.sessionReview.largeDiff.meta": "上限: {{lines}} 行 / {{limit}}。現在: {{current}}。",
+ "ui.sessionReview.largeDiff.renderAnyway": "それでも表示する",
"ui.lineComment.label.prefix": "",
"ui.lineComment.label.suffix": "へのコメント",
"ui.lineComment.editorLabel.prefix": "",
diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts
index fd394dbb7b..aba793a11b 100644
--- a/packages/ui/src/i18n/ko.ts
+++ b/packages/ui/src/i18n/ko.ts
@@ -8,6 +8,11 @@ export const dict = {
"ui.sessionReview.change.added": "추가됨",
"ui.sessionReview.change.removed": "삭제됨",
"ui.sessionReview.change.modified": "수정됨",
+ "ui.sessionReview.image.loading": "로딩 중...",
+ "ui.sessionReview.image.placeholder": "이미지",
+ "ui.sessionReview.largeDiff.title": "차이가 너무 커서 렌더링할 수 없습니다",
+ "ui.sessionReview.largeDiff.meta": "제한: {{lines}}줄 / {{limit}}. 현재: {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "그래도 렌더링",
"ui.lineComment.label.prefix": "",
"ui.lineComment.label.suffix": "에 댓글 달기",
diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts
index dcb353614d..7982b3ac75 100644
--- a/packages/ui/src/i18n/no.ts
+++ b/packages/ui/src/i18n/no.ts
@@ -11,6 +11,11 @@ export const dict: Record = {
"ui.sessionReview.change.added": "Lagt til",
"ui.sessionReview.change.removed": "Fjernet",
"ui.sessionReview.change.modified": "Endret",
+ "ui.sessionReview.image.loading": "Laster...",
+ "ui.sessionReview.image.placeholder": "Bilde",
+ "ui.sessionReview.largeDiff.title": "Diff er for stor til å gjengi",
+ "ui.sessionReview.largeDiff.meta": "Grense: {{lines}} linjer / {{limit}}. Nåværende: {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "Gjengi likevel",
"ui.lineComment.label.prefix": "Kommenter på ",
"ui.lineComment.label.suffix": "",
diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts
index fb10debbb9..2489ac7f2e 100644
--- a/packages/ui/src/i18n/pl.ts
+++ b/packages/ui/src/i18n/pl.ts
@@ -9,6 +9,11 @@ export const dict = {
"ui.sessionReview.change.added": "Dodano",
"ui.sessionReview.change.removed": "Usunięto",
"ui.sessionReview.change.modified": "Zmodyfikowano",
+ "ui.sessionReview.image.loading": "Ładowanie...",
+ "ui.sessionReview.image.placeholder": "Obraz",
+ "ui.sessionReview.largeDiff.title": "Diff jest zbyt duży, aby go wyrenderować",
+ "ui.sessionReview.largeDiff.meta": "Limit: {{lines}} linii / {{limit}}. Obecnie: {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "Renderuj mimo to",
"ui.lineComment.label.prefix": "Komentarz do ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "Komentowanie: ",
diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts
index 417fe0ce8b..8e6bb678f2 100644
--- a/packages/ui/src/i18n/ru.ts
+++ b/packages/ui/src/i18n/ru.ts
@@ -9,6 +9,11 @@ export const dict = {
"ui.sessionReview.change.added": "Добавлено",
"ui.sessionReview.change.removed": "Удалено",
"ui.sessionReview.change.modified": "Изменено",
+ "ui.sessionReview.image.loading": "Загрузка...",
+ "ui.sessionReview.image.placeholder": "Изображение",
+ "ui.sessionReview.largeDiff.title": "Diff слишком большой для отображения",
+ "ui.sessionReview.largeDiff.meta": "Лимит: {{lines}} строк / {{limit}}. Текущий: {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "Отобразить всё равно",
"ui.lineComment.label.prefix": "Комментарий к ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "Комментирование: ",
diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts
index 68bb0d733d..b036eca2e8 100644
--- a/packages/ui/src/i18n/th.ts
+++ b/packages/ui/src/i18n/th.ts
@@ -8,6 +8,11 @@ export const dict = {
"ui.sessionReview.change.added": "เพิ่ม",
"ui.sessionReview.change.removed": "ลบ",
"ui.sessionReview.change.modified": "แก้ไข",
+ "ui.sessionReview.image.loading": "กำลังโหลด...",
+ "ui.sessionReview.image.placeholder": "รูปภาพ",
+ "ui.sessionReview.largeDiff.title": "Diff มีขนาดใหญ่เกินไปจนไม่สามารถแสดงผลได้",
+ "ui.sessionReview.largeDiff.meta": "ขีดจำกัด: {{lines}} บรรทัด / {{limit}}. ปัจจุบัน: {{current}}.",
+ "ui.sessionReview.largeDiff.renderAnyway": "แสดงผลต่อไป",
"ui.lineComment.label.prefix": "แสดงความคิดเห็นบน ",
"ui.lineComment.label.suffix": "",
diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts
index 53beeb1e4f..dcb8062a33 100644
--- a/packages/ui/src/i18n/zh.ts
+++ b/packages/ui/src/i18n/zh.ts
@@ -12,6 +12,11 @@ export const dict = {
"ui.sessionReview.change.added": "已添加",
"ui.sessionReview.change.removed": "已移除",
"ui.sessionReview.change.modified": "已修改",
+ "ui.sessionReview.image.loading": "加载中...",
+ "ui.sessionReview.image.placeholder": "图片",
+ "ui.sessionReview.largeDiff.title": "差异过大,无法渲染",
+ "ui.sessionReview.largeDiff.meta": "限制:{{lines}} 行 / {{limit}}。当前:{{current}}。",
+ "ui.sessionReview.largeDiff.renderAnyway": "仍然渲染",
"ui.lineComment.label.prefix": "评论 ",
"ui.lineComment.label.suffix": "",
diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts
index 1449b0530a..271a6ded32 100644
--- a/packages/ui/src/i18n/zht.ts
+++ b/packages/ui/src/i18n/zht.ts
@@ -12,6 +12,11 @@ export const dict = {
"ui.sessionReview.change.added": "已新增",
"ui.sessionReview.change.removed": "已移除",
"ui.sessionReview.change.modified": "已修改",
+ "ui.sessionReview.image.loading": "載入中...",
+ "ui.sessionReview.image.placeholder": "圖片",
+ "ui.sessionReview.largeDiff.title": "差異過大,無法渲染",
+ "ui.sessionReview.largeDiff.meta": "限制:{{lines}} 行 / {{limit}}。目前:{{current}}。",
+ "ui.sessionReview.largeDiff.renderAnyway": "仍然渲染",
"ui.lineComment.label.prefix": "評論 ",
"ui.lineComment.label.suffix": "",
diff --git a/packages/util/package.json b/packages/util/package.json
index 078adbe142..bf0cb074be 100644
--- a/packages/util/package.json
+++ b/packages/util/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/util",
- "version": "1.1.64",
+ "version": "1.2.1",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/util/src/encode.ts b/packages/util/src/encode.ts
index 138cf16086..e4c6e70acb 100644
--- a/packages/util/src/encode.ts
+++ b/packages/util/src/encode.ts
@@ -28,3 +28,24 @@ export function checksum(content: string): string | undefined {
}
return (hash >>> 0).toString(36)
}
+
+export function sampledChecksum(content: string, limit = 500_000): string | undefined {
+ if (!content) return undefined
+ if (content.length <= limit) return checksum(content)
+
+ const size = 4096
+ const points = [
+ 0,
+ Math.floor(content.length * 0.25),
+ Math.floor(content.length * 0.5),
+ Math.floor(content.length * 0.75),
+ content.length - size,
+ ]
+ const hashes = points
+ .map((point) => {
+ const start = Math.max(0, Math.min(content.length - size, point - Math.floor(size / 2)))
+ return checksum(content.slice(start, start + size)) ?? ""
+ })
+ .join(":")
+ return `${content.length}:${hashes}`
+}
diff --git a/packages/web/package.json b/packages/web/package.json
index 6f5fe726f5..6bdecea178 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -2,7 +2,7 @@
"name": "@opencode-ai/web",
"type": "module",
"license": "MIT",
- "version": "1.1.64",
+ "version": "1.2.1",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
diff --git a/packages/web/src/content/docs/ar/index.mdx b/packages/web/src/content/docs/ar/index.mdx
index fef8844dc0..ff2de9c512 100644
--- a/packages/web/src/content/docs/ar/index.mdx
+++ b/packages/web/src/content/docs/ar/index.mdx
@@ -84,7 +84,8 @@ curl -fsSL https://opencode.ai/install | bash
- **باستخدام Paru على Arch Linux**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/da/index.mdx b/packages/web/src/content/docs/da/index.mdx
index b2623b93d5..65cf34f9a9 100644
--- a/packages/web/src/content/docs/da/index.mdx
+++ b/packages/web/src/content/docs/da/index.mdx
@@ -84,7 +84,8 @@ Du kan også installere det med følgende kommandoer:
- **Brug af Paru på Arch Linux**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/de/index.mdx b/packages/web/src/content/docs/de/index.mdx
index e1337254df..1d7e1f4f96 100644
--- a/packages/web/src/content/docs/de/index.mdx
+++ b/packages/web/src/content/docs/de/index.mdx
@@ -84,7 +84,8 @@ Sie können es auch mit den folgenden Befehlen installieren:
- **Verwendung von Paru unter Arch Linux**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/es/index.mdx b/packages/web/src/content/docs/es/index.mdx
index fa8c40c7ff..b4d3f95b5c 100644
--- a/packages/web/src/content/docs/es/index.mdx
+++ b/packages/web/src/content/docs/es/index.mdx
@@ -84,7 +84,8 @@ También puedes instalarlo con los siguientes comandos:
- **Usando Paru en Arch Linux**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/fr/index.mdx b/packages/web/src/content/docs/fr/index.mdx
index 06d650de7e..4f6b2f2a5b 100644
--- a/packages/web/src/content/docs/fr/index.mdx
+++ b/packages/web/src/content/docs/fr/index.mdx
@@ -84,7 +84,8 @@ Vous pouvez également l'installer avec les commandes suivantes :
- **Via Paru sur Arch Linux**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/index.mdx b/packages/web/src/content/docs/index.mdx
index bb3b8cb5d0..90e7eafb2f 100644
--- a/packages/web/src/content/docs/index.mdx
+++ b/packages/web/src/content/docs/index.mdx
@@ -81,10 +81,11 @@ You can also install it with the following commands:
> We recommend using the OpenCode tap for the most up to date releases. The official `brew install opencode` formula is maintained by the Homebrew team and is updated less frequently.
-- **Using Paru on Arch Linux**
+- **Installing on Arch Linux**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/it/index.mdx b/packages/web/src/content/docs/it/index.mdx
index 685bdf0c57..1b48d9df61 100644
--- a/packages/web/src/content/docs/it/index.mdx
+++ b/packages/web/src/content/docs/it/index.mdx
@@ -84,7 +84,8 @@ Puoi anche installarlo con i seguenti comandi:
- **Con Paru su Arch Linux**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/ja/index.mdx b/packages/web/src/content/docs/ja/index.mdx
index 6a6612715f..e4e84d7b1c 100644
--- a/packages/web/src/content/docs/ja/index.mdx
+++ b/packages/web/src/content/docs/ja/index.mdx
@@ -84,7 +84,8 @@ curl -fsSL https://opencode.ai/install | bash
- **Arch Linux での Paru の使用**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/ko/acp.mdx b/packages/web/src/content/docs/ko/acp.mdx
index 2fbd58013f..a9842f2709 100644
--- a/packages/web/src/content/docs/ko/acp.mdx
+++ b/packages/web/src/content/docs/ko/acp.mdx
@@ -1,12 +1,12 @@
---
-title: ACP 지원
-description: ACP 호환 편집기에서 opencode를 사용하세요.
+title: ACP Support
+description: Use OpenCode in any ACP-compatible editor.
---
-opencode는 [Agent Client Protocol](https://agentclientprotocol.com) 또는 (ACP)을 지원하며, 호환 편집기 및 IDE에서 직접 사용할 수 있습니다.
+OpenCode는 [Agent Client Protocol](https://agentclientprotocol.com)(ACP)을 지원하므로, ACP 호환 편집기와 IDE에서 OpenCode를 직접 사용할 수 있습니다.
:::tip
-ACP를 지원하는 편집기 및 도구 목록의 경우 [ACP 진행 보고서](https://zed.dev/blog/acp-progress-report#available-now)를 확인하십시오.
+ACP를 지원하는 편집기와 tool 목록은 [ACP progress report](https://zed.dev/blog/acp-progress-report#available-now)에서 확인하세요.
:::
ACP는 코드 편집기와 AI 코딩 에이전트 간의 통신을 표준화하는 개방형 프로토콜입니다.
@@ -15,17 +15,17 @@ ACP는 코드 편집기와 AI 코딩 에이전트 간의 통신을 표준화하
## 구성
-ACP를 통해 opencode를 사용하려면 `opencode acp` 명령을 실행하려면 편집기를 구성하십시오.
+ACP로 OpenCode를 사용하려면, 편집기에서 `opencode acp` 명령을 실행하도록 config를 설정하세요.
-명령은 opencode를 실행하여 JSON-RPC를 통해 편집기와 통신하는 ACP 호환 하위 프로세스로 시작합니다.
+이 명령은 OpenCode를 ACP 호환 subprocess로 시작하며, stdio 기반 JSON-RPC를 통해 편집기와 통신합니다.
-아래는 ACP를 지원하는 인기있는 편집기의 예입니다.
+아래는 ACP를 지원하는 주요 편집기 예시입니다.
---
-##### Zed
+### Zed
-[Zed](https://zed.dev) 구성 (`~/.config/zed/settings.json`)에 추가 :
+[Zed](https://zed.dev) config(`~/.config/zed/settings.json`)에 다음을 추가하세요.
```json title="~/.config/zed/settings.json"
{
@@ -38,9 +38,9 @@ ACP를 통해 opencode를 사용하려면 `opencode acp` 명령을 실행하려
}
```
-그것을 열려면 **Command Palette **에서 `agent: new thread` 동작을 사용하십시오.
+열려면 **Command Palette**에서 `agent: new thread` action을 사용하세요.
-`keymap.json`를 편집하여 키보드 단축키도 결합할 수 있습니다.
+`keymap.json`을 수정해 키보드 단축키를 바인딩할 수도 있습니다.
```json title="keymap.json"
[
@@ -67,9 +67,9 @@ ACP를 통해 opencode를 사용하려면 `opencode acp` 명령을 실행하려
---
-#### JetBrains IDEs
+### JetBrains IDEs
-[JetBrains IDE]에 추가하십시오 (https://www.jetbrains.com/) [documentation]에 따라 acp.json (https://www.jetbrains.com/help/ai-assistant/acp.html):
+[JetBrains IDE](https://www.jetbrains.com/)에서는 [documentation](https://www.jetbrains.com/help/ai-assistant/acp.html)에 따라 `acp.json`에 다음을 추가하세요.
```json title="acp.json"
{
@@ -82,13 +82,13 @@ ACP를 통해 opencode를 사용하려면 `opencode acp` 명령을 실행하려
}
```
-그것을 열려면 AI Chat Agent selector의 새로운 'opencode' 에이전트를 사용하십시오.
+열려면 AI Chat agent selector에서 새 `OpenCode` agent를 선택하세요.
---
-#### Avante.nvim
+### Avante.nvim
-[Avante.nvim](https://github.com/yetone/avante.nvim) 구성에 추가하십시오:
+[Avante.nvim](https://github.com/yetone/avante.nvim) config에 다음을 추가하세요.
```lua
{
@@ -101,7 +101,7 @@ ACP를 통해 opencode를 사용하려면 `opencode acp` 명령을 실행하려
}
```
-환경 변수를 전달해야 하는 경우:
+환경 변수를 전달해야 한다면 다음과 같이 설정하세요.
```lua {6-8}
{
@@ -119,9 +119,9 @@ ACP를 통해 opencode를 사용하려면 `opencode acp` 명령을 실행하려
---
-#### CodeCompanion.nvim
+### CodeCompanion.nvim
-opencode를 [CodeCompanion.nvim](https://github.com/olimorris/codecompanion.nvim)에서 ACP 에이전트로 사용하려면 Neovim config에 다음을 추가하십시오.
+[CodeCompanion.nvim](https://github.com/olimorris/codecompanion.nvim)에서 OpenCode를 ACP agent로 사용하려면 Neovim config에 다음을 추가하세요.
```lua
require("codecompanion").setup({
@@ -136,21 +136,21 @@ require("codecompanion").setup({
})
```
-이 구성은 CodeCompanion을 설정하여 채팅을 위한 ACP 에이전트로 opencode를 사용합니다.
+이 config는 chat에서 OpenCode를 ACP agent로 사용하도록 CodeCompanion을 설정합니다.
-환경 변수 (`OPENCODE_API_KEY`와 같은)를 전달해야하는 경우, CodeCompanion.nvim 문서에서 [Configuring Adapters: Environment variables](https://codecompanion.olimorris.dev/getting-started#setting-an-api-key)를 참조하십시오.
+환경 변수(`OPENCODE_API_KEY` 등)를 전달해야 한다면 CodeCompanion.nvim documentation의 [Configuring Adapters: Environment Variables](https://codecompanion.olimorris.dev/getting-started#setting-an-api-key)를 참고하세요.
-## 지원 기능
+## 지원
-opencode는 터미널과 동일하게 ACP를 통해 작동합니다. 모든 기능은 지원됩니다:
+OpenCode는 ACP를 통해서도 터미널과 동일하게 동작합니다. 다음 기능을 모두 지원합니다.
:::note
`/undo` 및 `/redo`와 같은 일부 내장 슬래시 명령은 현재 지원되지 않습니다.
:::
-- 내장 도구 (파일 작업, terminal 명령 등)
-- 사용자 정의 도구 및 슬래시 명령
-- opencode config에서 설정된 MCP 서버
-- `AGENTS.md`의 프로젝트 별 규칙
-- 사용자 정의 포맷 및 라이터
-- 에이전트 및 권한 시스템
+- 내장 tool(파일 작업, terminal 명령 등)
+- 사용자 정의 tool과 slash command
+- OpenCode config에 설정한 MCP 서버
+- `AGENTS.md`의 프로젝트별 규칙
+- 사용자 정의 formatter와 linter
+- agent 및 권한 시스템
diff --git a/packages/web/src/content/docs/ko/agents.mdx b/packages/web/src/content/docs/ko/agents.mdx
index 5b37122435..34de6250d1 100644
--- a/packages/web/src/content/docs/ko/agents.mdx
+++ b/packages/web/src/content/docs/ko/agents.mdx
@@ -1,141 +1,141 @@
---
-title: 에이전트
-description: 전문 에이전트를 구성하고 사용하세요.
+title: Agents
+description: Configure and use specialized agents.
---
-에이전트는 특정 작업과 워크플로우를 전문으로 하는 구성 가능한 AI 보조자입니다. 사용자 정의 프롬프트, 모델, 도구 액세스로 집중된 도구를 만들 수 있습니다.
+agent는 특정 작업과 워크플로에 맞게 설정할 수 있는 전문 AI assistant입니다. custom prompt, model, tool 접근 권한을 조합해 목적에 맞는 agent를 만들 수 있습니다.
:::tip
-코드 변경 없이 제안을 검토할 때는 Plan 에이전트를 사용하세요.
+코드를 수정하지 않고 분석과 제안 검토만 하고 싶다면 plan agent를 사용하세요.
:::
-세션 중에 에이전트를 전환하거나 `@` 멘션으로 호출할 수 있습니다.
+세션 중에 agent를 전환하거나 `@` mention으로 호출할 수 있습니다.
---
## 유형
-OpenCode에는 두 가지 유형의 에이전트가 있습니다: 기본 에이전트(Primary Agent)와 서브 에이전트(Subagent).
+OpenCode의 agent는 primary agent와 subagent, 두 가지 유형으로 나뉩니다.
---
-### 기본 에이전트
+### Primary agents
-기본 에이전트는 사용자가 직접 상호 작용하는 주요 보조자입니다. **Tab** 키 또는 설정된 `switch_agent` 키바인드를 사용하여 순환할 수 있습니다. 이 에이전트는 주요 대화를 처리합니다. 도구 액세스는 권한을 통해 구성됩니다 — 예를 들어, Build는 모든 도구를 사용할 수 있지만 Plan은 제한됩니다.
+primary agent는 사용자가 직접 상호작용하는 메인 assistant입니다. **Tab** 키 또는 설정한 `switch_agent` keybind로 순환 전환할 수 있습니다. primary agent는 메인 대화를 처리하며, tool 접근은 permission으로 제어합니다. 예를 들어 Build는 모든 tool이 활성화되어 있고 Plan은 제한되어 있습니다.
:::tip
-세션 중에 **Tab** 키를 사용하여 기본 에이전트를 전환할 수 있습니다.
+세션 중 **Tab** 키로 primary agent를 빠르게 전환할 수 있습니다.
:::
-OpenCode는 두 가지 내장 기본 에이전트, **Build** 및 **Plan**을 제공합니다. 아래에서 자세히 살펴봅니다.
+OpenCode에는 기본 제공 primary agent인 **Build**와 **Plan**이 포함되어 있습니다. 아래에서 각각 살펴보겠습니다.
---
-### 서브 에이전트
+### Subagents
-서브 에이전트는 기본 에이전트가 특정 작업을 위해 호출할 수 있는 전문 보조자입니다. 또한 메시지에서 **@멘션**을 통해 수동으로 호출할 수도 있습니다.
+subagent는 primary agent가 특정 작업을 위해 호출하는 전문 assistant입니다. 메시지에서 **@ mention**으로 직접 호출할 수도 있습니다.
-OpenCode는 두 가지 내장 서브 에이전트, **General** 및 **Explore**를 제공합니다. 아래에서 자세히 살펴봅니다.
+OpenCode에는 기본 제공 subagent인 **General**과 **Explore**가 포함되어 있습니다. 아래에서 살펴보겠습니다.
---
## 기본 제공
-OpenCode는 기본 에이전트와 두 개의 내장 서브 에이전트를 제공합니다.
+OpenCode는 기본적으로 primary agent 2개와 subagent 2개를 제공합니다.
---
-### Build
+### Use build
-_모드_: `primary`
+_Mode_: `primary`
-Build는 모든 도구가 활성화된 **기본** 에이전트입니다. 파일 조작 및 시스템 명령에 대한 전체 액세스가 필요한 개발 작업을 위한 표준 에이전트입니다.
+Build는 모든 tool이 활성화된 **default** primary agent입니다. 파일 작업과 시스템 명령에 대한 전체 접근이 필요한 일반적인 개발 작업에 사용하는 표준 agent입니다.
---
-### Plan
+### Use plan
-_모드_: `primary`
+_Mode_: `primary`
-계획 및 분석을 위해 설계된 제한된 에이전트입니다. 더 많은 제어권을 부여하고 의도하지 않은 변경을 방지하기 위해 권한 시스템을 사용합니다.
-기본적으로 다음은 모두 `ask`로 설정됩니다:
+Plan은 계획과 분석에 특화된 제한형 agent입니다. 더 높은 제어력과 의도치 않은 변경 방지를 위해 permission 시스템을 사용합니다.
+기본값으로 아래 항목은 모두 `ask`로 설정됩니다.
-- `file edits`: 모든 쓰기, 패치 및 편집
+- `file edits`: 모든 write, patch, edit
- `bash`: 모든 bash 명령
-이 에이전트는 코드를 분석하거나 변경을 제안받고 싶지만, 코드베이스에 실제 수정 없이 계획만 만들고 싶을 때 유용합니다.
+코드베이스를 실제로 수정하지 않고 LLM 분석, 변경 제안, 계획 수립만 진행하고 싶을 때 유용합니다.
---
-### General
+### Use general
-_모드_: `subagent`
+_Mode_: `subagent`
-복잡한 질문을 연구하고 다단계 작업을 실행하기 위한 범용 에이전트입니다. 전체 도구 액세스(todo 제외)를 가지므로 필요할 때 파일 변경을 수행할 수 있습니다. 여러 단위의 작업을 병렬로 실행할 때 사용하세요.
+복잡한 질문을 조사하고 다단계 작업을 수행하기 위한 범용 agent입니다. todo를 제외한 모든 tool 접근이 가능하므로 필요하면 파일 수정도 할 수 있습니다. 여러 작업 단위를 병렬로 처리할 때 사용하세요.
---
-### Explore
+### Use explore
-_모드_: `subagent`
+_Mode_: `subagent`
-코드베이스를 탐색하는 빠르고 읽기 전용인 에이전트입니다. 파일을 수정할 수 없습니다. 패턴, 키워드로 코드를 검색하거나 코드베이스에 대한 질문에 답하기 위해 파일을 빠르게 찾아야 할 때 사용하세요.
+코드베이스 탐색에 최적화된 빠른 읽기 전용 agent입니다. 파일을 수정할 수 없습니다. 패턴 기반 파일 탐색, 키워드 검색, 코드베이스 관련 질의 응답을 빠르게 처리할 때 사용하세요.
---
-### Compaction
+### Use compaction
-_모드_: `primary`
+_Mode_: `primary`
-긴 컨텍스트를 작은 요약으로 압축하는 숨겨진 시스템 에이전트입니다. 필요한 경우 자동으로 실행되며 UI에서 선택할 수 없습니다.
+긴 context를 더 짧은 요약으로 압축하는 숨겨진 시스템 agent입니다. 필요할 때 자동으로 실행되며 UI에서 직접 선택할 수 없습니다.
---
-### Title
+### Use title
-_모드_: `primary`
+_Mode_: `primary`
-짧은 세션 제목을 생성하는 숨겨진 시스템 에이전트입니다. 자동으로 실행되며 UI에서 선택할 수 없습니다.
+짧은 세션 제목을 생성하는 숨겨진 시스템 agent입니다. 자동으로 실행되며 UI에서 직접 선택할 수 없습니다.
---
-### Summary
+### Use summary
-_모드_: `primary`
+_Mode_: `primary`
-세션 요약을 만드는 숨겨진 시스템 에이전트입니다. 자동으로 실행되며 UI에서 선택할 수 없습니다.
+세션 요약을 생성하는 숨겨진 시스템 agent입니다. 자동으로 실행되며 UI에서 직접 선택할 수 없습니다.
---
## 사용법
-1. 기본 에이전트의 경우, 세션 중에 **Tab** 키를 사용하여 순환합니다. 구성된 `switch_agent` 키바인드도 사용할 수 있습니다.
+1. primary agent는 세션 중 **Tab** 키로 순환 전환할 수 있습니다. 설정한 `switch_agent` keybind를 사용할 수도 있습니다.
-2. 서브 에이전트는 다음과 같이 호출할 수 있습니다:
- - 설명에 근거하여 전문적인 작업을 위해 기본 에이전트에 의해 **자동으로** 호출됨.
- - 메시지에서 서브 에이전트를 **@멘션**. 예를 들어:
+2. subagent 호출 방법:
+ - **Automatically**: primary agent가 설명(description)을 바탕으로 특화 작업에 자동 호출합니다.
+ - 수동 호출: 메시지에서 subagent를 **@ mention**하여 호출합니다. 예:
```txt frame="none"
@general help me search for this function
```
-3. **세션 간 이동**: 서브 에이전트가 자체 자식 세션을 만들 때, 부모 세션과 자식 세션 간을 탐색할 수 있습니다.
- - **\+Right** (또는 부모 → 자식1 → 자식2 순으로 이동하기 위해 설정된 `session_child_cycle` 키바인드)
- - **\+Left** (또는 `session_child_cycle_reverse` 키바인드) 부모 방향으로 되돌아가기: 자식2 → 자식1 → 부모
+3. **세션 간 이동**: subagent가 child session을 만들면 아래 키로 parent session과 child session 사이를 이동할 수 있습니다.
+ - **\+Right** (또는 설정한 `session_child_cycle` keybind): parent → child1 → child2 → ... → parent 순방향 순환
+ - **\+Left** (또는 설정한 `session_child_cycle_reverse` keybind): parent ← child1 ← child2 ← ... ← parent 역방향 순환
- 이를 통해 주요 대화와 전문적인 서브 에이전트 작업 간을 원활하게 전환할 수 있습니다.
+ 이를 통해 메인 대화와 특화 subagent 작업 사이를 자연스럽게 오갈 수 있습니다.
---
## 구성
-내장 에이전트를 사용자 정의하거나 구성을 통해 자신만의 에이전트를 만들 수 있습니다. 에이전트는 두 가지 방법으로 구성될 수 있습니다:
+기본 제공 agent를 커스터마이즈하거나 config를 통해 직접 agent를 만들 수 있습니다. agent는 두 가지 방식으로 설정합니다.
---
### JSON
-`opencode.json` config 파일에 에이전트 구성:
+`opencode.json` config 파일에서 agent를 설정합니다.
```json title="opencode.json"
{
@@ -178,10 +178,10 @@ _모드_: `primary`
### Markdown
-Markdown 파일을 사용하여 에이전트를 정의 할 수 있습니다. 그들에 게:
+Markdown 파일로도 agent를 정의할 수 있습니다. 다음 위치에 두세요.
-- 글로벌: `~/.config/opencode/agents/`
-- 프로젝트: `.opencode/agents/`
+- Global: `~/.config/opencode/agents/`
+- Per-project: `.opencode/agents/`
```markdown title="~/.config/opencode/agents/review.md"
---
@@ -205,19 +205,19 @@ You are in code review mode. Focus on:
Provide constructive feedback without making direct changes.
```
-markdown 파일 이름은 에이전트 이름입니다. 예를 들어, `review.md`는 `review` 에이전트을 만듭니다.
+Markdown 파일명은 agent 이름이 됩니다. 예를 들어 `review.md`는 `review` agent를 만듭니다.
---
## 옵션
-이 구성 옵션들을 자세히 살펴봅시다.
+각 config 옵션을 자세히 살펴보겠습니다.
---
-### 설명
+### Description
-`description` 옵션을 사용하여 에이전트가 무엇을 하고 언제 사용해야 하는지에 대한 간단한 설명을 제공합니다.
+`description` 옵션으로 agent의 역할과 사용 시점을 간단히 설명하세요.
```json title="opencode.json"
{
@@ -229,15 +229,15 @@ markdown 파일 이름은 에이전트 이름입니다. 예를 들어, `review.m
}
```
-\*\* 필수 구성 옵션입니다.
+이 옵션은 **필수** config 항목입니다.
---
-### 온도
+### Temperature
-`temperature` 구성으로 LLM 응답의 무작위성과 창의성을 제어합니다.
+`temperature` config로 LLM 응답의 무작위성과 창의성을 제어합니다.
-값이 낮을수록 더 집중되고 결정적인 응답을 생성하며, 값이 높을수록 창의성과 가변성이 증가합니다.
+값이 낮을수록 응답이 더 집중되고 결정적이며, 값이 높을수록 창의성과 다양성이 커집니다.
```json title="opencode.json"
{
@@ -252,11 +252,11 @@ markdown 파일 이름은 에이전트 이름입니다. 예를 들어, `review.m
}
```
-온도 값은 일반적으로 0.0에서 1.0 사이입니다:
+Temperature 값은 일반적으로 0.0~1.0 범위를 사용합니다.
-- **0.0-0.2**: 매우 집중되고 신중한 응답, 코드 분석 및 계획에 이상적
-- **0.3-0.5**: 창의성과 정확성의 균형, 일반 개발 작업에 좋음
-- **0.6-1.0**: 더 창의적이고 다양한 응답, 브레인스토밍 및 탐색에 유용함
+- **0.0-0.2**: 매우 집중되고 결정적인 응답, 코드 분석/계획에 적합
+- **0.3-0.5**: 적당한 창의성이 섞인 균형형 응답, 일반 개발 작업에 적합
+- **0.6-1.0**: 더 창의적이고 다양한 응답, 브레인스토밍/탐색에 유용
```json title="opencode.json"
{
@@ -276,15 +276,15 @@ markdown 파일 이름은 에이전트 이름입니다. 예를 들어, `review.m
}
```
-온도가 지정되지 않은 경우, OpenCode는 모델별 기본값을 사용합니다. 일반적으로 대부분의 모델은 0, Qwen 모델의 경우 0.55입니다.
+temperature를 지정하지 않으면 OpenCode는 model별 기본값을 사용합니다. 일반적으로 대부분의 model은 0, Qwen model은 0.55를 사용합니다.
---
-## 최대 단계
+### Max steps
-에이전트가 중지하고 사용자와 다시 상호 작용하기 전에 실행할 수 있는 최대 단계 수를 제어합니다. 이를 통해 에이전트의 행동과 비용을 제어할 수 있습니다.
+agent가 텍스트 응답만 하도록 강제되기 전까지 수행할 수 있는 agentic iteration의 최대 횟수를 제어합니다. 비용을 관리하려는 사용자에게 agentic action 제한을 제공하기 위한 옵션입니다.
-설정되지 않은 경우, 에이전트는 모델이 중지하거나 사용자가 세션을 중단할 때까지 계속됩니다.
+이 값을 설정하지 않으면 model이 중단을 선택하거나 사용자가 세션을 중단할 때까지 agent는 계속 반복합니다.
```json title="opencode.json"
{
@@ -298,17 +298,17 @@ markdown 파일 이름은 에이전트 이름입니다. 예를 들어, `review.m
}
```
-제한에 도달하면, 에이전트는 작업 요약과 권장되는 남은 작업을 신속하게 응답하도록 지시받습니다.
+제한에 도달하면 agent는 작업 요약과 남은 권장 작업을 응답하도록 지시하는 특수 시스템 prompt를 받습니다.
:::caution
-레거시 `maxSteps` 필드는 더 이상 사용되지 않습니다. 대신 `steps`를 사용하십시오.
+레거시 `maxSteps` 필드는 deprecated입니다. 대신 `steps`를 사용하세요.
:::
---
-#### 비활성화
+### Disable
-`true`로 설정하여 에이전트를 비활성화합니다.
+`true`로 설정하면 agent를 비활성화합니다.
```json title="opencode.json"
{
@@ -322,9 +322,9 @@ markdown 파일 이름은 에이전트 이름입니다. 예를 들어, `review.m
---
-#### 프롬프트
+### Prompt
-`prompt` 구성으로 이 에이전트를 위한 사용자 정의 시스템 프롬프트 파일을 지정하십시오. 프롬프트 파일은 에이전트의 목적에 따른 지시를 포함해야 합니다.
+`prompt` config로 해당 agent의 custom 시스템 prompt 파일을 지정합니다. prompt 파일에는 agent 목적에 맞는 지시사항을 작성하세요.
```json title="opencode.json"
{
@@ -336,16 +336,16 @@ markdown 파일 이름은 에이전트 이름입니다. 예를 들어, `review.m
}
```
-이 경로는 구성 파일이 있는 위치를 기준으로 합니다. 따라서 글로벌 OpenCode 구성과 프로젝트별 구성 모두에서 작동합니다.
+이 경로는 config 파일 위치 기준의 상대 경로입니다. 따라서 전역 OpenCode config와 프로젝트별 config 모두에서 동일하게 동작합니다.
---
-### 모델
+### Model
-`model` 구성을 사용하여 이 에이전트에 대한 모델을 재정의합니다. 다른 작업에 최적화된 다른 모델을 사용하는 데 유용합니다. 예를 들어, 계획을 위한 빠른 모델, 구현을 위한 더 강력한 모델 등입니다.
+`model` config로 해당 agent의 model을 override할 수 있습니다. 작업 특성에 맞춰 model을 달리 쓸 때 유용합니다. 예를 들어 계획에는 더 빠른 model, 구현에는 더 강력한 model을 사용할 수 있습니다.
:::tip
-모델을 지정하지 않는 경우, 기본 에이전트는 [전역 구성 모델](/docs/config#models)을 사용하며, 서브 에이전트는 자신을 호출한 기본 에이전트의 모델을 사용합니다.
+model을 지정하지 않으면 primary agent는 [전역으로 설정한 model](/docs/config#models)을 사용하고, subagent는 해당 subagent를 호출한 primary agent의 model을 사용합니다.
:::
```json title="opencode.json"
@@ -358,13 +358,13 @@ markdown 파일 이름은 에이전트 이름입니다. 예를 들어, `review.m
}
```
-OpenCode 구성의 모델 ID는 `provider/model-id` 형식을 사용합니다. 예를 들어, [OpenCode Zen](/docs/zen)을 사용한다면, GPT 5.1 Codex에 `opencode/gpt-5.1-codex`를 사용할 수 있습니다.
+OpenCode config의 model ID는 `provider/model-id` 형식을 사용합니다. 예를 들어 [OpenCode Zen](/docs/zen)을 사용한다면 GPT 5.1 Codex에 `opencode/gpt-5.1-codex`를 사용합니다.
---
-## 도구
+### Tools
-`tools` 구성으로 이 에이전트가 사용할 수 있는 도구를 제어합니다. `true` 또는 `false`로 설정하여 특정 도구를 활성화하거나 비활성화할 수 있습니다.
+`tools` config로 agent에서 사용할 tool을 제어합니다. 각 tool을 `true` 또는 `false`로 설정해 활성화/비활성화할 수 있습니다.
```json title="opencode.json" {3-6,9-12}
{
@@ -385,10 +385,10 @@ OpenCode 구성의 모델 ID는 `provider/model-id` 형식을 사용합니다.
```
:::note
-에이전트별 구성은 글로벌 구성을 덮어씁니다.
+agent별 config는 전역 config를 override합니다.
:::
-한 번에 여러 도구를 제어하기 위해 와일드카드를 사용할 수 있습니다. 예를 들어, MCP 서버에서 모든 도구를 비활성화하려면:
+와일드카드를 사용하면 여러 tool을 한 번에 제어할 수 있습니다. 예를 들어 MCP 서버의 모든 tool을 비활성화하려면 다음과 같이 설정합니다.
```json title="opencode.json"
{
@@ -405,17 +405,17 @@ OpenCode 구성의 모델 ID는 `provider/model-id` 형식을 사용합니다.
}
```
-[도구에 대해 더 알아보기](/docs/tools).
+[tool에 대해 더 알아보기](/docs/tools).
---
-## 권한
+### Permissions
-에이전트가 수행할 수 있는 작업을 관리할 수 있는 권한을 구성할 수 있습니다. 현재 `edit`, `bash` 및 `webfetch` 도구에 대한 권한은 다음과 같습니다.
+permission을 설정해 agent가 수행할 수 있는 action을 제어할 수 있습니다. 현재 `edit`, `bash`, `webfetch` tool의 permission은 다음 값으로 설정할 수 있습니다.
-- `"ask"` - 도구를 실행하기 전에 승인 요청
-- `"allow"` - 승인 없이 모든 작업 허용
-- `"deny"` - 도구 비활성화
+- `"ask"` — tool 실행 전에 승인 요청
+- `"allow"` — 승인 없이 모든 작업 허용
+- `"deny"` — tool 비활성화
```json title="opencode.json"
{
@@ -426,7 +426,7 @@ OpenCode 구성의 모델 ID는 `provider/model-id` 형식을 사용합니다.
}
```
-에이전트별로 이 권한을 재정의(override)할 수 있습니다.
+이 permission은 agent별로 override할 수 있습니다.
```json title="opencode.json" {3-5,8-10}
{
@@ -444,7 +444,7 @@ OpenCode 구성의 모델 ID는 `provider/model-id` 형식을 사용합니다.
}
```
-Markdown 에이전트에서도 권한을 설정할 수 있습니다.
+Markdown agent에서도 permission을 설정할 수 있습니다.
```markdown title="~/.config/opencode/agents/review.md"
---
@@ -463,7 +463,7 @@ permission:
Only analyze code and suggest changes.
```
-특정 bash 명령에 대한 권한을 설정할 수 있습니다.
+특정 bash 명령에 대해서도 permission을 설정할 수 있습니다.
```json title="opencode.json" {7}
{
@@ -481,7 +481,7 @@ Only analyze code and suggest changes.
}
```
-이것은 glob 패턴을 사용할 수 있습니다.
+여기에는 glob 패턴을 사용할 수 있습니다.
```json title="opencode.json" {7}
{
@@ -498,8 +498,8 @@ Only analyze code and suggest changes.
}
```
-또한 `*` 와일드카드를 사용하여 모든 명령에 대한 권한을 관리할 수 있습니다.
-마지막 일치 규칙이 우선하므로, `*` 와일드카드를 먼저 두고 특정 규칙을 나중에 두십시오.
+또한 `*` 와일드카드로 모든 명령의 permission을 제어할 수 있습니다.
+마지막으로 일치한 규칙이 우선하므로 `*` 와일드카드를 먼저 두고, 구체적인 규칙을 뒤에 두세요.
```json title="opencode.json" {8}
{
@@ -517,13 +517,13 @@ Only analyze code and suggest changes.
}
```
-[권한에 대해 더 알아보기](/docs/permissions).
+[permission에 대해 더 알아보기](/docs/permissions).
---
-### 모드
+### Mode
-`mode` 구성으로 에이전트 모드를 제어합니다. `mode` 옵션은 에이전트가 어떻게 사용될 수 있는지 결정하는 데 사용됩니다.
+`mode` config로 agent 모드를 제어합니다. `mode` 옵션은 agent를 어떤 방식으로 사용할지 결정합니다.
```json title="opencode.json"
{
@@ -535,13 +535,13 @@ Only analyze code and suggest changes.
}
```
-`mode` 옵션은 `primary`, `subagent`, 또는 `all`로 설정할 수 있습니다. `mode`가 지정되지 않은 경우 `all`이 기본값입니다.
+`mode`는 `primary`, `subagent`, `all` 중 하나로 설정할 수 있습니다. 설정하지 않으면 기본값은 `all`입니다.
---
-## 숨김
+### Hidden
-`hidden: true`를 사용하여 `@` 자동 완성 메뉴에서 에이전트를 숨깁니다. 작업 도구를 통해 다른 에이전트에 의해 프로그래밍 방식으로 호출되어야 하는 내부 에이전트에 유용합니다.
+`hidden: true`를 설정하면 `@` 자동완성 메뉴에서 subagent를 숨길 수 있습니다. 다른 agent가 Task tool을 통해 programmatic으로만 호출해야 하는 내부 subagent에 유용합니다.
```json title="opencode.json"
{
@@ -554,17 +554,17 @@ Only analyze code and suggest changes.
}
```
-자동 완성 메뉴의 사용자 가시성에만 영향을 미칩니다. 숨겨진 에이전트는 권한이 허용된다면 여전히 작업 도구를 통해 모델에 의해 호출될 수 있습니다.
+이 설정은 자동완성 메뉴에서의 사용자 가시성에만 영향을 줍니다. permission이 허용되면 hidden agent도 모델이 Task tool을 통해 호출할 수 있습니다.
:::note
-`mode: subagent` 에이전트에만 적용됩니다.
+`mode: subagent` agent에만 적용됩니다.
:::
---
-## 작업 권한
+### Task permissions
-`permission.task`와 작업 도구를 통해 에이전트가 호출할 수 있는 서브 에이전트를 제어합니다. 유연한 일치를 위한 glob 패턴을 사용합니다.
+`permission.task`로 Task tool을 통해 해당 agent가 호출할 수 있는 subagent 범위를 제어합니다. 유연한 매칭을 위해 glob 패턴을 사용합니다.
```json title="opencode.json"
{
@@ -583,23 +583,23 @@ Only analyze code and suggest changes.
}
```
-`deny`로 설정할 때, 서브 에이전트는 작업 도구 설명에서 완전히 제거됩니다. 따라서 모델은 그것을 호출하려고 시도하지 않습니다.
+`deny`로 설정되면 해당 subagent는 Task tool 설명에서 완전히 제거되므로 모델이 호출을 시도하지 않습니다.
:::tip
-규칙은 순서대로 평가되며, **마지막 일치 규칙**이 우선합니다. 위의 예에서 `orchestrator-planner`는 `*` (deny)와 `orchestrator-*` (allow) 모두 일치하지만 `orchestrator-*`가 `*` 뒤에 오므로 결과는 `allow`입니다.
+규칙은 선언 순서대로 평가되며, **마지막으로 일치한 규칙이 승리합니다**. 위 예시에서 `orchestrator-planner`는 `*`(deny)와 `orchestrator-*`(allow) 모두에 일치하지만, `orchestrator-*`가 뒤에 있으므로 결과는 `allow`입니다.
:::
:::tip
-사용자는 에이전트의 작업 권한이 거부되더라도 `@` 자동 완성 메뉴를 통해 직접 어떤 서브 에이전트든 호출할 수 있습니다.
+사용자는 agent의 task permission이 deny여도 `@` 자동완성 메뉴를 통해 어떤 subagent든 직접 호출할 수 있습니다.
:::
---
-### 색상
+### Color
-`color` 옵션으로 UI에서 에이전트의 시각적 모양을 사용자 정의합니다.
+`color` 옵션으로 UI에서 agent의 시각 스타일을 지정할 수 있습니다. 인터페이스에서 agent가 표시되는 방식에 영향을 줍니다.
-유효한 hex 색상(예: `#FF5733`) 또는 테마 색상을 사용하십시오: `primary`, `secondary`, `accent`, `success`, `warning`, `error`, `info`.
+유효한 hex 색상(예: `#FF5733`) 또는 theme 색상(`primary`, `secondary`, `accent`, `success`, `warning`, `error`, `info`)을 사용하세요.
```json title="opencode.json"
{
@@ -618,7 +618,7 @@ Only analyze code and suggest changes.
### Top P
-`top_p` 옵션으로 응답의 다양성을 제어합니다. 무작위성 제어를 위한 온도의 대안입니다.
+`top_p` 옵션으로 응답 다양성을 제어합니다. 무작위성을 제어하는 Temperature의 대안입니다.
```json title="opencode.json"
{
@@ -630,15 +630,15 @@ Only analyze code and suggest changes.
}
```
-값은 0.0에서 1.0 사이입니다. 낮은 값은 더 집중되고, 높은 값은 더 다양합니다.
+값 범위는 0.0~1.0입니다. 값이 낮을수록 집중되고, 높을수록 다양해집니다.
---
-### 추가 옵션
+### Additional
-에이전트 구성에 지정하는 다른 옵션은 모델 옵션으로 공급자에게 **직접 전달**됩니다. 이를 통해 공급자별 기능 및 매개변수를 사용할 수 있습니다.
+agent config에 지정한 나머지 옵션은 모델 옵션으로 provider에 **그대로 전달(pass through)** 됩니다. 이를 통해 provider별 기능과 파라미터를 활용할 수 있습니다.
-예를 들어, OpenAI의 추론 모델과 함께, 추론 노력을 제어할 수 있습니다:
+예를 들어 OpenAI reasoning model에서는 reasoning effort를 제어할 수 있습니다.
```json title="opencode.json" {6,7}
{
@@ -653,55 +653,55 @@ Only analyze code and suggest changes.
}
```
-이 추가 옵션은 모델과 공급자별로 다릅니다. 사용 가능한 매개변수는 공급자의 문서를 확인하십시오.
+이 추가 옵션은 model 및 provider마다 다릅니다. 사용 가능한 파라미터는 provider 문서를 확인하세요.
:::tip
-`opencode models`를 실행하여 사용 가능한 모델 목록을 볼 수 있습니다.
+사용 가능한 model 목록은 `opencode models` 명령으로 확인할 수 있습니다.
:::
---
-## 에이전트 만들기
+## 에이전트 생성
-다음 명령을 사용하여 새로운 에이전트를 만들 수 있습니다:
+아래 명령으로 새 agent를 만들 수 있습니다.
```bash
opencode agent create
```
-이 대화형 명령은:
+이 인터랙티브 명령은 다음을 수행합니다.
-1. 에이전트를 저장할 위치를 묻습니다 (전역 또는 프로젝트별).
-2. 에이전트가 해야 할 일에 대한 설명을 묻습니다.
-3. 적절한 시스템 프롬프트 및 식별자를 생성합니다.
-4. 에이전트가 접근할 수 있는 도구를 선택하게 합니다.
-5. 마지막으로, 에이전트 구성을 가진 markdown 파일을 생성합니다.
+1. agent 저장 위치를 묻습니다(전역/프로젝트).
+2. agent가 수행할 작업의 설명을 받습니다.
+3. 적절한 시스템 prompt와 식별자를 생성합니다.
+4. agent가 접근할 tool을 선택하게 합니다.
+5. 마지막으로 agent config가 담긴 Markdown 파일을 생성합니다.
---
## 사용 사례
-다른 에이전트를 위한 몇 가지 일반적인 사용 사례는 다음과 같습니다.
+서로 다른 agent의 대표적인 사용 사례는 다음과 같습니다.
-- **Build Agent**: 모든 도구와 함께 전체 개발 작업
-- **Plan Agent**: 변경 없이 분석 및 계획
-- **Review Agent**: 읽기 전용 액세스 및 문서 도구와 함께 코드 리뷰
-- **Debug Agent**: bash 및 읽기 도구와 함께 조사에 집중
-- **Docs Agent**: 파일 작업과 문서 작성을 하지만 시스템 명령 없음
+- **Build agent**: 모든 tool을 활성화한 전체 개발 작업
+- **Plan agent**: 코드 변경 없이 분석과 계획 수행
+- **Review agent**: 읽기 전용 접근 + 문서화 tool 기반 코드 리뷰
+- **Debug agent**: bash/read tool 중심의 조사 작업
+- **Docs agent**: 파일 작업은 가능하지만 시스템 명령은 없는 문서 작성 작업
---
-## 예제
+## 예시
-여기에 유용 할 수있는 몇 가지 예 에이전트가 있습니다.
+실제로 유용하게 쓸 수 있는 예시 agent를 소개합니다.
:::tip
-공유하고 싶은 에이전트가 있습니까? [PR](https://github.com/anomalyco/opencode).
+공유하고 싶은 agent가 있나요? [PR 제출하기](https://github.com/anomalyco/opencode).
:::
---
-### 문서 에이전트
+### Documentation agent
```markdown title="~/.config/opencode/agents/docs-writer.md"
---
@@ -723,7 +723,7 @@ Focus on:
---
-## 보안 감사
+### Security auditor
```markdown title="~/.config/opencode/agents/security-auditor.md"
---
diff --git a/packages/web/src/content/docs/ko/config.mdx b/packages/web/src/content/docs/ko/config.mdx
index 0357ded785..e906eaf47b 100644
--- a/packages/web/src/content/docs/ko/config.mdx
+++ b/packages/web/src/content/docs/ko/config.mdx
@@ -1,15 +1,15 @@
---
-title: 구성
-description: OpenCode JSON 구성을 사용합니다.
+title: Config
+description: Using the OpenCode JSON config.
---
-JSON 구성 파일을 사용하여 OpenCode를 구성할 수 있습니다.
+JSON config 파일로 OpenCode를 설정할 수 있습니다.
---
## 형식
-OpenCode는 **JSON** 및 **JSONC** (주석이 있는 JSON) 형식을 지원합니다.
+OpenCode는 **JSON**과 **JSONC**(주석이 포함된 JSON) 형식을 모두 지원합니다.
```jsonc title="opencode.jsonc"
{
@@ -25,44 +25,44 @@ OpenCode는 **JSON** 및 **JSONC** (주석이 있는 JSON) 형식을 지원합
## 위치
-구성을 여러 위치에 배치할 수 있으며, 이들은 서로 다른 우선 순위(precedence)를 가집니다.
+config 파일은 여러 위치에 둘 수 있으며, 각 위치에는 우선순위가 있습니다.
:::note
-구성 파일은 **병합**되며, 대체되지 않습니다.
+config 파일은 **교체되지 않고 병합**됩니다.
:::
-구성 파일은 함께 병합되며 대체되지 않습니다. 다음 구성 위치의 설정이 결합됩니다. 나중의 구성은 충돌하는 키에 대해 이전 구성을 덮어씁니다. 모든 구성의 설정이 보존됩니다.
+config 파일은 서로 대체되는 방식이 아니라 병합됩니다. 아래 config 위치의 설정이 결합되며, 충돌하는 key에 대해서만 나중에 로드된 config가 앞선 값을 override합니다. 충돌하지 않는 설정은 모두 유지됩니다.
-예를 들어, 전역 구성이 `theme: "opencode"` 및 `autoupdate: true`를 설정하고 프로젝트 구성이 `model: "anthropic/claude-sonnet-4-5"`를 설정하면 최종 구성에는 세 가지 설정이 모두 포함됩니다.
+예를 들어, 전역 config에 `theme: "opencode"`와 `autoupdate: true`가 있고 프로젝트 config에 `model: "anthropic/claude-sonnet-4-5"`가 있으면 최종 config에는 이 세 설정이 모두 포함됩니다.
---
-### 우선 순위
+### 우선순위
-구성 소스는 다음 순서로 로드됩니다 (나중 소스가 이전 소스를 덮어씀):
+config source는 다음 순서로 로드됩니다(나중 source가 앞선 source를 override).
-1. **원격 구성** (`.well-known/opencode`에서) - 조직 기본값
-2. **전역 구성** (`~/.config/opencode/opencode.json`) - 사용자 환경설정
-3. **사용자 정의 구성** (`OPENCODE_CONFIG` 환경 변수) - 사용자 정의 재정의
-4. **프로젝트별 구성** (`opencode.json`) - 프로젝트별 설정
-5. **`.opencode` 디렉토리** - 에이전트, 명령, 플러그인
-6. **인라인 구성** (`OPENCODE_CONFIG_CONTENT` 환경 변수) - 런타임 재정의
+1. **Remote config**(`.well-known/opencode`) - 조직 기본값
+2. **Global config**(`~/.config/opencode/opencode.json`) - 사용자 기본 설정
+3. **Custom config**(`OPENCODE_CONFIG` env var) - custom override
+4. **Project config**(프로젝트의 `opencode.json`) - 프로젝트별 설정
+5. **`.opencode` directories** - agents, commands, plugins
+6. **Inline config**(`OPENCODE_CONFIG_CONTENT` env var) - 런타임 override
-이것은 프로젝트 구성이 전역 기본값을 덮어쓸 수 있고, 전역 구성이 원격 조직 기본값을 덮어쓸 수 있음을 의미합니다.
+즉, 프로젝트 config는 전역 기본값을 override할 수 있고, 전역 config는 조직의 Remote 기본값을 override할 수 있습니다.
:::note
-`.opencode`와 `~/.config/opencode` 디렉토리는 하위 디렉토리에 대해 **복수형 이름**을 사용합니다: `agents/`, `commands/`, `modes/`, `plugins/`, `skills/`, `tools/`, 그리고 `themes/`. 단수형 이름(예: `agent/`)도 하위 호환성을 위해 지원됩니다.
+`.opencode` 및 `~/.config/opencode` 디렉토리는 하위 디렉토리에 **복수형 이름**을 사용합니다: `agents/`, `commands/`, `modes/`, `plugins/`, `skills/`, `tools/`, `themes/`. 단수형 이름(예: `agent/`)도 하위 호환성을 위해 지원합니다.
:::
---
-### 원격
+### Remote
-조직은 `.well-known/opencode` 엔드포인트를 통해 기본 구성을 제공할 수 있습니다. 이를 지원하는 공급자로 인증할 때 자동으로 가져옵니다.
+조직은 `.well-known/opencode` endpoint로 기본 config를 제공할 수 있습니다. 이를 지원하는 provider로 인증하면 자동으로 가져옵니다.
-원격 구성은 기본 레이어로 가장 먼저 로드됩니다. 다른 구성 소스(전역, 프로젝트)는 이러한 기본값을 무시(override)할 수 있습니다.
+Remote config는 가장 먼저 로드되어 기본 레이어 역할을 합니다. 이후의 모든 config source(전역, 프로젝트)는 이 기본값을 override할 수 있습니다.
-예를 들어, 조직이 기본적으로 비활성화된 MCP 서버를 제공하는 경우:
+예를 들어, 조직에서 기본 비활성화된 MCP 서버를 제공하는 경우:
```json title="Remote config from .well-known/opencode"
{
@@ -76,7 +76,7 @@ OpenCode는 **JSON** 및 **JSONC** (주석이 있는 JSON) 형식을 지원합
}
```
-로컬 설정에서 특정 서버를 활성화할 수 있습니다:
+로컬 config에서 특정 서버를 활성화할 수 있습니다.
```json title="opencode.json"
{
@@ -92,65 +92,65 @@ OpenCode는 **JSON** 및 **JSONC** (주석이 있는 JSON) 형식을 지원합
---
-## 전역
+### Global
-`~/.config/opencode/opencode.json`에 전역 OpenCode 구성을 배치합니다. 테마, 공급자, 키바인드와 같은 사용자 전체 기본 설정에 전역 구성을 사용하십시오.
+전역 OpenCode config는 `~/.config/opencode/opencode.json`에 두세요. theme, provider, keybind 같은 사용자 전체 기본 설정은 전역 config로 관리하세요.
-전역 구성은 원격 조직 기본값을 덮어씁니다.
+전역 config는 조직의 Remote 기본값을 override합니다.
---
-## 프로젝트별
+### Per project
-프로젝트 루트에 `opencode.json`을 추가합니다. 프로젝트 구성은 표준 구성 파일 중 가장 높은 우선순위를 가집니다. 이는 전역 및 원격 구성을 모두 덮어씁니다.
+프로젝트 루트에 `opencode.json`을 추가하세요. 프로젝트 config는 표준 config 파일 중 우선순위가 가장 높아 전역 및 Remote config를 모두 override합니다.
:::tip
-프로젝트의 루트에 특정 설정을 둡니다.
+프로젝트별 config는 프로젝트 루트에 두세요.
:::
-OpenCode가 시작될 때, 현재 디렉토리의 설정 파일이나 가장 가까운 Git 디렉토리를 찾습니다.
+OpenCode 시작 시 현재 디렉토리에서 config 파일을 찾고, 없으면 가장 가까운 Git 디렉토리까지 상위로 탐색합니다.
-이것은 Git으로 관리되며 전역 구성과 동일한 스키마를 사용합니다.
+이 파일은 Git에 커밋해도 안전하며 전역 config와 동일한 schema를 사용합니다.
---
-### 사용자 정의 경로
+### Custom path
-`OPENCODE_CONFIG` 환경 변수를 사용하여 사용자 정의 구성 파일 경로를 지정합니다.
+`OPENCODE_CONFIG` 환경 변수로 custom config 파일 경로를 지정하세요.
```bash
export OPENCODE_CONFIG=/path/to/my/custom-config.json
opencode run "Hello world"
```
-사용자 정의 구성은 우선 순위에서 전역 구성과 프로젝트 구성 사이에 로드됩니다.
+Custom config는 우선순위상 전역 config와 프로젝트 config 사이에서 로드됩니다.
---
-## 사용자 정의 디렉토리
+### Custom directory
-`OPENCODE_CONFIG_DIR` 환경 변수를 사용하여 사용자 정의 구성 디렉토리를 지정할 수 있습니다. 이 디렉토리는 표준 `.opencode` 디렉토리와 마찬가지로 에이전트, 명령, 모드 및 플러그인을 검색하며 동일한 구조를 따라야 합니다.
+`OPENCODE_CONFIG_DIR` 환경 변수로 custom config 디렉토리를 지정할 수 있습니다. 이 디렉토리는 표준 `.opencode` 디렉토리와 동일하게 agents, commands, modes, plugins를 검색하며, 동일한 구조를 따라야 합니다.
```bash
export OPENCODE_CONFIG_DIR=/path/to/my/config-directory
opencode run "Hello world"
```
-사용자 정의 디렉토리는 전역 구성 이후 및 `.opencode` 디렉토리 이전에 로드됩니다.
+custom 디렉토리는 전역 config와 `.opencode` 디렉토리 뒤에 로드되므로 해당 설정을 **override할 수 있습니다**.
---
-## 스키마
+## Schema
-구성 파일에는 [**`opencode.ai/config.json`**](https://opencode.ai/config.json)에 정의된 스키마가 있습니다.
+config 파일의 schema는 [**`opencode.ai/config.json`**](https://opencode.ai/config.json)에 정의되어 있습니다.
-편집기는 스키마에 따라 유효성 검사 및 자동 완성을 제공해야 합니다.
+편집기에서 이 schema를 기반으로 validation과 autocomplete를 사용할 수 있습니다.
---
-#### TUI
+### TUI
-`tui` 옵션을 통해 TUI 관련 설정을 구성할 수 있습니다.
+`tui` 옵션으로 TUI 관련 설정을 구성할 수 있습니다.
```json title="opencode.json"
{
@@ -165,19 +165,19 @@ opencode run "Hello world"
}
```
-유효한 옵션:
+사용 가능한 옵션:
- `scroll_acceleration.enabled` - macOS 스타일 스크롤 가속을 활성화합니다. **`scroll_speed`보다 우선합니다.**
-- `scroll_speed` - 사용자 정의 스크롤 속도 배수 (기본값: `3`, 최소값: `1`). `scroll_acceleration.enabled`가 `true`이면 무시됩니다.
-- `diff_style` - diff 렌더링을 제어합니다. `"auto"`는 터미널 너비에 맞추고, `"stacked"`는 항상 단일 열을 보여줍니다.
+- `scroll_speed` - 사용자 정의 스크롤 속도 배수(기본: `3`, 최소: `1`). `scroll_acceleration.enabled`가 `true`이면 무시됩니다.
+- `diff_style` - diff 렌더링 방식을 제어합니다. `"auto"`는 터미널 너비에 맞춰 조정되고, `"stacked"`는 항상 단일 컬럼으로 표시합니다.
-[TUI 사용법에 대해 더 알아보기](/docs/tui).
+[TUI에 대해 더 알아보기](/docs/tui).
---
-## 서버
+### Server
-`opencode serve` 및 `opencode web` 명령에 대한 서버 설정을 구성할 수 있습니다.
+`server` 옵션으로 `opencode serve`와 `opencode web` 명령의 server 설정을 구성할 수 있습니다.
```json title="opencode.json"
{
@@ -192,21 +192,21 @@ opencode run "Hello world"
}
```
-유효한 옵션:
+사용 가능한 옵션:
-- `port` - 리스닝 포트.
-- `hostname` - 리스닝 호스트 이름. `mdns`가 활성화되고 hostname이 설정되지 않으면 `0.0.0.0`이 기본값이 됩니다.
-- `mdns` - mDNS 서비스 발견 활성화. 로컬 네트워크의 다른 장치가 OpenCode 서버를 찾을 수 있습니다.
-- `mdnsDomain` - mDNS 서비스를 위한 사용자 정의 도메인 이름. 기본값은 `opencode.local`입니다. 동일한 네트워크에서 여러 인스턴스를 실행할 때 유용합니다.
-- `cors` - 브라우저 기반 클라이언트에서 HTTP 서버를 사용할 때 CORS를 허용할 추가 출처(Origin). 값은 전체 출처(스킴 + 호스트 + 선택적 포트)여야 합니다. 예: `https://app.example.com`.
+- `port` - 수신할 포트입니다.
+- `hostname` - 수신할 호스트명입니다. `mdns`가 활성화되어 있고 hostname이 없으면 기본값은 `0.0.0.0`입니다.
+- `mdns` - mDNS service discovery를 활성화합니다. 네트워크 내 다른 기기에서 OpenCode server를 찾을 수 있습니다.
+- `mdnsDomain` - mDNS service의 custom 도메인 이름입니다. 기본값은 `opencode.local`입니다. 같은 네트워크에서 여러 인스턴스를 실행할 때 유용합니다.
+- `cors` - 브라우저 기반 client에서 HTTP server를 사용할 때 허용할 추가 CORS origin입니다. 값은 전체 origin(scheme + host + optional port) 형식이어야 하며, 예: `https://app.example.com`.
-[서버에 대해 더 알아보기](/docs/server).
+[server에 대해 더 알아보기](/docs/server).
---
-## 도구
+### Tools
-`tools` 옵션을 통해 LLM이 사용할 수 있는 도구를 구성할 수 있습니다.
+`tools` 옵션으로 LLM이 사용할 수 있는 tool을 관리할 수 있습니다.
```json title="opencode.json"
{
@@ -218,13 +218,13 @@ opencode run "Hello world"
}
```
-[도구에 대해 더 알아보기](/docs/tools).
+[tool에 대해 더 알아보기](/docs/tools).
---
-## 모델
+### Models
-`provider`, `model`, `small_model` 옵션을 통해 OpenCode 구성에서 사용할 공급자와 모델을 구성할 수 있습니다.
+OpenCode config의 `provider`, `model`, `small_model` 옵션으로 사용할 provider와 model을 설정할 수 있습니다.
```json title="opencode.json"
{
@@ -235,9 +235,9 @@ opencode run "Hello world"
}
```
-`small_model` 옵션은 제목 생성과 같은 가벼운 작업을 위한 별도의 모델을 구성합니다. 기본적으로, OpenCode는 공급자에게서 사용 가능한 더 저렴한 모델이 있다면 그것을 사용하고, 그렇지 않으면 주 모델로 돌아갑니다.
+`small_model`은 제목 생성 같은 경량 작업에 사용할 별도 model을 설정합니다. 기본적으로 OpenCode는 provider에서 더 저렴한 model을 사용할 수 있으면 우선 사용하고, 없으면 메인 model로 fallback합니다.
-공급자 옵션은 `timeout`과 `setCacheKey`를 포함할 수 있습니다:
+provider 옵션에는 `timeout`, `setCacheKey`를 포함할 수 있습니다.
```json title="opencode.json"
{
@@ -253,20 +253,20 @@ opencode run "Hello world"
}
```
-- `timeout` - 요청 타임아웃(밀리초) (기본값: 300000). `false`로 설정하여 비활성화할 수 있습니다.
-- `setCacheKey` - 지정된 공급자에 대해 캐시 키가 항상 설정되도록 강제합니다.
+- `timeout` - 요청 timeout(밀리초, 기본값: 300000). `false`로 비활성화할 수 있습니다.
+- `setCacheKey` - 지정된 provider에 대해 cache key가 항상 설정되도록 보장합니다.
-[로컬 모델](/docs/models#local)을 구성할 수도 있습니다. [더 알아보기](/docs/models).
+[local model](/docs/models#local)도 설정할 수 있습니다. [더 알아보기](/docs/models).
---
-### 공급자별 옵션
+#### Provider-Specific Options
-일반적인 `timeout` 및 `apiKey` 외에도 일부 공급자는 추가 구성 옵션을 지원합니다.
+일부 provider는 공통 옵션인 `timeout`, `apiKey` 외에 추가 config 옵션을 지원합니다.
##### Amazon Bedrock
-Amazon Bedrock는 AWS 관련 구성을 지원합니다:
+Amazon Bedrock은 AWS 전용 config를 지원합니다.
```json title="opencode.json"
{
@@ -283,21 +283,21 @@ Amazon Bedrock는 AWS 관련 구성을 지원합니다:
}
```
-- `region` - Bedrock를 위한 AWS 리전 (`AWS_REGION` 환경 변수 또는 `us-east-1`이 기본값)
-- `profile` - `~/.aws/credentials`의 AWS 프로필 이름 (`AWS_PROFILE` 환경 변수가 기본값)
-- `endpoint` - VPC 엔드포인트 등을 위한 사용자 정의 엔드포인트 URL. 이는 AWS 관련 용어를 사용한 일반적인 `baseURL` 옵션의 별칭입니다. 둘 다 지정된 경우 `endpoint`가 우선합니다.
+- `region` - Bedrock용 AWS 리전(`AWS_REGION` env var 또는 기본값 `us-east-1`)
+- `profile` - `~/.aws/credentials`의 AWS named profile(기본값: `AWS_PROFILE` env var)
+- `endpoint` - VPC endpoint용 custom endpoint URL입니다. AWS 용어를 사용한 일반 `baseURL` 옵션의 별칭(alias)입니다. 둘 다 지정하면 `endpoint`가 우선합니다.
:::note
-Bearer 토큰(`AWS_BEARER_TOKEN_BEDROCK` 또는 `/connect`)은 프로필 기반 인증보다 우선합니다. 자세한 내용은 [인증 우선 순위](/docs/providers#authentication-precedence)를 참조하십시오.
+Bearer token(`AWS_BEARER_TOKEN_BEDROCK` 또는 `/connect`)은 profile 기반 인증보다 우선합니다. 자세한 내용은 [authentication precedence](/docs/providers#authentication-precedence)를 참고하세요.
:::
-[Amazon Bedrock에 대해 더 알아보기](/docs/providers#amazon-bedrock).
+[Amazon Bedrock config 더 알아보기](/docs/providers#amazon-bedrock).
---
-## 테마
+### Themes
-`theme` 옵션을 통해 OpenCode 구성에서 사용할 테마를 설정할 수 있습니다.
+`theme` 옵션으로 OpenCode config에서 사용할 theme를 설정할 수 있습니다.
```json title="opencode.json"
{
@@ -310,9 +310,9 @@ Bearer 토큰(`AWS_BEARER_TOKEN_BEDROCK` 또는 `/connect`)은 프로필 기반
---
-## 에이전트
+### Agents
-`agent` 옵션을 통해 특정 작업을 전문으로 하는 에이전트를 구성할 수 있습니다.
+`agent` 옵션으로 특정 작업용 전문 agent를 구성할 수 있습니다.
```jsonc title="opencode.jsonc"
{
@@ -332,13 +332,13 @@ Bearer 토큰(`AWS_BEARER_TOKEN_BEDROCK` 또는 `/connect`)은 프로필 기반
}
```
-`~/.config/opencode/agents/` 또는 `.opencode/agents/`에서 Markdown 파일을 사용하여 에이전트를 정의할 수도 있습니다. [더 알아보기](/docs/agents).
+`~/.config/opencode/agents/` 또는 `.opencode/agents/`의 Markdown 파일로 agent를 정의할 수도 있습니다. [더 알아보기](/docs/agents).
---
-### 기본 에이전트
+### Default agent
-`default_agent` 옵션을 사용하여 기본 에이전트를 설정할 수 있습니다. 명시적으로 지정되지 않았을 때 어떤 에이전트가 사용될지 결정합니다.
+`default_agent` 옵션으로 기본 agent를 설정할 수 있습니다. 별도 지정이 없을 때 어떤 agent를 사용할지 결정합니다.
```json title="opencode.json"
{
@@ -347,15 +347,15 @@ Bearer 토큰(`AWS_BEARER_TOKEN_BEDROCK` 또는 `/connect`)은 프로필 기반
}
```
-기본 에이전트는 기본(primary) 에이전트여야 합니다(서브 에이전트 불가). `"build"` 또는 `"plan"`과 같은 내장 에이전트이거나 정의된 [사용자 정의 에이전트](./agents)일 수 있습니다. 지정된 에이전트가 존재하지 않는 경우, OpenCode는 경고와 함께 `"build"`로 돌아갑니다.
+기본 agent는 primary agent여야 합니다(subagent 불가). `"build"`, `"plan"` 같은 내장 agent나 직접 정의한 [custom agent](/docs/agents)를 지정할 수 있습니다. 지정한 agent가 없거나 subagent이면 OpenCode는 경고와 함께 `"build"`로 fallback합니다.
-이 설정은 모든 인터페이스에 적용됩니다: TUI, CLI (`opencode run`), 데스크톱 앱 및 GitHub Action.
+이 설정은 TUI, CLI(`opencode run`), 데스크톱 앱, GitHub Action 등 모든 인터페이스에 적용됩니다.
---
-## 공유
+### Sharing
-`share` 옵션을 통해 [공유](/docs/share) 기능을 구성할 수 있습니다.
+`share` 옵션으로 [share](/docs/share) 기능을 설정할 수 있습니다.
```json title="opencode.json"
{
@@ -364,19 +364,19 @@ Bearer 토큰(`AWS_BEARER_TOKEN_BEDROCK` 또는 `/connect`)은 프로필 기반
}
```
-값은 다음과 같습니다:
+허용 값:
-- `"manual"` - 명령을 통한 수동 공유 허용 (기본값)
-- `"auto"` - 새로운 대화를 자동으로 공유
-- `"disabled"` - 공유 기능 완전히 비활성화
+- `"manual"` - 명령으로 수동 공유 허용(기본값)
+- `"auto"` - 새 대화를 자동 공유
+- `"disabled"` - 공유 기능 완전 비활성화
-기본적으로 `/share` 명령을 사용하여 대화를 명시적으로 공유해야 하는 수동 모드로 설정됩니다.
+기본값은 manual 모드이며, `/share` 명령으로 명시적으로 공유해야 합니다.
---
-## 명령
+### Commands
-`command` 옵션을 통해 반복 작업을 위한 사용자 정의 명령을 구성할 수 있습니다.
+`command` 옵션으로 반복 작업용 custom command를 구성할 수 있습니다.
```jsonc title="opencode.jsonc"
{
@@ -387,8 +387,6 @@ Bearer 토큰(`AWS_BEARER_TOKEN_BEDROCK` 또는 `/connect`)은 프로필 기반
"description": "Run tests with coverage",
"agent": "build",
"model": "anthropic/claude-haiku-4-5",
- "agent": "build",
- "model": "anthropic/claude-haiku-4-5",
},
"component": {
"template": "Create a new React component named $ARGUMENTS with TypeScript support.\nInclude proper typing and basic structure.",
@@ -398,13 +396,13 @@ Bearer 토큰(`AWS_BEARER_TOKEN_BEDROCK` 또는 `/connect`)은 프로필 기반
}
```
-`~/.config/opencode/commands/` 또는 `.opencode/commands/`에서 Markdown 파일을 사용하여 명령을 정의할 수도 있습니다. [더 알아보기](/docs/commands).
+`~/.config/opencode/commands/` 또는 `.opencode/commands/`의 Markdown 파일로 command를 정의할 수도 있습니다. [더 알아보기](/docs/commands).
---
-## 키바인드
+### Keybinds
-`keybinds` 옵션을 통해 키바인드를 사용자 정의할 수 있습니다.
+`keybinds` 옵션으로 keybind를 커스터마이즈할 수 있습니다.
```json title="opencode.json"
{
@@ -417,9 +415,9 @@ Bearer 토큰(`AWS_BEARER_TOKEN_BEDROCK` 또는 `/connect`)은 프로필 기반
---
-## 자동 업데이트
+### Autoupdate
-OpenCode는 시작될 때 자동으로 새로운 업데이트를 다운로드합니다. `autoupdate` 옵션으로 이를 비활성화할 수 있습니다.
+OpenCode는 시작 시 새 업데이트를 자동으로 다운로드합니다. `autoupdate` 옵션으로 비활성화할 수 있습니다.
```json title="opencode.json"
{
@@ -428,14 +426,14 @@ OpenCode는 시작될 때 자동으로 새로운 업데이트를 다운로드합
}
```
-업데이트를 원하지 않지만 새 버전을 알림받고 싶다면 `autoupdate`를 `"notify"`로 설정하십시오.
-Homebrew와 같은 패키지 관리자를 사용하여 설치되지 않은 경우에만 작동합니다.
+업데이트를 자동 적용하지 않고 새 버전 알림만 받고 싶다면 `autoupdate`를 `"notify"`로 설정하세요.
+이 옵션은 Homebrew 같은 패키지 매니저로 설치하지 않은 경우에만 동작합니다.
---
-## 포매터
+### Formatters
-`formatter` 옵션을 통해 코드 포매터를 구성할 수 있습니다.
+`formatter` 옵션으로 코드 formatter를 설정할 수 있습니다.
```json title="opencode.json"
{
@@ -455,15 +453,15 @@ Homebrew와 같은 패키지 관리자를 사용하여 설치되지 않은 경
}
```
-[포매터에 대해 더 알아보기](/docs/formatters).
+[formatter에 대해 더 알아보기](/docs/formatters).
---
-## 권한
+### Permissions
-기본적으로, OpenCode는 **명시적 승인 없이 모든 작업을 허용**합니다. `permission` 옵션을 사용하여 이를 변경할 수 있습니다.
+기본적으로 OpenCode는 **명시적 승인 없이 모든 작업을 허용**합니다. `permission` 옵션으로 이 동작을 바꿀 수 있습니다.
-예를 들어, `edit` 및 `bash` 도구가 사용자 승인을 요구하도록 설정하려면:
+예를 들어 `edit`, `bash` tool이 사용자 승인을 요구하게 하려면:
```json title="opencode.json"
{
@@ -475,32 +473,34 @@ Homebrew와 같은 패키지 관리자를 사용하여 설치되지 않은 경
}
```
-[권한에 대해 더 알아보기](/docs/permissions).
+[permission에 대해 더 알아보기](/docs/permissions).
---
-### 압축
+### Compaction
-`compaction` 옵션을 통해 컨텍스트 압축 동작을 제어할 수 있습니다.
+`compaction` 옵션으로 context compaction 동작을 제어할 수 있습니다.
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"compaction": {
"auto": true,
- "prune": true
+ "prune": true,
+ "reserved": 10000
}
}
```
-- `auto` - 컨텍스트가 꽉 차면 자동으로 세션을 압축합니다 (기본값: `true`).
-- `prune` - 토큰을 절약하기 위해 오래된 도구 출력을 제거합니다 (기본값: `true`).
+- `auto` - context가 가득 찼을 때 세션을 자동 compact합니다(기본값: `true`).
+- `prune` - token 절약을 위해 오래된 tool 출력을 제거합니다(기본값: `true`).
+- `reserved` - compaction용 token buffer입니다. compaction 중 overflow가 나지 않도록 충분한 window를 남깁니다.
---
-### 파일 감시자
+### Watcher
-`watcher` 옵션을 통해 파일 감시자가 무시할 패턴을 설정할 수 있습니다.
+`watcher` 옵션으로 파일 watcher ignore 패턴을 설정할 수 있습니다.
```json title="opencode.json"
{
@@ -511,13 +511,13 @@ Homebrew와 같은 패키지 관리자를 사용하여 설치되지 않은 경
}
```
-패턴은 glob 구문을 따릅니다. 잡음이 많은 디렉토리를 제외하는 데 사용하십시오.
+패턴은 glob 문법을 따릅니다. 파일 감시에서 노이즈가 많은 디렉토리를 제외할 때 유용합니다.
---
-### MCP 서버
+### MCP servers
-`mcp` 옵션을 통해 사용하려는 MCP 서버를 구성할 수 있습니다.
+`mcp` 옵션으로 사용할 MCP server를 설정할 수 있습니다.
```json title="opencode.json"
{
@@ -530,11 +530,11 @@ Homebrew와 같은 패키지 관리자를 사용하여 설치되지 않은 경
---
-### 플러그인
+### Plugins
-[플러그인](/docs/plugins)은 사용자 정의 도구, 훅(hook), 통합으로 OpenCode를 확장합니다.
+[Plugins](/docs/plugins)는 custom tool, hook, integration으로 OpenCode를 확장합니다.
-`.opencode/plugins/` 또는 `~/.config/opencode/plugins/`에 플러그인 파일을 배치하십시오. `plugin` 옵션을 통해 npm에서 플러그인을 로드할 수 있습니다.
+plugin 파일은 `.opencode/plugins/` 또는 `~/.config/opencode/plugins/`에 두세요. `plugin` 옵션으로 npm plugin을 로드할 수도 있습니다.
```json title="opencode.json"
{
@@ -547,9 +547,9 @@ Homebrew와 같은 패키지 관리자를 사용하여 설치되지 않은 경
---
-### 지침
+### Instructions
-`instructions` 옵션을 통해 모델에 대한 지침(Rules)을 구성할 수 있습니다.
+`instructions` 옵션으로 사용 중인 model에 제공할 지침 파일을 설정할 수 있습니다.
```json title="opencode.json"
{
@@ -558,13 +558,13 @@ Homebrew와 같은 패키지 관리자를 사용하여 설치되지 않은 경
}
```
-지침 파일에 대한 경로와 glob 패턴의 배열을 사용합니다. [규칙에 대해 더 알아보기](/docs/rules).
+이 옵션은 지침 파일 경로 및 glob 패턴 배열을 받습니다. [rules에 대해 더 알아보기](/docs/rules).
---
-## 비활성화된 공급자
+### Disabled providers
-`disabled_providers` 옵션을 통해 자동으로 로드되는 공급자를 비활성화할 수 있습니다. 자격 증명이 유효하더라도 특정 공급자가 로드되는 것을 방지할 때 유용합니다.
+`disabled_providers` 옵션으로 자동 로드되는 provider를 비활성화할 수 있습니다. credential이 있어도 특정 provider를 로드하지 않게 하고 싶을 때 유용합니다.
```json title="opencode.json"
{
@@ -577,17 +577,17 @@ Homebrew와 같은 패키지 관리자를 사용하여 설치되지 않은 경
`disabled_providers`는 `enabled_providers`보다 우선합니다.
:::
-`disabled_providers` 옵션은 공급자 ID의 배열을 허용합니다. 공급자가 비활성화되면:
+`disabled_providers`는 provider ID 배열을 받습니다. provider가 비활성화되면:
-- 환경 변수가 설정된 경우에도 로드되지 않습니다.
-- API 키가 `/connect` 명령을 통해 구성되는 경우에도 로드되지 않습니다.
-- 공급자의 모델은 모델 선택 목록에 표시되지 않습니다.
+- 환경 변수가 설정되어 있어도 로드되지 않습니다.
+- `/connect` 명령으로 API key를 설정해도 로드되지 않습니다.
+- 해당 provider의 model은 model 선택 목록에 표시되지 않습니다.
---
-### 활성화된 공급자
+### Enabled providers
-`enabled_providers` 옵션을 통해 허용할 공급자를 지정할 수 있습니다. 설정하면 지정된 공급자만 활성화되고 다른 모든 공급자는 무시됩니다.
+`enabled_providers` 옵션으로 provider allowlist를 지정할 수 있습니다. 이 값을 설정하면 지정한 provider만 활성화되고 나머지는 무시됩니다.
```json title="opencode.json"
{
@@ -596,19 +596,19 @@ Homebrew와 같은 패키지 관리자를 사용하여 설치되지 않은 경
}
```
-OpenCode를 제한하여 특정 공급자만 사용하도록 할 때 유용합니다.
+provider를 하나씩 비활성화하는 대신, OpenCode가 특정 provider만 사용하도록 제한하고 싶을 때 유용합니다.
:::note
`disabled_providers`는 `enabled_providers`보다 우선합니다.
:::
-공급자가 `enabled_providers`와 `disabled_providers` 둘 다에 나타나면, 하위 호환성을 위해 `disabled_providers`가 우선합니다.
+동일 provider가 `enabled_providers`와 `disabled_providers`에 모두 있으면 하위 호환성을 위해 `disabled_providers`가 우선합니다.
---
-### 실험적 기능
+### Experimental
-`experimental` 키는 활발히 개발 중인 옵션을 포함합니다.
+`experimental` key에는 현재 활발히 개발 중인 옵션이 포함됩니다.
```json title="opencode.json"
{
@@ -618,20 +618,20 @@ OpenCode를 제한하여 특정 공급자만 사용하도록 할 때 유용합
```
:::caution
-실험적 옵션은 안정적이지 않습니다. 예고 없이 변경되거나 제거될 수 있습니다.
+experimental 옵션은 안정적이지 않습니다. 예고 없이 변경되거나 제거될 수 있습니다.
:::
---
-## 변수
+## Variables
-구성 파일에서 환경 변수를 참조하고 파일 내용에 대한 변수 대체를 사용할 수 있습니다.
+config 파일에서 환경 변수와 파일 내용을 참조할 수 있도록 변수 치환을 사용할 수 있습니다.
---
-##### 환경 변수
+### Env vars
-`{env:VARIABLE_NAME}`을 사용하여 환경 변수를 대체합니다.
+`{env:VARIABLE_NAME}` 형식으로 환경 변수를 치환할 수 있습니다.
```json title="opencode.json"
{
@@ -648,13 +648,13 @@ OpenCode를 제한하여 특정 공급자만 사용하도록 할 때 유용합
}
```
-환경 변수가 설정되지 않으면 빈 문자열로 대체됩니다.
+환경 변수가 설정되지 않았으면 빈 문자열로 치환됩니다.
---
-## 파일
+### Files
-`{file:path/to/file}`를 사용하여 파일의 내용을 대체합니다.
+`{file:path/to/file}` 형식으로 파일 내용을 치환할 수 있습니다.
```json title="opencode.json"
{
@@ -670,13 +670,13 @@ OpenCode를 제한하여 특정 공급자만 사용하도록 할 때 유용합
}
```
-파일 경로는:
+파일 경로는 다음을 지원합니다.
-- 구성 파일 디렉토리에 상대적이거나
-- `/` 또는 `~`로 시작하는 절대 경로여야 합니다.
+- config 파일 디렉토리 기준 상대 경로
+- `/` 또는 `~`로 시작하는 절대 경로
-이것은 다음에 유용합니다:
+이 기능은 다음 상황에 유용합니다.
-- API 키와 같은 민감한 데이터를 별도의 파일에 유지할 때.
-- 구성을 어지럽히지 않고 큰 지침 파일을 포함할 때.
-- 여러 구성 파일에서 공통 구성 스니펫을 공유할 때.
+- API key 같은 민감 정보를 별도 파일로 분리
+- 큰 지침 파일을 config를 복잡하게 만들지 않고 포함
+- 여러 config 파일에서 공통 설정 스니펫 재사용
diff --git a/packages/web/src/content/docs/ko/custom-tools.mdx b/packages/web/src/content/docs/ko/custom-tools.mdx
index 5da050f49b..77310557fa 100644
--- a/packages/web/src/content/docs/ko/custom-tools.mdx
+++ b/packages/web/src/content/docs/ko/custom-tools.mdx
@@ -1,30 +1,30 @@
---
-title: 사용자 정의 도구
-description: OpenCode에서 LLM이 호출할 수 있는 도구를 만듭니다.
+title: Custom Tools
+description: Create tools the LLM can call in OpenCode.
---
-사용자 정의 도구는 LLM이 대화 중에 호출 할 수있는 기능을 만듭니다. 그들은 `read`, `write` 및 `bash`와 같은 opencode의 [붙박이 도구](./tools)와 함께 작동합니다.
+custom tool은 대화 중 LLM이 호출할 수 있도록 사용자가 직접 만든 함수입니다. `read`, `write`, `bash` 같은 OpenCode의 [built-in tools](/docs/tools)와 함께 동작합니다.
---
## 도구 만들기
-도구는 **TypeScript** 또는 **JavaScript** 파일로 정의됩니다. 그러나 도구 정의는 ** 어떤 언어로 작성된 스크립트를 호출 할 수 있습니다 ** - TypeScript 또는 JavaScript는 도구 정의 자체에서만 사용됩니다.
+tool은 **TypeScript** 또는 **JavaScript** 파일로 정의합니다. 다만 tool 정의에서 호출하는 스크립트는 **어떤 언어든** 사용할 수 있습니다. 즉, TypeScript/JavaScript는 tool 정의 자체에만 필요합니다.
---
-## 위치
+### 위치
-그들은 정의 할 수 있습니다:
+tool은 다음 위치에 둘 수 있습니다.
-- 프로젝트의 `.opencode/tools/` 디렉토리에 배치하여 로컬.
-- 또는 전 세계적으로 `~/.config/opencode/tools/`에 배치하여.
+- 프로젝트의 `.opencode/tools/` 디렉토리(로컬)
+- `~/.config/opencode/tools/` 디렉토리(전역)
---
-## 구조
+### 구조
-도구를 만드는 가장 쉬운 방법은 `tool()` helper를 사용하여 유형 안전 및 검증을 제공합니다.
+tool을 가장 쉽게 만드는 방법은 타입 안정성과 validation을 제공하는 `tool()` helper를 사용하는 것입니다.
```ts title=".opencode/tools/database.ts" {1}
import { tool } from "@opencode-ai/plugin"
@@ -41,13 +41,13 @@ export default tool({
})
```
-**파일 이름**는 **tool name**가 됩니다. 위는 `database` 공구를 만듭니다.
+**파일 이름**이 **tool 이름**이 됩니다. 위 예시는 `database` tool을 생성합니다.
---
-### 파일당 여러 도구
+#### 파일 하나에 여러 tool 정의
-단일 파일에서 여러 도구를 수출할 수 있습니다. 각 수출은 ** 별도의 도구 ** 이름 ** `_`**:
+하나의 파일에서 여러 tool을 export할 수도 있습니다. 각 export는 **별도의 tool**이 되며 이름은 **`_`** 형식을 사용합니다.
```ts title=".opencode/tools/math.ts"
import { tool } from "@opencode-ai/plugin"
@@ -75,13 +75,13 @@ export const multiply = tool({
})
```
-이것은 2개의 공구를 만듭니다: `math_add`와 `math_multiply`.
+이 경우 `math_add`, `math_multiply` 두 tool이 생성됩니다.
---
-#### 스키마
+### 인자
-`tool.schema`를 사용할 수 있습니다, 그냥 [Zod](https://zod.dev), 인수 유형을 정의합니다.
+인자 타입은 `tool.schema`로 정의할 수 있습니다. `tool.schema`는 [Zod](https://zod.dev) 기반입니다.
```ts "tool.schema"
args: {
@@ -89,7 +89,7 @@ args: {
}
```
-[Zod](https://zod.dev)를 직접 가져오고 일반 객체를 반환할 수 있습니다.
+[Zod](https://zod.dev)를 직접 import해서 일반 객체를 반환하는 방식도 사용할 수 있습니다.
```ts {6}
import { z } from "zod"
@@ -108,9 +108,9 @@ export default {
---
-### 컨텍스트
+### Context
-도구는 현재 세션에 대한 컨텍스트를받습니다.
+tool은 현재 세션의 context 정보를 전달받습니다.
```ts title=".opencode/tools/project.ts" {8}
import { tool } from "@opencode-ai/plugin"
@@ -126,18 +126,18 @@ export default tool({
})
```
-세션 작업 디렉토리에 `context.directory`를 사용합니다.
-git worktree 루트에 `context.worktree`를 사용합니다.
+세션 작업 디렉토리는 `context.directory`를 사용하세요.
+git worktree 루트는 `context.worktree`를 사용하세요.
---
-## 예제
+## 예시
-### Python 도구 작성
+### Python으로 tool 작성
-원하는 모든 언어로 도구를 쓸 수 있습니다. 여기에 Python을 사용하여 두 개의 숫자를 추가하는 예입니다.
+tool은 원하는 언어로 작성할 수 있습니다. 아래는 Python으로 두 숫자를 더하는 예시입니다.
-먼저 Python 스크립트로 도구를 만듭니다.
+먼저 Python 스크립트로 tool을 만듭니다.
```python title=".opencode/tools/add.py"
import sys
@@ -147,7 +147,7 @@ b = int(sys.argv[2])
print(a + b)
```
-그런 다음 도구 정의를 만듭니다.
+그다음 이 스크립트를 호출하는 tool 정의를 만듭니다.
```ts title=".opencode/tools/python-add.ts" {10}
import { tool } from "@opencode-ai/plugin"
@@ -167,4 +167,4 @@ export default tool({
})
```
-여기에 우리는 [`Bun.$`](https://bun.com/docs/runtime/shell) 유틸리티를 사용하여 Python 스크립트를 실행합니다.
+여기서는 Python 스크립트를 실행하기 위해 [`Bun.$`](https://bun.com/docs/runtime/shell) 유틸리티를 사용합니다.
diff --git a/packages/web/src/content/docs/ko/index.mdx b/packages/web/src/content/docs/ko/index.mdx
index d5bd6a979c..b94d0750f6 100644
--- a/packages/web/src/content/docs/ko/index.mdx
+++ b/packages/web/src/content/docs/ko/index.mdx
@@ -84,7 +84,8 @@ curl -fsSL https://opencode.ai/install | bash
- **Arch Linux에서 Paru 사용**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/nb/index.mdx b/packages/web/src/content/docs/nb/index.mdx
index b10662537d..0e6846d2b9 100644
--- a/packages/web/src/content/docs/nb/index.mdx
+++ b/packages/web/src/content/docs/nb/index.mdx
@@ -84,7 +84,8 @@ Du kan også installere den med følgende kommandoer:
- **Bruke Paru på Arch Linux**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/pl/index.mdx b/packages/web/src/content/docs/pl/index.mdx
index 1c4ea7ea2b..fc571ec5d9 100644
--- a/packages/web/src/content/docs/pl/index.mdx
+++ b/packages/web/src/content/docs/pl/index.mdx
@@ -84,7 +84,8 @@ Możesz też użyć poniższych metod instalacji:
- **Korzystanie z Paru na Arch Linux**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/pt-br/index.mdx b/packages/web/src/content/docs/pt-br/index.mdx
index acbf12f2aa..6f23f4ebc5 100644
--- a/packages/web/src/content/docs/pt-br/index.mdx
+++ b/packages/web/src/content/docs/pt-br/index.mdx
@@ -84,7 +84,8 @@ Você também pode instalá-lo com os seguintes comandos:
- **Usando Paru no Arch Linux**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/ru/index.mdx b/packages/web/src/content/docs/ru/index.mdx
index b4a115250f..0b17c23785 100644
--- a/packages/web/src/content/docs/ru/index.mdx
+++ b/packages/web/src/content/docs/ru/index.mdx
@@ -84,7 +84,8 @@ curl -fsSL https://opencode.ai/install | bash
- **Использование Paru в Arch Linux**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/th/index.mdx b/packages/web/src/content/docs/th/index.mdx
index fbf1f7e575..5336db3c9e 100644
--- a/packages/web/src/content/docs/th/index.mdx
+++ b/packages/web/src/content/docs/th/index.mdx
@@ -84,7 +84,8 @@ curl -fsSL https://opencode.ai/install | bash
- **ใช้ Paru บน Arch Linux**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/tr/index.mdx b/packages/web/src/content/docs/tr/index.mdx
index 7c31f5bd34..291d3d490c 100644
--- a/packages/web/src/content/docs/tr/index.mdx
+++ b/packages/web/src/content/docs/tr/index.mdx
@@ -84,7 +84,8 @@ Ayrıca aşağıdaki komutlarla da yükleyebilirsiniz:
- **Paru'yu Arch Linux'ta kullanma**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx
index 11091de87e..ae364fc9e1 100644
--- a/packages/web/src/content/docs/zen.mdx
+++ b/packages/web/src/content/docs/zen.mdx
@@ -82,8 +82,10 @@ You can also access our models through the following API endpoints.
| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
-| MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
+| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
+| MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
+| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -117,7 +119,9 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
| --------------------------------- | ------ | ------ | ----------- | ------------ |
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
+| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - |
| MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - |
+| GLM 5 | $1.00 | $3.20 | $0.20 | - |
| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
| Kimi K2.5 Free | Free | Free | Free | - |
diff --git a/packages/web/src/content/docs/zh-cn/index.mdx b/packages/web/src/content/docs/zh-cn/index.mdx
index bc71221372..5bff7628ac 100644
--- a/packages/web/src/content/docs/zh-cn/index.mdx
+++ b/packages/web/src/content/docs/zh-cn/index.mdx
@@ -84,7 +84,8 @@ curl -fsSL https://opencode.ai/install | bash
- **在 Arch Linux 上使用 Paru**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/content/docs/zh-cn/mcp-servers.mdx b/packages/web/src/content/docs/zh-cn/mcp-servers.mdx
index eb00156387..6a5fe2075d 100644
--- a/packages/web/src/content/docs/zh-cn/mcp-servers.mdx
+++ b/packages/web/src/content/docs/zh-cn/mcp-servers.mdx
@@ -169,7 +169,7 @@ opencode自动处理远程MCP服务器的OAuth身份验证。当服务器需要
1. 检测 401 响应并启动 OAuth 流程
2. 如果服务器支持,请使用**动态客户端注册 (RFC 7591)**
-3. 安全地存儲Tokens以供将來的请求
+3. 安全地存储Tokens以供将来的请求
---
@@ -189,7 +189,7 @@ opencode自动处理远程MCP服务器的OAuth身份验证。当服务器需要
}
```
-如果服務器需要身份验证,opencode 将在您第一次嘗試使用它時提示您进行身份验证。如果沒有,您可以使用 `timeout`[手动觸發流量](#authenticating)。
+如果服务器需要身份验证,opencode 将在您第一次尝试使用它时提示您进行身份验证。如果没有,您可以使用 `timeout`[手动触发流量](#authenticating)。
---
@@ -218,7 +218,7 @@ opencode自动处理远程MCP服务器的OAuth身份验证。当服务器需要
### 身份验证
-您可以手动觸發身份验证或管理憑據。
+您可以手动触发身份验证或管理凭据。
使用特定MCP服务器进行身份验证:
@@ -232,7 +232,7 @@ opencode mcp auth my-oauth-server
opencode mcp list
```
-删除存儲的憑據:
+删除存储的凭据:
```bash
opencode mcp logout my-oauth-server
@@ -297,7 +297,7 @@ opencode mcp debug my-oauth-server
### 全局
-这意味著您可以全局启用或禁用它們。
+这意味著您可以全局启用或禁用它们。
```json title="opencode.json" {14}
{
diff --git a/packages/web/src/content/docs/zh-cn/models.mdx b/packages/web/src/content/docs/zh-cn/models.mdx
index 1a9b5b4c07..84d542efdd 100644
--- a/packages/web/src/content/docs/zh-cn/models.mdx
+++ b/packages/web/src/content/docs/zh-cn/models.mdx
@@ -17,7 +17,7 @@ opencode 使用 [AI SDK](https://ai-sdk.dev/) 和 [Models.dev](https://models.de
## 选择模型
-配置完提供商後,您可以通过輸入以下內容來选择您想要的模型:
+配置完提供商后,您可以通过输入以下内容来选择您想要的模型:
```bash frame="none"
/models
@@ -27,29 +27,29 @@ opencode 使用 [AI SDK](https://ai-sdk.dev/) 和 [Models.dev](https://models.de
## 推荐模型
-那裡有很多模型,每週都会有新模型問世。
+那里有很多模型,每周都会有新模型问世。
:::tip
-考慮使用我们推薦的模型之一。
+考虑使用我们推荐的模型之一。
:::
-然而,既擅長生成代碼又擅長工具调用的只有少數。
+然而,既擅长生成代码又擅长工具调用的只有少数。
以下是与 opencode 配合良好的几个模型,排名不分前面。(这不是好看的列表,也不一定是最新的):
- GPT 5.2
-- GPT 5.1 法典
-- 近距離工作4.5
-- 克勞德十四行詩 4.5
-- 极小极M2.1
-- 雙子座 3 专業版
+- GPT 5.1 Codex
+- Claude Opus 4.5
+- Claude Sonnet 4.5
+- Minimax M2.1
+- Gemini 3 Pro
---
## 设置默认值
-要将其中之一设置为默認模型,您可以在您的
-打開代碼配置。
+要将其中之一设置为默认模型,您可以在您的
+打开代码配置。
```json title="opencode.json" {3}
{
@@ -143,14 +143,14 @@ opencode 使用 [AI SDK](https://ai-sdk.dev/) 和 [Models.dev](https://models.de
opencode 附带了许多重大的默认变体:
-**人擇**:
+**Anthropic**:
- `high` - 高思维预算(默认)
- `max` - 最大预算规划
**OpenAI**:
-因模型而異,但大致如下:
+因模型而异,但大致如下:
- `none` - 没有推理
- `minimal` - 最少的推理工作
@@ -159,18 +159,18 @@ opencode 附带了许多重大的默认变体:
- `high` - 高推理能力
- `xhigh` - 极高的推理能力
-**谷歌**:
+**Google**:
-- `low` - 降低工作量/Tokens預算
-- `high` - 更高的努力/Tokens預算
+- `low` - 降低工作量/Tokens预算
+- `high` - 更高的努力/Tokens预算
:::tip
-该列表并不全面。許多其他提供商也有内置的默認值。
+该列表并不全面。许多其他提供商也有内置的默认值。
:::
### 自定义变体
-您可以覆盖現有變體或添加您自己的變體:
+您可以覆盖现有变体或添加您自己的变体:
```jsonc title="opencode.jsonc" {7-18}
{
@@ -195,7 +195,7 @@ opencode 附带了许多重大的默认变体:
}
```
-### 循环变体
+### 切换变体
使用按键绑定`variant_cycle`在变体之间快速切换。 [了解更多](/docs/keybinds)。
@@ -220,4 +220,4 @@ opencode 附带了许多重大的默认变体:
3. 最后使用的模型。
-4. 第一个模型使用内部优先級。
+4. 第一个模型使用内部优先级。
diff --git a/packages/web/src/content/docs/zh-cn/modes.mdx b/packages/web/src/content/docs/zh-cn/modes.mdx
index f4c2c78fdd..79c437884f 100644
--- a/packages/web/src/content/docs/zh-cn/modes.mdx
+++ b/packages/web/src/content/docs/zh-cn/modes.mdx
@@ -1,6 +1,6 @@
---
title: 模式
-description: 不同的模式適用于不同的用例。
+description: 不同的模式适用于不同的用例。
---
:::caution
@@ -13,7 +13,7 @@ opencode 中的模式允许自定义不同的示例行为、工具和提示。
它具有两种内置模式:**构建**和**计划**。您可以定制
这些或通过 opencode 配置配置您自己的。
-您可以在会话期間在模式之間切換或在配置文件中配置它們。
+您可以在会话期间在模式之间切换或在配置文件中配置它们。
---
@@ -25,13 +25,13 @@ opencode 有两种内置模式。
### 构建
-構建是启用所有工具的**默認**模式。这是開發工作的標準模式,您需要完全访问文件操作和系統命令。
+构建是启用所有工具的**默认**模式。这是开发工作的标准模式,您需要完全访问文件操作和系统命令。
---
### 计划
-专为規劃和分析而設計的受限模式。在計劃模式下,默認情况下禁用以下工具:
+专为规划和分析而设计的受限模式。在计划模式下,默认情况下禁用以下工具:
- `write` - 无法创建新文件
- `edit` - 无法修改现有文件,位于 `.opencode/plans/*.md` 的用于详细说明计划本身的文件另外
@@ -52,7 +52,7 @@ opencode 有两种内置模式。
## 配置
-您可以自定義内置模式或通过配置创建自己的模式。可以通过两种方式配置模式:
+您可以自定义内置模式或通过配置创建自己的模式。可以通过两种方式配置模式:
### JSON 配置
@@ -112,7 +112,7 @@ Provide constructive feedback without making direct changes.
Markdown 文件名成为模式名称(例如,`review.md` 创建`review` 模式)。
-讓我们詳細看看这些配置選項。
+让我们详细看看这些配置选项。
---
@@ -191,7 +191,7 @@ Markdown 文件名成为模式名称(例如,`review.md` 创建`review` 模
}
```
-该路徑是相对于配置文件所在位置的。所以这適用于
+该路径是相对于配置文件所在位置的。所以这适用于
全局opencode配置和项目特定配置。
---
@@ -217,13 +217,13 @@ Markdown 文件名成为模式名称(例如,`review.md` 创建`review` 模
}
```
-如果未指定任何工具,则默認启用所有工具。
+如果未指定任何工具,则默认启用所有工具。
---
#### 可用工具
-这裡是所有可以通过模式配置控制的工具。
+这里是所有可以通过模式配置控制的工具。
| 工具 | 描述 |
| ----------- | ---------------- |
@@ -243,7 +243,7 @@ Markdown 文件名成为模式名称(例如,`review.md` 创建`review` 模
## 自定义模式
-您可以通过将自定義模式添加到配置來创建自己的自定義模式。以下是使用这两种方法的示例:
+您可以通过将自定义模式添加到配置来创建自己的自定义模式。以下是使用这两种方法的示例:
### 使用 JSON 配置
@@ -320,12 +320,12 @@ Priorities:
### 使用案例
-以下是不同模式的一些常見用例。
+以下是不同模式的一些常见用例。
-- **构建模式**:启用所有工具的完整開發工作
-- **计划模式**:分析和計劃,無需更改
-- **审阅模式**:使用只讀访问权限和文档工具進行代碼审阅
+- **构建模式**:启用所有工具的完整开发工作
+- **计划模式**:分析和计划,无需更改
+- **审阅模式**:使用只读访问权限和文档工具进行代码审阅
- **调试模式**:专注于启用bash和读取工具的调查
-- **文档模式**:使用文件操作但不使用系統命令的文档編寫
+- **文档模式**:使用文件操作但不使用系统命令的文档编写
-您可能还会發現不同的模型適用于不同的用例。
+您可能还会发现不同的模型适用于不同的用例。
diff --git a/packages/web/src/content/docs/zh-cn/network.mdx b/packages/web/src/content/docs/zh-cn/network.mdx
index f64d099c65..2ba6b2f481 100644
--- a/packages/web/src/content/docs/zh-cn/network.mdx
+++ b/packages/web/src/content/docs/zh-cn/network.mdx
@@ -1,6 +1,6 @@
---
title: 网络
-description: 配置代理和自定義證書。
+description: 配置代理和自定义证书。
---
opencode支持企业网络环境的标准代理环境变量和自定义证书。
@@ -9,7 +9,7 @@ opencode支持企业网络环境的标准代理环境变量和自定义证书。
## 代理
-opencode 尊重标准代理环境变量。
+opencode 遵循标准代理环境变量。
```bash
# HTTPS proxy (recommended)
@@ -39,7 +39,7 @@ export HTTPS_PROXY=http://username:password@proxy.example.com:8080
```
:::caution
-避免对密碼進行硬編碼。使用環境變量或安全憑證存儲。
+避免对密码进行硬编码。使用环境变量或安全凭证存储。
:::
对于需要高级身份验证(例如 NTLM 或 Kerberos)的代理,请考虑使用支持您的身份验证方法的 LLM 网关。
diff --git a/packages/web/src/content/docs/zh-cn/permissions.mdx b/packages/web/src/content/docs/zh-cn/permissions.mdx
index 47f7ef95df..87bcb62ed0 100644
--- a/packages/web/src/content/docs/zh-cn/permissions.mdx
+++ b/packages/web/src/content/docs/zh-cn/permissions.mdx
@@ -1,6 +1,6 @@
---
title: 权限
-description: 控制哪些操作需要批准才能運行。
+description: 控制哪些操作需要批准才能运行。
---
opencode 使用`permission` 配置来决定给定的操作是否应自动运行、提示您或被阻止。
@@ -47,7 +47,7 @@ opencode 使用`permission` 配置来决定给定的操作是否应自动运行
## 粒度规则(对象语法)
-对于大多數权限,您可以使用对像根據工具輸入应用不同的操作。
+对于大多数权限,您可以使用对象根据工具输入应用不同的操作。
```json title="opencode.json"
{
@@ -68,11 +68,11 @@ opencode 使用`permission` 配置来决定给定的操作是否应自动运行
}
```
-规则通过模式匹配進行評估,**最后匹配的规则獲勝**。常見的模式是将包羅万象的 `"*"` 规则放在前面,然後再放置更具體的规则。
+规则通过模式匹配进行评估,**最后匹配的规则获胜**。常见的模式是将包罗万象的 `"*"` 规则放在前面,然后再放置更具体的规则。
### 通配符
-权限模式使用簡單的通配符匹配:
+权限模式使用简单的通配符匹配:
- `*` 匹配零个或多个任意字符
- `?` 恰好匹配一个字符
@@ -80,7 +80,7 @@ opencode 使用`permission` 配置来决定给定的操作是否应自动运行
### 主目录扩展
-您可以在模式目录中使用 `~` 或 `$HOME` 来引用您的主目录。这对于 [`external_directory`](#external-directories) 规则特别有用。
+您可以在模式目录中使用 `~` 或 `$HOME` 来引用您的主目录。这对于 [`外部目录`](#external_directory) 规则特别有用。
- `~/projects/*` -> `/Users/username/projects/*`
- `$HOME/projects/*` -> `/Users/username/projects/*`
@@ -133,14 +133,14 @@ opencode权限由工具名称和一些安全防护措施决定:
- `edit` — 所有文件修改(头部`edit`、`write`、`patch`、`multiedit`)
- `glob` — 文件通配符(匹配通配符模式)
- `grep` — 内容搜索(匹配正则表达式模式)
-- `list` — 上市目录中的文件(与目录路径匹配)
+- `list` — 列出目录中的文件(与目录路径匹配)
- `bash` — 运行 shell 命令(匹配 `git status --porcelain` 等解析命令)
- `task` — 启动子代理(与子代理类型匹配)
- `skill` — 加载技能(与技能名称匹配)
- `lsp` — 运行 LSP 查询(当前非粒度)
-- `todoread`、`todowrite` — 讀取/更新待辦事項列表
+- `todoread`、`todowrite` — 读取/更新待办事项列表
- `webfetch` — 获取 URL(与 URL 匹配)
-- `websearch`、`codesearch` — 網頁/代碼搜索(与查询匹配)
+- `websearch`、`codesearch` — 网页/代码搜索(与查询匹配)
- `external_directory` — 当工具访问项目工作目录外部的路径时触发
- `doom_loop` — 当相同的工具调用相同的输入重复 3 次时触发
@@ -148,7 +148,7 @@ opencode权限由工具名称和一些安全防护措施决定:
## 默认值
-如果您未指定任何内容,opencode分散许可的默认值开始:
+如果您未指定任何内容,opencode将从宽松的默认值开始:
- 大部分权限默认为`"allow"`。
- `doom_loop`和`external_directory`默认为`"ask"`。
@@ -171,7 +171,7 @@ opencode权限由工具名称和一些安全防护措施决定:
## “询问”的作用是什么
-当 opencode 提示批准时,UI 会提供清晰的结果:
+当 opencode 提示批准时,UI 会提供三种结果:
- `once` — 仅批准此请求
- `always` — 批准与建议模式匹配的未来请求(对于当前 opencode 会话的其余部分)
diff --git a/packages/web/src/content/docs/zh-cn/plugins.mdx b/packages/web/src/content/docs/zh-cn/plugins.mdx
index 3a4a80753c..69810e67c7 100644
--- a/packages/web/src/content/docs/zh-cn/plugins.mdx
+++ b/packages/web/src/content/docs/zh-cn/plugins.mdx
@@ -11,7 +11,7 @@ description: 编写您自己的插件来扩展 opencode。
## 使用插件
-有两种加載插件的方法。
+有两种加载插件的方法。
---
@@ -22,7 +22,7 @@ description: 编写您自己的插件来扩展 opencode。
- `.opencode/plugins/` - 项目级插件
- `~/.config/opencode/plugins/` - 全局插件
-这些目录中的文件会在启动時自动加載。
+这些目录中的文件会在启动时自动加载。
---
@@ -53,7 +53,7 @@ description: 编写您自己的插件来扩展 opencode。
### 加载顺序
-插件從所有源加載,所有掛鉤按顺序運行。加載顺序为:
+插件从所有源加载,所有挂钩按顺序运行。加载顺序为:
1. 全局配置 (`~/.config/opencode/opencode.json`)
2. 项目配置(`opencode.json`)
@@ -67,7 +67,7 @@ description: 编写您自己的插件来扩展 opencode。
## 创建一个插件
插件是一个 **JavaScript/TypeScript 模块多个**,它导出一个或插件
-功能。每个函數接收一个上下文对象并返回一个鉤子对象。
+功能。每个函数接收一个上下文对象并返回一个钩子对象。
---
@@ -113,7 +113,7 @@ export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
}
```
-插件函數接收:
+插件函数接收:
- `project`:当前项目信息。
- `directory`:当前工作目录。
@@ -141,7 +141,7 @@ export const MyPlugin: Plugin = async ({ project, client, $, directory, worktree
### 事件
-插件可以訂閱事件,如下面的示例部分所示。以下是可用的不同事件的列表。
+插件可以订阅事件,如下面的示例部分所示。以下是可用的不同事件的列表。
#### 命令事件
@@ -217,7 +217,7 @@ export const MyPlugin: Plugin = async ({ project, client, $, directory, worktree
### 发送通知
-当某些事件發生時發送通知:
+当某些事件发生时发送通知:
```js title=".opencode/plugins/notification.js"
export const NotificationPlugin = async ({ project, client, $, directory, worktree }) => {
@@ -306,7 +306,7 @@ export const CustomToolsPlugin: Plugin = async (ctx) => {
- `args`:Zod 模式的工具参数
- `execute`:调用工具时运行的函数
-您的自定義工具将可与内置工具一起用于opencode。
+您的自定义工具将可与内置工具一起用于opencode。
---
@@ -333,7 +333,7 @@ export const MyPlugin = async ({ client }) => {
### 压缩钩子
-自定義壓縮会话時包含的上下文:
+自定义压缩会话时包含的上下文:
```ts title=".opencode/plugins/compaction.ts"
import type { Plugin } from "@opencode-ai/plugin"
diff --git a/packages/web/src/content/docs/zh-cn/providers.mdx b/packages/web/src/content/docs/zh-cn/providers.mdx
index b9b35d6195..5b064e4ac0 100644
--- a/packages/web/src/content/docs/zh-cn/providers.mdx
+++ b/packages/web/src/content/docs/zh-cn/providers.mdx
@@ -85,7 +85,7 @@ OpenCode Zen 是opencode团队提供的模型列表,这些模型已被
## 目录
-讓我们詳細瞭解一些提供商。如果您想将提供商添加到
+让我们详细了解一些提供商。如果您想将提供商添加到
列表,请随时开启PR。
:::note
@@ -156,7 +156,7 @@ OpenCode Zen 是opencode团队提供的模型列表,这些模型已被
export AWS_REGION=us-east-1
```
- #### 配置文件(推薦)
+ #### 配置文件(推荐)
For project-specific or persistent configuration, use `opencode.json`:
@@ -174,13 +174,13 @@ OpenCode Zen 是opencode团队提供的模型列表,这些模型已被
}
```
- **可用選項:**
+ **可用选项:**
- `region` - AWS 区域(例如 `us-east-1`、`eu-west-1`)
- `profile` - 来自 `~/.aws/credentials` 的 AWS 命名配置档案
- `endpoint` - VPC 节点节点的自定义节点 URL(通用 `baseURL` 选项的别名)
:::提示
- 配置文件選項优先于环境变量。
+ 配置文件选项优先于环境变量。
:::
#### 高阶:VPC 端点
@@ -206,13 +206,13 @@ OpenCode Zen 是opencode团队提供的模型列表,这些模型已被
`endpoint` 选项是通用 `baseURL` 选项的别名,使用 AWS 术语特定。如果同时指定了 `endpoint` 和 `baseURL`,则 `endpoint` 优先。
:::
- #### 認證方式
+ #### 认证方式
- **`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`**:创建IAM用户并在AWS控制台中生成访问金币。
- **`AWS_PROFILE`**:使用 `~/.aws/credentials` 中的命名配置文件。首先配置 `aws configure --profile my-profile` 或 `aws sso login`
- **`AWS_BEARER_TOKEN_BEDROCK`**:从 Amazon Bedrock 控制台生成长期 API 密钥
- **`AWS_WEB_IDENTITY_TOKEN_FILE` / `AWS_ROLE_ARN`**:适用于 EKS IRSA(服务账户的 IAM 角色)或具有 OIDC 联合的其他 Kubernetes 环境。使用服务账户注释时,这些环境变量由 Kubernetes 自动注入。
- #### 認證优先顺序
+ #### 认证优先顺序
Amazon Bedrock 使用以下身份验证优先顺序:
1. **不记名Tokens** - `AWS_BEARER_TOKEN_BEDROCK`环境变化数据或来自`/connect`Tokens的Tokens
@@ -663,7 +663,7 @@ GitLab Duo 通过 GitLab 的人工代理提供具有本机工具呼叫功能的
2. 单击**添加新Tokens**
3. Name: `OpenCode`, Scopes: `api`
4. 复制Tokens(以 `glpat-` 发起人)
- 5. 在终端中輸入
+ 5. 在终端中输入
3. 执行 `/models` 命令查看可用模型。
@@ -834,7 +834,7 @@ Google Vertex AI 与 opencode 结合使用:
2. 设置所需的环境变量:
- `GOOGLE_CLOUD_PROJECT`:您的Google云专案ID
- `VERTEX_LOCATION`(可选):Vertex AI的区域(默认为`global`)
- - 身份验证(选择一項):
+ - 身份验证(选择一项):
- `GOOGLE_APPLICATION_CREDENTIALS`:服务帐户 JSON 密钥文件的路径
- 使用 gcloud CLI 进行身份验证:`gcloud auth application-default login`
@@ -903,7 +903,7 @@ The `global` region improves availability and reduces errors at no extra cost. U
/connect
```
-3. 輸入您的擁抱臉標記。
+3. 输入您的 Hugging Face API 密钥。
```txt
┌ API key
@@ -949,7 +949,7 @@ The `global` region improves availability and reduces errors at no extra cost. U
For more providers and advanced features like caching and rate limiting, check the [Helicone documentation](https://docs.helicone.ai).
-#### 可選配置
+#### 可选配置
如果您发现Helicone的某些功能或模型未通过opencode自动配置,您始终可以自行配置。
@@ -979,7 +979,7 @@ Here's [Helicone's Model Directory](https://helicone.ai/models), you'll need thi
}
```
-#### 自定義標頭
+#### 自定义标头
Helicone 支持快速获取、用户跟踪和会话管理等功能的自定义标头。使用 `options.headers` 将它们添加到您提供的方案配置中:
@@ -1002,7 +1002,7 @@ Helicone 支持快速获取、用户跟踪和会话管理等功能的自定义
}
```
-##### 会话跟蹤
+##### 会话跟踪
Helicone's [Sessions](https://docs.helicone.ai/features/sessions) feature lets you group related LLM requests together. Use the [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) plugin to automatically log each opencode conversation as a session in Helicone.
@@ -1022,7 +1022,7 @@ npm install -g opencode-helicone-session
##### 常见螺旋接头
-| 標題 | 描述 |
+| 标题 | 描述 |
| -------------------------- | ----------------------------------------------------- |
| `Helicone-Cache-Enabled` | Enable response caching (`true`/`false`) |
| `Helicone-User-Id` | 点击用户跟踪指标 |
@@ -1635,9 +1635,9 @@ Vercel AI Gateway 可以让您跨统一端点访问来自 OpenAI、Anthropic、G
}
```
-一些有用的路由選項:
+一些有用的路由选项:
-| 選項 | 描述 |
+| 选项 | 描述 |
| ------------------- | ---------------------- |
| `order` | 提供商尝试顺序 |
| `only` | 限制特定提供商 |
@@ -1816,7 +1816,7 @@ Vercel AI Gateway 可以让您跨统一端点访问来自 OpenAI、Anthropic、G
}
```
- 以下是配置選項:
+ 以下是配置选项:
- **npm**:要使用AI的SDK包,`@ai-sdk/openai-compatible`用于OpenAI兼容的事业
- **名称**:UI中的显示名称。
- **模型**:可用模型。
@@ -1824,7 +1824,7 @@ Vercel AI Gateway 可以让您跨统一端点访问来自 OpenAI、Anthropic、G
- **options.apiKey**:如果不使用身份验证,可以选择设置API 密钥。
- **options.headers**:可选择设置自定义标头。
- 有關高階選項的更多資訊,请參見下面的示例。
+ 有关高阶选项的更多资讯,请参见下面的示例。
5. 执行 `/models` 命令,您提供的自定义程序和模型将出现在选择列表中。
@@ -1875,7 +1875,7 @@ Vercel AI Gateway 可以让您跨统一端点访问来自 OpenAI、Anthropic、G
## 故障排除
-如果您在配置提供商時遇到問題,请检查以下內容:
+如果您在配置提供商时遇到问题,请检查以下内容:
1. **Check the auth setup**: Run `opencode auth list` to see if the credentials
提供商的配置已添加到您的配置中。
diff --git a/packages/web/src/content/docs/zh-cn/rules.mdx b/packages/web/src/content/docs/zh-cn/rules.mdx
index c9135c2994..0ad73b33ff 100644
--- a/packages/web/src/content/docs/zh-cn/rules.mdx
+++ b/packages/web/src/content/docs/zh-cn/rules.mdx
@@ -1,6 +1,6 @@
---
title: 规则
-description: 设置opencode的自定義指令。
+description: 设置opencode的自定义指令。
---
您可以通过 `AGENTS.md` 文件创建 opencode 的自定义指令。这和 Cursor 的规则类似。它包含将包含在 LLM 上下文中的说明,方便您的特定项目自定义其行为。
@@ -48,7 +48,7 @@ This is an SST v3 monorepo with TypeScript. The project uses bun workspaces for
- Import shared modules using workspace names: `@my-app/core/example`
```
-我们在此處添加特定于項目的說明,这将在您的團隊中共享。
+我们在此处添加特定于项目的说明,这将在您的团队中共享。
---
@@ -66,7 +66,7 @@ opencode 还支持从多个位置读取 `AGENTS.md` 文件。这有不同的目
由于此未提交给 Git 或与您的团队共享,因此我们建议使用它来指定 LLM 应遵循的任何个人规则。
-### 克勞德代碼兼容性
+### 克劳德代码兼容性
Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
@@ -118,7 +118,7 @@ export OPENCODE_DISABLE_CLAUDE_CODE_SKILLS=1 # Disable only .claude/skills
}
```
-遠程指令的获取有 5 秒的超時時間。
+远程指令的获取有 5 秒的超时时间。
所有说明文件均与您的`AGENTS.md`文件合并。
@@ -168,9 +168,9 @@ For testing strategies and coverage requirements: @test/testing-guidelines.md
Read the following file immediately as it's relevant to all workflows: @rules/general-guidelines.md.
```
-这種方法允许您:
+这种方法允许您:
-- 创建模塊化、可重用的规则文件
+- 创建模块化、可重用的规则文件
- 通过符号链接或git子模块在项目之间共享规则
- 保持 AGENTS.md 简洁,同时参考详细指南
- 确保opencode仅在特定任务需要时加载文件
diff --git a/packages/web/src/content/docs/zh-cn/sdk.mdx b/packages/web/src/content/docs/zh-cn/sdk.mdx
index 0a8f63fa17..4b6929b627 100644
--- a/packages/web/src/content/docs/zh-cn/sdk.mdx
+++ b/packages/web/src/content/docs/zh-cn/sdk.mdx
@@ -33,7 +33,7 @@ import { createOpencode } from "@opencode-ai/sdk"
const { client } = await createOpencode()
```
-这会同時启动服务器和客戶端
+这会同时启动服务器和客户端
#### 选项
@@ -99,7 +99,7 @@ SDK 包括所有 API 型以外的 TypeScript 定义。直接汇入其中:
import type { Session, Message, Part } from "@opencode-ai/sdk"
```
-所有型別均根據服务器的 OpenAPI 規範生成,并可在 型別文件 中找到。
+所有型别均根据服务器的 OpenAPI 规范生成,并可在 型别文件 中找到。
---
@@ -192,7 +192,7 @@ const currentProject = await client.project.current()
| 方法 | 描述 | 回应 |
| ------------ | ------------ | ---------------------------------------- |
-| `path.get()` | 获取当前路徑 | 路徑 |
+| `path.get()` | 获取当前路径 | 路径 |
---
@@ -209,7 +209,7 @@ const pathInfo = await client.path.get()
| 方法 | 描述 | 回应 |
| -------------------- | -------------------- | --------------------------------------------------------------------------------------------------- |
-| `config.get()` | 获取配置資訊 | 配置 |
+| `config.get()` | 获取配置资讯 | 配置 |
| `config.providers()` | 列出提供商和默认模型 | `{ providers: `提供商[] `, default: { [key: string]: string } }` |
---
@@ -226,14 +226,14 @@ const { providers, default: defaults } = await client.config.providers()
### 会话
-| 方法 | 描述 | 筆記 |
+| 方法 | 描述 | 备注 |
| ---------------------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `session.list()` | 列出会话 | 返回 Session[] |
| `session.get({ path })` | 获取会话 | 返回 会话 |
| `session.children({ path })` | 列出子会话 | 返回 Session[] |
| `session.create({ body })` | 建立会话 | 返回 会话 |
| `session.delete({ path })` | 离开会话 | 返回`boolean` |
-| `session.update({ path, body })` | 更新会话屬性 | 返回 会话 |
+| `session.update({ path, body })` | 更新会话属性 | 返回 会话 |
| `session.init({ path, body })` | Analyze app and create `AGENTS.md` | Returns `boolean` |
| `session.abort({ path })` | 中止正在执行的会话 | 返回`boolean` |
| `session.share({ path })` | 分享会 | 返回 会话 |
@@ -241,7 +241,7 @@ const { providers, default: defaults } = await client.config.providers()
| `session.summarize({ path, body })` | 会议总结 | 返回`boolean` |
| `session.messages({ path })` | 列出会话中的消息 | 返回 `{ info: `消息 `, parts: `部分[] `}[]` |
| `session.message({ path })` | 获取消息详情 | 返回 `{ info: `消息 `, parts: `部分[] `}` |
-| `session.prompt({ path, body })` | 发送提示資訊 | `body.noReply: true` 返回 UserMessage(仅限上下文)。默认返回 AssistantMessage 以及 AI 響应 |
+| `session.prompt({ path, body })` | 发送提示资讯 | `body.noReply: true` 返回 UserMessage(仅限上下文)。默认返回 AssistantMessage 以及 AI 响应 |
| `session.command({ path, body })` | 向会话发送命令 | 返回 `{ info: `AssistantMessage `, parts: `部分[] `}` |
| `session.shell({ path, body })` | 执行 shell 命令 | 返回 AssistantMessage |
| `session.revert({ path, body })` | 回复消息 | 返回 会话 |
@@ -287,9 +287,9 @@ await client.session.prompt({
| ------------------------- | -------------------- | ----------------------------------------------------------------------------------- |
| `find.text({ query })` | 搜索档案中文字 | 具有 `path`、`lines`、`line_number`、`absolute_offset`、`submatches` 的匹配对象数组 |
| `find.files({ query })` | 按名称查询档案和目录 | `string[]`(路径) |
-| `find.symbols({ query })` | 查询工作区符號 | 符號[] |
+| `find.symbols({ query })` | 查询工作区符号 | 符号[] |
| `file.read({ query })` | 读取档案 | `{ type: "raw" \| "patch", content: string }` |
-| `file.status({ query? })` | 获取跟蹤文件的狀態 | 文件[] |
+| `file.status({ query? })` | 获取跟踪文件的状态 | 文件[] |
`find.files` 支持一些可选的查询栏位:
diff --git a/packages/web/src/content/docs/zh-cn/server.mdx b/packages/web/src/content/docs/zh-cn/server.mdx
index 27604021a0..fafff87d7c 100644
--- a/packages/web/src/content/docs/zh-cn/server.mdx
+++ b/packages/web/src/content/docs/zh-cn/server.mdx
@@ -44,7 +44,7 @@ OPENCODE_SERVER_PASSWORD=your-password opencode serve
---
-### 它是如何運作的
+### 它是如何运作的
When you run `opencode` it starts a TUI and a server. Where the TUI is the
与服务器器对话的客户端。服务器器公开 OpenAPI 3.1 规范
@@ -61,7 +61,7 @@ opencode TUI running, `opencode serve` will start a new server.
---
-#### 连接到現有服务器
+#### 连接到现有服务器
当您启动 TUI 时,它会随机分配端口和主机名。您可以重新设置 `--hostname` 和 `--port` [flags](/docs/cli)。使用它连线到其服务器然后器。
@@ -69,7 +69,7 @@ opencode TUI running, `opencode serve` will start a new server.
---
-## 規格
+## 规格
服务器发布了OpenAPI 3.1规范,可以在以下位置查看:
@@ -89,7 +89,7 @@ opencode服务器公开以下API。
### 全局
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ----- | ---------------- | ------------------------ | ------------------------------------ |
| `GET` | `/global/health` | 获取服务器运行状况和版本 | `{ healthy: true, version: string }` |
| `GET` | `/global/event` | 获取全域性事件(SSE 流) | 事件流 |
@@ -98,7 +98,7 @@ opencode服务器公开以下API。
### 项目
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ----- | ------------------ | ------------ | ------------------------------------------ |
| `GET` | `/project` | 列出所有专案 | 专案[] |
| `GET` | `/project/current` | 获取当前专案 | 专案 |
@@ -107,16 +107,16 @@ opencode服务器公开以下API。
### 路径和VCS
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ----- | ------- | ----------------------- | ------------------------------------------- |
-| `GET` | `/path` | 获取当前路徑 | 路徑 |
-| `GET` | `/vcs` | 获取当前专案的 VCS 資訊 | VcsInfo |
+| `GET` | `/path` | 获取当前路径 | 路径 |
+| `GET` | `/vcs` | 获取当前专案的 VCS 资讯 | VcsInfo |
---
-### 例項
+### 例项
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ------ | ------------------- | -------------- | --------- |
| `POST` | `/instance/dispose` | 执行当前实例项 | `boolean` |
@@ -124,9 +124,9 @@ opencode服务器公开以下API。
### 配置
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ------- | ------------------- | -------------------- | -------------------------------------------------------------------------------------- |
-| `GET` | `/config` | 获取配置資訊 | 配置 |
+| `GET` | `/config` | 获取配置资讯 | 配置 |
| `PATCH` | `/config` | 更新配置 | 配置 |
| `GET` | `/config/providers` | 列出提供商和默认模型 | `{ providers: `提供商[] `, default: { [key: string]: string } }` |
@@ -134,33 +134,33 @@ opencode服务器公开以下API。
### 提供商
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ------ | -------------------------------- | ----------------------- | --------------------------------------------------------------------------------- |
| `GET` | `/provider` | 列出所有提供商 | `{ all: `提供商[] `, default: {...}, connected: string[] }` |
| `GET` | `/provider/auth` | 获取提供商身份验证方法 | `{ [providerID: string]: `ProviderAuthMethod[] ` }` |
-| `POST` | `/provider/{id}/oauth/authorize` | 使用 OAuth 授權提供商 | ProviderAuthAuthorization |
+| `POST` | `/provider/{id}/oauth/authorize` | 使用 OAuth 授权提供商 | ProviderAuthAuthorization |
| `POST` | `/provider/{id}/oauth/callback` | 处理提供商的 OAuth 回调 | `boolean` |
---
### 会话
-| 方法 | 路徑 | 描述 | 筆記 |
+| 方法 | 路径 | 描述 | 笔记 |
| -------- | ---------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------- |
| `GET` | `/session` | 列出所有会话 | 返回 Session[] |
| `POST` | `/session` | 建立新会话 | 正文: `{ parentID?, title? }`,返回 Session |
-| `GET` | `/session/status` | 获取所有会话的会话狀態 | 返回 `{ [sessionID: string]: `SessionStatus ` }` |
+| `GET` | `/session/status` | 获取所有会话的会话状态 | 返回 `{ [sessionID: string]: `SessionStatus ` }` |
| `GET` | `/session/:id` | 获取会话详细信息 | 返回会话 |
| `DELETE` | `/session/:id` | 删除会话及所有资料 | 返回`boolean` |
-| `PATCH` | `/session/:id` | 更新会话屬性 | 正文: `{ title? }`,返回 Session |
+| `PATCH` | `/session/:id` | 更新会话属性 | 正文: `{ title? }`,返回 Session |
| `GET` | `/session/:id/children` | 获取会话的子会话 | 返回 Session[] |
-| `GET` | `/session/:id/todo` | 获取会话的待辦事項列表 | 返回 Todo[] |
+| `GET` | `/session/:id/todo` | 获取会话的待办事项列表 | 返回 Todo[] |
| `POST` | `/session/:id/init` | Analyze app and create `AGENTS.md` | body: `{ messageID, providerID, modelID }`, returns `boolean` |
-| `POST` | `/session/:id/fork` | 在消息中分叉現有会话 | 正文: `{ messageID? }`,返回 Session |
+| `POST` | `/session/:id/fork` | 在消息中分叉现有会话 | 正文: `{ messageID? }`,返回 Session |
| `POST` | `/session/:id/abort` | 中止正在执行的会话 | 返回`boolean` |
| `POST` | `/session/:id/share` | 分享会话 | 返回会话 |
| `DELETE` | `/session/:id/share` | 取消共享会话 | 返回会话 |
-| `GET` | `/session/:id/diff` | 获取本次会话的差異 | 查询:`messageID?`,返回 FileDiff[] |
+| `GET` | `/session/:id/diff` | 获取本次会话的差异 | 查询:`messageID?`,返回 FileDiff[] |
| `POST` | `/session/:id/summarize` | 会议总结 | 正文:`{ providerID, modelID }`,返回 `boolean` |
| `POST` | `/session/:id/revert` | 回复讯息 | 正文:`{ messageID, partID? }`,返回 `boolean` |
| `POST` | `/session/:id/unrevert` | 恢复所有已恢复的消息 | 返回`boolean` |
@@ -170,20 +170,20 @@ opencode服务器公开以下API。
### 留言
-| 方法 | 路徑 | 描述 | 筆記 |
+| 方法 | 路径 | 描述 | 笔记 |
| ------ | --------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET` | `/session/:id/message` | 列出会话中的消息 | 查询: `limit?`,返回 `{ info: `消息 `, parts: `Part[] `}[]` |
| `POST` | `/session/:id/message` | 发送消息并等待回复 | 正文: `{ messageID?, model?, agent?, noReply?, system?, tools?, parts }`,返回 `{ info: `消息 `, parts: `部分[] `}` |
| `GET` | `/session/:id/message/:messageID` | 获取消息详情 | 返回 `{ info: `消息 `, parts: `部分[] `}` |
| `POST` | `/session/:id/prompt_async` | 非同步传送消息(休眠等待) | 主体:与 `/session/:id/message` 相同,返回 `204 No Content` |
-| `POST` | `/session/:id/command` | 执行斜槓命令 | 正文: `{ messageID?, agent?, model?, command, arguments }`,返回 `{ info: `消息 `, parts: `部分[] `}` |
+| `POST` | `/session/:id/command` | 执行斜杠命令 | 正文: `{ messageID?, agent?, model?, command, arguments }`,返回 `{ info: `消息 `, parts: `部分[] `}` |
| `POST` | `/session/:id/shell` | 执行 shell 命令 | 正文: `{ agent, model?, command }`,返回 `{ info: `消息 `, parts: `部分[] `}` |
---
### 命令
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ----- | ---------- | ------------ | ------------------------------------------ |
| `GET` | `/command` | 列出所有命令 | 命令[] |
@@ -191,14 +191,14 @@ opencode服务器公开以下API。
### 文件
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ----- | ------------------------ | -------------------- | ----------------------------------------------------------------------------------- |
| `GET` | `/find?pattern=` | 搜索文件中的文字 | 具有 `path`、`lines`、`line_number`、`absolute_offset`、`submatches` 的匹配对像数组 |
-| `GET` | `/find/file?query=` | 按名称查询文件和目录 | `string[]`(路徑) |
-| `GET` | `/find/symbol?query=` | 查询工作区符號 | 符號[] |
+| `GET` | `/find/file?query=` | 按名称查询文件和目录 | `string[]`(路径) |
+| `GET` | `/find/symbol?query=` | 查询工作区符号 | 符号[] |
| `GET` | `/file?path=` | 列出文件和目录 | FileNode[] |
-| `GET` | `/file/content?path=` | 讀取文件 | 文件內容 |
-| `GET` | `/file/status` | 获取跟蹤文件的狀態 | 文件[] |
+| `GET` | `/file/content?path=
` | 读取文件 | 文件内容 |
+| `GET` | `/file/status` | 获取跟踪文件的状态 | 文件[] |
#### `/find/file` 查询参数
@@ -210,9 +210,9 @@ opencode服务器公开以下API。
---
-### 工具(實驗)
+### 工具(实验)
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ----- | ------------------------------------------- | ---------------------------- | -------------------------------------------- |
| `GET` | `/experimental/tool/ids` | 列出所有工具 ID | 工具ID |
| `GET` | `/experimental/tool?provider=
&model=` | 列出具有模型 JSON 模式的工具 | 工具列表 |
@@ -221,18 +221,18 @@ opencode服务器公开以下API。
### LSP、格式化程式和 MCP
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ------ | ------------ | ------------------- | ------------------------------------------------------ |
-| `GET` | `/lsp` | 获取 LSP 服务器狀態 | LSPStatus[] |
-| `GET` | `/formatter` | 获取格式化程式狀態 | FormatterStatus[] |
-| `GET` | `/mcp` | 获取 MCP 服务器狀態 | `{ [name: string]: `MCP狀態 ` }` |
+| `GET` | `/lsp` | 获取 LSP 服务器状态 | LSPStatus[] |
+| `GET` | `/formatter` | 获取格式化程式状态 | FormatterStatus[] |
+| `GET` | `/mcp` | 获取 MCP 服务器状态 | `{ [name: string]: `MCP状态 ` }` |
| `POST` | `/mcp` | 动态添加 MCP 服务器 | 主体:`{ name, config }`,返回 MCP 状态对象 |
---
### 代理商
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ----- | -------- | ------------------ | ------------------------------------------ |
| `GET` | `/agent` | 列出所有可用的代理 | 代理[] |
@@ -240,7 +240,7 @@ opencode服务器公开以下API。
### 日志
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ------ | ------------------------------------------- | ------ | -------------------- |
| `POST` | 身体:`{ service, level, message, extra? }` | `/log` | 写入日志。 `boolean` |
@@ -248,7 +248,7 @@ opencode服务器公开以下API。
### TUI
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ------ | ----------------------- | ----------------------------------------- | ------------ |
| `POST` | `/tui/append-prompt` | 将文字附加到提示 | `boolean` |
| `POST` | `/tui/open-help` | 开启帮助对话方块 | `boolean` |
@@ -266,7 +266,7 @@ opencode服务器公开以下API。
### 授权
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ----- | ----------- | ------------------------------------------ | --------- |
| `PUT` | `/auth/:id` | 设置身份验证凭据。正文必须与提供商架构匹配 | `boolean` |
@@ -274,7 +274,7 @@ opencode服务器公开以下API。
### 活动
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ----- | -------- | ------------------------------------------------------------------- | ------------------ |
| `GET` | `/event` | 服务器发送事件流。第一个事件是 `server.connected`,之后是总线事件。 | 服务器发送事件流。 |
@@ -282,6 +282,6 @@ opencode服务器公开以下API。
### 文档
-| 方法 | 路徑 | 描述 | 回应 |
+| 方法 | 路径 | 描述 | 回应 |
| ----- | ------ | --------------- | ------------------------- |
| `GET` | `/doc` | 开启API 3.1规范 | 具有OpenAPI规范的HTML页面 |
diff --git a/packages/web/src/content/docs/zh-cn/share.mdx b/packages/web/src/content/docs/zh-cn/share.mdx
index e73eac373d..484e49f870 100644
--- a/packages/web/src/content/docs/zh-cn/share.mdx
+++ b/packages/web/src/content/docs/zh-cn/share.mdx
@@ -6,24 +6,24 @@ description: 分享您的 opencode 对话。
opencode 的共享功能允许您建立指向 opencode 对话的公共链接,以便您可以与蓝牙进行战斗或从其他人那里获得帮助。
:::note
-任何知道链接的人都可以公開访问共享对话。
+任何知道链接的人都可以公开访问共享对话。
:::
---
-## 它是如何運作的
+## 它是如何运作的
当您分享对话时,opencode:
1. 为您的会话建立唯一的公共 URL
-2. 将您的对话歷史記錄同步到我们的服务器
+2. 将您的对话历史记录同步到我们的服务器
3. 通过可共享链接访问对话 — `opncd.ai/s/`
---
## 分享
-opencode 支持清晰控制对话共享方式的共享模式:
+opencode 支持三种控制对话共享方式的共享模式:
---
@@ -35,7 +35,7 @@ opencode 支持清晰控制对话共享方式的共享模式:
/share
```
-这将生成一个唯一的 URL,将其复制到您的剪贴簿中。
+这将生成一个唯一的 URL,将其复制到您的剪贴板中。
要在[配置文件](/docs/config) 中显式设置手动模式:
@@ -50,7 +50,7 @@ opencode 支持清晰控制对话共享方式的共享模式:
### 自动分享
-您可以通过将 [config file](/docs/config) 中的 `share` 选项设置为 `"auto"` 来为所有新对话启用自动共享:
+您可以通过将 [配置文件](/docs/config) 中的 `share` 选项设置为 `"auto"` 来为所有新对话启用自动共享:
```json title="opencode.json"
{
@@ -59,13 +59,13 @@ opencode 支持清晰控制对话共享方式的共享模式:
}
```
-启用自动共享後,每个新对话都会自动共享并生成链接。
+启用自动共享后,每个新对话都会自动共享并生成链接。
---
### 已禁用
-您可以通过将 [config file](/docs/config) 中的 `share` 选项设置为 `"disabled"` 来完全禁用共享:
+您可以通过将 [配置文件](/docs/config) 中的 `share` 选项设置为 `"disabled"` 来完全禁用共享:
```json title="opencode.json"
{
@@ -74,13 +74,13 @@ opencode 支持清晰控制对话共享方式的共享模式:
}
```
-To enforce this across your team for a given project, add it to the `opencode.json` in your project and check into Git.
+为了在您的团队中针对特定项目强制执行此操作,请将其添加到您项目的 `opencode.json` 文件中,并将其提交到Git。
---
## 取消共享
-要停止共享对话并将其從公共访问中删除:
+要停止共享对话并将其从公共访问中删除:
```
/unshare
@@ -90,39 +90,39 @@ To enforce this across your team for a given project, add it to the `opencode.js
---
-## 隱私
+## 隐私
-分享对话時需要記住一些事項。
+分享对话时需要记住一些事项。
---
### 数据保留
-共享对话仍然可以访问,直到您明確取消共享。这
+共享对话仍然可以访问,直到您明确取消共享。这
包括:
-- 完整的对话歷史記錄
+- 完整的对话历史记录
- 所有消息和回复
- 会话元数据
---
-### 建議
+### 建议
-- 仅共享不包含敏感資訊的对话。
-- 分享之前查看对话內容。
-- 協作完成後取消共享对话。
-- 避免与专有程式碼或機密数据共享对话。
-- 对于敏感专案,完全禁用共享。
+- 仅共享不包含敏感资讯的对话。
+- 分享之前查看对话内容。
+- 协作完成后取消共享对话。
+- 避免共享包括专有代码或机密数据的对话。
+- 对于敏感项目,完全禁用共享。
---
-## 对于企業
+## 对于企业
-对于企業部署,共享功能可以是:
+对于企业部署,共享功能可以是:
-- **出于安全合規性完全禁用**
+- **出于安全合规性完全禁用**
- **仅限** 仅通过 SSO 进行身份验证的用户
-- **在您自己的基礎設施上自行託管**
+- **在您自己的基础设施上自行托管**
[了解更多关于在您的组织中使用opencode的](/docs/enterprise)。
diff --git a/packages/web/src/content/docs/zh-cn/skills.mdx b/packages/web/src/content/docs/zh-cn/skills.mdx
index f5c588d1ed..f708a6812a 100644
--- a/packages/web/src/content/docs/zh-cn/skills.mdx
+++ b/packages/web/src/content/docs/zh-cn/skills.mdx
@@ -1,10 +1,10 @@
---
-title: 《代理技巧》
-description: “贯穿 SKILL.md 定义可重用行为”
+title: 代理技能
+description: “通过 SKILL.md 定义可复用的行为”
---
-代理让 opencode 技能从您的存储库或主目录中找到可重用的指令。
-技能贯穿本机 `skill` 工具输入导入 - 代理可以查看可用技能并可以在需要时加载完整内容。
+代理技能使 OpenCode 能够从您的仓库或主目录中发现可重用的指令。
+技能通过原生 `skill` 工具输入导入 - 代理可以查看可用技能并可以在需要时加载完整内容。
---
@@ -34,7 +34,7 @@ Global definitions are also loaded from `~/.config/opencode/skills/*/SKILL.md`,
## 编写 Frontmatter
每个 `SKILL.md` 必须以 YAML frontmatter 。
-仅識別这些欄位:
+仅识别这些栏位:
- `name`(必填)
- `description`(必填)
@@ -50,13 +50,13 @@ Global definitions are also loaded from `~/.config/opencode/skills/*/SKILL.md`,
`name` 必须:
-- 長度为 1–64 个字元
-- 为小寫字母數字并带有單个連字元分隔符
-- 不以 `-` 開始或結束
-- 不包含連續的 `--`
+- 长度为 1–64 个字元
+- 为小写字母数字并带有单个连字元分隔符
+- 不以 `-` 开始或结束
+- 不包含连续的 `--`
- 匹配包含 `SKILL.md` 的目录名
-等效的正規表示式:
+等效的正规表示式:
```text
^[a-z0-9]+(-[a-z0-9]+)*$
@@ -67,7 +67,7 @@ Global definitions are also loaded from `~/.config/opencode/skills/*/SKILL.md`,
## 遵循长度规则
`description` 必须是 1-1024 个字元。
-保持足夠具體,以便代理能夠正确选择。
+保持足够具体,以便代理能够正确选择。
---
@@ -114,7 +114,7 @@ opencode 列出了 `skill` 工具描述中的可用技能。
```
-代理通过呼叫工具來載入技能:
+代理通过呼叫工具来载入技能:
```
skill({ name: "git-release" })
@@ -139,7 +139,7 @@ Control which skills agents can access using pattern-based permissions in `openc
}
```
-| 許可 | 行为 |
+| 许可 | 行为 |
| ------- | -------------------------- |
| `allow` | 技能立即加载 |
| `deny` | 对特工隐藏技能,访问被拒绝 |
@@ -153,7 +153,7 @@ Control which skills agents can access using pattern-based permissions in `openc
为特定代理授予与全域性默认权限不同的权限。
-**对于自定義代理**(在代理前言中):
+**对于自定义代理**(在代理前言中):
```yaml
---
@@ -185,7 +185,7 @@ permission:
完全禁用不应该使用技能的特工:
-**对于定製代理**:
+**对于定制代理**:
```yaml
---
@@ -194,7 +194,7 @@ tools:
---
```
-**对于內建代理**:
+**对于内建代理**:
```json
{
@@ -208,15 +208,15 @@ tools:
}
```
-禁用後,`` 部分将被完全省略。
+禁用后,`` 部分将被完全省略。
---
## 解决加载问题
-如果某項技能沒有显示:
+如果某项技能没有显示:
1. 验证 `SKILL.md` 拼写为全部大写
2. 检查 frontmatter 是否包括 `name` 和 `description`
-3. 確保技能名称在所有位置都是唯一的
+3. 确保技能名称在所有位置都是唯一的
4. 查询权限——具有`deny`的代理隐藏技能
diff --git a/packages/web/src/content/docs/zh-cn/themes.mdx b/packages/web/src/content/docs/zh-cn/themes.mdx
index 2f28f15d55..a885fed19b 100644
--- a/packages/web/src/content/docs/zh-cn/themes.mdx
+++ b/packages/web/src/content/docs/zh-cn/themes.mdx
@@ -1,6 +1,6 @@
---
title: 主题
-description: 选择內建主题或定義您自己的主题。
+description: 选择内建主题或定义您自己的主题。
---
使用 opencode,您可以从多个内建主题中进行选择,使用适合您的终端主题的主题,或定义您自己的自定义主题。
@@ -11,13 +11,13 @@ By default, opencode uses our own `opencode` theme.
## 终端要求
-为了使主题能夠正确显示完整的调色板,您的终端必須支持**真彩色**(24 位顏色)。大多數现代终端默认支持此功能,但您可能需要启用它:
+为了使主题能够正确显示完整的调色板,您的终端必须支持**真彩色**(24 位颜色)。大多数现代终端默认支持此功能,但您可能需要启用它:
- **检查支持**:执行 `echo $COLORTERM` - 它应该输出 `truecolor` 或 `24bit`
- **启用真彩色**:在shell配置文件中设置环境变量`COLORTERM=truecolor`
- **您的终端兼容性**:确保终端模拟器支持24位颜色(大多数现代终端,如iTerm2、Alacritty、Kitty、Windows终端和最新版本的GNOME终端都支持)
-如果沒有真彩色支持,主题的顏色精度可能会降低或回落到最接近的 256 色近似值。
+如果没有真彩色支持,主题的颜色精度可能会降低或回落到最接近的 256 色近似值。
---
@@ -39,7 +39,7 @@ opencode 带有几个内建主题。
| `matrix` | 骇客风格黑底绿主题 |
| `one-dark` | Based on the [Atom One](https://github.com/Th3Whit3Wolf/one-nvim) Dark theme |
-此外,我们还在不斷添加新主题。
+此外,我们还在不断添加新主题。
---
@@ -47,15 +47,15 @@ opencode 带有几个内建主题。
`system` 主题旨在自动适应您的最终方案。与使用固定颜色的传统主题不同,_system_ 主题:
-- **生成灰度**:根據终端的背景顏色建立自定義灰度,確保最佳对比度。
+- **生成灰度**:根据终端的背景颜色建立自定义灰度,确保最佳对比度。
- **使用 ANSI 颜色**:使用标准 ANSI 颜色 (0-15) 进行语法突出显示和 UI 元素,尊重 Windows 的调色盘。
- **保留默认设置**:使用 `none` 作为文字和背景颜色以保持本机的外观。
-系統主题適合以下用户:
+系统主题适合以下用户:
- 希望 opencode 与终端的外观相匹配
-- 使用自定義终端配色方案
-- 希望所有终端应用程序具有一致的外觀
+- 使用自定义终端配色方案
+- 希望所有终端应用程序具有一致的外观
---
@@ -80,14 +80,14 @@ opencode 支持灵活的基于 JSON 的主题系统,允许用户轻松创建
### 优先级
-主题按以下顺序從多个目录載入,其中后面的目录覆盖前面的目录:
+主题按以下顺序从多个目录载入,其中后面的目录覆盖前面的目录:
-1. **內建主题** - 这些主题嵌入在二進製文件中
+1. **内建主题** - 这些主题嵌入在二进制文件中
2. **User config directory** - Defined in `~/.config/opencode/themes/*.json` or `$XDG_CONFIG_HOME/opencode/themes/*.json`
3. **Project root directory** - Defined in the `/.opencode/themes/*.json`
4. **Current working directory** - Defined in `./.opencode/themes/*.json`
-如果多个目录包含同名主题,则将使用优先顺序較高的目录中的主题。
+如果多个目录包含同名主题,则将使用优先顺序较高的目录中的主题。
---
@@ -102,7 +102,7 @@ mkdir -p ~/.config/opencode/themes
vim ~/.config/opencode/themes/my-theme.json
```
-以及針对特定专案的主题。
+以及针对特定项目的主题。
```bash no-frame
mkdir -p .opencode/themes
@@ -140,7 +140,7 @@ vim .opencode/themes/my-theme.json
### 例子
-以下是自定義主题的示例:
+以下是自定义主题的示例:
```json title="my-theme.json"
{
diff --git a/packages/web/src/content/docs/zh-cn/tools.mdx b/packages/web/src/content/docs/zh-cn/tools.mdx
index 1be9d66901..82aa3b8a31 100644
--- a/packages/web/src/content/docs/zh-cn/tools.mdx
+++ b/packages/web/src/content/docs/zh-cn/tools.mdx
@@ -24,7 +24,7 @@ Tools allow the LLM to perform actions in your codebase. opencode comes with a s
}
```
-您还可以使用万用字元同时控制多个工具。例如,要求 MCP 服务器批准所有工具:
+您还可以使用通配符同时控制多个工具。例如,要求 MCP 服务器批准所有工具:
```json title="opencode.json"
{
@@ -39,15 +39,15 @@ Tools allow the LLM to perform actions in your codebase. opencode comes with a s
---
-## 內建
+## 內建工具
以下是 opencode 中可用的所有内置工具。
---
-### 巴什
+### bash
-在专案环境中执行shell命令。
+在项目环境中执行shell命令。
```json title="opencode.json" {4}
{
@@ -58,13 +58,13 @@ Tools allow the LLM to perform actions in your codebase. opencode comes with a s
}
```
-This tool allows the LLM to run terminal commands like `npm install`, `git status`, or any other shell command.
+这个工具允许 LLM 运行终端命令,例如:`npm install`, `git status`,或者其他任何终端命令。
---
-### 編輯
+### edit
-使用精確的字符串替換修改現有文件。
+使用精确的字符串替换来修改现有文件。
```json title="opencode.json" {4}
{
@@ -75,13 +75,13 @@ This tool allows the LLM to run terminal commands like `npm install`, `git statu
}
```
-该工具取消替换精确的文字来匹配对文件执行精确编辑。这是 LLM 修改代码的主要方式。
+该工具通过替换完全匹配的文本来对文件进行精确编辑。这是 LLM 修改代码的主要方式。
---
-### 寫
+### write
-建立新文件或覆盖現有文件。
+建立新文件或覆盖现有文件。
```json title="opencode.json" {4}
{
@@ -92,17 +92,17 @@ This tool allows the LLM to run terminal commands like `npm install`, `git statu
}
```
-使用它允许 LLM 创建新文件。如果现有文件已经存在,将会覆盖它们。
+使用此功能可允许 LLM 创建新文件。如果文件已存在,则会覆盖现有文件。
:::note
-`write`工具由`edit`许可权控制,该许可权主题所有文件修改(`edit`、`write`、`patch`、`multiedit`)。
+`写入`工具由`编辑`权限控制,涵盖所有文件修改(`编辑`、`写入`、`修补`、`多重编辑`)。
:::
---
-### 讀
+### read
-從程式碼庫中讀取文件內容。
+读取代码库中的文件内容。
```json title="opencode.json" {4}
{
@@ -113,13 +113,13 @@ This tool allows the LLM to run terminal commands like `npm install`, `git statu
}
```
-该工具讀取文件并返回其內容。它支持讀取大文件的特定行范围。
+该工具读取文件并返回其内容。它支持读取大型文件中的特定行范围。
---
### grep
-使用正規表示式搜索文件內容。
+使用正则表达式搜索文件内容。
```json title="opencode.json" {4}
{
@@ -130,13 +130,13 @@ This tool allows the LLM to run terminal commands like `npm install`, `git statu
}
```
-在您的程式碼庫中快速進行內容搜索。支持完整的正規表示式語法和文件模式过濾。
+快速搜索代码库中的内容。支持完整的正则表达式语法和文件模式过滤。
---
-### 全域性
+### glob
-通过模式匹配查询文件。
+通过模式匹配查找文件。
```json title="opencode.json" {4}
{
@@ -147,13 +147,13 @@ This tool allows the LLM to run terminal commands like `npm install`, `git statu
}
```
-使用 `**/*.js` 或 `src/**/*.ts` 等全域性模式搜索档案。返回按时间排序的匹配档案路径修改。
+使用类似 **/\*.js 或 src/**/\*.ts 的通配符模式搜索文件。返回按修改时间排序的匹配文件路径。
---
-### 列表
+### list
-列出給定路徑中的文件和目录。
+列出给定路径中的文件和目录。
```json title="opencode.json" {4}
{
@@ -164,16 +164,16 @@ This tool allows the LLM to run terminal commands like `npm install`, `git statu
}
```
-该工具列出目录內容。它接受全域性模式來过濾結果。
+此工具用于列出目录内容。它接受通配符模式来筛选结果。
---
### lsp(实验性)
-与您配置的LSP服务器交互,通知计划码智慧功能,例如定义、引用、悬停资讯和呼叫层次结构。
+与已配置的 LSP 服务器交互,以获取代码智能功能,例如定义、引用、悬停信息和调用层次结构。
:::note
-This tool is only available when `OPENCODE_EXPERIMENTAL_LSP_TOOL=true` (or `OPENCODE_EXPERIMENTAL=true`).
+只有当 OPENCODE_EXPERIMENTAL_LSP_TOOL=true(或 OPENCODE_EXPERIMENTAL=true)时,此工具才可用。
:::
```json title="opencode.json" {4}
@@ -187,13 +187,13 @@ This tool is only available when `OPENCODE_EXPERIMENTAL_LSP_TOOL=true` (or `OPEN
支持的操作包括 `goToDefinition`、`findReferences`、`hover`、`documentSymbol`、`workspaceSymbol`、`goToImplementation`、`prepareCallHierarchy`、`incomingCalls` 和 `outgoingCalls`。
-To configure which LSP servers are available for your project, see [LSP Servers](/docs/lsp).
+要配置哪些 LSP 服务器可用于您的项目,请参阅 [LSP Servers](/docs/lsp).
---
-### 修補
+### patch
-对文件应用補丁。
+对文件应用补丁。
```json title="opencode.json" {4}
{
@@ -204,17 +204,17 @@ To configure which LSP servers are available for your project, see [LSP Servers]
}
```
-该工具将補丁文件应用到您的程式碼庫。对于应用來自各種來源的差異和補丁很有帮助。
+此工具可将补丁文件应用到您的代码库。它可用于应用来自各种来源的差异和补丁。
:::note
-`patch`工具由`edit`许可权控制,该许可权主题所有文件修改(`edit`、`write`、`patch`、`multiedit`)。
+`修补`工具由`编辑`权限控制,涵盖所有文件修改(`编辑`、`写入`、`修补`、`多重编辑`)。
:::
---
-### 技能
+### skill
-加载[skill](/docs/skills)(`SKILL.md` 档案)并在对话中返回其内容。
+加载[技能](/docs/skills)(`SKILL.md` 文件)并在对话中返回其内容。
```json title="opencode.json" {4}
{
@@ -227,9 +227,9 @@ To configure which LSP servers are available for your project, see [LSP Servers]
---
-### 待辦寫入
+### todowrite
-在編碼会话期間管理待辦事項列表。
+在编码会话过程中管理待办事项列表。
```json title="opencode.json" {4}
{
@@ -240,17 +240,17 @@ To configure which LSP servers are available for your project, see [LSP Servers]
}
```
-建立和更新任务列表以跟踪复杂操作期间的详细信息。LLM 使用它来组织多步骤任务。
+创建和更新任务列表,以跟踪复杂操作的进度。LLM 利用此功能来组织多步骤任务。
:::note
-默认情况下,子代理取消此工具,但您可以手动启用它。 [了解更多](/docs/agents/#permissions)
+此工具默认情况下对子代理禁用,但您可以手动启用它。 [了解更多](/docs/agents/#permissions)
:::
---
-### 託多雷德
+### todoread
-閱讀現有的待辦事項列表。
+阅读现有的待办事项清单。
```json title="opencode.json" {4}
{
@@ -261,17 +261,17 @@ To configure which LSP servers are available for your project, see [LSP Servers]
}
```
-读取当前完成待办事项列表状态。由 LLM 用于跟踪哪些任务待处理或已已。
+读取当前待办事项列表状态。LLM 使用此信息来跟踪哪些任务处于待处理状态或已完成状态。
:::note
-默认情况下,子代理取消此工具,但您可以手动启用它。 [了解更多](/docs/agents/#permissions)
+此工具默认情况下对子代理禁用,但您可以手动启用它。 [了解更多](/docs/agents/#permissions)
:::
---
-### 網頁抓取
+### webfetch
-获取網頁內容。
+获取网页内容。
```json title="opencode.json" {4}
{
@@ -282,18 +282,18 @@ To configure which LSP servers are available for your project, see [LSP Servers]
}
```
-允许 LLM 获取和读取网页。对于查询文件或研究线上资源很有帮助。
+允许LLM获取并读取网页。可用于查找文档或研究在线资源。
---
-### 網路搜索
+### websearch
-在網路上搜索資訊。
+在网络上搜索资料。
:::note
-仅当使用 opencode 提供或 `OPENCODE_ENABLE_EXA` 程序环境变量设置为任何真值(例如 `true` 或 `1`)时,此工具才可用。
+只有在使用 OpenCode 提供程序时,或者当 OPENCODE_ENABLE_EXA 环境变量被设置为任何真值(例如 true 或 1)时,此工具才可用。
-要在启动 opencode 时启用:
+在启动 OpenCode 时启用:
```bash
OPENCODE_ENABLE_EXA=1 opencode
@@ -310,19 +310,19 @@ OPENCODE_ENABLE_EXA=1 opencode
}
```
-使用 Exa AI 执行网路搜索以线上查询相关资讯。对于研究主题、查询时事或收集训练超出数据范围的资讯很有帮助。
+利用 Exa AI 进行网络搜索,查找相关信息。可用于研究特定主题、了解时事新闻或收集超出训练数据范围的信息。
-不需要 API 密钥 — 该工具消耗身份验证即可直接连线到 Exa AI 的托管 MCP 服务。
+无需 API 密钥——该工具无需身份验证即可直接连接到 Exa AI 托管的 MCP 服务。
:::tip
-当您需要查询资讯(发现)时,请使用 `websearch`;当您需要从特定 URL 检索内容(搜索)时,请使用 `webfetch`。
+当您需要查找信息时,请使用`网页搜索`;当您需要从特定 URL 检索内容时,请使用`网页获取`。
:::
---
-### 問題
+### question
-在执行过程中詢問用户問題。
+在执行过程中向用户提问。
```json title="opencode.json" {4}
{
@@ -333,20 +333,20 @@ OPENCODE_ENABLE_EXA=1 opencode
}
```
-该工具允许 LLM 在任务期间询问用户问题。它适用于:
+该工具允许 LLM 在执行任务期间向用户提问。它在以下方面很有用:
-- 收集用户偏好或要求
-- 澄清不明確的指令
-- 就實施选择做出決策
-- 提供选择方向
+- 收集用户偏好或需求
+- 澄清含糊不清的指示
+- 就实施方案做出决定
+- 提供关于选择下一步方向的选项
-每个問題都包含標題、問題文字和選項列表。用户可以從提供的選項中進行选择或輸入自定義答案。当存在多个問題時,用户可以在提交所有答案之前在这些問題之间导航。
+每个问题都包含标题、问题正文和选项列表。用户可以从提供的选项中选择答案,也可以输入自定义答案。如果有多个问题,用户可以在提交所有答案之前在不同问题之间切换。
---
-## 定製工具
+## 自定义工具
-自定义工具可以让您定义LLM可以调用自己的函式。这些是在您的配置文件中定义的并且可以执行任何代码。
+自定义工具允许您定义LLM可以调用的自定义函数。这些函数在您的配置文件中定义,并且可以执行任意代码。
[了解更多](/docs/custom-tools)关于创建自定义工具。
@@ -360,15 +360,15 @@ MCP(模型上下文协议)服务器允许您集成外部工具和服务。
---
-## 内部結構
+## 内部规则
-Internally, tools like `grep`, `glob`, and `list` use [ripgrep](https://github.com/BurntSushi/ripgrep) under the hood. By default, ripgrep respects `.gitignore` patterns, which means files and directories listed in your `.gitignore` will be excluded from searches and listings.
+在内部,`grep`、 `通配符` 和 `罗列` 等工具底层都使用了 ripgrep。默认情况下,ripgrep 会遵循 .gitignore 文件中的规则,这意味着 .gitignore 文件中列出的文件和目录将被排除在搜索和列表之外。
---
### 忽略模式
-要包含通常会被忽略的文件,请在专案根目录中建立 `.ignore` 文件。该文件可以明确允许某些路径。
+为了使工具不跳过那些通常会被忽略的文件,请在项目根目录下创建一个 `.ignore` 文件。该文件内定义的目录可以不会被跳过。
```text title=".ignore"
!node_modules/
@@ -376,4 +376,4 @@ Internally, tools like `grep`, `glob`, and `list` use [ripgrep](https://github.c
!build/
```
-例如,此 `.ignore` 档案允许 ripgrep 在 `node_modules/`、`dist/` 和 `build/` 目录中搜索,即使它们列在 `.gitignore` 中。
+例如,这个 `.ignore` 文件允许 ripgrep 在 `node_modules/`、`dist/` 和 `build/` 目录中搜索,即使它们已在 `.gitignore` 中列出。
diff --git a/packages/web/src/content/docs/zh-cn/troubleshooting.mdx b/packages/web/src/content/docs/zh-cn/troubleshooting.mdx
index b658f6938e..5aa2764d9e 100644
--- a/packages/web/src/content/docs/zh-cn/troubleshooting.mdx
+++ b/packages/web/src/content/docs/zh-cn/troubleshooting.mdx
@@ -1,15 +1,15 @@
---
title: 故障排除
-description: 常見問題以及如何解決它們。
+description: 常见问题以及如何解决它们。
---
-要排除 opencode 的问题,请首先检查其存储在磁碟上的日志和本地数据。
+要排除 opencode 的问题,请首先检查其存储在磁盘上的日志和本地数据。
---
## 日志
-日誌文件寫入:
+日志文件写入:
- **macOS/Linux**: `~/.local/share/opencode/log/`
- **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.local\share\opencode\log`
@@ -51,7 +51,7 @@ opencode Desktop runs a local opencode server (the `opencode-cli` sidecar) in th
### 禁用插件
-如果桌面应用程序在启动時崩潰、掛起或行为異常,请首先禁用插件。
+如果桌面应用程序在启动时崩溃、挂起或行为异常,请首先禁用插件。
#### 检查全域性配置
@@ -61,7 +61,7 @@ opencode Desktop runs a local opencode server (the `opencode-cli` sidecar) in th
- **macOS/Linux** (older installs): `~/.local/share/opencode/opencode.jsonc`
- **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.config\opencode\opencode.jsonc`
-如果您配置了插件,请通过删除密钥或将其设置为空数组來暂时禁用它們:
+如果您配置了插件,请通过删除密钥或将其设置为空数组来暂时禁用它们:
```jsonc
{
@@ -77,10 +77,10 @@ opencode 还可以从磁碟加载本地外挂。暂时将它们移开(或重
- **全域性插件**
- **macOS/Linux**: `~/.config/opencode/plugins/`
- **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.config\opencode\plugins`
-- **专案插件**(仅当您使用每个专案配置時)
+- **项目插件**(仅当您使用每个项目配置时)
- `/.opencode/plugins/`
-如果应用程序再次開始工作,请一次重新启用一个插件,以找出導致問題的插件。
+如果应用程序再次开始工作,请一次重新启用一个插件,以找出导致问题的插件。
---
@@ -99,7 +99,7 @@ opencode 还可以从磁碟加载本地外挂。暂时将它们移开(或重
---
-### 修復服务器连接問題
+### 修复服务器连接问题
opencode Desktop 可以启动自己的本地服务器(默认配置)或连线到您的服务器 URL。
@@ -147,11 +147,11 @@ If you're experiencing slow performance, file access issues, or terminal problem
opencode 桌面仅在以下情况下显示系统通知:
- 在您的作业系统中设置为 opencode 启用了通知,并且
-- 应用程序視窗未聚焦。
+- 应用程序视窗未聚焦。
---
-### 重置桌面应用程序儲存(最后的手段)
+### 重置桌面应用程序储存(最后的手段)
如果应用程序无法并且启动您无法从 UI 内部清除设置,请重置桌面应用程序的存储状态。
@@ -179,9 +179,9 @@ opencode 桌面仅在以下情况下显示系统通知:
[**github.com/anomalyco/opencode/issues**](https://github.com/anomalyco/opencode/issues)
- 在建立新問題之前,请搜索現有問題以查看您的問題是否已被報告。
+ 在建立新问题之前,请搜索现有问题以查看您的问题是否已被报告。
-2. **加入我们的不和諧**
+2. **加入我们的不和谐**
获得实时帮助和社群讨论,请加入我们的Discord服务器:
@@ -191,19 +191,19 @@ opencode 桌面仅在以下情况下显示系统通知:
## 常见问题
-以下是一些常見問題以及解決方法。
+以下是一些常见问题以及解决方法。
---
### opencode 无法启动
-1. 检查日誌中是否有错误消息
+1. 检查日志中是否有错误消息
2. 尝试使用 `--print-logs` 执行以查看终端中的输出
3. Ensure you have the latest version with `opencode upgrade`
---
-### 身份验证問題
+### 身份验证问题
1. 尝试使用 TUI 中的 `/connect` 命令重新进行身份验证
2. 检查您的API 密钥是否有效
@@ -214,12 +214,12 @@ opencode 桌面仅在以下情况下显示系统通知:
### 模型不可用
1. 检查您是否已通过提供商的身份验证
-2. 驗證配置中的模型名称是否正确
-3. 某些模型可能需要特定的访问权限或訂閱
+2. 验证配置中的模型名称是否正确
+3. 某些模型可能需要特定的访问权限或订阅
如果您遇到 `ProviderModelNotFoundError` 您很可能是错误的
-在某處引用模型。
-模型应该像这樣引用:`/`
+在某处引用模型。
+模型应该像这样引用:`/`
示例:
@@ -235,10 +235,10 @@ To figure out what models you have access to, run `opencode models`
如果遇到 ProviderInitError,您的配置可能无效或损坏。
-要解決这个問題:
+要解决这个问题:
1. 首先,按照[提供商指南](/docs/providers) 验证您的事业是否已正确设置
-2. 如果問題仍然存在,请嘗試清除儲存的配置:
+2. 如果问题仍然存在,请尝试清除储存的配置:
```bash
rm -rf ~/.local/share/opencode
@@ -254,7 +254,7 @@ To figure out what models you have access to, run `opencode models`
如果您遇到 API 呼叫错误,这可能是由于过去提供包造成的。 opencode 根据需要动态安装提供包(OpenAI、Anthropic、Google 等)将其缓存放在本地。
-要解決provider 包問題:
+要解决provider 包问题:
1. 清除provider 包缓存:
@@ -288,7 +288,7 @@ apt install -y xsel
apt install -y wl-clipboard
```
-**对于無頭環境:**
+**对于无头环境:**
```bash
apt install -y xvfb
diff --git a/packages/web/src/content/docs/zh-cn/tui.mdx b/packages/web/src/content/docs/zh-cn/tui.mdx
index e1beb9abff..abc5d0c1e9 100644
--- a/packages/web/src/content/docs/zh-cn/tui.mdx
+++ b/packages/web/src/content/docs/zh-cn/tui.mdx
@@ -29,17 +29,17 @@ Give me a quick summary of the codebase.
## 文件引用
-您可以使用 `@` 在消息中引用文件。这会在当前工作目录中進行模糊文件搜索。
+您可以使用 `@` 在消息中引用文件。这会在当前工作目录中进行模糊文件搜索。
:::tip
-您还可以使用 `@` 來引用消息中的文件。
+您还可以使用 `@` 来引用消息中的文件。
:::
```text "@packages/functions/src/api/index.ts"
How is auth handled in @packages/functions/src/api/index.ts?
```
-文件的內容会自动添加到对话中。
+文件的内容会自动添加到对话中。
---
@@ -51,7 +51,7 @@ How is auth handled in @packages/functions/src/api/index.ts?
!ls -la
```
-命令的輸出将作为工具結果添加到对话中。
+命令的输出将作为工具结果添加到对话中。
---
@@ -65,11 +65,11 @@ How is auth handled in @packages/functions/src/api/index.ts?
大多数命令还是以使用 `ctrl+x` 作为主键的键系结,其中 `ctrl+x` 是默认主键。 [了解更多](/docs/keybinds)。
-以下是所有可用的斜槓命令:
+以下是所有可用的斜杠命令:
---
-### 连接
+### connect
将提供商添加到 opencode。你可以从可用提供商中选择,并添加它们的 API 密钥。
@@ -79,7 +79,7 @@ How is auth handled in @packages/functions/src/api/index.ts?
---
-### 紧凑
+### compact
压缩当前会话。_别名_: `/summarize`
@@ -91,9 +91,9 @@ How is auth handled in @packages/functions/src/api/index.ts?
---
-### 細節
+### details
-切換工具执行详细信息。
+切换工具执行详细信息。
```bash frame="none"
/details
@@ -103,7 +103,7 @@ How is auth handled in @packages/functions/src/api/index.ts?
---
-### 編輯
+### editor
开启外部编辑器来编写消息。使用`EDITOR`环境变量中设定的编辑器。 [了解更多](#editor-setup)。
@@ -115,7 +115,7 @@ How is auth handled in @packages/functions/src/api/index.ts?
---
-### 退出
+### exit
退出opencode。 _别名_:`/quit`、`/q`
@@ -127,7 +127,7 @@ How is auth handled in @packages/functions/src/api/index.ts?
---
-### 导出
+### export
将当前对话汇出到 Markdown 并在默认编辑器中开启。使用 `EDITOR` 环境变数中设定的编辑器。 [了解更多](#editor-setup)。
@@ -139,9 +139,9 @@ How is auth handled in @packages/functions/src/api/index.ts?
---
-### 幫助
+### help
-显示幫助对话方塊。
+显示帮助对话方块。
```bash frame="none"
/help
@@ -151,7 +151,7 @@ How is auth handled in @packages/functions/src/api/index.ts?
---
-### 初始化
+### init
Create or update `AGENTS.md` file. [Learn more](/docs/rules).
@@ -163,7 +163,7 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules).
---
-### 模型
+### models
列出可用模型。
@@ -175,7 +175,7 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules).
---
-### 新的
+### new
开始新的会话。 _别名_: `/clear`
@@ -187,7 +187,7 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules).
---
-### 重做
+### redo
删除之前重做消除的讯息。仅在使用`/undo`后可用。
@@ -206,7 +206,7 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules).
---
-### 会话
+### sessions
上市会话并在会话之间切换。 _别名_:`/resume`、`/continue`
@@ -218,7 +218,7 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules).
---
-### 分享
+### share
共享当前会话。 [了解更多](/docs/share)。
@@ -230,7 +230,7 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules).
---
-### 主题
+### themes
列出可用的主题。
@@ -242,9 +242,9 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules).
---
-### 思維
+### thinking
-切换对话中 thinking/reasoning 块的可视性。启用后,您可以看到支持增强套件思维的模型的推理过程。
+切换对话中 thinking/reasoning 块的可视性。启用后,您可以看到支持扩展思考的模型的推理过程。
:::note
该命令仅控制是否**显示** - 不启用或取消模型的推理功能。要切换实际推理功能,请使用 `ctrl+t` 回圈切换模型变体。
@@ -256,9 +256,9 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules).
---
-### 撤消
+### undo
-撤消对话中的最后一条消息。删除最近的用户消息、所有后续響应以及任何文件更改。
+撤消对话中的最后一条消息。删除最近的用户消息、所有后续响应以及任何文件更改。
:::tip
所做的任何文件更改也将被恢复。
@@ -275,7 +275,7 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules).
---
-### 取消共享
+### unshare
取消共享当前会话。 [了解更多](/docs/share#un-sharing)。
@@ -334,7 +334,7 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules).
-流行的编辑器選項包括:
+流行的编辑器选项包括:
- `code` - Visual Studio Code
- `cursor` - 游标
@@ -369,7 +369,7 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules).
}
```
-### 選項
+### 选项
- `scroll_acceleration` - 启用 macOS 式滚动加速以实现平滑、自然的滚动。启用后,滚动速度会随着快速滚动滚动而增加,并在较慢的移动时保持精确。 **此设定优先于 `scroll_speed` 并在启用时覆盖它。 **
- `scroll_speed` - 控制使用滚动控制器时 TUI 滚动的速度(简单:`1`)。默认为 `3`。 **注意:如果 `scroll_acceleration.enabled` 设置为 `true`,则忽略此设置。 **
@@ -384,7 +384,7 @@ Create or update `AGENTS.md` file. [Learn more](/docs/rules).
#### 用户名称显示
-切換您的用户名称是否出現在聊天消息中。通过以下方式访问:
+切换您的用户名称是否出现在聊天消息中。通过以下方式访问:
- 命令面板:搜索“用户名称”或“隐藏用户名称”
- 该设置会自动保留,放在 TUI 会话中被记住
diff --git a/packages/web/src/content/docs/zh-cn/web.mdx b/packages/web/src/content/docs/zh-cn/web.mdx
index efb55aaab5..e13540d1fd 100644
--- a/packages/web/src/content/docs/zh-cn/web.mdx
+++ b/packages/web/src/content/docs/zh-cn/web.mdx
@@ -1,5 +1,5 @@
---
-title: 網路
+title: Web
description: 在浏览器中使用opencode。
---
@@ -7,7 +7,7 @@ opencode 可以在浏览器中作为 Web 应用程序执行,消耗终端可以

-## 入門
+## 入门
绕过执行以下命令启动 Web 简介:
@@ -29,7 +29,7 @@ For the best experience, run `opencode web` from [WSL](/docs/windows-wsl) rather
## 配置
-您可以使用命令列标志或在[config file](/docs/config).conf 中配置Web服务器。
+您可以使用命令行标志或在[config file](/docs/config).conf 中配置Web服务器。
### 端口
@@ -39,9 +39,9 @@ For the best experience, run `opencode web` from [WSL](/docs/windows-wsl) rather
opencode web --port 4096
```
-### 主機名
+### 主机名
-情况默认下,服务器系结到`127.0.0.1`(仅限本地主机)。要使opencode在您的网路上可访问:
+默认情况下,服务器绑定到`127.0.0.1`(仅限本地主机)。要使opencode在您的网路上可访问:
```bash
opencode web --hostname 0.0.0.0
@@ -78,9 +78,9 @@ opencode web --mdns --mdns-domain myproject.local
opencode web --cors https://example.com
```
-### 驗證
+### 验证
-要保护访问,请使用 `OPENCODE_SERVER_PASSWORD` 环境变数设置密码:
+要保护访问,请使用 `OPENCODE_SERVER_PASSWORD` 环境变量设置密码:
```bash
OPENCODE_SERVER_PASSWORD=secret opencode web
@@ -90,19 +90,19 @@ The username defaults to `opencode` but can be changed with `OPENCODE_SERVER_USE
---
-## 使用网络界面
+## 使用web界面
-启动后,网路界面将提供对您的 opencode 会话的访问。
+启动后,web界面将提供对您的 opencode 会话的访问。
### 会话
-從主頁查看和管理您的会话。您可以查看活动会话并開始新会话。
+从主页查看和管理您的会话。您可以查看活动会话并开始新会话。

-### 服务器狀態
+### 服务器状态
-单击“查看服务器”可查看连接的服务器及其狀態。
+单击“查看服务器”可查看连接的服务器及其状态。

diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx
index 1a2e77aabc..7c00b5cdf4 100644
--- a/packages/web/src/content/docs/zh-cn/zen.mdx
+++ b/packages/web/src/content/docs/zh-cn/zen.mdx
@@ -1,5 +1,5 @@
---
-title: 禪
+title: Zen
description: 由 opencode 提供的精选模型列表。
---
@@ -7,10 +7,10 @@ import config from "../../../../config.mjs"
export const console = config.console
export const email = `mailto:${config.email}`
-Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+OpenCode Zen 是由 OpenCode 团队提供的一组经过测试和验证的模型列表。
:::note
-OpenCode Zen 目前处于一个测试阶段。
+OpenCode Zen 目前处于测试阶段。
:::
Zen 的工作方式与 opencode 中的任何其他提供商相同。您登录 OpenCode Zen 并获得
@@ -21,31 +21,31 @@ opencode。
## 背景
-市面上有很多模型,但其中只有少數幾个
-这些模型可以很好地用作編碼代理。此外,大多數提供商都
-配置非常不同;所以你会得到截然不同的效能和質量。
+市面上有很多模型,但其中只有少数几个
+这些模型可以很好地用作编码代理。此外,大多数提供商都
+配置非常不同;所以你会得到截然不同的效率和质量。
:::tip
我们测试了一组与 opencode 配合良好的模型并提供商。
:::
-因此,如果您使用 OpenRouter 之类的东西使用模型,您永远无法
-確定您是否獲得了您想要的模型的最佳版本。
+因此,如果您通过 OpenRouter 之类的东西使用模型,您永远无法
+确定您是否获得了您想要的模型的最佳版本。
-为瞭解決这个問題,我们做了幾件事:
+为了解决这个问题,我们做了几件事:
-1. 我们測試了一組選定的模型,并与他們的團隊討論瞭如何
- 最好执行它們。
-2. 然後我们与一些提供商合作以確保这些服務得到服務
+1. 我们测试了一组选定的模型,并与他们的团队讨论了如何
+ 最好执行它们。
+2. 然后我们与一些提供商合作以确保这些服务得到服务
正确。
3. 最后,我们对 model/provider 的组合进行了基准测试,总结了
- 并附上一份我们覺得不錯的推薦清單。
+ 并附上一份我们觉得不错的推荐清单。
-OpenCode Zen 是一个AI闸道器,可以让您访问这些模型。
+OpenCode Zen 是一个AI网关,让您可以访问这些模型。
---
-## 它是如何運作的
+## 它是如何运作的
OpenCode Zen 的工作方式与 opencode 中的任何其他功能相同。
@@ -54,7 +54,7 @@ OpenCode Zen 的工作方式与 opencode 中的任何其他功能相同。
2. 您在 TUI 中执行 `/connect` 命令,选择 OpenCode Zen,然后贴上 API 密钥。
3. 在 TUI 中执行 `/models` 以查看我们推荐的模型列表。
-您需要按请求付費,并且您可以将積分添加到您的账户中。
+您需要按请求付费,并且您可以将积分添加到您的账户中。
---
@@ -62,7 +62,7 @@ OpenCode Zen 的工作方式与 opencode 中的任何其他功能相同。
您还可以通过以下 API 端点访问我们的模型。
-| 模型 | 模型ID | 端点 | 人工智慧SDK包 |
+| 模型 | 模型ID | 端点 | AI SDK 套件 |
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
@@ -102,7 +102,7 @@ use `opencode/gpt-5.2-codex` in your config.
### 模型
-您可以從以下位置获取可用模型及其元数据的完整列表:
+您可以从以下位置获取可用模型及其元数据的完整列表:
```
https://opencode.ai/zen/v1/models
@@ -114,9 +114,9 @@ https://opencode.ai/zen/v1/models
我们支持即用即付模式。以下是**每 100 万Tokens的价格**。
-| 模型 | 輸入 | 輸出 | 缓存讀取 | 缓存寫入 |
+| 模型 | 输入 | 输出 | 缓存读取 | 缓存写入 |
| ---------------------------------- | ---------- | ---------- | ---------- | ---------- |
-| 大泡菜 | 免費 | 免費 | 免費 | - |
+| 大泡菜 | 免费 | 免费 | 免费 | - |
| MiniMax M2.1 免费 | 免费 | 免费 | 免费 | - |
| 迷你最大M2.1 | 0.30 美元 | 1.20 美元 | 0.10 美元 | - |
| GLM 4.7 免费 | 免费 | 免费 | 免费 | - |
@@ -153,32 +153,32 @@ https://opencode.ai/zen/v1/models
您可能会在您的使用历史记录中注意到*Claude Haiku 3.5*。这是一个[低成本模型](/docs/config/#models),用于生成会话标题。
:::note
-信用卡费用按成本轉嫁(4.4% + 每筆交易 0.30 美元);除此之外我们不收取任何费用。
+信用卡费用按成本转嫁(4.4% + 每笔交易 0.30 美元);除此之外我们不收取任何费用。
:::
-免費模型:
+免费模型:
- GLM 4.7 免费版本在 opencode 上限时提供。团队正在利用这段时间收集反馈并改进模型。
- Kimi K2.5 在 opencode 限时免费发布。团队正在利用这段时间收集反馈并改进模型。
- MiniMax M2.1 在 opencode 限时免费供应。团队正在利用这段时间收集反馈并改进模型。
- Big Pickle 是一个隐形模型,在 opencode 上限时免费。团队正在利用这个临时收集反馈并改进模型。
-如果您有任何疑問,请聯絡我们 。
+如果您有任何疑问,请联络我们 。
---
-### 自动重新載入
+### 自动重新载入
如果您的余额低于 5 美元,Zen 将自动充值 20 美元。
-您可以更改自动充值金額。您还可以完全禁用自动重新載入。
+您可以更改自动充值金额。您还可以完全禁用自动重新载入。
---
### 每月限额
您还可以为整个工作区和每个工作区设置每月使用限制
-你的團隊成员。
+你的团队成员。
例如,假设您将每月使用中断设置为 20 美元,Zen 将不会使用
一个月超过 20 美元。但如果你启用了自动重新加载,Zen 可能会结束
@@ -188,7 +188,7 @@ https://opencode.ai/zen/v1/models
## 隐私
-我们所有的模型都在美国託管。我们的提供商遵循零保留政策,不会将您的数据用于模型训练,但以下情况除外:
+我们所有的模型都在美国托管。我们的提供商遵循零保留政策,不会将您的数据用于模型训练,但以下情况除外:
- Big Pickle:在免费期间,收集可用于改进模型的数据。
- GLM 4.7 免费:在免费期间,收集可用于改进模型的数据。
@@ -201,21 +201,20 @@ https://opencode.ai/zen/v1/models
## 对于团队
-Zen 对团队也很有效。您可以参与第三方、角色、策划
-您的團隊使用的模型等等。
+Zen 也非常适合团队使用。您可以邀请您可以邀请队友,分配角色,管理团队使用的模型等。
:::note
-作为測試版的一部分,工作空間目前对團隊免費。
+作为测试版的一部分,工作空间目前对团队免费。
:::
-作为測試版的一部分,管理工作空間目前对團隊免費。我们将会
-很快就会分享更多有關定價的細節。
+作为测试版的一部分,管理工作空间目前对团队免费。我们将会
+很快就会分享更多有关定价的细节。
---
### 角色
-您可以邀请團隊成员到您的工作区并分配角色:
+您可以邀请团队成员到您的工作区并分配角色:
- **管理员**:管理模型、成员、API 密钥和设备
- **成员**:仅管理自己的API 金?
diff --git a/packages/web/src/content/docs/zh-tw/index.mdx b/packages/web/src/content/docs/zh-tw/index.mdx
index ab22bfb5c7..5de780503c 100644
--- a/packages/web/src/content/docs/zh-tw/index.mdx
+++ b/packages/web/src/content/docs/zh-tw/index.mdx
@@ -84,7 +84,8 @@ curl -fsSL https://opencode.ai/install | bash
- **在 Arch Linux 上使用 Paru**
```bash
- paru -S opencode-bin
+ sudo pacman -S opencode # Arch Linux (Stable)
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
```
#### Windows
diff --git a/packages/web/src/middleware.ts b/packages/web/src/middleware.ts
index 97d085dfbf..cf9f97b0b1 100644
--- a/packages/web/src/middleware.ts
+++ b/packages/web/src/middleware.ts
@@ -12,7 +12,28 @@ function docsAlias(pathname: string) {
const next = locale === "root" ? `/docs${tail}` : `/docs/${locale}${tail}`
if (next === pathname) return null
- return next
+ return {
+ path: next,
+ locale,
+ }
+}
+
+function cookie(locale: string) {
+ const value = locale === "root" ? "en" : locale
+ return `oc_locale=${encodeURIComponent(value)}; Path=/; Max-Age=31536000; SameSite=Lax`
+}
+
+function redirect(url: URL, path: string, locale?: string) {
+ const next = new URL(url.toString())
+ next.pathname = path
+ const headers = new Headers({
+ Location: next.toString(),
+ })
+ if (locale) headers.set("Set-Cookie", cookie(locale))
+ return new Response(null, {
+ status: 302,
+ headers,
+ })
}
function localeFromCookie(header: string | null) {
@@ -59,9 +80,7 @@ function localeFromAcceptLanguage(header: string | null) {
export const onRequest = defineMiddleware((ctx, next) => {
const alias = docsAlias(ctx.url.pathname)
if (alias) {
- const url = new URL(ctx.request.url)
- url.pathname = alias
- return ctx.redirect(url.toString(), 302)
+ return redirect(ctx.url, alias.path, alias.locale)
}
if (ctx.url.pathname !== "/docs" && ctx.url.pathname !== "/docs/") return next()
@@ -71,7 +90,5 @@ export const onRequest = defineMiddleware((ctx, next) => {
localeFromAcceptLanguage(ctx.request.headers.get("accept-language"))
if (!locale || locale === "root") return next()
- const url = new URL(ctx.request.url)
- url.pathname = `/docs/${locale}/`
- return ctx.redirect(url.toString(), 302)
+ return redirect(ctx.url, `/docs/${locale}/`)
})
diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json
index 30c07d3139..d19dcbf467 100644
--- a/sdks/vscode/package.json
+++ b/sdks/vscode/package.json
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
- "version": "1.1.64",
+ "version": "1.2.1",
"publisher": "sst-dev",
"repository": {
"type": "git",