mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-04 17:26:22 -04:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e84efb181c | |||
| 2e8d690ab1 | |||
| 1ff8d289af | |||
| d54ffbda1c | |||
| c00058ed7a | |||
| 2c2fc3499b | |||
| ea3c6c3481 | |||
| 9b68b7195a | |||
| 7739cc53b4 | |||
| 3fa78a8b01 | |||
| e57d0c2fee | |||
| 2a4f2bf527 | |||
| aa07f38b07 | |||
| 9d1f17d836 | |||
| b420952e59 | |||
| bb9e445257 | |||
| 528fb1d404 | |||
| c8d9f7aa89 | |||
| cd7ec93cdf | |||
| 796b652d2b | |||
| 4d74849c1a | |||
| 937a7c48a5 | |||
| 704eb00de4 | |||
| bad4599bf9 | |||
| 892fd85ba7 | |||
| 0eaa47d857 | |||
| faca24d487 | |||
| c103202ad5 | |||
| ce78a4265d | |||
| c4a2353ac3 | |||
| 576efed196 | |||
| dfc0075f90 | |||
| acd15dcc8a | |||
| 139c4fd555 | |||
| e0f3df8252 | |||
| 9cd2e3a1c3 | |||
| f584f80219 | |||
| 45eac589f8 | |||
| fab1768826 | |||
| 51fc10e407 | |||
| 7a1c8465f5 | |||
| 5290e9ca7e | |||
| c361c2953f | |||
| ccb7669736 | |||
| f25f1485d5 | |||
| 55ecb06748 | |||
| dc6991e5a8 | |||
| 738b3065dc | |||
| 26cc537cb1 | |||
| ede354b0e6 | |||
| 61eabfc60c | |||
| 2789b770aa | |||
| 8718b98ee1 | |||
| c8b2f987f9 | |||
| 52b55b826f | |||
| e8c20235b8 | |||
| 17701628bd | |||
| 0efc6163f1 | |||
| 1e191ba815 | |||
| f19d863689 | |||
| 4a1ef327ca | |||
| 025a6392ce | |||
| e578c442be | |||
| 0cecb1bff2 | |||
| 5d8971c1ed | |||
| a9b62d67df | |||
| 3525e61906 | |||
| 059e6c46db | |||
| 5cf195e0af | |||
| 244d1debe4 | |||
| 35734b42fe |
+3
-1
@@ -12,8 +12,10 @@ adamdotdevin
|
||||
ariane-emory
|
||||
-atharvau AI review spamming literally every PR
|
||||
-borealbytes
|
||||
-carycooper777
|
||||
-danieljoshuanazareth
|
||||
-danieljoshuanazareth
|
||||
-davidbernat looks to be a clawdbot that spams team and sends super weird emails, doesnt appear to be a real person
|
||||
edemaine
|
||||
-florianleibert
|
||||
fwang
|
||||
@@ -31,6 +33,6 @@ rubdos
|
||||
shantur
|
||||
simonklee
|
||||
-spider-yamet clawdbot/llm psychosis, spam pinging the team
|
||||
-terisuke
|
||||
thdxr
|
||||
-toastythebot
|
||||
-davidbernat looks to be a clawdbot that spams team and sends super weird emails, doesnt appear to be a real person
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -83,7 +83,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -117,7 +117,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -144,7 +144,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.64",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
@@ -168,7 +168,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -192,7 +192,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -226,7 +226,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -259,7 +259,7 @@
|
||||
},
|
||||
"packages/desktop-electron": {
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -303,7 +303,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -332,7 +332,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -348,7 +348,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -491,7 +491,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -526,7 +526,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -541,7 +541,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -576,7 +576,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -625,7 +625,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -677,8 +677,8 @@
|
||||
},
|
||||
"catalog": {
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@effect/opentelemetry": "4.0.0-beta.48",
|
||||
"@effect/platform-node": "4.0.0-beta.48",
|
||||
"@effect/opentelemetry": "4.0.0-beta.57",
|
||||
"@effect/platform-node": "4.0.0-beta.57",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@lydell/node-pty": "1.2.0-beta.10",
|
||||
@@ -708,7 +708,7 @@
|
||||
"dompurify": "3.3.1",
|
||||
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
||||
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
||||
"effect": "4.0.0-beta.48",
|
||||
"effect": "4.0.0-beta.57",
|
||||
"fuzzysort": "3.1.0",
|
||||
"hono": "4.10.7",
|
||||
"hono-openapi": "1.1.2",
|
||||
@@ -1071,11 +1071,11 @@
|
||||
|
||||
"@effect/language-service": ["@effect/language-service@0.84.2", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-l04qNxpiA8rY5yXWckRPJ7Mk5MNerXuNymSFf+IdflfI5i8jgL1bpBNLuP6ijg7wgjdHc/KmTnCj2kT0SCntuA=="],
|
||||
|
||||
"@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.48", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.48" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-vHk/X1vgDrviGcOTHQqzm2D81TtyPE/C7Qdksg5eAdbGpnqL4Dm4lk6PzTReQ0pO1/avIvWqpxy315IURV0Ldw=="],
|
||||
"@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.57", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.57" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-gdjZPEP0QQg4qmI1vd+443kheeQZKytrjJIzCJncy6ZEpyk/SfrqeStLqLXdTRcms3IB0ls0vOV7KNq7YmBRVA=="],
|
||||
|
||||
"@effect/platform-node": ["@effect/platform-node@4.0.0-beta.48", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.48", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.48", "ioredis": "^5.7.0" } }, "sha512-8J6H0k9rtbp9O1QvKOyOPRcCTJ8WrR7IzZLJtYFTZ4bXVEEMCTo84h0CRpi7ccpA9t7DLqotip0NeFgiBosNKQ=="],
|
||||
"@effect/platform-node": ["@effect/platform-node@4.0.0-beta.57", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.57", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.57", "ioredis": "^5.7.0" } }, "sha512-la0xxPSAYOsY0d+uVxEBxok3jYB31iPQmIaZZRUj2SNWqcGGHJc6KorKtI8guqSLuv9FGZ255kBWXRbG6hMeeg=="],
|
||||
|
||||
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.48", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.48" } }, "sha512-wlhcdDHyacydCgiWdM8JwtQkViQhZsC8uJZ9wMoZXYxlCTvqfdzLeWw4A1UVMoq7sS6/KR1aZVeFkUjrqonncQ=="],
|
||||
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.57", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.57" } }, "sha512-C976X6f+qHUtLSqcqImuCrjhAHnJV17NC2RvvybsAuDfkyIWU4MyiO2XwgiBeijeNupyr1M/KPKnyjtkNxV9Hw=="],
|
||||
|
||||
"@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="],
|
||||
|
||||
@@ -1601,7 +1601,7 @@
|
||||
|
||||
"@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w=="],
|
||||
|
||||
"@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="],
|
||||
"@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
|
||||
|
||||
"@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="],
|
||||
|
||||
@@ -3035,7 +3035,7 @@
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"effect": ["effect@4.0.0-beta.48", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw=="],
|
||||
"effect": ["effect@4.0.0-beta.57", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-rg32VgXnLKaPRs9tbRDaZ5jxmzNY7ojXt85gSHGUTwdlbWH5Ik+OCUY2q14TXliygPGoHwCAvNWS4bQJOqf00g=="],
|
||||
|
||||
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
|
||||
|
||||
@@ -5595,18 +5595,6 @@
|
||||
|
||||
"@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="],
|
||||
|
||||
"@opentelemetry/exporter-trace-otlp-http/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
|
||||
|
||||
"@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
|
||||
|
||||
"@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="],
|
||||
|
||||
"@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
|
||||
|
||||
"@opentelemetry/sdk-metrics/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
|
||||
|
||||
"@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
|
||||
|
||||
"@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="],
|
||||
|
||||
"@opentui/solid/babel-preset-solid": ["babel-preset-solid@1.9.10", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.10" }, "optionalPeers": ["solid-js"] }, "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ=="],
|
||||
@@ -5675,6 +5663,10 @@
|
||||
|
||||
"@solidjs/start/vite-plugin-solid": ["vite-plugin-solid@2.11.12", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-FgjPcx2OwX9h6f28jli7A4bG7PP3te8uyakE5iqsmpq3Jqi1TWLgSroC9N6cMfGRU2zXsl4Q6ISvTr2VL0QHpA=="],
|
||||
|
||||
"@standard-community/standard-json/effect": ["effect@4.0.0-beta.48", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw=="],
|
||||
|
||||
"@standard-community/standard-openapi/effect": ["effect@4.0.0-beta.48", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw=="],
|
||||
|
||||
"@tailwindcss/oxide/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||
@@ -6593,6 +6585,10 @@
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
|
||||
|
||||
"@standard-community/standard-json/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@standard-community/standard-openapi/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
|
||||
|
||||
@@ -115,6 +115,27 @@ const zenLiteCouponFirstMonth100 = new stripe.Coupon("ZenLiteCouponFirstMonth100
|
||||
appliesToProducts: [zenLiteProduct.id],
|
||||
duration: "once",
|
||||
})
|
||||
const zenLiteCouponThreeMonths100 = new stripe.Coupon("ZenLiteCoupon3Months100", {
|
||||
name: "3 months 100% off",
|
||||
percentOff: 100,
|
||||
appliesToProducts: [zenLiteProduct.id],
|
||||
duration: "repeating",
|
||||
durationInMonths: 3,
|
||||
})
|
||||
const zenLiteCouponSixMonths100 = new stripe.Coupon("ZenLiteCoupon6Months100", {
|
||||
name: "6 months 100% off",
|
||||
percentOff: 100,
|
||||
appliesToProducts: [zenLiteProduct.id],
|
||||
duration: "repeating",
|
||||
durationInMonths: 6,
|
||||
})
|
||||
const zenLiteCouponTwelveMonths100 = new stripe.Coupon("ZenLiteCoupon12Months100", {
|
||||
name: "12 months 100% off",
|
||||
percentOff: 100,
|
||||
appliesToProducts: [zenLiteProduct.id],
|
||||
duration: "repeating",
|
||||
durationInMonths: 12,
|
||||
})
|
||||
const zenLitePrice = new stripe.Price("ZenLitePrice", {
|
||||
product: zenLiteProduct.id,
|
||||
currency: "usd",
|
||||
@@ -131,6 +152,9 @@ const ZEN_LITE_PRICE = new sst.Linkable("ZEN_LITE_PRICE", {
|
||||
priceInr: 92900,
|
||||
firstMonth50Coupon: zenLiteCouponFirstMonth50.id,
|
||||
firstMonth100Coupon: zenLiteCouponFirstMonth100.id,
|
||||
threeMonths100Coupon: zenLiteCouponThreeMonths100.id,
|
||||
sixMonths100Coupon: zenLiteCouponSixMonths100.id,
|
||||
twelveMonths100Coupon: zenLiteCouponTwelveMonths100.id,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-zQOuZkuOLm9xV5YzfcQiWSzvussXVw/dRPw4HhuYsdc=",
|
||||
"aarch64-linux": "sha256-J8Ak9rCCkKlBj0KhdUOklglpR3ntRP2VjtdmEJox6Ro=",
|
||||
"aarch64-darwin": "sha256-r4F6S9CfGR6qxSaSqu9AIjWCEKd/PuT1Wprcbzk0IwY=",
|
||||
"x86_64-darwin": "sha256-I/ztb7VnLbJYzAxf2egN0+YsJcP5yix3tIoHtiuRUXE="
|
||||
"x86_64-linux": "sha256-h2T/LnUnISZZDn9ZQkZ/A59P+6+QdfOlrgl4RXK/vgM=",
|
||||
"aarch64-linux": "sha256-+DRohG1ZEB/2LtZU90GWoqJkeyu/sW8A8oKT3f/TtQ0=",
|
||||
"aarch64-darwin": "sha256-k4nsk/WduuxY8HgjRuqzGT9EjEo7V/2mAzBTYee0fZ0=",
|
||||
"x86_64-darwin": "sha256-3dSvfN2+5lXwOx57x8NSIWbEZ1fp6+1T6bJpAuUNPyk="
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -27,8 +27,8 @@
|
||||
"packages/slack"
|
||||
],
|
||||
"catalog": {
|
||||
"@effect/opentelemetry": "4.0.0-beta.48",
|
||||
"@effect/platform-node": "4.0.0-beta.48",
|
||||
"@effect/opentelemetry": "4.0.0-beta.57",
|
||||
"@effect/platform-node": "4.0.0-beta.57",
|
||||
"@npmcli/arborist": "9.4.0",
|
||||
"@types/bun": "1.3.12",
|
||||
"@types/cross-spawn": "6.0.6",
|
||||
@@ -53,7 +53,7 @@
|
||||
"dompurify": "3.3.1",
|
||||
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
||||
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
||||
"effect": "4.0.0-beta.48",
|
||||
"effect": "4.0.0-beta.57",
|
||||
"ai": "6.0.168",
|
||||
"cross-spawn": "7.0.6",
|
||||
"hono": "4.10.7",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -82,7 +82,15 @@ declare global {
|
||||
}
|
||||
|
||||
function QueryProvider(props: ParentProps) {
|
||||
const client = new QueryClient()
|
||||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
return <QueryClientProvider client={client}>{props.children}</QueryClientProvider>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useMutation } from "@tanstack/solid-query"
|
||||
import { Component, createEffect, createMemo, on, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useMutation, useQueryClient } from "@tanstack/solid-query"
|
||||
import { Component, createMemo, Show } from "solid-js"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||
import { List } from "@opencode-ai/ui/list"
|
||||
import { Switch } from "@opencode-ai/ui/switch"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { loadMcpQuery } from "@/context/global-sync"
|
||||
|
||||
const statusLabels = {
|
||||
connected: "mcp.status.connected",
|
||||
@@ -20,48 +19,7 @@ export const DialogSelectMcp: Component = () => {
|
||||
const sync = useSync()
|
||||
const sdk = useSDK()
|
||||
const language = useLanguage()
|
||||
const [state, setState] = createStore({
|
||||
done: false,
|
||||
loading: false,
|
||||
})
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => sync.data.mcp_ready,
|
||||
(ready, prev) => {
|
||||
if (!ready && prev) setState("done", false)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
|
||||
createEffect(() => {
|
||||
if (state.done || state.loading) return
|
||||
if (sync.data.mcp_ready) {
|
||||
setState("done", true)
|
||||
return
|
||||
}
|
||||
|
||||
setState("loading", true)
|
||||
void sdk.client.mcp
|
||||
.status()
|
||||
.then((result) => {
|
||||
sync.set("mcp", result.data ?? {})
|
||||
sync.set("mcp_ready", true)
|
||||
setState("done", true)
|
||||
})
|
||||
.catch((err) => {
|
||||
setState("done", true)
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: language.t("common.requestFailed"),
|
||||
description: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
setState("loading", false)
|
||||
})
|
||||
})
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const items = createMemo(() =>
|
||||
Object.entries(sync.data.mcp ?? {})
|
||||
@@ -71,16 +29,10 @@ export const DialogSelectMcp: Component = () => {
|
||||
|
||||
const toggle = useMutation(() => ({
|
||||
mutationFn: async (name: string) => {
|
||||
const status = sync.data.mcp[name]
|
||||
if (status?.status === "connected") {
|
||||
await sdk.client.mcp.disconnect({ name })
|
||||
} else {
|
||||
await sdk.client.mcp.connect({ name })
|
||||
}
|
||||
|
||||
const result = await sdk.client.mcp.status()
|
||||
if (result.data) sync.set("mcp", result.data)
|
||||
if (sync.data.mcp[name]?.status === "connected") await sdk.client.mcp.disconnect({ name })
|
||||
else await sdk.client.mcp.connect({ name })
|
||||
},
|
||||
onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }),
|
||||
}))
|
||||
|
||||
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Switch } from "@opencode-ai/ui/switch"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { useMutation } from "@tanstack/solid-query"
|
||||
import { useMutation, useQueryClient } from "@tanstack/solid-query"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { useNavigate } from "@solidjs/router"
|
||||
import { type Accessor, createEffect, createMemo, For, type JSXElement, onCleanup, Show } from "solid-js"
|
||||
@@ -15,6 +15,7 @@ import { useSDK } from "@/context/sdk"
|
||||
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health"
|
||||
import { loadMcpQuery } from "@/context/global-sync"
|
||||
|
||||
const pollMs = 10_000
|
||||
|
||||
@@ -137,14 +138,14 @@ const useMcpToggleMutation = () => {
|
||||
const sync = useSync()
|
||||
const sdk = useSDK()
|
||||
const language = useLanguage()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation(() => ({
|
||||
mutationFn: async (name: string) => {
|
||||
const status = sync.data.mcp[name]
|
||||
await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name }))
|
||||
const result = await sdk.client.mcp.status()
|
||||
if (result.data) sync.set("mcp", result.data)
|
||||
},
|
||||
onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }),
|
||||
onError: (err) => {
|
||||
showToast({
|
||||
variant: "error",
|
||||
@@ -162,14 +163,6 @@ export function StatusPopoverBody(props: { shown: Accessor<boolean> }) {
|
||||
const dialog = useDialog()
|
||||
const language = useLanguage()
|
||||
const navigate = useNavigate()
|
||||
const sdk = useSDK()
|
||||
|
||||
const [load, setLoad] = createStore({
|
||||
lspDone: false,
|
||||
lspLoading: false,
|
||||
mcpDone: false,
|
||||
mcpLoading: false,
|
||||
})
|
||||
|
||||
const fail = (err: unknown) => {
|
||||
showToast({
|
||||
@@ -181,40 +174,6 @@ export function StatusPopoverBody(props: { shown: Accessor<boolean> }) {
|
||||
|
||||
createEffect(() => {
|
||||
if (!props.shown()) return
|
||||
|
||||
if (!sync.data.mcp_ready && !load.mcpDone && !load.mcpLoading) {
|
||||
setLoad("mcpLoading", true)
|
||||
void sdk.client.mcp
|
||||
.status()
|
||||
.then((result) => {
|
||||
sync.set("mcp", result.data ?? {})
|
||||
sync.set("mcp_ready", true)
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoad("mcpDone", true)
|
||||
fail(err)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoad("mcpLoading", false)
|
||||
})
|
||||
}
|
||||
|
||||
if (!sync.data.lsp_ready && !load.lspDone && !load.lspLoading) {
|
||||
setLoad("lspLoading", true)
|
||||
void sdk.client.lsp
|
||||
.status()
|
||||
.then((result) => {
|
||||
sync.set("lsp", result.data ?? [])
|
||||
sync.set("lsp_ready", true)
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoad("lspDone", true)
|
||||
fail(err)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoad("lspLoading", false)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
let dialogRun = 0
|
||||
|
||||
@@ -14,17 +14,25 @@ import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import type { InitError } from "../pages/error"
|
||||
import { useGlobalSDK } from "./global-sdk"
|
||||
import { bootstrapDirectory, bootstrapGlobal, clearProviderRev } from "./global-sync/bootstrap"
|
||||
import {
|
||||
bootstrapDirectory,
|
||||
bootstrapGlobal,
|
||||
clearProviderRev,
|
||||
loadGlobalConfigQuery,
|
||||
loadPathQuery,
|
||||
loadProjectsQuery,
|
||||
loadProvidersQuery,
|
||||
} from "./global-sync/bootstrap"
|
||||
import { createChildStoreManager } from "./global-sync/child-store"
|
||||
import { applyDirectoryEvent, applyGlobalEvent, cleanupDroppedSessionCaches } from "./global-sync/event-reducer"
|
||||
import { createRefreshQueue } from "./global-sync/queue"
|
||||
import { clearSessionPrefetchDirectory } from "./global-sync/session-prefetch"
|
||||
import { estimateRootSessionTotal, loadRootSessionsWithFallback } from "./global-sync/session-load"
|
||||
import { trimSessions } from "./global-sync/session-trim"
|
||||
import type { ProjectMeta } from "./global-sync/types"
|
||||
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
||||
import { formatServerError } from "@/utils/server-errors"
|
||||
import { queryOptions, skipToken, useQueryClient } from "@tanstack/solid-query"
|
||||
import { queryOptions, skipToken, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query"
|
||||
import { createRefreshQueue } from "./global-sync/queue"
|
||||
|
||||
type GlobalStore = {
|
||||
ready: boolean
|
||||
@@ -43,6 +51,18 @@ type GlobalStore = {
|
||||
export const loadSessionsQuery = (directory: string) =>
|
||||
queryOptions<null>({ queryKey: [directory, "loadSessions"], queryFn: skipToken })
|
||||
|
||||
export const loadMcpQuery = (directory: string, sdk?: OpencodeClient) =>
|
||||
queryOptions({
|
||||
queryKey: [directory, "mcp"],
|
||||
queryFn: sdk ? () => sdk.mcp.status().then((r) => r.data ?? {}) : skipToken,
|
||||
})
|
||||
|
||||
export const loadLspQuery = (directory: string, sdk?: OpencodeClient) =>
|
||||
queryOptions({
|
||||
queryKey: [directory, "lsp"],
|
||||
queryFn: sdk ? () => sdk.lsp.status().then((r) => r.data ?? []) : skipToken,
|
||||
})
|
||||
|
||||
function createGlobalSync() {
|
||||
const globalSDK = useGlobalSDK()
|
||||
const language = useLanguage()
|
||||
@@ -54,15 +74,34 @@ function createGlobalSync() {
|
||||
const sessionLoads = new Map<string, Promise<void>>()
|
||||
const sessionMeta = new Map<string, { limit: number }>()
|
||||
|
||||
const [configQuery, providerQuery, pathQuery] = useQueries(() => ({
|
||||
queries: [loadGlobalConfigQuery(), loadProvidersQuery(null), loadPathQuery(null), loadProjectsQuery()],
|
||||
}))
|
||||
|
||||
const [globalStore, setGlobalStore] = createStore<GlobalStore>({
|
||||
ready: false,
|
||||
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
||||
get ready() {
|
||||
return bootstrap.isPending
|
||||
},
|
||||
project: [],
|
||||
session_todo: {},
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
provider_auth: {},
|
||||
config: {},
|
||||
reload: undefined,
|
||||
get path() {
|
||||
const EMPTY = { state: "", config: "", worktree: "", directory: "", home: "" }
|
||||
if (pathQuery.isLoading) return EMPTY
|
||||
return pathQuery.data ?? EMPTY
|
||||
},
|
||||
get provider() {
|
||||
const EMPTY = { all: [], connected: [], default: {} }
|
||||
if (providerQuery.isLoading) return EMPTY
|
||||
return providerQuery.data ?? EMPTY
|
||||
},
|
||||
get config() {
|
||||
if (configQuery.isLoading) return {}
|
||||
return configQuery.data ?? {}
|
||||
},
|
||||
get reload() {
|
||||
return updateConfigMutation.isPending ? "pending" : undefined
|
||||
},
|
||||
})
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@@ -88,6 +127,22 @@ function createGlobalSync() {
|
||||
return (setGlobalStore as (...args: unknown[]) => unknown)(...input)
|
||||
}) as typeof setGlobalStore
|
||||
|
||||
const bootstrap = useQuery(() => ({
|
||||
queryKey: ["bootstrap"],
|
||||
queryFn: async () => {
|
||||
await bootstrapGlobal({
|
||||
globalSDK: globalSDK.client,
|
||||
requestFailedTitle: language.t("common.requestFailed"),
|
||||
translate: language.t,
|
||||
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
|
||||
setGlobalStore: setBootStore,
|
||||
queryClient,
|
||||
})
|
||||
bootedAt = Date.now()
|
||||
return bootedAt
|
||||
},
|
||||
}))
|
||||
|
||||
const set = ((...input: unknown[]) => {
|
||||
if (input[0] === "project" && (Array.isArray(input[1]) || typeof input[1] === "function")) {
|
||||
setProjects(input[1] as Project[] | ((draft: Project[]) => Project[]))
|
||||
@@ -114,10 +169,21 @@ function createGlobalSync() {
|
||||
|
||||
const queue = createRefreshQueue({
|
||||
paused,
|
||||
bootstrap,
|
||||
bootstrap: () => queryClient.fetchQuery({ queryKey: ["bootstrap"] }),
|
||||
bootstrapInstance,
|
||||
})
|
||||
|
||||
const sdkFor = (directory: string) => {
|
||||
const cached = sdkCache.get(directory)
|
||||
if (cached) return cached
|
||||
const sdk = globalSDK.createClient({
|
||||
directory,
|
||||
throwOnError: true,
|
||||
})
|
||||
sdkCache.set(directory, sdk)
|
||||
return sdk
|
||||
}
|
||||
|
||||
const children = createChildStoreManager({
|
||||
owner,
|
||||
isBooting: (directory) => booting.has(directory),
|
||||
@@ -133,19 +199,9 @@ function createGlobalSync() {
|
||||
clearSessionPrefetchDirectory(directory)
|
||||
},
|
||||
translate: language.t,
|
||||
getSdk: sdkFor,
|
||||
})
|
||||
|
||||
const sdkFor = (directory: string) => {
|
||||
const cached = sdkCache.get(directory)
|
||||
if (cached) return cached
|
||||
const sdk = globalSDK.createClient({
|
||||
directory,
|
||||
throwOnError: true,
|
||||
})
|
||||
sdkCache.set(directory, sdk)
|
||||
return sdk
|
||||
}
|
||||
|
||||
async function loadSessions(directory: string) {
|
||||
const pending = sessionLoads.get(directory)
|
||||
if (pending) return pending
|
||||
@@ -264,26 +320,13 @@ function createGlobalSync() {
|
||||
const event = e.details
|
||||
const recent = bootingRoot || Date.now() - bootedAt < 1500
|
||||
|
||||
if (event.type === "session.error") {
|
||||
const error = event.properties.error
|
||||
if (error?.name !== "MessageAbortedError") {
|
||||
console.error("[global-sync] session error", {
|
||||
scope: directory === "global" ? "global" : "workspace",
|
||||
directory: directory === "global" ? undefined : directory,
|
||||
project: directory === "global" ? undefined : getFilename(directory),
|
||||
sessionID: event.properties.sessionID,
|
||||
error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (directory === "global") {
|
||||
applyGlobalEvent({
|
||||
event,
|
||||
project: globalStore.project,
|
||||
refresh: () => {
|
||||
if (recent) return
|
||||
queue.refresh()
|
||||
bootstrap.refetch()
|
||||
},
|
||||
setGlobalProject: setProjects,
|
||||
})
|
||||
@@ -309,12 +352,7 @@ function createGlobalSync() {
|
||||
setSessionTodo,
|
||||
vcsCache: children.vcsCache.get(directory),
|
||||
loadLsp: () => {
|
||||
void sdkFor(directory)
|
||||
.lsp.status()
|
||||
.then((x) => {
|
||||
setStore("lsp", x.data ?? [])
|
||||
setStore("lsp_ready", true)
|
||||
})
|
||||
void queryClient.fetchQuery(loadLspQuery(directory, sdkFor(directory)))
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -329,23 +367,6 @@ function createGlobalSync() {
|
||||
}
|
||||
})
|
||||
|
||||
async function bootstrap() {
|
||||
bootingRoot = true
|
||||
try {
|
||||
await bootstrapGlobal({
|
||||
globalSDK: globalSDK.client,
|
||||
requestFailedTitle: language.t("common.requestFailed"),
|
||||
translate: language.t,
|
||||
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
|
||||
setGlobalStore: setBootStore,
|
||||
queryClient,
|
||||
})
|
||||
bootedAt = Date.now()
|
||||
} finally {
|
||||
bootingRoot = false
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (typeof requestAnimationFrame === "function") {
|
||||
eventFrame = requestAnimationFrame(() => {
|
||||
@@ -361,7 +382,6 @@ function createGlobalSync() {
|
||||
void globalSDK.event.start()
|
||||
}, 0)
|
||||
}
|
||||
void bootstrap()
|
||||
})
|
||||
|
||||
const projectApi = {
|
||||
@@ -374,21 +394,10 @@ function createGlobalSync() {
|
||||
},
|
||||
}
|
||||
|
||||
const updateConfig = async (config: Config) => {
|
||||
setGlobalStore("reload", "pending")
|
||||
return globalSDK.client.global.config
|
||||
.update({ config })
|
||||
.then(bootstrap)
|
||||
.then(() => {
|
||||
queue.refresh()
|
||||
setGlobalStore("reload", undefined)
|
||||
queue.refresh()
|
||||
})
|
||||
.catch((error) => {
|
||||
setGlobalStore("reload", undefined)
|
||||
throw error
|
||||
})
|
||||
}
|
||||
const updateConfigMutation = useMutation(() => ({
|
||||
mutationFn: (config: Config) => globalSDK.client.global.config.update({ config }),
|
||||
onSuccess: () => bootstrap.refetch(),
|
||||
}))
|
||||
|
||||
return {
|
||||
data: globalStore,
|
||||
@@ -401,8 +410,8 @@ function createGlobalSync() {
|
||||
},
|
||||
child: children.child,
|
||||
peek: children.peek,
|
||||
bootstrap,
|
||||
updateConfig,
|
||||
// bootstrap,
|
||||
updateConfig: updateConfigMutation.mutateAsync,
|
||||
project: projectApi,
|
||||
todo: {
|
||||
set: setSessionTodo,
|
||||
|
||||
@@ -19,6 +19,7 @@ import type { State, VcsCache } from "./types"
|
||||
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
|
||||
import { formatServerError } from "@/utils/server-errors"
|
||||
import { QueryClient, queryOptions, skipToken } from "@tanstack/solid-query"
|
||||
import { loadMcpQuery } from "../global-sync"
|
||||
|
||||
type GlobalStore = {
|
||||
ready: boolean
|
||||
@@ -66,6 +67,62 @@ function runAll(list: Array<() => Promise<unknown>>) {
|
||||
return Promise.allSettled(list.map((item) => item()))
|
||||
}
|
||||
|
||||
function showErrors(input: {
|
||||
errors: unknown[]
|
||||
title: string
|
||||
translate: (key: string, vars?: Record<string, string | number>) => string
|
||||
formatMoreCount: (count: number) => string
|
||||
}) {
|
||||
if (input.errors.length === 0) return
|
||||
const message = formatServerError(input.errors[0], input.translate)
|
||||
const more = input.errors.length > 1 ? input.formatMoreCount(input.errors.length - 1) : ""
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: input.title,
|
||||
description: message + more,
|
||||
})
|
||||
}
|
||||
|
||||
export const loadGlobalConfigQuery = (
|
||||
sdk?: OpencodeClient,
|
||||
transform?: (x: Awaited<ReturnType<OpencodeClient["global"]["config"]["get"]>>) => void,
|
||||
) =>
|
||||
queryOptions({
|
||||
queryKey: ["config"],
|
||||
queryFn: sdk
|
||||
? () =>
|
||||
retry(() =>
|
||||
sdk.global.config.get().then((x) => {
|
||||
transform?.(x)
|
||||
return x.data!
|
||||
}),
|
||||
)
|
||||
: skipToken,
|
||||
})
|
||||
|
||||
export const loadProjectsQuery = (
|
||||
sdk?: OpencodeClient,
|
||||
transform?: (x: Awaited<ReturnType<OpencodeClient["project"]["list"]>>["data"]) => void,
|
||||
) =>
|
||||
queryOptions({
|
||||
queryKey: ["project"],
|
||||
queryFn: sdk
|
||||
? () =>
|
||||
retry(() =>
|
||||
sdk.project
|
||||
.list()
|
||||
.then((x) => {
|
||||
return (x.data ?? [])
|
||||
.filter((p) => !!p?.id)
|
||||
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
|
||||
.slice()
|
||||
.sort((a, b) => cmp(a.id, b.id))
|
||||
})
|
||||
.then(transform),
|
||||
)
|
||||
: skipToken,
|
||||
})
|
||||
|
||||
export async function bootstrapGlobal(input: {
|
||||
globalSDK: OpencodeClient
|
||||
requestFailedTitle: string
|
||||
@@ -74,53 +131,15 @@ export async function bootstrapGlobal(input: {
|
||||
setGlobalStore: SetStoreFunction<GlobalStore>
|
||||
queryClient: QueryClient
|
||||
}) {
|
||||
const fast = [
|
||||
() =>
|
||||
retry(() =>
|
||||
input.globalSDK.global.config.get().then((x) => {
|
||||
input.setGlobalStore("config", reconcile(x.data!, { merge: false }))
|
||||
}),
|
||||
),
|
||||
]
|
||||
|
||||
const slow = [
|
||||
() => input.queryClient.fetchQuery(loadGlobalConfigQuery(input.globalSDK)),
|
||||
() => input.queryClient.fetchQuery(loadProvidersQuery(null, input.globalSDK)),
|
||||
() => input.queryClient.fetchQuery(loadPathQuery(null, input.globalSDK)),
|
||||
() =>
|
||||
input.queryClient.fetchQuery({
|
||||
...loadProvidersQuery(null),
|
||||
queryFn: () =>
|
||||
retry(() =>
|
||||
input.globalSDK.provider.list().then((x) => {
|
||||
input.setGlobalStore("provider", normalizeProviderList(x.data!))
|
||||
return null
|
||||
}),
|
||||
),
|
||||
}),
|
||||
() =>
|
||||
retry(() =>
|
||||
input.globalSDK.path.get().then((x) => {
|
||||
input.setGlobalStore("path", x.data!)
|
||||
}),
|
||||
),
|
||||
() =>
|
||||
retry(() =>
|
||||
input.globalSDK.project.list().then((x) => {
|
||||
const projects = (x.data ?? [])
|
||||
.filter((p) => !!p?.id)
|
||||
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
|
||||
.slice()
|
||||
.sort((a, b) => cmp(a.id, b.id))
|
||||
input.setGlobalStore("project", projects)
|
||||
}),
|
||||
input.queryClient.fetchQuery(
|
||||
loadProjectsQuery(input.globalSDK, (data) => input.setGlobalStore("project", data ?? [])),
|
||||
),
|
||||
]
|
||||
await runAll(fast)
|
||||
// showErrors({
|
||||
// errors: errors(await runAll(fast)),
|
||||
// title: input.requestFailedTitle,
|
||||
// translate: input.translate,
|
||||
// formatMoreCount: input.formatMoreCount,
|
||||
// })
|
||||
await waitForPaint()
|
||||
await runAll(slow)
|
||||
// showErrors({
|
||||
// errors: errors(),
|
||||
@@ -128,7 +147,6 @@ export async function bootstrapGlobal(input: {
|
||||
// translate: input.translate,
|
||||
// formatMoreCount: input.formatMoreCount,
|
||||
// })
|
||||
input.setGlobalStore("ready", true)
|
||||
}
|
||||
|
||||
function groupBySession<T extends { id: string; sessionID: string }>(input: T[]) {
|
||||
@@ -179,26 +197,28 @@ function warmSessions(input: {
|
||||
).then(() => undefined)
|
||||
}
|
||||
|
||||
export const loadProvidersQuery = (directory: string | null) =>
|
||||
queryOptions<null>({ queryKey: [directory, "providers"], queryFn: skipToken })
|
||||
export const loadProvidersQuery = (directory: string | null, sdk?: OpencodeClient) =>
|
||||
queryOptions({
|
||||
queryKey: [directory, "providers"],
|
||||
queryFn: sdk ? () => retry(() => sdk.provider.list().then((x) => normalizeProviderList(x.data!))) : skipToken,
|
||||
})
|
||||
|
||||
export const loadAgentsQuery = (
|
||||
directory: string | null,
|
||||
sdk?: OpencodeClient,
|
||||
transform?: (x: Awaited<ReturnType<OpencodeClient["app"]["agents"]>>) => void,
|
||||
) =>
|
||||
queryOptions<null>({
|
||||
queryOptions({
|
||||
queryKey: [directory, "agents"],
|
||||
queryFn:
|
||||
sdk && transform
|
||||
? () =>
|
||||
retry(() =>
|
||||
sdk.app
|
||||
.agents()
|
||||
.then(transform)
|
||||
.then(() => null),
|
||||
)
|
||||
: skipToken,
|
||||
queryFn: sdk
|
||||
? () =>
|
||||
retry(() =>
|
||||
sdk.app.agents().then((x) => {
|
||||
transform?.(x)
|
||||
return x.data!
|
||||
}),
|
||||
)
|
||||
: skipToken,
|
||||
})
|
||||
|
||||
export const loadPathQuery = (
|
||||
@@ -208,16 +228,15 @@ export const loadPathQuery = (
|
||||
) =>
|
||||
queryOptions<Path>({
|
||||
queryKey: [directory, "path"],
|
||||
queryFn:
|
||||
sdk && transform
|
||||
? () =>
|
||||
retry(() =>
|
||||
sdk.path.get().then(async (x) => {
|
||||
transform(x)
|
||||
return x.data!
|
||||
}),
|
||||
)
|
||||
: skipToken,
|
||||
queryFn: sdk
|
||||
? () =>
|
||||
retry(() =>
|
||||
sdk.path.get().then(async (x) => {
|
||||
transform?.(x)
|
||||
return x.data!
|
||||
}),
|
||||
)
|
||||
: skipToken,
|
||||
})
|
||||
|
||||
export async function bootstrapDirectory(input: {
|
||||
@@ -247,13 +266,6 @@ export async function bootstrapDirectory(input: {
|
||||
if (Object.keys(input.store.config).length === 0 && Object.keys(input.global.config).length > 0) {
|
||||
input.setStore("config", reconcile(input.global.config, { merge: false }))
|
||||
}
|
||||
if (loading || input.store.provider.all.length === 0) {
|
||||
input.setStore("provider_ready", false)
|
||||
}
|
||||
input.setStore("mcp_ready", false)
|
||||
input.setStore("mcp", {})
|
||||
input.setStore("lsp_ready", false)
|
||||
input.setStore("lsp", [])
|
||||
if (loading) input.setStore("status", "partial")
|
||||
|
||||
const rev = (providerRev.get(input.directory) ?? 0) + 1
|
||||
@@ -340,33 +352,15 @@ export async function bootstrapDirectory(input: {
|
||||
}),
|
||||
),
|
||||
() => Promise.resolve(input.loadSessions(input.directory)),
|
||||
() => input.queryClient.fetchQuery(loadMcpQuery(input.directory, input.sdk)),
|
||||
() =>
|
||||
retry(() =>
|
||||
input.sdk.mcp.status().then((x) => {
|
||||
input.setStore("mcp", x.data!)
|
||||
input.setStore("mcp_ready", true)
|
||||
}),
|
||||
),
|
||||
() =>
|
||||
input.queryClient.ensureQueryData({
|
||||
...loadProvidersQuery(input.directory),
|
||||
queryFn: () =>
|
||||
retry(() => input.sdk.provider.list())
|
||||
.then((x) => {
|
||||
if (providerRev.get(input.directory) !== rev) return
|
||||
input.setStore("provider", normalizeProviderList(x.data!))
|
||||
input.setStore("provider_ready", true)
|
||||
})
|
||||
.catch((err) => {
|
||||
if (providerRev.get(input.directory) !== rev) console.error("Failed to refresh provider list", err)
|
||||
const project = getFilename(input.directory)
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: input.translate("toast.project.reloadFailed.title", { project }),
|
||||
description: formatServerError(err, input.translate),
|
||||
})
|
||||
})
|
||||
.then(() => null),
|
||||
input.queryClient.fetchQuery(loadProvidersQuery(input.directory, input.sdk)).catch((err) => {
|
||||
const project = getFilename(input.directory)
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: input.translate("toast.project.reloadFailed.title", { project }),
|
||||
description: formatServerError(err, input.translate),
|
||||
})
|
||||
}),
|
||||
].filter(Boolean) as (() => Promise<any>)[]
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ describe("createChildStoreManager", () => {
|
||||
onBootstrap() {},
|
||||
onDispose() {},
|
||||
translate: (key) => key,
|
||||
getSdk: () => null!,
|
||||
})
|
||||
|
||||
Array.from({ length: 30 }, (_, index) => `/pinned-${index}`).forEach((directory) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createRoot, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js"
|
||||
import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import type { VcsInfo } from "@opencode-ai/sdk/v2/client"
|
||||
import type { OpencodeClient, VcsInfo } from "@opencode-ai/sdk/v2/client"
|
||||
import {
|
||||
DIR_IDLE_TTL_MS,
|
||||
MAX_DIR_STORES,
|
||||
@@ -14,8 +14,9 @@ import {
|
||||
type VcsCache,
|
||||
} from "./types"
|
||||
import { canDisposeDirectory, pickDirectoriesToEvict } from "./eviction"
|
||||
import { useQuery } from "@tanstack/solid-query"
|
||||
import { loadPathQuery } from "./bootstrap"
|
||||
import { useQueries } from "@tanstack/solid-query"
|
||||
import { loadPathQuery, loadProvidersQuery } from "./bootstrap"
|
||||
import { loadLspQuery, loadMcpQuery } from "../global-sync"
|
||||
|
||||
export function createChildStoreManager(input: {
|
||||
owner: Owner
|
||||
@@ -24,6 +25,7 @@ export function createChildStoreManager(input: {
|
||||
onBootstrap: (directory: string) => void
|
||||
onDispose: (directory: string) => void
|
||||
translate: (key: string, vars?: Record<string, string | number>) => string
|
||||
getSdk: (directory: string) => OpencodeClient
|
||||
}) {
|
||||
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
|
||||
const vcsCache = new Map<string, VcsCache>()
|
||||
@@ -156,14 +158,27 @@ export function createChildStoreManager(input: {
|
||||
|
||||
const init = () =>
|
||||
createRoot((dispose) => {
|
||||
const sdk = input.getSdk(directory)
|
||||
|
||||
const initialMeta = meta[0].value
|
||||
const initialIcon = icon[0].value
|
||||
|
||||
const pathQuery = useQuery(() => loadPathQuery(directory))
|
||||
const [pathQuery, mcpQuery, lspQuery, providerQuery] = useQueries(() => ({
|
||||
queries: [
|
||||
loadPathQuery(directory, sdk),
|
||||
loadMcpQuery(directory, sdk),
|
||||
loadLspQuery(directory, sdk),
|
||||
loadProvidersQuery(directory, sdk),
|
||||
],
|
||||
}))
|
||||
|
||||
const child = createStore<State>({
|
||||
project: "",
|
||||
projectMeta: undefined,
|
||||
projectMeta: initialMeta,
|
||||
icon: initialIcon,
|
||||
provider_ready: false,
|
||||
get provider_ready() {
|
||||
return providerQuery.isLoading
|
||||
},
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
config: {},
|
||||
get path() {
|
||||
@@ -181,10 +196,18 @@ export function createChildStoreManager(input: {
|
||||
todo: {},
|
||||
permission: {},
|
||||
question: {},
|
||||
mcp_ready: false,
|
||||
mcp: {},
|
||||
lsp_ready: false,
|
||||
lsp: [],
|
||||
get mcp_ready() {
|
||||
return mcpQuery.isLoading
|
||||
},
|
||||
get mcp() {
|
||||
return mcpQuery.isLoading ? {} : (mcpQuery.data ?? {})
|
||||
},
|
||||
get lsp_ready() {
|
||||
return lspQuery.isLoading
|
||||
},
|
||||
get lsp() {
|
||||
return lspQuery.isLoading ? [] : (lspQuery.data ?? [])
|
||||
},
|
||||
vcs: vcsStore.value,
|
||||
limit: 5,
|
||||
message: {},
|
||||
@@ -207,6 +230,11 @@ export function createChildStoreManager(input: {
|
||||
child[1]("vcs", (value) => value ?? cached)
|
||||
})
|
||||
|
||||
onPersistedInit(meta[2], () => {
|
||||
if (child[0].projectMeta !== initialMeta) return
|
||||
child[1]("projectMeta", meta[0].value)
|
||||
})
|
||||
|
||||
onPersistedInit(icon[2], () => {
|
||||
if (child[0].icon !== initialIcon) return
|
||||
child[1]("icon", icon[0].value)
|
||||
|
||||
@@ -391,7 +391,14 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
? globalSync.data.project.find((x) => x.id === projectID)
|
||||
: globalSync.data.project.find((x) => x.worktree === project.worktree)
|
||||
|
||||
return { ...metadata, ...project }
|
||||
// Preserve local icon override from per-workspace localStorage cache (childStore.icon).
|
||||
// Without this, different subdirectories of the same git repo would share the same
|
||||
// icon from the database instead of using their individual overrides.
|
||||
const base = { ...metadata, ...project }
|
||||
if (childStore.icon) {
|
||||
return { ...base, icon: { ...base.icon, override: childStore.icon } }
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
const roots = createMemo(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -204,6 +204,14 @@ export function IconGemini(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function IconDeepSeek(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.249-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 0 1-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 0 0-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 0 1-.465.137 9.597 9.597 0 0 0-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 0 0 1.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 0 1 1.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 0 1 .415-.287.302.302 0 0 1 .2.288.306.306 0 0 1-.31.307.303.303 0 0 1-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 0 1-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 0 1 .016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 0 1-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconMiMo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
|
||||
@@ -9,8 +9,8 @@ export const config = {
|
||||
github: {
|
||||
repoUrl: "https://github.com/anomalyco/opencode",
|
||||
starsFormatted: {
|
||||
compact: "140K",
|
||||
full: "140,000",
|
||||
compact: "150K",
|
||||
full: "150,000",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Footer } from "~/component/footer"
|
||||
import { Header } from "~/component/header"
|
||||
import { config } from "~/config"
|
||||
import { getLastSeenWorkspaceID } from "../workspace/common"
|
||||
import { IconMiniMax, IconMiMo, IconZai, IconAlibaba } from "~/component/icon"
|
||||
import { IconMiniMax, IconMiMo, IconZai, IconAlibaba, IconDeepSeek } from "~/component/icon"
|
||||
import { useI18n } from "~/context/i18n"
|
||||
import { useLanguage } from "~/context/language"
|
||||
import { LocaleLinks } from "~/component/locale-links"
|
||||
@@ -65,11 +65,11 @@ function LimitsGraph(props: { href: string }) {
|
||||
{ id: "glm-5.1", name: "GLM-5.1", req: 880, d: "100ms" },
|
||||
{ id: "kimi-k2.6", name: "Kimi K2.6 (3x usage)", req: 3450, baseReq: 1150, d: "150ms" },
|
||||
{ id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", req: 1290, d: "150ms" },
|
||||
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", req: 1300, d: "200ms" },
|
||||
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", req: 3300, d: "280ms" },
|
||||
{ id: "minimax-m2.7", name: "MiniMax M2.7", req: 3400, d: "300ms" },
|
||||
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", req: 7450, d: "340ms" },
|
||||
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", req: 3450, d: "200ms" },
|
||||
{ id: "qwen3.5-plus", name: "Qwen3.5 Plus", req: 10200, d: "360ms" },
|
||||
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", req: 31650, d: "340ms" },
|
||||
]
|
||||
|
||||
const w = 720
|
||||
@@ -340,6 +340,9 @@ export default function Home() {
|
||||
<div>
|
||||
<IconAlibaba width="24" height="24" />
|
||||
</div>
|
||||
<div>
|
||||
<IconDeepSeek width="24" height="24" />
|
||||
</div>
|
||||
<div>
|
||||
<IconMiMo width="24" height="24" />
|
||||
</div>
|
||||
|
||||
@@ -160,8 +160,16 @@ export async function POST(input: APIEvent) {
|
||||
userID: userID,
|
||||
})
|
||||
|
||||
if (userEmail && coupon === LiteData.firstMonth100Coupon) {
|
||||
await Billing.redeemCoupon(userEmail, "GOFREEMONTH")
|
||||
if (userEmail) {
|
||||
if (coupon === LiteData.firstMonth100Coupon) {
|
||||
await Billing.redeemCoupon(userEmail, "GOFREEMONTH")
|
||||
} else if (coupon === LiteData.threeMonths100Coupon) {
|
||||
await Billing.redeemCoupon(userEmail, "GO3MONTHS100")
|
||||
} else if (coupon === LiteData.sixMonths100Coupon) {
|
||||
await Billing.redeemCoupon(userEmail, "GO6MONTHS100")
|
||||
} else if (coupon === LiteData.twelveMonths100Coupon) {
|
||||
await Billing.redeemCoupon(userEmail, "GO12MONTHS100")
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,15 +24,23 @@ import { useI18n } from "~/context/i18n"
|
||||
|
||||
Chart.register(BarController, BarElement, CategoryScale, LinearScale, Tooltip, Legend)
|
||||
|
||||
async function getCosts(workspaceID: string, year: number, month: number) {
|
||||
async function getCosts(workspaceID: string, year: number, month: number, tzOffset: string) {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
const startDate = new Date(year, month, 1)
|
||||
const endDate = new Date(year, month + 1, 1)
|
||||
const timezoneOffset = (() => {
|
||||
const m = /^([+-])(\d{2}):(\d{2})$/.exec(tzOffset)
|
||||
if (!m) return 0
|
||||
const sign = m[1] === "-" ? -1 : 1
|
||||
return sign * (Number(m[2]) * 60 + Number(m[3])) * 60_000
|
||||
})()
|
||||
|
||||
const monthStartUTC = new Date(Date.UTC(year, month, 1, 0, 0, 0) - timezoneOffset)
|
||||
const monthEndUTC = new Date(Date.UTC(year, month + 1, 1, 0, 0, 0) - timezoneOffset)
|
||||
const dateExpr = sql<string>`DATE(CONVERT_TZ(${UsageTable.timeCreated}, '+00:00', ${tzOffset}))`
|
||||
const usageData = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
date: sql<string>`DATE(${UsageTable.timeCreated})`,
|
||||
date: dateExpr,
|
||||
model: UsageTable.model,
|
||||
totalCost: sum(UsageTable.cost),
|
||||
keyId: UsageTable.keyID,
|
||||
@@ -42,16 +50,11 @@ async function getCosts(workspaceID: string, year: number, month: number) {
|
||||
.where(
|
||||
and(
|
||||
eq(UsageTable.workspaceID, workspaceID),
|
||||
gte(UsageTable.timeCreated, startDate),
|
||||
lt(UsageTable.timeCreated, endDate),
|
||||
gte(UsageTable.timeCreated, monthStartUTC),
|
||||
lt(UsageTable.timeCreated, monthEndUTC),
|
||||
),
|
||||
)
|
||||
.groupBy(
|
||||
sql`DATE(${UsageTable.timeCreated})`,
|
||||
UsageTable.model,
|
||||
UsageTable.keyID,
|
||||
sql`JSON_EXTRACT(${UsageTable.enrichment}, '$.plan')`,
|
||||
)
|
||||
.groupBy(dateExpr, UsageTable.model, UsageTable.keyID, sql`JSON_EXTRACT(${UsageTable.enrichment}, '$.plan')`)
|
||||
.then((x) =>
|
||||
x.map((r) => ({
|
||||
...r,
|
||||
@@ -125,15 +128,45 @@ function getModelColor(model: string): string {
|
||||
}
|
||||
|
||||
function formatDateLabel(dateStr: string): string {
|
||||
const date = new Date()
|
||||
const [y, m, d] = dateStr.split("-").map(Number)
|
||||
date.setFullYear(y)
|
||||
date.setMonth(m - 1)
|
||||
date.setDate(d)
|
||||
date.setHours(0, 0, 0, 0)
|
||||
const month = date.toLocaleDateString(undefined, { month: "short" })
|
||||
const day = date.getUTCDate().toString().padStart(2, "0")
|
||||
return `${month} ${day}`
|
||||
const [, m, d] = dateStr.split("-").map(Number)
|
||||
const month = new Date(2000, m - 1, 1).toLocaleDateString(undefined, { month: "short" })
|
||||
return `${month} ${d.toString().padStart(2, "0")}`
|
||||
}
|
||||
|
||||
// Compute the UTC offset (in MySQL CONVERT_TZ format like "+05:30") for the
|
||||
// given IANA timezone at the given instant. Honors DST.
|
||||
function getTimezoneOffset(timezone: string, at: Date): string {
|
||||
const parts = new Intl.DateTimeFormat("en-US", {
|
||||
timeZone: timezone,
|
||||
hourCycle: "h23",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
})
|
||||
.formatToParts(at)
|
||||
.reduce<Record<string, string>>((acc, p) => {
|
||||
if (p.type !== "literal") acc[p.type] = p.value
|
||||
return acc
|
||||
}, {})
|
||||
const asUTC = Date.UTC(
|
||||
Number(parts.year),
|
||||
Number(parts.month) - 1,
|
||||
Number(parts.day),
|
||||
Number(parts.hour),
|
||||
Number(parts.minute),
|
||||
Number(parts.second),
|
||||
)
|
||||
const diffMinutes = Math.round((asUTC - at.getTime()) / 60_000)
|
||||
const sign = diffMinutes < 0 ? "-" : "+"
|
||||
const abs = Math.abs(diffMinutes)
|
||||
const hh = Math.floor(abs / 60)
|
||||
.toString()
|
||||
.padStart(2, "0")
|
||||
const mm = (abs % 60).toString().padStart(2, "0")
|
||||
return `${sign}${hh}:${mm}`
|
||||
}
|
||||
|
||||
function addOpacityToColor(color: string, opacity: number): string {
|
||||
@@ -152,6 +185,7 @@ export function GraphSection() {
|
||||
let chartInstance: Chart | undefined
|
||||
const params = useParams()
|
||||
const i18n = useI18n()
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
const now = new Date()
|
||||
const [store, setStore] = createStore({
|
||||
data: null as Awaited<ReturnType<typeof getCosts>> | null,
|
||||
@@ -185,10 +219,13 @@ export function GraphSection() {
|
||||
})
|
||||
|
||||
const getDates = createMemo(() => {
|
||||
const daysInMonth = new Date(store.year, store.month + 1, 0).getDate()
|
||||
// Number of days in the month is independent of timezone.
|
||||
const daysInMonth = new Date(Date.UTC(store.year, store.month + 1, 0)).getUTCDate()
|
||||
const yyyy = store.year.toString().padStart(4, "0")
|
||||
const mm = (store.month + 1).toString().padStart(2, "0")
|
||||
return Array.from({ length: daysInMonth }, (_, i) => {
|
||||
const date = new Date(store.year, store.month, i + 1)
|
||||
return date.toISOString().split("T")[0]
|
||||
const dd = (i + 1).toString().padStart(2, "0")
|
||||
return `${yyyy}-${mm}-${dd}`
|
||||
})
|
||||
})
|
||||
|
||||
@@ -415,7 +452,11 @@ export function GraphSection() {
|
||||
})
|
||||
|
||||
createEffect(async () => {
|
||||
const data = await getCosts(params.id!, store.year, store.month)
|
||||
// Compute the offset for mid-month so DST transitions don't bias to the
|
||||
// wrong side.
|
||||
const midMonth = new Date(Date.UTC(store.year, store.month, 15, 12, 0, 0))
|
||||
const tzOffset = getTimezoneOffset(timezone, midMonth)
|
||||
const data = await getCosts(params.id!, store.year, store.month, tzOffset)
|
||||
setStore({ data })
|
||||
})
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { getHandler, optionsHandler } from "../../util/modelsHandler"
|
||||
import { ZenData } from "@opencode-ai/console-core/model.js"
|
||||
import { buildModelsResponse, buildOptionsResponse } from "../../util/modelsHandler"
|
||||
|
||||
export async function OPTIONS(_input: APIEvent) {
|
||||
return optionsHandler()
|
||||
return buildOptionsResponse()
|
||||
}
|
||||
|
||||
export async function GET(input: APIEvent) {
|
||||
return getHandler({ modelList: "lite" })
|
||||
export async function GET(_input: APIEvent) {
|
||||
const models = Object.keys(ZenData.list("lite").models)
|
||||
return buildModelsResponse(models)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { ZenData } from "@opencode-ai/console-core/model.js"
|
||||
|
||||
export async function optionsHandler() {
|
||||
export async function buildOptionsResponse() {
|
||||
return new Response(null, {
|
||||
status: 200,
|
||||
headers: {
|
||||
@@ -11,16 +9,13 @@ export async function optionsHandler() {
|
||||
})
|
||||
}
|
||||
|
||||
export async function getHandler(opts: { modelList: "lite" | "full"; disabledModels?: string[] }) {
|
||||
const zenData = ZenData.list(opts.modelList)
|
||||
|
||||
export async function buildModelsResponse(models: string[]) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
object: "list",
|
||||
data: Object.entries(zenData.models)
|
||||
.filter(([id]) => !opts.disabledModels?.includes(id))
|
||||
.filter(([id]) => !id.startsWith("alpha-"))
|
||||
.map(([id, _model]) => ({
|
||||
data: models
|
||||
.filter((id) => !id.startsWith("alpha-"))
|
||||
.map((id) => ({
|
||||
id,
|
||||
object: "model",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { ZenData } from "@opencode-ai/console-core/model.js"
|
||||
import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js"
|
||||
import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
|
||||
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
|
||||
import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
|
||||
import { optionsHandler, getHandler } from "~/routes/zen/util/modelsHandler"
|
||||
import { buildOptionsResponse, buildModelsResponse } from "~/routes/zen/util/modelsHandler"
|
||||
|
||||
export async function OPTIONS(_input: APIEvent) {
|
||||
return optionsHandler()
|
||||
return buildOptionsResponse()
|
||||
}
|
||||
|
||||
export async function GET(input: APIEvent) {
|
||||
const disabledModels = await (() => {
|
||||
const apiKey = input.request.headers.get("authorization")?.split(" ")[1]
|
||||
if (!apiKey) return []
|
||||
if (!apiKey) return [] as string[]
|
||||
|
||||
return Database.use((tx) =>
|
||||
tx
|
||||
@@ -27,5 +28,7 @@ export async function GET(input: APIEvent) {
|
||||
)
|
||||
})()
|
||||
|
||||
return getHandler({ modelList: "full", disabledModels })
|
||||
const models = Object.keys(ZenData.list("full").models).filter((id) => !disabledModels.includes(id))
|
||||
|
||||
return buildModelsResponse(models)
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `coupon` MODIFY COLUMN `type` enum('BUILDATHON','GOFREEMONTH','GO3MONTHS100','GO6MONTHS100','GO12MONTHS100') NOT NULL;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -176,13 +176,13 @@ export namespace Billing {
|
||||
)
|
||||
}
|
||||
|
||||
export const hasCoupon = async (email: string, type: (typeof CouponType)[number]) => {
|
||||
export const getCoupons = async (email: string) => {
|
||||
return await Database.use((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.select({ type: CouponTable.type, timeRedeemed: CouponTable.timeRedeemed })
|
||||
.from(CouponTable)
|
||||
.where(and(eq(CouponTable.email, email), eq(CouponTable.type, type), isNull(CouponTable.timeRedeemed)))
|
||||
.then((rows) => rows.length > 0),
|
||||
.where(and(eq(CouponTable.email, email), isNull(CouponTable.timeRedeemed)))
|
||||
.then((rows) => rows.map((row) => row.type)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -290,9 +290,16 @@ export namespace Billing {
|
||||
if (billing.subscriptionID) throw new Error("Already subscribed to Black")
|
||||
if (billing.liteSubscriptionID) throw new Error("Already subscribed to Lite")
|
||||
|
||||
const coupon = (await Billing.hasCoupon(email, "GOFREEMONTH"))
|
||||
? LiteData.firstMonth100Coupon
|
||||
: LiteData.firstMonth50Coupon
|
||||
const coupons = await Billing.getCoupons(email)
|
||||
const coupon = coupons.includes("GO12MONTHS100")
|
||||
? LiteData.twelveMonths100Coupon
|
||||
: coupons.includes("GO6MONTHS100")
|
||||
? LiteData.sixMonths100Coupon
|
||||
: coupons.includes("GO3MONTHS100")
|
||||
? LiteData.threeMonths100Coupon
|
||||
: coupons.includes("GOFREEMONTH")
|
||||
? LiteData.firstMonth100Coupon
|
||||
: LiteData.firstMonth50Coupon
|
||||
const createSession = () =>
|
||||
Billing.stripe().checkout.sessions.create({
|
||||
mode: "subscription",
|
||||
|
||||
@@ -13,5 +13,8 @@ export namespace LiteData {
|
||||
export const priceInr = fn(z.void(), () => Resource.ZEN_LITE_PRICE.priceInr)
|
||||
export const firstMonth100Coupon = Resource.ZEN_LITE_PRICE.firstMonth100Coupon
|
||||
export const firstMonth50Coupon = Resource.ZEN_LITE_PRICE.firstMonth50Coupon
|
||||
export const threeMonths100Coupon = Resource.ZEN_LITE_PRICE.threeMonths100Coupon
|
||||
export const sixMonths100Coupon = Resource.ZEN_LITE_PRICE.sixMonths100Coupon
|
||||
export const twelveMonths100Coupon = Resource.ZEN_LITE_PRICE.twelveMonths100Coupon
|
||||
export const planName = fn(z.void(), () => "lite")
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ export const UsageTable = mysqlTable(
|
||||
(table) => [...workspaceIndexes(table), index("usage_time_created").on(table.workspaceID, table.timeCreated)],
|
||||
)
|
||||
|
||||
export const CouponType = ["BUILDATHON", "GOFREEMONTH"] as const
|
||||
export const CouponType = ["BUILDATHON", "GOFREEMONTH", "GO3MONTHS100", "GO6MONTHS100", "GO12MONTHS100"] as const
|
||||
export const CouponTable = mysqlTable(
|
||||
"coupon",
|
||||
{
|
||||
|
||||
Vendored
+3
@@ -148,6 +148,9 @@ declare module "sst" {
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"sixMonths100Coupon": string
|
||||
"threeMonths100Coupon": string
|
||||
"twelveMonths100Coupon": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_MODELS1": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
+3
@@ -148,6 +148,9 @@ declare module "sst" {
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"sixMonths100Coupon": string
|
||||
"threeMonths100Coupon": string
|
||||
"twelveMonths100Coupon": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_MODELS1": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
+3
@@ -148,6 +148,9 @@ declare module "sst" {
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"sixMonths100Coupon": string
|
||||
"threeMonths100Coupon": string
|
||||
"twelveMonths100Coupon": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_MODELS1": {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "bun test",
|
||||
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
|
||||
"typecheck": "tsgo --noEmit"
|
||||
},
|
||||
"bin": {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
export * as NpmConfig from "./npm-config"
|
||||
|
||||
import { fileURLToPath } from "url"
|
||||
// @ts-expect-error npm does not publish types for this internal config API.
|
||||
import Config from "@npmcli/config"
|
||||
// @ts-expect-error npm does not publish types for this internal config API.
|
||||
import { definitions, flatten, nerfDarts, shorthands } from "@npmcli/config/lib/definitions/index.js"
|
||||
import { Effect } from "effect"
|
||||
|
||||
const npmPath = fileURLToPath(new URL("..", import.meta.url))
|
||||
|
||||
export const load = (dir: string) =>
|
||||
Effect.tryPromise({
|
||||
try: async () => {
|
||||
const config = new Config({
|
||||
npmPath,
|
||||
cwd: dir,
|
||||
env: { ...process.env },
|
||||
argv: [process.execPath, process.execPath],
|
||||
execPath: process.execPath,
|
||||
platform: process.platform,
|
||||
definitions,
|
||||
flatten,
|
||||
nerfDarts,
|
||||
shorthands,
|
||||
warn: false,
|
||||
})
|
||||
await config.load()
|
||||
return config.flat as Record<string, unknown>
|
||||
},
|
||||
catch: (cause) => cause,
|
||||
}).pipe(Effect.orElseSucceed(() => ({}) as Record<string, unknown>))
|
||||
|
||||
export const registry = (dir: string) =>
|
||||
load(dir).pipe(
|
||||
Effect.map((config) => {
|
||||
const registry = typeof config.registry === "string" ? config.registry : "https://registry.npmjs.org"
|
||||
return registry.endsWith("/") ? registry.slice(0, -1) : registry
|
||||
}),
|
||||
)
|
||||
@@ -1,22 +1,14 @@
|
||||
export * as Npm from "./npm"
|
||||
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import npa from "npm-package-arg"
|
||||
import semver from "semver"
|
||||
// @ts-expect-error npm does not publish types for this internal config API.
|
||||
import Config from "@npmcli/config"
|
||||
// @ts-expect-error npm does not publish types for this internal config API.
|
||||
import { definitions, flatten, nerfDarts, shorthands } from "@npmcli/config/lib/definitions/index.js"
|
||||
import { Effect, Schema, Context, Layer, Option, FileSystem, Stream } from "effect"
|
||||
import { Effect, Schema, Context, Layer, Option, FileSystem } from "effect"
|
||||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { AppFileSystem } from "./filesystem"
|
||||
import { Global } from "./global"
|
||||
import { EffectFlock } from "./util/effect-flock"
|
||||
import { makeRuntime } from "./effect/runtime"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
|
||||
import { CrossSpawnSpawner } from "./cross-spawn-spawner"
|
||||
import { NpmConfig } from "./npm-config"
|
||||
|
||||
export class InstallFailedError extends Schema.TaggedErrorClass<InstallFailedError>()("NpmInstallFailedError", {
|
||||
add: Schema.Array(Schema.String).pipe(Schema.optional),
|
||||
@@ -40,46 +32,18 @@ export interface Interface {
|
||||
}[]
|
||||
},
|
||||
) => Effect.Effect<void, EffectFlock.LockError | InstallFailedError>
|
||||
readonly outdated: (pkg: string, cachedVersion: string) => Effect.Effect<boolean>
|
||||
readonly which: (pkg: string, bin?: string) => Effect.Effect<Option.Option<string>>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Npm") {}
|
||||
|
||||
const illegal = process.platform === "win32" ? new Set(["<", ">", ":", '"', "|", "?", "*"]) : undefined
|
||||
const npmPath = fileURLToPath(new URL("..", import.meta.url))
|
||||
|
||||
export function sanitize(pkg: string) {
|
||||
if (!illegal) return pkg
|
||||
return Array.from(pkg, (char) => (illegal.has(char) || char.charCodeAt(0) < 32 ? "_" : char)).join("")
|
||||
}
|
||||
|
||||
const loadOptions = (dir: string) =>
|
||||
Effect.tryPromise({
|
||||
try: async () => {
|
||||
const config = new Config({
|
||||
npmPath,
|
||||
cwd: dir,
|
||||
env: { ...process.env },
|
||||
argv: [process.execPath, process.execPath],
|
||||
execPath: process.execPath,
|
||||
platform: process.platform,
|
||||
definitions,
|
||||
flatten,
|
||||
nerfDarts,
|
||||
shorthands,
|
||||
warn: false,
|
||||
})
|
||||
await config.load()
|
||||
return config.flat
|
||||
},
|
||||
catch: (cause) =>
|
||||
new InstallFailedError({
|
||||
cause,
|
||||
dir,
|
||||
}),
|
||||
})
|
||||
|
||||
const resolveEntryPoint = (name: string, dir: string): EntryPoint => {
|
||||
let entrypoint: Option.Option<string>
|
||||
try {
|
||||
@@ -110,39 +74,13 @@ export const layer = Layer.effect(
|
||||
const global = yield* Global.Service
|
||||
const fs = yield* FileSystem.FileSystem
|
||||
const flock = yield* EffectFlock.Service
|
||||
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
||||
const directory = (pkg: string) => path.join(global.cache, "packages", sanitize(pkg))
|
||||
const runView = Effect.fnUntraced(function* (cmd: string[]) {
|
||||
const handle = yield* spawner.spawn(
|
||||
ChildProcess.make(cmd[0], cmd.slice(1), {
|
||||
extendEnv: true,
|
||||
}),
|
||||
)
|
||||
const [stdout, stderr] = yield* Effect.all(
|
||||
[Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))],
|
||||
{ concurrency: 2 },
|
||||
)
|
||||
const code = yield* handle.exitCode
|
||||
if (code !== 0 || !stdout.trim()) {
|
||||
return yield* Effect.fail(stderr || stdout || `Failed to run ${cmd.join(" ")}`)
|
||||
}
|
||||
return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.String))(stdout)
|
||||
}, Effect.scoped)
|
||||
const viewLatestVersion = Effect.fnUntraced(function* (pkg: string) {
|
||||
return yield* runView(["npm", "view", pkg, "dist-tags.latest", "--json"]).pipe(
|
||||
Effect.catch(() =>
|
||||
runView(["pnpm", "view", pkg, "dist-tags.latest", "--json"]).pipe(
|
||||
Effect.catch(() => runView(["bun", "pm", "view", pkg, "dist-tags.latest", "--json"])),
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
const reify = (input: { dir: string; add?: string[] }) =>
|
||||
Effect.gen(function* () {
|
||||
yield* flock.acquire(`npm-install:${input.dir}`)
|
||||
const { Arborist } = yield* Effect.promise(() => import("@npmcli/arborist"))
|
||||
const add = input.add ?? []
|
||||
const npmOptions = yield* loadOptions(input.dir)
|
||||
const npmOptions = yield* NpmConfig.load(input.dir)
|
||||
const arborist = new Arborist({
|
||||
...npmOptions,
|
||||
path: input.dir,
|
||||
@@ -172,18 +110,6 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
const outdated = Effect.fn("Npm.outdated")(function* (pkg: string, cachedVersion: string) {
|
||||
const latestVersion = yield* viewLatestVersion(pkg).pipe(Effect.option)
|
||||
if (Option.isNone(latestVersion)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const range = /[\s^~*xX<>|=]/.test(cachedVersion)
|
||||
if (range) return !semver.satisfies(latestVersion.value, cachedVersion)
|
||||
|
||||
return semver.lt(cachedVersion, latestVersion.value)
|
||||
})
|
||||
|
||||
const add = Effect.fn("Npm.add")(function* (pkg: string) {
|
||||
const dir = directory(pkg)
|
||||
const name = (() => {
|
||||
@@ -309,7 +235,6 @@ export const layer = Layer.effect(
|
||||
return Service.of({
|
||||
add,
|
||||
install,
|
||||
outdated,
|
||||
which,
|
||||
})
|
||||
}),
|
||||
@@ -320,7 +245,6 @@ export const defaultLayer = layer.pipe(
|
||||
Layer.provide(AppFileSystem.layer),
|
||||
Layer.provide(Global.layer),
|
||||
Layer.provide(NodeFileSystem.layer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
)
|
||||
|
||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||
@@ -337,10 +261,6 @@ export async function add(...args: Parameters<Interface["add"]>) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function outdated(...args: Parameters<Interface["outdated"]>) {
|
||||
return runPromise((svc) => svc.outdated(...args))
|
||||
}
|
||||
|
||||
export async function which(...args: Parameters<Interface["which"]>) {
|
||||
const resolved = await runPromise((svc) => svc.which(...args))
|
||||
return Option.getOrUndefined(resolved)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import fs from "fs/promises"
|
||||
import { tmpdir as osTmpdir } from "os"
|
||||
import path from "path"
|
||||
|
||||
export const tmpdir = async () => {
|
||||
const dir = await fs.mkdtemp(path.join(osTmpdir(), "opencode-core-test-"))
|
||||
return {
|
||||
path: dir,
|
||||
async [Symbol.asyncDispose]() {
|
||||
await fs.rm(dir, { recursive: true, force: true })
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import path from "path"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { NpmConfig } from "@opencode-ai/core/npm-config"
|
||||
import { tmpdir } from "./fixture/tmpdir"
|
||||
|
||||
describe("NpmConfig.load", () => {
|
||||
test("reads registry from project .npmrc", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await Bun.write(path.join(tmp.path, ".npmrc"), "registry=https://registry.example.test/\n")
|
||||
|
||||
const config = await Effect.runPromise(NpmConfig.load(tmp.path))
|
||||
|
||||
expect(config.registry).toBe("https://registry.example.test/")
|
||||
})
|
||||
|
||||
test("reads scoped registries from project .npmrc", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await Bun.write(path.join(tmp.path, ".npmrc"), "@acme:registry=https://npm.acme.test/\n")
|
||||
|
||||
const config = await Effect.runPromise(NpmConfig.load(tmp.path))
|
||||
|
||||
expect(config["@acme:registry"]).toBe("https://npm.acme.test/")
|
||||
})
|
||||
|
||||
test("flattens boolean and list options", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await Bun.write(path.join(tmp.path, ".npmrc"), "ignore-scripts=true\nomit[]=dev\nomit[]=optional\n")
|
||||
|
||||
const config = await Effect.runPromise(NpmConfig.load(tmp.path))
|
||||
|
||||
expect(config.ignoreScripts).toBe(true)
|
||||
expect(config.omit).toEqual(["dev", "optional"])
|
||||
})
|
||||
})
|
||||
|
||||
describe("NpmConfig.registry", () => {
|
||||
test("normalizes configured registry without trailing slash", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await Bun.write(path.join(tmp.path, ".npmrc"), "registry=https://registry.example.test/\n")
|
||||
|
||||
await expect(Effect.runPromise(NpmConfig.registry(tmp.path))).resolves.toBe("https://registry.example.test")
|
||||
})
|
||||
|
||||
test("leaves configured registry without trailing slash unchanged", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await Bun.write(path.join(tmp.path, ".npmrc"), "registry=https://registry.example.test\n")
|
||||
|
||||
await expect(Effect.runPromise(NpmConfig.registry(tmp.path))).resolves.toBe("https://registry.example.test")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,56 @@
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
import { tmpdir } from "./fixture/tmpdir"
|
||||
|
||||
const win = process.platform === "win32"
|
||||
|
||||
const writePackage = (dir: string, pkg: Record<string, unknown>) =>
|
||||
Bun.write(
|
||||
path.join(dir, "package.json"),
|
||||
JSON.stringify({
|
||||
version: "1.0.0",
|
||||
...pkg,
|
||||
}),
|
||||
)
|
||||
|
||||
describe("Npm.sanitize", () => {
|
||||
test("keeps normal scoped package specs unchanged", () => {
|
||||
expect(Npm.sanitize("@opencode/acme")).toBe("@opencode/acme")
|
||||
expect(Npm.sanitize("@opencode/acme@1.0.0")).toBe("@opencode/acme@1.0.0")
|
||||
expect(Npm.sanitize("prettier")).toBe("prettier")
|
||||
})
|
||||
|
||||
test("handles git https specs", () => {
|
||||
const spec = "acme@git+https://github.com/opencode/acme.git"
|
||||
const expected = win ? "acme@git+https_//github.com/opencode/acme.git" : spec
|
||||
expect(Npm.sanitize(spec)).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Npm.install", () => {
|
||||
test("respects omit from project .npmrc", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
|
||||
await writePackage(tmp.path, {
|
||||
name: "fixture",
|
||||
dependencies: {
|
||||
"prod-pkg": "file:./prod-pkg",
|
||||
},
|
||||
devDependencies: {
|
||||
"dev-pkg": "file:./dev-pkg",
|
||||
},
|
||||
})
|
||||
await Bun.write(path.join(tmp.path, ".npmrc"), "omit=dev\n")
|
||||
await fs.mkdir(path.join(tmp.path, "prod-pkg"))
|
||||
await fs.mkdir(path.join(tmp.path, "dev-pkg"))
|
||||
await writePackage(path.join(tmp.path, "prod-pkg"), { name: "prod-pkg" })
|
||||
await writePackage(path.join(tmp.path, "dev-pkg"), { name: "dev-pkg" })
|
||||
|
||||
await Npm.install(tmp.path)
|
||||
|
||||
await expect(fs.stat(path.join(tmp.path, "node_modules", "prod-pkg"))).resolves.toBeDefined()
|
||||
await expect(fs.stat(path.join(tmp.path, "node_modules", "dev-pkg"))).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"private": true,
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
Vendored
+3
@@ -148,6 +148,9 @@ declare module "sst" {
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"sixMonths100Coupon": string
|
||||
"threeMonths100Coupon": string
|
||||
"twelveMonths100Coupon": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_MODELS1": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.14.27"
|
||||
version = "1.14.28"
|
||||
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.14.27/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.28/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.27/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.28/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.27/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.28/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.14.27/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.28/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.14.27/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.28/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
Vendored
+3
@@ -148,6 +148,9 @@ declare module "sst" {
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"sixMonths100Coupon": string
|
||||
"threeMonths100Coupon": string
|
||||
"twelveMonths100Coupon": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_MODELS1": {
|
||||
|
||||
@@ -7,3 +7,4 @@ src/provider/models-snapshot.js
|
||||
src/provider/models-snapshot.d.ts
|
||||
script/build-*.ts
|
||||
temporary-*.md
|
||||
.artifacts
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `session` ADD `path` text;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.14.27",
|
||||
"version": "1.14.28",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { z } from "zod"
|
||||
import { Config } from "../src/config"
|
||||
import { Config } from "@/config/config"
|
||||
import { TuiConfig } from "../src/cli/cmd/tui/config/tui"
|
||||
|
||||
function generate(schema: z.ZodType) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { eq } from "drizzle-orm"
|
||||
import { Effect, Layer, Option, Schema, Context } from "effect"
|
||||
|
||||
import { Database } from "@/storage"
|
||||
import { Database } from "@/storage/db"
|
||||
import { AccountStateTable, AccountTable } from "./account.sql"
|
||||
import { AccessToken, AccountID, AccountRepoError, Info, OrgID, RefreshToken } from "./schema"
|
||||
import { normalizeServerUrl } from "./url"
|
||||
|
||||
@@ -31,19 +31,19 @@ import {
|
||||
type Usage,
|
||||
} from "@agentclientprotocol/sdk"
|
||||
|
||||
import { Log } from "../util"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { pathToFileURL } from "url"
|
||||
import { Filesystem } from "../util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { Hash } from "@opencode-ai/core/util/hash"
|
||||
import { ACPSessionManager } from "./session"
|
||||
import type { ACPConfig } from "./types"
|
||||
import { Provider } from "../provider"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { ModelID, ProviderID } from "../provider/schema"
|
||||
import { Agent as AgentModule } from "../agent/agent"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Installation } from "@/installation"
|
||||
import { MessageV2 } from "@/session/message-v2"
|
||||
import { Config } from "@/config"
|
||||
import { Config } from "@/config/config"
|
||||
import { ConfigMCP } from "@/config/mcp"
|
||||
import { Todo } from "@/session/todo"
|
||||
import { Result, Schema } from "effect"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RequestError, type McpServer } from "@agentclientprotocol/sdk"
|
||||
import type { ACPSessionState } from "./types"
|
||||
import { Log } from "@/util"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import type { OpencodeClient } from "@opencode-ai/sdk/v2"
|
||||
|
||||
const log = Log.create({ service: "acp-session-manager" })
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Config } from "../config"
|
||||
import { Config } from "@/config/config"
|
||||
import z from "zod"
|
||||
import { Provider } from "../provider"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { ModelID, ProviderID } from "../provider/schema"
|
||||
import { generateObject, streamObject, type ModelMessage } from "ai"
|
||||
import { Truncate } from "../tool"
|
||||
import { Truncate } from "@/tool/truncate"
|
||||
import { Auth } from "../auth"
|
||||
import { ProviderTransform } from "../provider"
|
||||
import { ProviderTransform } from "@/provider/transform"
|
||||
|
||||
import PROMPT_GENERATE from "./generate.txt"
|
||||
import PROMPT_COMPACTION from "./prompt/compaction.txt"
|
||||
@@ -19,7 +19,7 @@ import path from "path"
|
||||
import { Plugin } from "@/plugin"
|
||||
import { Skill } from "../skill"
|
||||
import { Effect, Context, Layer, Schema } from "effect"
|
||||
import { InstanceState } from "@/effect"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import * as Option from "effect/Option"
|
||||
import * as OtelTracer from "@effect/opentelemetry/Tracer"
|
||||
import { zod } from "@/util/effect-zod"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Effect, Exit, Layer, PubSub, Scope, Context, Stream, Schema } from "effect"
|
||||
import { EffectBridge } from "@/effect"
|
||||
import { Log } from "../util"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { BusEvent } from "./bus-event"
|
||||
import { GlobalBus } from "./global"
|
||||
import { InstanceState } from "@/effect"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
|
||||
const log = Log.create({ service: "bus" })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Log } from "@/util"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { cmd } from "./cmd"
|
||||
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
|
||||
|
||||
@@ -4,10 +4,10 @@ import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { UI } from "../ui"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Agent } from "../../agent/agent"
|
||||
import { Provider } from "../../provider"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { Filesystem } from "../../util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import matter from "gray-matter"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { EOL } from "os"
|
||||
@@ -15,7 +15,23 @@ import type { Argv } from "yargs"
|
||||
|
||||
type AgentMode = "all" | "primary" | "subagent"
|
||||
|
||||
const AVAILABLE_TOOLS = ["bash", "read", "write", "edit", "glob", "grep", "webfetch", "task", "todowrite"]
|
||||
// Permission keys (not raw tool names). Multiple tools can map to a single
|
||||
// permission — e.g. write/edit/apply_patch all gate on `edit` — so we configure
|
||||
// agents at the permission level to match how the runtime actually enforces it.
|
||||
const AVAILABLE_PERMISSIONS = [
|
||||
"bash",
|
||||
"read",
|
||||
"edit",
|
||||
"glob",
|
||||
"grep",
|
||||
"webfetch",
|
||||
"task",
|
||||
"todowrite",
|
||||
"websearch",
|
||||
"codesearch",
|
||||
"lsp",
|
||||
"skill",
|
||||
]
|
||||
|
||||
const AgentCreateCommand = cmd({
|
||||
command: "create",
|
||||
@@ -35,9 +51,10 @@ const AgentCreateCommand = cmd({
|
||||
describe: "agent mode",
|
||||
choices: ["all", "primary", "subagent"] as const,
|
||||
})
|
||||
.option("tools", {
|
||||
.option("permissions", {
|
||||
type: "string",
|
||||
describe: `comma-separated list of tools to enable (default: all). Available: "${AVAILABLE_TOOLS.join(", ")}"`,
|
||||
alias: ["tools"],
|
||||
describe: `comma-separated list of permissions to allow (default: all). Available: "${AVAILABLE_PERMISSIONS.join(", ")}"`,
|
||||
})
|
||||
.option("model", {
|
||||
type: "string",
|
||||
@@ -51,9 +68,9 @@ const AgentCreateCommand = cmd({
|
||||
const cliPath = args.path
|
||||
const cliDescription = args.description
|
||||
const cliMode = args.mode as AgentMode | undefined
|
||||
const cliTools = args.tools
|
||||
const perms = args.permissions
|
||||
|
||||
const isFullyNonInteractive = cliPath && cliDescription && cliMode && cliTools !== undefined
|
||||
const isFullyNonInteractive = cliPath && cliDescription && cliMode && perms !== undefined
|
||||
|
||||
if (!isFullyNonInteractive) {
|
||||
UI.empty()
|
||||
@@ -120,21 +137,21 @@ const AgentCreateCommand = cmd({
|
||||
})
|
||||
spinner.stop(`Agent ${generated.identifier} generated`)
|
||||
|
||||
// Select tools
|
||||
let selectedTools: string[]
|
||||
if (cliTools !== undefined) {
|
||||
selectedTools = cliTools ? cliTools.split(",").map((t) => t.trim()) : AVAILABLE_TOOLS
|
||||
// Select permissions to allow
|
||||
let selected: string[]
|
||||
if (perms !== undefined) {
|
||||
selected = perms ? perms.split(",").map((t) => t.trim()) : AVAILABLE_PERMISSIONS
|
||||
} else {
|
||||
const result = await prompts.multiselect({
|
||||
message: "Select tools to enable (Space to toggle)",
|
||||
options: AVAILABLE_TOOLS.map((tool) => ({
|
||||
label: tool,
|
||||
value: tool,
|
||||
message: "Select permissions to allow (Space to toggle)",
|
||||
options: AVAILABLE_PERMISSIONS.map((permission) => ({
|
||||
label: permission,
|
||||
value: permission,
|
||||
})),
|
||||
initialValues: AVAILABLE_TOOLS,
|
||||
initialValues: AVAILABLE_PERMISSIONS,
|
||||
})
|
||||
if (prompts.isCancel(result)) throw new UI.CancelledError()
|
||||
selectedTools = result
|
||||
selected = result
|
||||
}
|
||||
|
||||
// Get mode
|
||||
@@ -167,11 +184,11 @@ const AgentCreateCommand = cmd({
|
||||
mode = modeResult
|
||||
}
|
||||
|
||||
// Build tools config
|
||||
const tools: Record<string, boolean> = {}
|
||||
for (const tool of AVAILABLE_TOOLS) {
|
||||
if (!selectedTools.includes(tool)) {
|
||||
tools[tool] = false
|
||||
// Build permissions config — deny anything not explicitly selected.
|
||||
const permissions: Record<string, "deny"> = {}
|
||||
for (const permission of AVAILABLE_PERMISSIONS) {
|
||||
if (!selected.includes(permission)) {
|
||||
permissions[permission] = "deny"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,13 +196,13 @@ const AgentCreateCommand = cmd({
|
||||
const frontmatter: {
|
||||
description: string
|
||||
mode: AgentMode
|
||||
tools?: Record<string, boolean>
|
||||
permission?: Record<string, "deny">
|
||||
} = {
|
||||
description: generated.whenToUse,
|
||||
mode,
|
||||
}
|
||||
if (Object.keys(tools).length > 0) {
|
||||
frontmatter.tools = tools
|
||||
if (Object.keys(permissions).length > 0) {
|
||||
frontmatter.permission = permissions
|
||||
}
|
||||
|
||||
// Write file
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { spawn } from "child_process"
|
||||
import { Database } from "../../storage"
|
||||
import { Database } from "@/storage/db"
|
||||
import { drizzle } from "drizzle-orm/bun-sqlite"
|
||||
import { Database as BunDatabase } from "bun:sqlite"
|
||||
import { UI } from "../ui"
|
||||
import { cmd } from "./cmd"
|
||||
import { JsonMigration } from "../../storage"
|
||||
import { JsonMigration } from "@/storage/json-migration"
|
||||
import { EOL } from "os"
|
||||
import { errorMessage } from "../../util/error"
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@ import { EOL } from "os"
|
||||
import { basename } from "path"
|
||||
import { Effect } from "effect"
|
||||
import { Agent } from "../../../agent/agent"
|
||||
import { Provider } from "../../../provider"
|
||||
import { Session } from "../../../session"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { Session } from "@/session/session"
|
||||
import type { MessageV2 } from "../../../session/message-v2"
|
||||
import { MessageID, PartID } from "../../../session/schema"
|
||||
import { ToolRegistry } from "../../../tool"
|
||||
import { ToolRegistry } from "@/tool/registry"
|
||||
import { Instance } from "../../../project/instance"
|
||||
import { Permission } from "../../../permission"
|
||||
import { iife } from "../../../util/iife"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EOL } from "os"
|
||||
import { Config } from "../../../config"
|
||||
import { Config } from "@/config/config"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { cmd } from "../cmd"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { LSP } from "../../../lsp"
|
||||
import { LSP } from "@/lsp/lsp"
|
||||
import { AppRuntime } from "../../../effect/app-runtime"
|
||||
import { Effect } from "effect"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { cmd } from "../cmd"
|
||||
import { Log } from "../../../util"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { EOL } from "os"
|
||||
|
||||
export const LSPCommand = cmd({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EOL } from "os"
|
||||
import { Project } from "../../../project"
|
||||
import { Log } from "../../../util"
|
||||
import { Project } from "@/project/project"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { cmd } from "../cmd"
|
||||
|
||||
export const ScrapCommand = cmd({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { Session } from "../../session"
|
||||
import { Session } from "@/session/session"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { SessionID } from "../../session/schema"
|
||||
import { cmd } from "./cmd"
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
import { Server } from "../../server/server"
|
||||
import { PublicApi } from "../../server/routes/instance/httpapi/public"
|
||||
import type { CommandModule } from "yargs"
|
||||
import { OpenApi } from "effect/unstable/httpapi"
|
||||
|
||||
type Args = {
|
||||
httpapi: boolean
|
||||
}
|
||||
|
||||
export const GenerateCommand = {
|
||||
command: "generate",
|
||||
handler: async () => {
|
||||
const specs = await Server.openapi()
|
||||
builder: (yargs) =>
|
||||
yargs.option("httpapi", {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
description: "Generate OpenAPI from the experimental Effect HttpApi contract",
|
||||
}),
|
||||
handler: async (args) => {
|
||||
const specs = args.httpapi ? OpenApi.fromApi(PublicApi) : await Server.openapi()
|
||||
for (const item of Object.values(specs.paths)) {
|
||||
for (const method of ["get", "post", "put", "delete", "patch"] as const) {
|
||||
const operation = item[method]
|
||||
if (!operation?.operationId) continue
|
||||
// @ts-expect-error
|
||||
operation["x-codeSamples"] = [
|
||||
{
|
||||
lang: "js",
|
||||
@@ -47,4 +58,4 @@ export const GenerateCommand = {
|
||||
})
|
||||
})
|
||||
},
|
||||
} satisfies CommandModule
|
||||
} satisfies CommandModule<object, Args>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "path"
|
||||
import { exec } from "child_process"
|
||||
import { Filesystem } from "../../util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import { map, pipe, sortBy, values } from "remeda"
|
||||
import { Octokit } from "@octokit/rest"
|
||||
@@ -18,21 +18,21 @@ import type {
|
||||
} from "@octokit/webhooks-types"
|
||||
import { UI } from "../ui"
|
||||
import { cmd } from "./cmd"
|
||||
import { ModelsDev } from "../../provider"
|
||||
import { ModelsDev } from "@/provider/models"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { SessionShare } from "@/share"
|
||||
import { Session } from "../../session"
|
||||
import { SessionShare } from "@/share/session"
|
||||
import { Session } from "@/session/session"
|
||||
import type { SessionID } from "../../session/schema"
|
||||
import { MessageID, PartID } from "../../session/schema"
|
||||
import { Provider } from "../../provider"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { Bus } from "../../bus"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { SessionPrompt } from "@/session/prompt"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Git } from "@/git"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
import { Process } from "@/util"
|
||||
import { Process } from "@/util/process"
|
||||
import { Effect } from "effect"
|
||||
|
||||
type GitHubAuthor = {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { Argv } from "yargs"
|
||||
import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2"
|
||||
import { Session } from "../../session"
|
||||
import { Session } from "@/session/session"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { cmd } from "./cmd"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { Database } from "../../storage"
|
||||
import { Database } from "@/storage/db"
|
||||
import { SessionTable, MessageTable, PartTable } from "../../session/session.sql"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { ShareNext } from "../../share"
|
||||
import { ShareNext } from "@/share/share-next"
|
||||
import { EOL } from "os"
|
||||
import { Filesystem } from "../../util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Schema } from "effect"
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { UI } from "../ui"
|
||||
import { MCP } from "../../mcp"
|
||||
import { McpAuth } from "../../mcp/auth"
|
||||
import { McpOAuthProvider } from "../../mcp/oauth-provider"
|
||||
import { Config } from "../../config"
|
||||
import { Config } from "@/config/config"
|
||||
import { ConfigMCP } from "../../config/mcp"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { Installation } from "../../installation"
|
||||
@@ -15,7 +15,7 @@ import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import path from "path"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { modify, applyEdits } from "jsonc-parser"
|
||||
import { Filesystem } from "../../util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { Bus } from "../../bus"
|
||||
import { AppRuntime } from "../../effect/app-runtime"
|
||||
import { Effect } from "effect"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { Provider } from "../../provider"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { ProviderID } from "../../provider/schema"
|
||||
import { ModelsDev } from "../../provider"
|
||||
import { ModelsDev } from "@/provider/models"
|
||||
import { cmd } from "./cmd"
|
||||
import { UI } from "../ui"
|
||||
import { EOL } from "os"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { intro, log, outro, spinner } from "@clack/prompts"
|
||||
import type { Argv } from "yargs"
|
||||
|
||||
import { ConfigPaths } from "../../config"
|
||||
import { ConfigPaths } from "@/config/paths"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { installPlugin, patchPluginConfig, readPluginManifest } from "../../plugin/install"
|
||||
import { resolvePluginTarget } from "../../plugin/shared"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { errorMessage } from "../../util/error"
|
||||
import { Filesystem } from "../../util"
|
||||
import { Process } from "../../util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { Process } from "@/util/process"
|
||||
import { UI } from "../ui"
|
||||
import { cmd } from "./cmd"
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { cmd } from "./cmd"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Git } from "@/git"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { Process } from "@/util"
|
||||
import { Process } from "@/util/process"
|
||||
|
||||
export const PrCommand = cmd({
|
||||
command: "pr <number>",
|
||||
|
||||
@@ -3,16 +3,16 @@ import { AppRuntime } from "../../effect/app-runtime"
|
||||
import { cmd } from "./cmd"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import { UI } from "../ui"
|
||||
import { ModelsDev } from "../../provider"
|
||||
import { ModelsDev } from "@/provider/models"
|
||||
import { map, pipe, sortBy, values } from "remeda"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import { Config } from "../../config"
|
||||
import { Config } from "@/config/config"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Plugin } from "../../plugin"
|
||||
import { Instance } from "../../project/instance"
|
||||
import type { Hooks } from "@opencode-ai/plugin"
|
||||
import { Process } from "../../util"
|
||||
import { Process } from "@/util/process"
|
||||
import { text } from "node:stream/consumers"
|
||||
import { Effect } from "effect"
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ import { cmd } from "./cmd"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { EOL } from "os"
|
||||
import { Filesystem } from "../../util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
|
||||
import { Server } from "../../server/server"
|
||||
import { Provider } from "../../provider"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { Agent } from "../../agent/agent"
|
||||
import { Permission } from "../../permission"
|
||||
import { Tool } from "../../tool"
|
||||
import { Tool } from "@/tool/tool"
|
||||
import { GlobTool } from "../../tool/glob"
|
||||
import { GrepTool } from "../../tool/grep"
|
||||
import { ReadTool } from "../../tool/read"
|
||||
@@ -25,7 +25,7 @@ import { TaskTool } from "../../tool/task"
|
||||
import { SkillTool } from "../../tool/skill"
|
||||
import { BashTool } from "../../tool/bash"
|
||||
import { TodoWriteTool } from "../../tool/todo"
|
||||
import { Locale } from "../../util"
|
||||
import { Locale } from "@/util/locale"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
|
||||
type ToolProps<T> = {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { cmd } from "./cmd"
|
||||
import { Session } from "../../session"
|
||||
import { Session } from "@/session/session"
|
||||
import { SessionID } from "../../session/schema"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { UI } from "../ui"
|
||||
import { Locale } from "../../util"
|
||||
import { Locale } from "@/util/locale"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Filesystem } from "../../util"
|
||||
import { Process } from "../../util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { Process } from "@/util/process"
|
||||
import { EOL } from "os"
|
||||
import path from "path"
|
||||
import { which } from "../../util/which"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { cmd } from "./cmd"
|
||||
import { Session } from "../../session"
|
||||
import { Session } from "@/session/session"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { Database } from "../../storage"
|
||||
import { Database } from "@/storage/db"
|
||||
import { SessionTable } from "../../session/session.sql"
|
||||
import { Project } from "../../project"
|
||||
import { Project } from "@/project/project"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
|
||||
|
||||
@@ -50,16 +50,18 @@ import { DialogAlert } from "./ui/dialog-alert"
|
||||
import { DialogConfirm } from "./ui/dialog-confirm"
|
||||
import { ToastProvider, useToast } from "./ui/toast"
|
||||
import { ExitProvider, useExit } from "./context/exit"
|
||||
import { Session as SessionApi } from "@/session"
|
||||
import { Session as SessionApi } from "@/session/session"
|
||||
import { TuiEvent } from "./event"
|
||||
import { KVProvider, useKV } from "./context/kv"
|
||||
import { Provider } from "@/provider"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { ArgsProvider, useArgs, type Args } from "./context/args"
|
||||
import open from "open"
|
||||
import { PromptRefProvider, usePromptRef } from "./context/prompt"
|
||||
import { TuiConfigProvider, useTuiConfig } from "./context/tui-config"
|
||||
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { createTuiApi, TuiPluginRuntime, type RouteMap } from "./plugin"
|
||||
import { createTuiApi } from "@/cli/cmd/tui/plugin/api"
|
||||
import { TuiPluginRuntime } from "@/cli/cmd/tui/plugin/runtime"
|
||||
import type { RouteMap } from "@/cli/cmd/tui/plugin/api"
|
||||
import { FormatError, FormatUnknownError } from "@/cli/error"
|
||||
|
||||
import type { EventSource } from "./context/sdk"
|
||||
@@ -725,6 +727,15 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: kv.get("file_context_enabled", true) ? "Disable file context" : "Enable file context",
|
||||
value: "app.toggle.file_context",
|
||||
category: "System",
|
||||
onSelect: (dialog) => {
|
||||
kv.set("file_context_enabled", !kv.get("file_context_enabled", true))
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: kv.get("diff_wrap_mode", "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping",
|
||||
value: "app.toggle.diffwrap",
|
||||
|
||||
@@ -77,6 +77,8 @@ export function DialogGoUpsell(props: DialogGoUpsellProps) {
|
||||
return
|
||||
}
|
||||
if (evt.name === "return") {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
if (selected() === "subscribe") subscribe(props, dialog)
|
||||
else dismiss(props, dialog)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useSync } from "@tui/context/sync"
|
||||
import { map, pipe, entries, sortBy } from "remeda"
|
||||
import { DialogSelect, type DialogSelectRef, type DialogSelectOption } from "@tui/ui/dialog-select"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { Keybind } from "@/util"
|
||||
import { Keybind } from "@/util/keybind"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ export function DialogSessionDeleteFailed(props: {
|
||||
|
||||
useKeyboard((evt) => {
|
||||
if (evt.name === "return") {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
void confirm()
|
||||
}
|
||||
if (evt.name === "left" || evt.name === "up") {
|
||||
|
||||
@@ -3,14 +3,14 @@ import { DialogSelect } from "@tui/ui/dialog-select"
|
||||
import { useRoute } from "@tui/context/route"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { createMemo, createResource, createSignal, onMount } from "solid-js"
|
||||
import { Locale } from "@/util"
|
||||
import { Locale } from "@/util/locale"
|
||||
import { useProject } from "@tui/context/project"
|
||||
import { useKeybind } from "../context/keybind"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useSDK } from "../context/sdk"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { DialogSessionRename } from "./dialog-session-rename"
|
||||
import { Keybind } from "@/util"
|
||||
import { Keybind } from "@/util/keybind"
|
||||
import { createDebouncedSignal } from "../util/signal"
|
||||
import { useToast } from "../ui/toast"
|
||||
import { DialogWorkspaceCreate, openWorkspaceSession, restoreWorkspaceSession } from "./dialog-workspace-create"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
import { DialogSelect } from "@tui/ui/dialog-select"
|
||||
import { createMemo, createSignal } from "solid-js"
|
||||
import { Locale } from "@/util"
|
||||
import { Locale } from "@/util/locale"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useKeybind } from "../context/keybind"
|
||||
import { usePromptStash, type StashEntry } from "./prompt/stash"
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useTheme, selectedForeground } from "@tui/context/theme"
|
||||
import { SplitBorder } from "@tui/component/border"
|
||||
import { useCommandDialog } from "@tui/component/dialog-command"
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
import { Locale } from "@/util"
|
||||
import { Locale } from "@/util/locale"
|
||||
import type { PromptInfo } from "./history"
|
||||
import { useFrecency } from "./frecency"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "path"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Filesystem } from "@/util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { onMount } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createSimpleContext } from "../../context/helper"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "path"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Filesystem } from "@/util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { onMount } from "solid-js"
|
||||
import { createStore, produce, unwrap } from "solid-js/store"
|
||||
import { createSimpleContext } from "../../context/helper"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createEffect, createMemo, onMount, createSignal, onCleanup, on, Show, S
|
||||
import "opentui-spinner/solid"
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import { Filesystem } from "@/util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { useLocal } from "@tui/context/local"
|
||||
import { tint, useTheme } from "@tui/context/theme"
|
||||
import { EmptyBorder, SplitBorder } from "@tui/component/border"
|
||||
@@ -29,7 +29,7 @@ import * as Clipboard from "../../util/clipboard"
|
||||
import type { AssistantMessage, FilePart, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { TuiEvent } from "../../event"
|
||||
import { iife } from "@/util/iife"
|
||||
import { Locale } from "@/util"
|
||||
import { Locale } from "@/util/locale"
|
||||
import { formatDuration } from "@/util/format"
|
||||
import { createColors, createFrames } from "../../ui/spinner.ts"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
@@ -112,9 +112,10 @@ export function Prompt(props: PromptProps) {
|
||||
const animationsEnabled = createMemo(() => kv.get("animations_enabled", true))
|
||||
const list = createMemo(() => props.placeholders?.normal ?? [])
|
||||
const shell = createMemo(() => props.placeholders?.shell ?? [])
|
||||
const editorPath = createMemo(() => editor.selection()?.filePath)
|
||||
const fileContextEnabled = createMemo(() => kv.get("file_context_enabled", true))
|
||||
const editorPath = createMemo(() => (fileContextEnabled() ? editor.selection()?.filePath : undefined))
|
||||
const editorSelectionLabel = createMemo(() => {
|
||||
const selection = editor.selection()?.selection
|
||||
const selection = fileContextEnabled() ? editor.selection()?.selection : undefined
|
||||
if (!selection) return
|
||||
if (selection.start.line === selection.end.line && selection.start.character === selection.end.character) return
|
||||
if (selection.start.line === selection.end.line) return `#${selection.start.line}`
|
||||
@@ -746,7 +747,7 @@ export function Prompt(props: PromptProps) {
|
||||
// Capture mode before it gets reset
|
||||
const currentMode = store.mode
|
||||
const variant = local.model.variant.current()
|
||||
const editorSelection = editor.selection()
|
||||
const editorSelection = fileContextEnabled() ? editor.selection() : undefined
|
||||
const editorParts = editorSelection
|
||||
? [
|
||||
{
|
||||
@@ -755,15 +756,25 @@ export function Prompt(props: PromptProps) {
|
||||
text: (() => {
|
||||
const start = editorSelection.selection.start
|
||||
const end = editorSelection.selection.end
|
||||
|
||||
let text = ""
|
||||
if (start.line === end.line && start.character === end.character) {
|
||||
return `Note: The user opened the file "${editorSelection.filePath}".`
|
||||
text = `Note: The user opened the file "${editorSelection.filePath}".`
|
||||
} else if (start.line === end.line) {
|
||||
text = `Note: The user selected line ${start.line + 1} from "${editorSelection.filePath}". \`\`\`${editorSelection.text}\`\`\`\n\n`
|
||||
} else {
|
||||
text = `Note: The user selected lines ${start.line + 1} to ${end.line + 1} from "${editorSelection.filePath}". \`\`\`${editorSelection.text}\`\`\`\n\n`
|
||||
}
|
||||
if (start.line === end.line) {
|
||||
return `Note: The user selected line ${start.line} from "${editorSelection.filePath}": ${editorSelection.text}`
|
||||
}
|
||||
return `Note: The user selected lines ${start.line} to ${end.line} from "${editorSelection.filePath}": ${editorSelection.text}`
|
||||
|
||||
return `<system-reminder>${text} This may or may not be relevant to the current task.</system-reminder>\n`
|
||||
})(),
|
||||
synthetic: true,
|
||||
metadata: {
|
||||
kind: "editor_context",
|
||||
source: editorSelection.source ?? "editor",
|
||||
filePath: editorSelection.filePath,
|
||||
selection: editorSelection.selection,
|
||||
},
|
||||
},
|
||||
]
|
||||
: []
|
||||
@@ -829,6 +840,7 @@ export function Prompt(props: PromptProps) {
|
||||
],
|
||||
})
|
||||
.catch(() => {})
|
||||
editor.clearSelection()
|
||||
}
|
||||
history.append({
|
||||
...store.prompt,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "path"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Filesystem } from "@/util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { onMount } from "solid-js"
|
||||
import { createStore, produce, unwrap } from "solid-js/store"
|
||||
import { createSimpleContext } from "../../context/helper"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createMemo } from "solid-js"
|
||||
import type { KeyBinding } from "@opentui/core"
|
||||
import { useKeybind } from "../context/keybind"
|
||||
import { Keybind } from "@/util"
|
||||
import { Keybind } from "@/util/keybind"
|
||||
|
||||
const TEXTAREA_ACTIONS = [
|
||||
"submit",
|
||||
|
||||
@@ -5,7 +5,8 @@ import z from "zod"
|
||||
import { TuiInfo, TuiOptions } from "./tui-schema"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Filesystem, Log } from "@/util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import * as ConfigPaths from "@/config/paths"
|
||||
|
||||
const log = Log.create({ service: "tui.migrate" })
|
||||
|
||||
@@ -16,7 +16,8 @@ import { ConfigPlugin } from "@/config/plugin"
|
||||
import { ConfigKeybinds } from "@/config/keybinds"
|
||||
import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
||||
import { Filesystem, Log } from "@/util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { ConfigVariable } from "@/config/variable"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@ import { Database } from "bun:sqlite"
|
||||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
import z from "zod"
|
||||
import { Filesystem } from "@/util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import type { EditorSelection } from "./editor"
|
||||
|
||||
const ZedEditorRowSchema = z.object({
|
||||
editor_id: z.number(),
|
||||
item_kind: z.string(),
|
||||
editor_id: z.number().nullable(),
|
||||
workspace_id: z.number(),
|
||||
workspace_paths: z.string().nullable(),
|
||||
timestamp: z.string(),
|
||||
@@ -19,26 +20,44 @@ const ZedEditorContentsSchema = z.object({
|
||||
contents: z.string().nullable(),
|
||||
})
|
||||
|
||||
const utf8 = new TextEncoder()
|
||||
|
||||
type ZedEditorRow = z.infer<typeof ZedEditorRowSchema>
|
||||
type ZedActiveEditorRow = ZedEditorRow & { item_kind: "Editor"; editor_id: number }
|
||||
|
||||
export async function resolveZedSelection(dbPath: string): Promise<EditorSelection | undefined> {
|
||||
const row = queryZedActiveEditor(dbPath, process.cwd())
|
||||
if (!row?.buffer_path || row.selection_start == null || row.selection_end == null) return
|
||||
export type ZedSelectionResult =
|
||||
| { type: "selection"; selection: EditorSelection }
|
||||
| { type: "empty" }
|
||||
| { type: "unavailable" }
|
||||
|
||||
export async function resolveZedSelection(dbPath: string, cwd = process.cwd()): Promise<ZedSelectionResult> {
|
||||
const active = queryZedActiveEditor(dbPath, cwd)
|
||||
if (active.type !== "row") return active
|
||||
|
||||
const row = active.row
|
||||
if (!row.buffer_path) return { type: "empty" }
|
||||
if (row.selection_start == null || row.selection_end == null) return { type: "unavailable" }
|
||||
|
||||
const contents = queryZedEditorContents(dbPath, row)
|
||||
const text =
|
||||
queryZedEditorContents(dbPath, row) ??
|
||||
(await Bun.file(row.buffer_path)
|
||||
.text()
|
||||
.catch(() => undefined))
|
||||
if (text == null) return
|
||||
contents.type === "contents" && contents.contents != null
|
||||
? contents.contents
|
||||
: await Bun.file(row.buffer_path)
|
||||
.text()
|
||||
.catch(() => undefined)
|
||||
if (text == null) return { type: "unavailable" }
|
||||
|
||||
const startOffset = Math.min(row.selection_start, row.selection_end)
|
||||
const endOffset = Math.max(row.selection_start, row.selection_end)
|
||||
const startOffset = utf8ByteOffsetToStringIndex(text, Math.min(row.selection_start, row.selection_end))
|
||||
const endOffset = utf8ByteOffsetToStringIndex(text, Math.max(row.selection_start, row.selection_end))
|
||||
|
||||
return {
|
||||
text: text.slice(startOffset, endOffset),
|
||||
filePath: row.buffer_path,
|
||||
selection: offsetsToSelection(text, startOffset, endOffset),
|
||||
type: "selection",
|
||||
selection: {
|
||||
text: text.slice(startOffset, endOffset),
|
||||
filePath: row.buffer_path,
|
||||
source: "zed",
|
||||
selection: offsetsToSelection(text, startOffset, endOffset),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +65,12 @@ function queryZedActiveEditor(dbPath: string, cwd: string) {
|
||||
let db: Database | undefined
|
||||
try {
|
||||
db = new Database(dbPath, { readonly: true })
|
||||
return db
|
||||
const raw = db
|
||||
.query(
|
||||
`select
|
||||
i.kind as item_kind,
|
||||
e.item_id as editor_id,
|
||||
e.workspace_id as workspace_id,
|
||||
i.workspace_id as workspace_id,
|
||||
w.paths as workspace_paths,
|
||||
w.timestamp as timestamp,
|
||||
e.buffer_path as buffer_path,
|
||||
@@ -59,31 +79,40 @@ function queryZedActiveEditor(dbPath: string, cwd: string) {
|
||||
from items i
|
||||
join panes p on p.pane_id = i.pane_id and p.workspace_id = i.workspace_id
|
||||
join workspaces w on w.workspace_id = i.workspace_id
|
||||
join editors e on e.item_id = i.item_id and e.workspace_id = i.workspace_id
|
||||
left join editors e on e.item_id = i.item_id and e.workspace_id = i.workspace_id
|
||||
left join editor_selections s on s.editor_id = e.item_id and s.workspace_id = e.workspace_id
|
||||
where i.active = 1 and p.active = 1 and i.kind = 'Editor' and e.buffer_path is not null
|
||||
where i.active = 1 and p.active = 1
|
||||
order by w.timestamp desc`,
|
||||
)
|
||||
.all()
|
||||
.flatMap((row) => {
|
||||
const parsed = ZedEditorRowSchema.safeParse(row)
|
||||
return parsed.success ? [parsed.data] : []
|
||||
})
|
||||
|
||||
const rows = raw.flatMap((row) => {
|
||||
const parsed = ZedEditorRowSchema.safeParse(row)
|
||||
return parsed.success ? [parsed.data] : []
|
||||
})
|
||||
|
||||
if (raw.length > 0 && rows.length === 0) return { type: "unavailable" as const }
|
||||
|
||||
const row = rows
|
||||
.map((row) => ({ row, score: scoreZedWorkspace(row.workspace_paths, cwd) }))
|
||||
.filter((entry) => entry.score > 0)
|
||||
.sort((left, right) => right.score - left.score || right.row.timestamp.localeCompare(left.row.timestamp))[0]?.row
|
||||
if (!row) return { type: "empty" as const }
|
||||
if (row.item_kind !== "Editor") return { type: "unavailable" as const }
|
||||
if (!isZedActiveEditorRow(row)) return { type: "empty" as const }
|
||||
return { type: "row" as const, row }
|
||||
} catch {
|
||||
return
|
||||
return { type: "unavailable" as const }
|
||||
} finally {
|
||||
db?.close()
|
||||
}
|
||||
}
|
||||
|
||||
function queryZedEditorContents(dbPath: string, row: ZedEditorRow) {
|
||||
function queryZedEditorContents(dbPath: string, row: ZedActiveEditorRow) {
|
||||
let db: Database | undefined
|
||||
try {
|
||||
db = new Database(dbPath, { readonly: true })
|
||||
return ZedEditorContentsSchema.safeParse(
|
||||
const parsed = ZedEditorContentsSchema.safeParse(
|
||||
db
|
||||
.query(
|
||||
`select contents
|
||||
@@ -91,14 +120,20 @@ function queryZedEditorContents(dbPath: string, row: ZedEditorRow) {
|
||||
where item_id = $editorID and workspace_id = $workspaceID`,
|
||||
)
|
||||
.get({ $editorID: row.editor_id, $workspaceID: row.workspace_id }),
|
||||
).data?.contents
|
||||
)
|
||||
if (!parsed.success) return { type: "unavailable" as const }
|
||||
return { type: "contents" as const, contents: parsed.data.contents }
|
||||
} catch {
|
||||
return
|
||||
return { type: "unavailable" as const }
|
||||
} finally {
|
||||
db?.close()
|
||||
}
|
||||
}
|
||||
|
||||
function isZedActiveEditorRow(row: ZedEditorRow): row is ZedActiveEditorRow {
|
||||
return row.item_kind === "Editor" && row.editor_id != null
|
||||
}
|
||||
|
||||
export function resolveZedDbPath() {
|
||||
const candidates = [
|
||||
process.env.OPENCODE_ZED_DB,
|
||||
@@ -125,7 +160,25 @@ function zedWorkspacePaths(value: string | null) {
|
||||
}
|
||||
|
||||
export function offsetToPosition(text: string, offset: number) {
|
||||
return offsetsToSelection(text, offset, offset).start
|
||||
const stringOffset = utf8ByteOffsetToStringIndex(text, offset)
|
||||
return offsetsToSelection(text, stringOffset, stringOffset).start
|
||||
}
|
||||
|
||||
function utf8ByteOffsetToStringIndex(text: string, byteOffset: number) {
|
||||
if (byteOffset <= 0) return 0
|
||||
|
||||
let bytes = 0
|
||||
for (let index = 0; index < text.length; ) {
|
||||
const codePoint = text.codePointAt(index)
|
||||
if (codePoint === undefined) return text.length
|
||||
|
||||
const nextIndex = index + (codePoint > 0xffff ? 2 : 1)
|
||||
bytes += utf8.encode(text.slice(index, nextIndex)).length
|
||||
if (bytes >= byteOffset) return nextIndex
|
||||
index = nextIndex
|
||||
}
|
||||
|
||||
return text.length
|
||||
}
|
||||
|
||||
function offsetsToSelection(text: string, startOffset: number, endOffset: number) {
|
||||
|
||||
@@ -31,6 +31,7 @@ const PositionSchema = z.object({
|
||||
const EditorSelectionSchema = z.object({
|
||||
text: z.string(),
|
||||
filePath: z.string(),
|
||||
source: z.enum(["websocket", "zed"]).optional(),
|
||||
selection: z.object({
|
||||
start: PositionSchema,
|
||||
end: PositionSchema,
|
||||
@@ -107,12 +108,20 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create
|
||||
send({ id: requestID, method, params })
|
||||
}
|
||||
|
||||
const scheduleReconnect = (delay: number) => {
|
||||
const scheduleReconnect = () => {
|
||||
if (closed) return
|
||||
if (reconnect) clearTimeout(reconnect)
|
||||
attempt += 1
|
||||
const delay = Math.min(1000 * 2 ** (attempt - 1), 10_000)
|
||||
reconnect = setTimeout(connect, delay)
|
||||
}
|
||||
|
||||
const scheduleZedPoll = () => {
|
||||
if (closed) return
|
||||
if (reconnect) clearTimeout(reconnect)
|
||||
reconnect = setTimeout(connect, 1000)
|
||||
}
|
||||
|
||||
const connect = () => {
|
||||
if (closed) return
|
||||
|
||||
@@ -121,12 +130,14 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create
|
||||
const dbPath = resolveZedDbPath()
|
||||
if (!dbPath) {
|
||||
setStore("status", "disabled")
|
||||
scheduleReconnect(1000)
|
||||
scheduleReconnect()
|
||||
return
|
||||
}
|
||||
zedSelection ??= resolveZedSelection(dbPath)
|
||||
.then((selection) => {
|
||||
.then((result) => {
|
||||
if (closed || socket) return
|
||||
if (result.type === "unavailable") return
|
||||
const selection = result.type === "selection" ? result.selection : undefined
|
||||
const key = editorSelectionKey(selection)
|
||||
if (key !== lastZedSelectionKey) {
|
||||
lastZedSelectionKey = key
|
||||
@@ -135,13 +146,12 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (closed || socket) return
|
||||
setStore("status", "disabled")
|
||||
// Keep the last known Zed selection for transient polling failures.
|
||||
})
|
||||
.finally(() => {
|
||||
zedSelection = undefined
|
||||
})
|
||||
scheduleReconnect(1000)
|
||||
scheduleZedPoll()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -171,7 +181,7 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create
|
||||
const selection =
|
||||
message.method === "selection_changed" ? EditorSelectionSchema.safeParse(message.params) : undefined
|
||||
if (selection?.success) {
|
||||
setStore("selection", selection.data)
|
||||
setStore("selection", { ...selection.data, source: "websocket" })
|
||||
return
|
||||
}
|
||||
|
||||
@@ -205,13 +215,11 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create
|
||||
if (closed) return
|
||||
|
||||
setStore("status", "connecting")
|
||||
attempt += 1
|
||||
const delay = Math.min(1000 * 2 ** (attempt - 1), 30000)
|
||||
scheduleReconnect(delay)
|
||||
scheduleReconnect()
|
||||
})
|
||||
}
|
||||
|
||||
scheduleReconnect(0)
|
||||
connect()
|
||||
|
||||
onCleanup(() => {
|
||||
closed = true
|
||||
@@ -230,6 +238,9 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create
|
||||
selection() {
|
||||
return store.selection
|
||||
},
|
||||
clearSelection() {
|
||||
setStore("selection", undefined)
|
||||
},
|
||||
onMention(listener: (mention: EditorMention) => void) {
|
||||
mentionListeners.add(listener)
|
||||
return () => mentionListeners.delete(listener)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createMemo } from "solid-js"
|
||||
import { Keybind } from "@/util"
|
||||
import { Keybind } from "@/util/keybind"
|
||||
import { pipe, mapValues } from "remeda"
|
||||
import type { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import type { ParsedKey, Renderable } from "@opentui/core"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Filesystem } from "@/util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { Flock } from "@opencode-ai/core/util/flock"
|
||||
import { rename, rm } from "fs/promises"
|
||||
import { createSignal, type Setter } from "solid-js"
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useToast } from "../ui/toast"
|
||||
import { useArgs } from "./args"
|
||||
import { useSDK } from "./sdk"
|
||||
import { RGBA } from "@opentui/core"
|
||||
import { Filesystem } from "@/util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
|
||||
export function parseModel(model: string) {
|
||||
const [providerID, ...rest] = model.split("/")
|
||||
|
||||
@@ -28,7 +28,7 @@ import type { Snapshot } from "@/snapshot"
|
||||
import { useExit } from "./exit"
|
||||
import { useArgs } from "./args"
|
||||
import { batch, onMount } from "solid-js"
|
||||
import { Log } from "@/util"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { emptyConsoleState, type ConsoleState } from "@/config/console-state"
|
||||
|
||||
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
|
||||
@@ -40,7 +40,7 @@ import { useKV } from "./kv"
|
||||
import { useRenderer } from "@opentui/solid"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Filesystem } from "@/util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { useTuiConfig } from "./tui-config"
|
||||
import { isRecord } from "@/util/record"
|
||||
import type { TuiThemeCurrent } from "@opencode-ai/plugin/tui"
|
||||
@@ -500,7 +500,8 @@ async function getCustomThemes() {
|
||||
symlink: true,
|
||||
})) {
|
||||
const name = path.basename(item, ".json")
|
||||
result[name] = await Filesystem.readJson(item)
|
||||
const theme = await Filesystem.readJson(item)
|
||||
if (isTheme(theme)) result[name] = theme
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Keybind } from "@/util"
|
||||
import { Keybind } from "@/util/keybind"
|
||||
import type { TuiPlugin, TuiPluginApi, TuiPluginModule, TuiPluginStatus } from "@opencode-ai/plugin/tui"
|
||||
import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export { TuiPluginRuntime } from "./runtime"
|
||||
export { createTuiApi } from "./api"
|
||||
export type { RouteMap } from "./api"
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { Log } from "@/util"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { errorData, errorMessage } from "@/util/error"
|
||||
import { isRecord } from "@/util/record"
|
||||
import { Instance } from "@/project/instance"
|
||||
@@ -30,8 +30,8 @@ import { PluginMeta } from "@/plugin/meta"
|
||||
import { installPlugin as installModulePlugin, patchPluginConfig, readPluginManifest } from "@/plugin/install"
|
||||
import { hasTheme, upsertTheme } from "../context/theme"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Filesystem } from "@/util"
|
||||
import { Process } from "@/util"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { Process } from "@/util/process"
|
||||
import { Flock } from "@opencode-ai/core/util/flock"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { INTERNAL_TUI_PLUGINS, type InternalTuiPlugin } from "./internal"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user