mirror of
https://fastgit.cc/https://github.com/anomalyco/opencode
synced 2026-05-03 15:21:31 +08:00
Compare commits
143 Commits
effect-syn
...
kit/httpap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98fef45553 | ||
|
|
5f03d892c0 | ||
|
|
bdabb102fe | ||
|
|
a79a6594b0 | ||
|
|
a3d282a4c2 | ||
|
|
db24f89313 | ||
|
|
31cb0bfa4f | ||
|
|
af9fdf0a1c | ||
|
|
be88cd5cb9 | ||
|
|
b4cc7d13b6 | ||
|
|
0ba013f8de | ||
|
|
0956b15c52 | ||
|
|
61150f6391 | ||
|
|
7409dcc6bd | ||
|
|
2829943ad1 | ||
|
|
c4311dda31 | ||
|
|
ad05a46d74 | ||
|
|
a6cadba814 | ||
|
|
a3bc5d35b0 | ||
|
|
1409a0715c | ||
|
|
e98c291866 | ||
|
|
e709dc34fb | ||
|
|
9293cddb3a | ||
|
|
68b3448b09 | ||
|
|
80f2b13a55 | ||
|
|
7d91d3b1ed | ||
|
|
a6464062b7 | ||
|
|
fd01dc9c89 | ||
|
|
d10fb88b66 | ||
|
|
6b68b1020e | ||
|
|
85bb9007ba | ||
|
|
9bef88e3b0 | ||
|
|
f98053c34e | ||
|
|
36007aecf4 | ||
|
|
4de44bbbef | ||
|
|
9d03d4419e | ||
|
|
7ab1c1c74a | ||
|
|
3f459819ba | ||
|
|
1986a6e817 | ||
|
|
dfe1325fca | ||
|
|
c1686c6ddc | ||
|
|
79b6ce5db4 | ||
|
|
0c816eb4b1 | ||
|
|
e318e173d8 | ||
|
|
b314781a1a | ||
|
|
8396d6b016 | ||
|
|
43e20874f4 | ||
|
|
c444e971b0 | ||
|
|
430bde9e9b | ||
|
|
05b82a6a30 | ||
|
|
6cd02c05c2 | ||
|
|
b3a7513765 | ||
|
|
f8738c9002 | ||
|
|
b460db15d7 | ||
|
|
ff4779ca11 | ||
|
|
146ff8ad85 | ||
|
|
0d0ec7dc46 | ||
|
|
1ea6e6cd4b | ||
|
|
96061222d2 | ||
|
|
3b9155714d | ||
|
|
7371db5cc6 | ||
|
|
b09b7d28b8 | ||
|
|
31ed4602e1 | ||
|
|
6a76346734 | ||
|
|
78b3000031 | ||
|
|
4c4860fb24 | ||
|
|
5242a1c6b4 | ||
|
|
075f876e6f | ||
|
|
a849812e9f | ||
|
|
d99dde6306 | ||
|
|
becf57ee6a | ||
|
|
f33aec1139 | ||
|
|
1571933096 | ||
|
|
160928a9a9 | ||
|
|
d297c29f22 | ||
|
|
0b498dd448 | ||
|
|
cec9c6122a | ||
|
|
51e310c9ce | ||
|
|
478156456e | ||
|
|
6252412d94 | ||
|
|
c2609cbf04 | ||
|
|
2115df57bf | ||
|
|
29ec07700c | ||
|
|
bcae852d28 | ||
|
|
16ddf5f559 | ||
|
|
8c79c58c4d | ||
|
|
97ed9ba624 | ||
|
|
a6b6395c8a | ||
|
|
21f8027ef7 | ||
|
|
a5aa72bd7d | ||
|
|
563177c6ac | ||
|
|
4eae8ec037 | ||
|
|
08895c396e | ||
|
|
4e451a4b0f | ||
|
|
163290bcf0 | ||
|
|
c68c33d4fe | ||
|
|
3615d8e226 | ||
|
|
2283979199 | ||
|
|
33f7f593ee | ||
|
|
461e7345b3 | ||
|
|
6bd91c68e8 | ||
|
|
ff55a40749 | ||
|
|
8b56d77ea1 | ||
|
|
dd3aa96730 | ||
|
|
8b56d1712f | ||
|
|
3c24d22d42 | ||
|
|
4c70ea28d2 | ||
|
|
5ba68a28c0 | ||
|
|
bce4def2db | ||
|
|
3544ea0244 | ||
|
|
6434918794 | ||
|
|
5984d917dc | ||
|
|
c2a97a7a6c | ||
|
|
a083c88e87 | ||
|
|
ce3b0988c4 | ||
|
|
e8a194a2bb | ||
|
|
8aa8798e07 | ||
|
|
6d4629b566 | ||
|
|
a9d399699e | ||
|
|
bc805b3001 | ||
|
|
668d77bb4e | ||
|
|
5c2e06f353 | ||
|
|
a499fe2b17 | ||
|
|
451650b584 | ||
|
|
1b76bec0e2 | ||
|
|
96f4da1e1d | ||
|
|
96a0dd6b04 | ||
|
|
2dd1f2d453 | ||
|
|
510f01674a | ||
|
|
e3134a2a99 | ||
|
|
8805104b8d | ||
|
|
fc155e9fc5 | ||
|
|
3aaac0098e | ||
|
|
a12333310f | ||
|
|
247284b9af | ||
|
|
e0305e47f3 | ||
|
|
76a0f0f619 | ||
|
|
560baae15d | ||
|
|
5518ecaefe | ||
|
|
924ba97055 | ||
|
|
b80f52f8ad | ||
|
|
feb275d08b | ||
|
|
fbcbd24063 |
1
.github/VOUCHED.td
vendored
1
.github/VOUCHED.td
vendored
@@ -32,6 +32,7 @@ rekram1-node
|
||||
-ricardo-m-l
|
||||
-robinmordasiewicz
|
||||
rubdos
|
||||
-saisharan0103 spamming ai prs
|
||||
shantur
|
||||
simonklee
|
||||
-spider-yamet clawdbot/llm psychosis, spam pinging the team
|
||||
|
||||
6
.github/workflows/deploy.yml
vendored
6
.github/workflows/deploy.yml
vendored
@@ -36,3 +36,9 @@ jobs:
|
||||
PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
|
||||
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
|
||||
STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ vars.WEB_SENTRY_PROJECT }}
|
||||
SENTRY_RELEASE: web@${{ github.sha }}
|
||||
VITE_SENTRY_DSN: ${{ vars.WEB_SENTRY_DSN }}
|
||||
VITE_SENTRY_RELEASE: web@${{ github.sha }}
|
||||
|
||||
7
.github/workflows/publish.yml
vendored
7
.github/workflows/publish.yml
vendored
@@ -494,6 +494,13 @@ jobs:
|
||||
working-directory: packages/desktop-electron
|
||||
env:
|
||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ vars.WEB_SENTRY_PROJECT }}
|
||||
SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
||||
VITE_SENTRY_DSN: ${{ vars.WEB_SENTRY_DSN }}
|
||||
VITE_SENTRY_ENVIRONMENT: ${{ (github.ref_name == 'beta' && 'beta') || 'production' }}
|
||||
VITE_SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
||||
|
||||
- name: Package and publish
|
||||
if: needs.version.outputs.release
|
||||
|
||||
@@ -132,7 +132,7 @@ It's very similar to Claude Code in terms of capability. Here are the key differ
|
||||
|
||||
- 100% open source
|
||||
- Not coupled to any provider. Although we recommend the models we provide through [OpenCode Zen](https://opencode.ai/zen), OpenCode can be used with Claude, OpenAI, Google, or even local models. As models evolve, the gaps between them will close and pricing will drop, so being provider-agnostic is important.
|
||||
- Out-of-the-box LSP support
|
||||
- Built-in opt-in LSP support
|
||||
- A focus on TUI. OpenCode is built by neovim users and the creators of [terminal.shop](https://terminal.shop); we are going to push the limits of what's possible in the terminal.
|
||||
- A client/server architecture. This, for example, can allow OpenCode to run on your computer while you drive it remotely from a mobile app, meaning that the TUI frontend is just one of the possible clients.
|
||||
|
||||
|
||||
209
bun.lock
209
bun.lock
@@ -29,12 +29,13 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@shikijs/transformers": "3.9.2",
|
||||
"@solid-primitives/active-element": "2.1.3",
|
||||
"@solid-primitives/audio": "1.4.2",
|
||||
@@ -69,6 +70,7 @@
|
||||
"devDependencies": {
|
||||
"@happy-dom/global-registrator": "20.0.11",
|
||||
"@playwright/test": "catalog:",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@tailwindcss/vite": "catalog:",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@types/bun": "catalog:",
|
||||
@@ -83,7 +85,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -117,7 +119,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -144,7 +146,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.64",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
@@ -168,7 +170,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -192,7 +194,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -226,10 +228,11 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@solid-primitives/i18n": "2.2.1",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@solidjs/meta": "catalog:",
|
||||
@@ -250,6 +253,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/artifact": "4.0.0",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@types/bun": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
@@ -259,7 +263,7 @@
|
||||
},
|
||||
"packages/desktop-electron": {
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -275,6 +279,8 @@
|
||||
"@lydell/node-pty": "catalog:",
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@solid-primitives/i18n": "2.2.1",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@solidjs/meta": "catalog:",
|
||||
@@ -303,7 +309,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -332,7 +338,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -348,7 +354,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -456,7 +462,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.4",
|
||||
"@effect/language-service": "0.84.2",
|
||||
"@octokit/webhooks-types": "7.6.1",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
@@ -491,7 +496,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -506,8 +511,8 @@
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentui/core": ">=0.2.0",
|
||||
"@opentui/solid": ">=0.2.0",
|
||||
"@opentui/core": ">=0.2.2",
|
||||
"@opentui/solid": ">=0.2.2",
|
||||
},
|
||||
"optionalPeers": [
|
||||
"@opentui/core",
|
||||
@@ -526,7 +531,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -541,7 +546,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -576,7 +581,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -625,7 +630,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -685,10 +690,12 @@
|
||||
"@npmcli/arborist": "9.4.0",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@opentui/core": "0.2.0",
|
||||
"@opentui/solid": "0.2.0",
|
||||
"@opentui/core": "0.2.2",
|
||||
"@opentui/solid": "0.2.2",
|
||||
"@pierre/diffs": "1.1.0-beta.18",
|
||||
"@playwright/test": "1.59.1",
|
||||
"@sentry/solid": "10.36.0",
|
||||
"@sentry/vite-plugin": "4.6.0",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
@@ -708,7 +715,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.57",
|
||||
"effect": "4.0.0-beta.59",
|
||||
"fuzzysort": "3.1.0",
|
||||
"hono": "4.10.7",
|
||||
"hono-openapi": "1.1.2",
|
||||
@@ -1069,8 +1076,6 @@
|
||||
|
||||
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="],
|
||||
|
||||
"@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.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.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=="],
|
||||
@@ -1613,21 +1618,21 @@
|
||||
|
||||
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.2.0", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.2.0", "@opentui/core-darwin-x64": "0.2.0", "@opentui/core-linux-arm64": "0.2.0", "@opentui/core-linux-x64": "0.2.0", "@opentui/core-win32-arm64": "0.2.0", "@opentui/core-win32-x64": "0.2.0", "bun-webgpu": "0.1.7", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-7YOEqPUQmsgrOb9nmLEBlX8RVHPFy4HquK1C489DwfvvPTiws8nTbZ+webNQDWha7shgnYQK4Zo1EcOlpQ5+1Q=="],
|
||||
"@opentui/core": ["@opentui/core@0.2.2", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.2", "@opentui/core-darwin-x64": "0.2.2", "@opentui/core-linux-arm64": "0.2.2", "@opentui/core-linux-x64": "0.2.2", "@opentui/core-win32-arm64": "0.2.2", "@opentui/core-win32-x64": "0.2.2" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-wxg1CD58SVrowu+WgbhZNi3UP/wWxPio2Kj2IeTjomoIE+6EXLxR8eCCxHYVuQUd9E4fknrKkY5HmiSsp6oPow=="],
|
||||
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VVmKwth3hzsQPjAZ7WGJxmzuzx0uCtynd79JJDg26D7QRM9V5beVGbKwwU5SKsDlK74EyQoY85Mv9xFY5E4jrA=="],
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tY5n3ZRQx+b0kyhQJJLsyJMeZ+0w4FV37YZc/Qqv3qvOqE9kZPw/7adR77FYwWDm/7fax94mLMrR8Y5bKUkDmw=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-eX+WNdbSNr7Bozdq/MH6p1vXIALGt0SqBHR4YtWyTh6X7KDz9FTtJT3ylxMPqiVRUGBNAiWOxoqKGXW7JLQ0TA=="],
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-W/R7OnqY30FXcTG0tiP2JkQFmgtYbIte5afQ5PC12TliRoee1RqG3iCG6kY1jxW+3Vg6jge88uiSjUEDpeV2gA=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ARZa+ywbN/OV7esT5ZdJMlQW3a4Pr56qLlEI/X65ik88C2sgmDze4Kf2FmqtvJ1hbv1YsMfLHH9MfhLl5twyHQ=="],
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-1pzTYFEZauYuw6AGycw2TYGtAlZVGjuUtSdxH1fP51kBPS3oVWduUY2j7GKREz3SU5NulvO2Wc6HWsm3feMqwQ=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ZjNxrD45P51cdbABoivVQLBakVYwDqAridJbHhkK6T/+EU7YsTrmAu9ae19N9ZGnrlKzLViQF8GOavNUNjAbhw=="],
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ucVwUtUYeOYGVFPBLbPoxzbrPdhD0PDyKNQ2X4n1AJ9jlQX4gqBZRcXMEF8hiXDjFxsikZwef7De0ciCcWvAMg=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-ImMjFPOWE8wcZQ2lUz1D418xonS/5EwnItUF1g5dbp1q9+A0vv2P3bxTenLwMqcYvG4wjO6gKT3n2QLnRd6qKg=="],
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-MPhYdJNdxmC5Bqsq6sis/+VkjRgkEjm+bQ1Tl++NSKLuiTU32Re0ImcZlgHbe+LZtZoGMZHVSgZlkGd3oYXO2g=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6yfYHTtJ4yzbl8kXCW3Pc4eWbZDYVw21GumwdNgkjJJ2JqQAQ861em0riEoucYAa5qPYYTiMUEw7X4Fv8lGwuQ=="],
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-19BroLfn2h0RDYfJS5o96Fc8kYCDhRBcseIXtHIkoKIsKMxx62KiDLo/byVye6rp+yQRRB7Xkd2uWqsbdiWo9w=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.2.0", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.0", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-kZR9i0FPAcVtomrPsKuSb+D9smooplo9zggFfU2vnnguNuQjGNbEmuJtxhCacy7ig9g3GomdNtQAzD4LiAY+3w=="],
|
||||
"@opentui/solid": ["@opentui/solid@0.2.2", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.2", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-ZBVfCoVAhcUGQWPAWOTdzuVldMaRkuPpCu4U1VZCqmIw9DtbCuiVr0WnDocDxKhJLbTu8bl3qEWtVCf6lTSi3w=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
@@ -1959,6 +1964,44 @@
|
||||
|
||||
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
|
||||
|
||||
"@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.36.0", "", { "dependencies": { "@sentry/core": "10.36.0" } }, "sha512-WILVR8HQBWOxbqLRuTxjzRCMIACGsDTo6jXvzA8rz6ezElElLmIrn3CFAswrESLqEEUa4CQHl5bLgSVJCRNweA=="],
|
||||
|
||||
"@sentry-internal/feedback": ["@sentry-internal/feedback@10.36.0", "", { "dependencies": { "@sentry/core": "10.36.0" } }, "sha512-zPjz7AbcxEyx8AHj8xvp28fYtPTPWU1XcNtymhAHJLS9CXOblqSC7W02Jxz6eo3eR1/pLyOo6kJBUjvLe9EoFA=="],
|
||||
|
||||
"@sentry-internal/replay": ["@sentry-internal/replay@10.36.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.36.0", "@sentry/core": "10.36.0" } }, "sha512-nLMkJgvHq+uCCrQKV2KgSdVHxTsmDk0r2hsAoTcKCbzUpXyW5UhCziMRS6ULjBlzt5sbxoIIplE25ZpmIEeNgg=="],
|
||||
|
||||
"@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.36.0", "", { "dependencies": { "@sentry-internal/replay": "10.36.0", "@sentry/core": "10.36.0" } }, "sha512-DLGIwmT2LX+O6TyYPtOQL5GiTm2rN0taJPDJ/Lzg2KEJZrdd5sKkzTckhh2x+vr4JQyeaLmnb8M40Ch1hvG/vQ=="],
|
||||
|
||||
"@sentry/babel-plugin-component-annotate": ["@sentry/babel-plugin-component-annotate@4.6.0", "", {}, "sha512-3soTX50JPQQ51FSbb4qvNBf4z/yP7jTdn43vMTp9E4IxvJ9HKJR7OEuKkCMszrZmWsVABXl02msqO7QisePdiQ=="],
|
||||
|
||||
"@sentry/browser": ["@sentry/browser@10.36.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.36.0", "@sentry-internal/feedback": "10.36.0", "@sentry-internal/replay": "10.36.0", "@sentry-internal/replay-canvas": "10.36.0", "@sentry/core": "10.36.0" } }, "sha512-yHhXbgdGY1s+m8CdILC9U/II7gb6+s99S2Eh8VneEn/JG9wHc+UOzrQCeFN0phFP51QbLkjkiQbbanjT1HP8UQ=="],
|
||||
|
||||
"@sentry/bundler-plugin-core": ["@sentry/bundler-plugin-core@4.6.0", "", { "dependencies": { "@babel/core": "^7.18.5", "@sentry/babel-plugin-component-annotate": "4.6.0", "@sentry/cli": "^2.57.0", "dotenv": "^16.3.1", "find-up": "^5.0.0", "glob": "^9.3.2", "magic-string": "0.30.8", "unplugin": "1.0.1" } }, "sha512-Fub2XQqrS258jjS8qAxLLU1k1h5UCNJ76i8m4qZJJdogWWaF8t00KnnTyp9TEDJzrVD64tRXS8+HHENxmeUo3g=="],
|
||||
|
||||
"@sentry/cli": ["@sentry/cli@2.58.5", "", { "dependencies": { "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.7", "progress": "^2.0.3", "proxy-from-env": "^1.1.0", "which": "^2.0.2" }, "optionalDependencies": { "@sentry/cli-darwin": "2.58.5", "@sentry/cli-linux-arm": "2.58.5", "@sentry/cli-linux-arm64": "2.58.5", "@sentry/cli-linux-i686": "2.58.5", "@sentry/cli-linux-x64": "2.58.5", "@sentry/cli-win32-arm64": "2.58.5", "@sentry/cli-win32-i686": "2.58.5", "@sentry/cli-win32-x64": "2.58.5" }, "bin": { "sentry-cli": "bin/sentry-cli" } }, "sha512-tavJ7yGUZV+z3Ct2/ZB6mg339i08sAk6HDkgqmSRuQEu2iLS5sl9HIvuXfM6xjv8fwlgFOSy++WNABNAcGHUbg=="],
|
||||
|
||||
"@sentry/cli-darwin": ["@sentry/cli-darwin@2.58.5", "", { "os": "darwin" }, "sha512-lYrNzenZFJftfwSya7gwrHGxtE+Kob/e1sr9lmHMFOd4utDlmq0XFDllmdZAMf21fxcPRI1GL28ejZ3bId01fQ=="],
|
||||
|
||||
"@sentry/cli-linux-arm": ["@sentry/cli-linux-arm@2.58.5", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm" }, "sha512-KtHweSIomYL4WVDrBrYSYJricKAAzxUgX86kc6OnlikbyOhoK6Fy8Vs6vwd52P6dvWPjgrMpUYjW2M5pYXQDUw=="],
|
||||
|
||||
"@sentry/cli-linux-arm64": ["@sentry/cli-linux-arm64@2.58.5", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm64" }, "sha512-/4gywFeBqRB6tR/iGMRAJ3HRqY6Z7Yp4l8ZCbl0TDLAfHNxu7schEw4tSnm2/Hh9eNMiOVy4z58uzAWlZXAYBQ=="],
|
||||
|
||||
"@sentry/cli-linux-i686": ["@sentry/cli-linux-i686@2.58.5", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "ia32" }, "sha512-G7261dkmyxqlMdyvyP06b+RTIVzp1gZNgglj5UksxSouSUqRd/46W/2pQeOMPhloDYo9yLtCN2YFb3Mw4aUsWw=="],
|
||||
|
||||
"@sentry/cli-linux-x64": ["@sentry/cli-linux-x64@2.58.5", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "x64" }, "sha512-rP04494RSmt86xChkQ+ecBNRYSPbyXc4u0IA7R7N1pSLCyO74e5w5Al+LnAq35cMfVbZgz5Sm0iGLjyiUu4I1g=="],
|
||||
|
||||
"@sentry/cli-win32-arm64": ["@sentry/cli-win32-arm64@2.58.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-AOJ2nCXlQL1KBaCzv38m3i2VmSHNurUpm7xVKd6yAHX+ZoVBI8VT0EgvwmtJR2TY2N2hNCC7UrgRmdUsQ152bA=="],
|
||||
|
||||
"@sentry/cli-win32-i686": ["@sentry/cli-win32-i686@2.58.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-EsuboLSOnlrN7MMPJ1eFvfMDm+BnzOaSWl8eYhNo8W/BIrmNgpRUdBwnWn9Q2UOjJj5ZopukmsiMYtU/D7ml9g=="],
|
||||
|
||||
"@sentry/cli-win32-x64": ["@sentry/cli-win32-x64@2.58.5", "", { "os": "win32", "cpu": "x64" }, "sha512-IZf+XIMiQwj+5NzqbOQfywlOitmCV424Vtf9c+ep61AaVScUFD1TSrQbOcJJv5xGxhlxNOMNgMeZhdexdzrKZg=="],
|
||||
|
||||
"@sentry/core": ["@sentry/core@10.36.0", "", {}, "sha512-EYJjZvofI+D93eUsPLDIUV0zQocYqiBRyXS6CCV6dHz64P/Hob5NJQOwPa8/v6nD+UvJXvwsFfvXOHhYZhZJOQ=="],
|
||||
|
||||
"@sentry/solid": ["@sentry/solid@10.36.0", "", { "dependencies": { "@sentry/browser": "10.36.0", "@sentry/core": "10.36.0" }, "peerDependencies": { "@solidjs/router": "^0.13.4 || ^0.14.0 || ^0.15.0", "@tanstack/solid-router": "^1.132.27", "solid-js": "^1.8.4" }, "optionalPeers": ["@solidjs/router", "@tanstack/solid-router"] }, "sha512-AaDqz3JGBrQCm2YVqODVyJHwg7LRTNSJig9mjfProFyvkC7eUXQ/HBJrrhAD1Dct9ufmDH3G+f3/Ut9LgpItSg=="],
|
||||
|
||||
"@sentry/vite-plugin": ["@sentry/vite-plugin@4.6.0", "", { "dependencies": { "@sentry/bundler-plugin-core": "4.6.0", "unplugin": "1.0.1" } }, "sha512-fMR2d+EHwbzBa0S1fp45SNUTProxmyFBp+DeBWWQOSP9IU6AH6ea2rqrpMAnp/skkcdW4z4LSRrOEpMZ5rWXLw=="],
|
||||
|
||||
"@shikijs/core": ["@shikijs/core@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3q/mzmw09B2B6PgFNeiaN8pkNOixWS726IHmJEpjDAcneDPMQmUg2cweT9cWXY4XcyQS3i6mOOUgQz9RRUP6HA=="],
|
||||
|
||||
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg=="],
|
||||
@@ -2725,21 +2768,21 @@
|
||||
|
||||
"builder-util-runtime": ["builder-util-runtime@9.5.1", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ=="],
|
||||
|
||||
"bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
|
||||
"bun-ffi-structs": ["bun-ffi-structs@0.2.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-N/ZWtyN0piZlrXQT7TO0V+q952orYqkfhXRXM1Hcbb+R3QSiBH4vLnib187Mrs1H7pWIYECAmPeapGYDOMCl+w=="],
|
||||
|
||||
"bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="],
|
||||
|
||||
"bun-webgpu": ["bun-webgpu@0.1.7", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.7", "bun-webgpu-darwin-x64": "^0.1.7", "bun-webgpu-linux-x64": "^0.1.7", "bun-webgpu-win32-x64": "^0.1.7" } }, "sha512-KUxUp+oQIf7pPBMD4Hv1TUu7DWaOZ4ciKulTk9to9+Uc8yHoYrMW7L2SJCJ4FHHkywgf/7aLRgRx0b7i6DvGIQ=="],
|
||||
"bun-webgpu": ["bun-webgpu@0.1.5", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.5", "bun-webgpu-darwin-x64": "^0.1.5", "bun-webgpu-linux-x64": "^0.1.5", "bun-webgpu-win32-x64": "^0.1.5" } }, "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA=="],
|
||||
|
||||
"bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mRrFFyHzPWjsTRidAZBRcu808CPQBOUL0P6b4nxLhp+XHcV/mbUHERZMgW9s58tsojQfSdzschiQa8q+JCgRWA=="],
|
||||
"bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lIsDkPzJzPl6yrB5CUOINJFPnTRv6fF/Q8J1mAr43ogSp86WZEg9XZKaT6f3EUJ+9ETogGoMnoj1q0AwHUTbAQ=="],
|
||||
|
||||
"bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-g0NXGNgvaVCSH/jCWWlfdiquOHkbUN6vP4zqzSkIxWKQeLnqm3oADcok7SO3yIgI7v5mKpRc/ks7NDEKNH+jNQ=="],
|
||||
"bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-uEddf5U7GvKIkM/BV18rUKtYHL6d0KeqBjNHwfqDH9QgEo9KVSKvJXS5I/sMefk5V5pIYE+8tQhtrREevhocng=="],
|
||||
|
||||
"bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-UEP7UZdEhx9otvkZczjsszL8ZVlrODANQvgl+C88/bNVmxDoFi7w1fWzGi1sZyakiETjmtFDq2/xCLhbSZxjqw=="],
|
||||
"bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-Y/f15j9r8ba0xUz+3lATtS74OE+PPzQXO7Do/1eCluJcuOlfa77kMjvBK/ShWnem3Y9xqi59pebTPOGRB+CaJA=="],
|
||||
|
||||
"bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.7", "", { "os": "win32", "cpu": "x64" }, "sha512-KZktiFkBz6sN7PEm1NVdeaLP5Q5X/PlSHZqefY4nNuWtf0LNvh54NhZe7yVv/Plz/nGbv92b0KHMBY3ki/pp6g=="],
|
||||
"bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-MHSFAKqizISb+C5NfDrFe3g0Al5Njnu0j/A+oO2Q+bIWX+fUYjBSowiYE1ZXJx65KuryuB+tiM7Qh6cQbVvkEg=="],
|
||||
|
||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||
|
||||
@@ -3035,7 +3078,7 @@
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"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=="],
|
||||
"effect": ["effect@4.0.0-beta.59", "", { "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-xyUDLeHSe8d6lWGOvR6Fgn2HL6gYeTZ/S4Jzk9uc4ZUxMPPsNZlNXrvk0C7/utQFzeX7uAWcVnG2BjbA0SRoAA=="],
|
||||
|
||||
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
|
||||
|
||||
@@ -3243,7 +3286,7 @@
|
||||
|
||||
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
|
||||
|
||||
"find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"finity": ["finity@0.5.4", "", {}, "sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA=="],
|
||||
|
||||
@@ -3737,7 +3780,7 @@
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
|
||||
|
||||
@@ -4141,7 +4184,7 @@
|
||||
|
||||
"p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="],
|
||||
|
||||
"p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="],
|
||||
|
||||
@@ -4161,7 +4204,7 @@
|
||||
|
||||
"pagefind": ["pagefind@1.5.2", "", { "optionalDependencies": { "@pagefind/darwin-arm64": "1.5.2", "@pagefind/darwin-x64": "1.5.2", "@pagefind/freebsd-x64": "1.5.2", "@pagefind/linux-arm64": "1.5.2", "@pagefind/linux-x64": "1.5.2", "@pagefind/windows-arm64": "1.5.2", "@pagefind/windows-x64": "1.5.2" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q=="],
|
||||
|
||||
"pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
||||
"pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
|
||||
|
||||
"param-case": ["param-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A=="],
|
||||
|
||||
@@ -4191,7 +4234,7 @@
|
||||
|
||||
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
||||
|
||||
"path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="],
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="],
|
||||
|
||||
@@ -4951,7 +4994,7 @@
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
|
||||
"unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
||||
"unplugin": ["unplugin@1.0.1", "", { "dependencies": { "acorn": "^8.8.1", "chokidar": "^3.5.3", "webpack-sources": "^3.2.3", "webpack-virtual-modules": "^0.5.0" } }, "sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA=="],
|
||||
|
||||
"unstorage": ["unstorage@2.0.0-alpha.7", "", { "peerDependencies": { "@azure/app-configuration": "^1.11.0", "@azure/cosmos": "^4.9.1", "@azure/data-tables": "^13.3.2", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", "@azure/storage-blob": "^12.31.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.13.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.36.2", "@vercel/blob": ">=0.27.3", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4 || ^5", "db0": ">=0.3.4", "idb-keyval": "^6.2.2", "ioredis": "^5.9.3", "lru-cache": "^11.2.6", "mongodb": "^6 || ^7", "ofetch": "*", "uploadthing": "^7.7.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-ELPztchk2zgFJnakyodVY3vJWGW9jy//keJ32IOJVGUMyaPydwcA1FtVvWqT0TNRch9H+cMNEGllfVFfScImog=="],
|
||||
|
||||
@@ -5059,7 +5102,9 @@
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
|
||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||
"webpack-sources": ["webpack-sources@3.4.0", "", {}, "sha512-gHwIe1cgBvvfLeu1Yz/dcFpmHfKDVxxyqI+kzqmuxZED81z2ChxpyqPaWcNqigPywhaEke7AjSGga+kxY55gjQ=="],
|
||||
|
||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.5.0", "", {}, "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw=="],
|
||||
|
||||
"whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
|
||||
|
||||
@@ -5595,6 +5640,8 @@
|
||||
|
||||
"@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="],
|
||||
|
||||
"@opentui/core/diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
|
||||
@@ -5611,6 +5658,16 @@
|
||||
|
||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/magic-string": ["magic-string@0.30.8", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ=="],
|
||||
|
||||
"@sentry/cli/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
|
||||
|
||||
"@sentry/cli/proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||
|
||||
"@sentry/cli/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"@shikijs/engine-javascript/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
||||
|
||||
"@shikijs/engine-oniguruma/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
||||
@@ -5665,6 +5722,8 @@
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@storybook/csf-plugin/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
||||
|
||||
"@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=="],
|
||||
@@ -5849,8 +5908,6 @@
|
||||
|
||||
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"find-up/path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
|
||||
@@ -5961,7 +6018,7 @@
|
||||
|
||||
"ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
"p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
|
||||
|
||||
@@ -5973,6 +6030,8 @@
|
||||
|
||||
"pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
|
||||
|
||||
"pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
||||
"pkg-up/find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="],
|
||||
|
||||
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
|
||||
@@ -6065,12 +6124,16 @@
|
||||
|
||||
"type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"unicode-trie/pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
|
||||
|
||||
"unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
||||
|
||||
"unplugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"unused-filename/path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="],
|
||||
|
||||
"uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
||||
|
||||
"venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||
|
||||
"vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
|
||||
@@ -6569,6 +6632,16 @@
|
||||
|
||||
"@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"@sentry/cli/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
||||
|
||||
"@sentry/cli/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
|
||||
@@ -6591,6 +6664,8 @@
|
||||
|
||||
"@standard-community/standard-openapi/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@storybook/csf-plugin/unplugin/webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||
|
||||
"@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=="],
|
||||
@@ -6725,7 +6800,7 @@
|
||||
|
||||
"opentui-spinner/@opentui/core/@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.105", "", { "os": "win32", "cpu": "x64" }, "sha512-f9FqqUmxehwhF+cgyazm0YT0v0BYTTCPzd6eztqhl74N3x/kC+jOOz2rdJDC/tTBo1JVsF64KupOnhIs6/Cogg=="],
|
||||
|
||||
"opentui-spinner/@opentui/core/bun-webgpu": ["bun-webgpu@0.1.5", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.5", "bun-webgpu-darwin-x64": "^0.1.5", "bun-webgpu-linux-x64": "^0.1.5", "bun-webgpu-win32-x64": "^0.1.5" } }, "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA=="],
|
||||
"opentui-spinner/@opentui/core/bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
|
||||
|
||||
"opentui-spinner/@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=="],
|
||||
|
||||
@@ -6737,8 +6812,12 @@
|
||||
|
||||
"ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"parse-bmfont-xml/xml2js/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
|
||||
|
||||
"readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
@@ -6767,6 +6846,8 @@
|
||||
|
||||
"type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"unplugin/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"vitest/@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
||||
@@ -6991,6 +7072,12 @@
|
||||
|
||||
"@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
|
||||
|
||||
"@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
|
||||
@@ -7073,20 +7160,12 @@
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
||||
|
||||
"opentui-spinner/@opentui/core/bun-webgpu/@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="],
|
||||
|
||||
"opentui-spinner/@opentui/core/bun-webgpu/bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lIsDkPzJzPl6yrB5CUOINJFPnTRv6fF/Q8J1mAr43ogSp86WZEg9XZKaT6f3EUJ+9ETogGoMnoj1q0AwHUTbAQ=="],
|
||||
|
||||
"opentui-spinner/@opentui/core/bun-webgpu/bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-uEddf5U7GvKIkM/BV18rUKtYHL6d0KeqBjNHwfqDH9QgEo9KVSKvJXS5I/sMefk5V5pIYE+8tQhtrREevhocng=="],
|
||||
|
||||
"opentui-spinner/@opentui/core/bun-webgpu/bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-Y/f15j9r8ba0xUz+3lATtS74OE+PPzQXO7Do/1eCluJcuOlfa77kMjvBK/ShWnem3Y9xqi59pebTPOGRB+CaJA=="],
|
||||
|
||||
"opentui-spinner/@opentui/core/bun-webgpu/bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-MHSFAKqizISb+C5NfDrFe3g0Al5Njnu0j/A+oO2Q+bIWX+fUYjBSowiYE1ZXJx65KuryuB+tiM7Qh6cQbVvkEg=="],
|
||||
|
||||
"opentui-spinner/@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"ora/bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
|
||||
@@ -7099,6 +7178,8 @@
|
||||
|
||||
"tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"unplugin/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"@astrojs/check/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@astrojs/check/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
@@ -7153,6 +7234,8 @@
|
||||
|
||||
"@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="],
|
||||
@@ -7179,6 +7262,8 @@
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-cBfg4pJ4mjsfS4MFFASBaZZykArgIoeo/3woOcSGy1U=",
|
||||
"aarch64-linux": "sha256-Q6cqUwfqbscdrPW0uHcfshhQINjJi0HiyURMSdOOCf4=",
|
||||
"aarch64-darwin": "sha256-1AtfsD1D9YxWSEsecPJF9XsvsxsWTtVtkP5l6UW43og=",
|
||||
"x86_64-darwin": "sha256-YS5/8YTf9LymAUbjXVrGDfxtKVJrpZbPnnCtsGHSHoU="
|
||||
"x86_64-linux": "sha256-SLWRe4uPSRWgU+NPa1BywmrUtNVIC0Oy2mjmxclxk+s=",
|
||||
"aarch64-linux": "sha256-toHEeIqMzrmThoV0B52juGKm4pa/aJN3gBFFtrSZp2Q=",
|
||||
"aarch64-darwin": "sha256-lYUsUxq5zR2RXjqZTEdjduOncnlwvTlxDJVKWXJuKPY=",
|
||||
"x86_64-darwin": "sha256-77XmuEYqGwb1mkEHfnghq1VtukFTneohA0FW6WDOk1U="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ stdenvNoCC.mkDerivation {
|
||||
--filter './packages/opencode' \
|
||||
--filter './packages/desktop' \
|
||||
--filter './packages/app' \
|
||||
--filter './packages/shared' \
|
||||
--frozen-lockfile \
|
||||
--ignore-scripts \
|
||||
--no-progress
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
"@types/cross-spawn": "6.0.6",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@opentui/core": "0.2.0",
|
||||
"@opentui/solid": "0.2.0",
|
||||
"@opentui/core": "0.2.2",
|
||||
"@opentui/solid": "0.2.2",
|
||||
"ulid": "3.0.1",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@types/luxon": "3.7.1",
|
||||
@@ -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.57",
|
||||
"effect": "4.0.0-beta.59",
|
||||
"ai": "6.0.168",
|
||||
"cross-spawn": "7.0.6",
|
||||
"hono": "4.10.7",
|
||||
@@ -77,6 +77,8 @@
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
|
||||
"@sentry/solid": "10.36.0",
|
||||
"@sentry/vite-plugin": "4.6.0",
|
||||
"solid-js": "1.9.10",
|
||||
"vite-plugin-solid": "2.11.10",
|
||||
"@lydell/node-pty": "1.2.0-beta.10"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -27,6 +27,7 @@
|
||||
"devDependencies": {
|
||||
"@happy-dom/global-registrator": "20.0.11",
|
||||
"@playwright/test": "catalog:",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@tailwindcss/vite": "catalog:",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@types/bun": "catalog:",
|
||||
@@ -40,6 +41,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "@/index.css"
|
||||
import * as Sentry from "@sentry/solid"
|
||||
import { I18nProvider } from "@opencode-ai/ui/context"
|
||||
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
|
||||
import { FileComponentProvider } from "@opencode-ai/ui/context/file"
|
||||
@@ -148,12 +149,19 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
|
||||
>
|
||||
<LanguageProvider locale={props.locale}>
|
||||
<UiI18nBridge>
|
||||
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
|
||||
<DialogProvider>
|
||||
<MarkedProvider>
|
||||
<FileComponentProvider component={File}>{props.children}</FileComponentProvider>
|
||||
</MarkedProvider>
|
||||
</DialogProvider>
|
||||
<ErrorBoundary
|
||||
fallback={(error) => {
|
||||
Sentry.captureException(error)
|
||||
return <ErrorPage error={error} />
|
||||
}}
|
||||
>
|
||||
<QueryProvider>
|
||||
<DialogProvider>
|
||||
<MarkedProvider>
|
||||
<FileComponentProvider component={File}>{props.children}</FileComponentProvider>
|
||||
</MarkedProvider>
|
||||
</DialogProvider>
|
||||
</QueryProvider>
|
||||
</ErrorBoundary>
|
||||
</UiI18nBridge>
|
||||
</LanguageProvider>
|
||||
|
||||
@@ -329,6 +329,7 @@ export const SettingsGeneral: Component = () => {
|
||||
label={(o) => o.label}
|
||||
onSelect={(option) => {
|
||||
if (!option) return
|
||||
if (option.value === currentShell()) return
|
||||
globalSync.updateConfig({ shell: option.value })
|
||||
}}
|
||||
variant="secondary"
|
||||
|
||||
@@ -204,6 +204,9 @@ function createGlobalSync() {
|
||||
},
|
||||
translate: language.t,
|
||||
getSdk: sdkFor,
|
||||
global: {
|
||||
provider: globalStore.provider,
|
||||
},
|
||||
})
|
||||
|
||||
async function loadSessions(directory: string) {
|
||||
|
||||
@@ -260,9 +260,6 @@ export async function bootstrapDirectory(input: {
|
||||
const seededPath = input.global.path.directory === input.directory ? input.global.path : undefined
|
||||
if (seededProject) input.setStore("project", seededProject)
|
||||
if (seededPath) input.setStore("path", seededPath)
|
||||
if (input.store.provider.all.length === 0 && input.global.provider.all.length > 0) {
|
||||
input.setStore("provider", input.global.provider)
|
||||
}
|
||||
if (Object.keys(input.store.config).length === 0 && Object.keys(input.global.config).length > 0) {
|
||||
input.setStore("config", reconcile(input.global.config, { merge: false }))
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ describe("createChildStoreManager", () => {
|
||||
onDispose() {},
|
||||
translate: (key) => key,
|
||||
getSdk: () => null!,
|
||||
global: { provider: 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 { OpencodeClient, VcsInfo } from "@opencode-ai/sdk/v2/client"
|
||||
import type { OpencodeClient, ProviderListResponse, VcsInfo } from "@opencode-ai/sdk/v2/client"
|
||||
import {
|
||||
DIR_IDLE_TTL_MS,
|
||||
MAX_DIR_STORES,
|
||||
@@ -27,6 +27,9 @@ export function createChildStoreManager(input: {
|
||||
onDispose: (directory: string) => void
|
||||
translate: (key: string, vars?: Record<string, string | number>) => string
|
||||
getSdk: (directory: string) => OpencodeClient
|
||||
global: {
|
||||
provider: ProviderListResponse
|
||||
}
|
||||
}) {
|
||||
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
|
||||
const vcsCache = new Map<string, VcsCache>()
|
||||
@@ -189,7 +192,13 @@ export function createChildStoreManager(input: {
|
||||
get provider_ready() {
|
||||
return !providerQuery.isLoading
|
||||
},
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
get provider() {
|
||||
const EMPTY = { all: [], connected: [], default: {} }
|
||||
if (providerQuery.isLoading) return EMPTY
|
||||
if (providerQuery.data?.all.length === 0 && input.global.provider.all.length > 0)
|
||||
return input.global.provider
|
||||
return providerQuery.data ?? EMPTY
|
||||
},
|
||||
config: {},
|
||||
get path() {
|
||||
if (pathQuery.isLoading || !pathQuery.data)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @refresh reload
|
||||
|
||||
import * as Sentry from "@sentry/solid"
|
||||
import { render } from "solid-js/web"
|
||||
import { AppBaseProviders, AppInterface } from "@/app"
|
||||
import { type Platform, PlatformProvider } from "@/context/platform"
|
||||
@@ -125,6 +126,25 @@ const platform: Platform = {
|
||||
setDefaultServer: writeDefaultServerUrl,
|
||||
}
|
||||
|
||||
if (import.meta.env.VITE_SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||||
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT ?? import.meta.env.MODE,
|
||||
release: import.meta.env.VITE_SENTRY_RELEASE ?? `web@${pkg.version}`,
|
||||
initialScope: {
|
||||
tags: {
|
||||
platform: "web",
|
||||
},
|
||||
},
|
||||
integrations: (integrations) => {
|
||||
return integrations.filter(
|
||||
(i) =>
|
||||
i.name !== "Breadcrumbs" && !(import.meta.env.OPENCODE_CHANNEL === "prod" && i.name === "GlobalHandlers"),
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (root instanceof HTMLElement) {
|
||||
const server: ServerConnection.Http = { type: "http", http: { url: getCurrentUrl() } }
|
||||
render(
|
||||
|
||||
4
packages/app/src/env.d.ts
vendored
4
packages/app/src/env.d.ts
vendored
@@ -2,6 +2,10 @@ interface ImportMetaEnv {
|
||||
readonly VITE_OPENCODE_SERVER_HOST: string
|
||||
readonly VITE_OPENCODE_SERVER_PORT: string
|
||||
readonly VITE_OPENCODE_CHANNEL?: "dev" | "beta" | "prod"
|
||||
|
||||
readonly VITE_SENTRY_DSN?: string
|
||||
readonly VITE_SENTRY_ENVIRONMENT?: string
|
||||
readonly VITE_SENTRY_RELEASE?: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
||||
@@ -402,6 +402,8 @@ export const dict = {
|
||||
"error.page.description": "حدث خطأ أثناء تحميل التطبيق.",
|
||||
"error.page.details.label": "تفاصيل الخطأ",
|
||||
"error.page.action.restart": "إعادة تشغيل",
|
||||
"error.page.action.report": "الإبلاغ عن الخطأ",
|
||||
"error.page.action.reported": "تم الإبلاغ عن الخطأ",
|
||||
"error.page.action.checking": "جارٍ التحقق...",
|
||||
"error.page.action.checkUpdates": "التحقق من وجود تحديثات",
|
||||
"error.page.action.updateTo": "تحديث إلى {{version}}",
|
||||
|
||||
@@ -403,6 +403,8 @@ export const dict = {
|
||||
"error.page.description": "Ocorreu um erro ao carregar a aplicação.",
|
||||
"error.page.details.label": "Detalhes do Erro",
|
||||
"error.page.action.restart": "Reiniciar",
|
||||
"error.page.action.report": "Reportar erro",
|
||||
"error.page.action.reported": "Erro reportado",
|
||||
"error.page.action.checking": "Verificando...",
|
||||
"error.page.action.checkUpdates": "Verificar atualizações",
|
||||
"error.page.action.updateTo": "Atualizar para {{version}}",
|
||||
|
||||
@@ -449,6 +449,8 @@ export const dict = {
|
||||
"error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.",
|
||||
"error.page.details.label": "Detalji greške",
|
||||
"error.page.action.restart": "Restartuj",
|
||||
"error.page.action.report": "Prijavi grešku",
|
||||
"error.page.action.reported": "Greška prijavljena",
|
||||
"error.page.action.checking": "Provjera...",
|
||||
"error.page.action.checkUpdates": "Provjeri ažuriranja",
|
||||
"error.page.action.updateTo": "Ažuriraj na {{version}}",
|
||||
|
||||
@@ -446,6 +446,8 @@ export const dict = {
|
||||
"error.page.description": "Der opstod en fejl under indlæsning af applikationen.",
|
||||
"error.page.details.label": "Fejldetaljer",
|
||||
"error.page.action.restart": "Genstart",
|
||||
"error.page.action.report": "Rapportér fejl",
|
||||
"error.page.action.reported": "Fejl rapporteret",
|
||||
"error.page.action.checking": "Tjekker...",
|
||||
"error.page.action.checkUpdates": "Tjek for opdateringer",
|
||||
"error.page.action.updateTo": "Opdater til {{version}}",
|
||||
|
||||
@@ -410,6 +410,8 @@ export const dict = {
|
||||
"error.page.description": "Beim Laden der Anwendung ist ein Fehler aufgetreten.",
|
||||
"error.page.details.label": "Fehlerdetails",
|
||||
"error.page.action.restart": "Neustart",
|
||||
"error.page.action.report": "Fehler melden",
|
||||
"error.page.action.reported": "Fehler gemeldet",
|
||||
"error.page.action.checking": "Prüfen...",
|
||||
"error.page.action.checkUpdates": "Nach Updates suchen",
|
||||
"error.page.action.updateTo": "Auf {{version}} aktualisieren",
|
||||
|
||||
@@ -465,6 +465,8 @@ export const dict = {
|
||||
"error.page.description": "An error occurred while loading the application.",
|
||||
"error.page.details.label": "Error Details",
|
||||
"error.page.action.restart": "Restart",
|
||||
"error.page.action.report": "Report Error",
|
||||
"error.page.action.reported": "Error Reported",
|
||||
"error.page.action.checking": "Checking...",
|
||||
"error.page.action.checkUpdates": "Check for updates",
|
||||
"error.page.action.updateTo": "Update to {{version}}",
|
||||
|
||||
@@ -449,6 +449,8 @@ export const dict = {
|
||||
"error.page.description": "Ocurrió un error al cargar la aplicación.",
|
||||
"error.page.details.label": "Detalles del error",
|
||||
"error.page.action.restart": "Reiniciar",
|
||||
"error.page.action.report": "Informar error",
|
||||
"error.page.action.reported": "Error informado",
|
||||
"error.page.action.checking": "Comprobando...",
|
||||
"error.page.action.checkUpdates": "Buscar actualizaciones",
|
||||
"error.page.action.updateTo": "Actualizar a {{version}}",
|
||||
|
||||
@@ -406,6 +406,8 @@ export const dict = {
|
||||
"error.page.description": "Une erreur s'est produite lors du chargement de l'application.",
|
||||
"error.page.details.label": "Détails de l'erreur",
|
||||
"error.page.action.restart": "Redémarrer",
|
||||
"error.page.action.report": "Signaler l'erreur",
|
||||
"error.page.action.reported": "Erreur signalée",
|
||||
"error.page.action.checking": "Vérification...",
|
||||
"error.page.action.checkUpdates": "Vérifier les mises à jour",
|
||||
"error.page.action.updateTo": "Mettre à jour vers {{version}}",
|
||||
|
||||
@@ -402,6 +402,8 @@ export const dict = {
|
||||
"error.page.description": "アプリケーションの読み込み中にエラーが発生しました。",
|
||||
"error.page.details.label": "エラー詳細",
|
||||
"error.page.action.restart": "再起動",
|
||||
"error.page.action.report": "エラーを報告",
|
||||
"error.page.action.reported": "エラーを報告しました",
|
||||
"error.page.action.checking": "確認中...",
|
||||
"error.page.action.checkUpdates": "アップデートを確認",
|
||||
"error.page.action.updateTo": "{{version}}にアップデート",
|
||||
|
||||
@@ -401,6 +401,8 @@ export const dict = {
|
||||
"error.page.description": "애플리케이션을 로드하는 동안 오류가 발생했습니다.",
|
||||
"error.page.details.label": "오류 세부 정보",
|
||||
"error.page.action.restart": "다시 시작",
|
||||
"error.page.action.report": "오류 신고",
|
||||
"error.page.action.reported": "오류가 신고됨",
|
||||
"error.page.action.checking": "확인 중...",
|
||||
"error.page.action.checkUpdates": "업데이트 확인",
|
||||
"error.page.action.updateTo": "{{version}} 버전으로 업데이트",
|
||||
|
||||
@@ -450,6 +450,8 @@ export const dict = {
|
||||
"error.page.description": "Det oppstod en feil under lasting av applikasjonen.",
|
||||
"error.page.details.label": "Feildetaljer",
|
||||
"error.page.action.restart": "Start på nytt",
|
||||
"error.page.action.report": "Rapporter feil",
|
||||
"error.page.action.reported": "Feil rapportert",
|
||||
"error.page.action.checking": "Sjekker...",
|
||||
"error.page.action.checkUpdates": "Se etter oppdateringer",
|
||||
"error.page.action.updateTo": "Oppdater til {{version}}",
|
||||
|
||||
@@ -403,6 +403,8 @@ export const dict = {
|
||||
"error.page.description": "Wystąpił błąd podczas ładowania aplikacji.",
|
||||
"error.page.details.label": "Szczegóły błędu",
|
||||
"error.page.action.restart": "Restartuj",
|
||||
"error.page.action.report": "Zgłoś błąd",
|
||||
"error.page.action.reported": "Błąd zgłoszony",
|
||||
"error.page.action.checking": "Sprawdzanie...",
|
||||
"error.page.action.checkUpdates": "Sprawdź aktualizacje",
|
||||
"error.page.action.updateTo": "Zaktualizuj do {{version}}",
|
||||
|
||||
@@ -448,6 +448,8 @@ export const dict = {
|
||||
"error.page.description": "Произошла ошибка при загрузке приложения.",
|
||||
"error.page.details.label": "Детали ошибки",
|
||||
"error.page.action.restart": "Перезапустить",
|
||||
"error.page.action.report": "Сообщить об ошибке",
|
||||
"error.page.action.reported": "Об ошибке сообщено",
|
||||
"error.page.action.checking": "Проверка...",
|
||||
"error.page.action.checkUpdates": "Проверить обновления",
|
||||
"error.page.action.updateTo": "Обновить до {{version}}",
|
||||
|
||||
@@ -447,6 +447,8 @@ export const dict = {
|
||||
"error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน",
|
||||
"error.page.details.label": "รายละเอียดข้อผิดพลาด",
|
||||
"error.page.action.restart": "รีสตาร์ท",
|
||||
"error.page.action.report": "รายงานข้อผิดพลาด",
|
||||
"error.page.action.reported": "รายงานข้อผิดพลาดแล้ว",
|
||||
"error.page.action.checking": "กำลังตรวจสอบ...",
|
||||
"error.page.action.checkUpdates": "ตรวจสอบการอัปเดต",
|
||||
"error.page.action.updateTo": "อัปเดตเป็น {{version}}",
|
||||
|
||||
@@ -452,6 +452,8 @@ export const dict = {
|
||||
"error.page.description": "Uygulama yüklenirken bir hata oluştu.",
|
||||
"error.page.details.label": "Hata Detayları",
|
||||
"error.page.action.restart": "Yeniden Başlat",
|
||||
"error.page.action.report": "Hatayı Bildir",
|
||||
"error.page.action.reported": "Hata Bildirildi",
|
||||
"error.page.action.checking": "Kontrol ediliyor...",
|
||||
"error.page.action.checkUpdates": "Güncellemeleri kontrol et",
|
||||
"error.page.action.updateTo": "{{version}} sürümüne güncelle",
|
||||
|
||||
@@ -452,6 +452,8 @@ export const dict = {
|
||||
"error.page.description": "加载应用程序时发生错误。",
|
||||
"error.page.details.label": "错误详情",
|
||||
"error.page.action.restart": "重启",
|
||||
"error.page.action.report": "上报错误",
|
||||
"error.page.action.reported": "错误已上报",
|
||||
"error.page.action.checking": "检查中...",
|
||||
"error.page.action.checkUpdates": "检查更新",
|
||||
"error.page.action.updateTo": "更新到 {{version}}",
|
||||
|
||||
@@ -445,6 +445,8 @@ export const dict = {
|
||||
"error.page.description": "載入應用程式時發生錯誤。",
|
||||
"error.page.details.label": "錯誤詳情",
|
||||
"error.page.action.restart": "重新啟動",
|
||||
"error.page.action.report": "回報錯誤",
|
||||
"error.page.action.reported": "已回報錯誤",
|
||||
"error.page.action.checking": "檢查中...",
|
||||
"error.page.action.checkUpdates": "檢查更新",
|
||||
"error.page.action.updateTo": "更新到 {{version}}",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { TextField } from "@opencode-ai/ui/text-field"
|
||||
import * as Sentry from "@sentry/solid"
|
||||
import { Logo } from "@opencode-ai/ui/logo"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { Component, Show } from "solid-js"
|
||||
import { Component, createSignal, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { useLanguage } from "@/context/language"
|
||||
@@ -270,10 +271,27 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
||||
label={language.t("error.page.details.label")}
|
||||
hideLabel
|
||||
/>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex flex-row items-center justify-center gap-3 flex-wrap max-w-64">
|
||||
<Button size="large" onClick={platform.restart}>
|
||||
{language.t("error.page.action.restart")}
|
||||
</Button>
|
||||
<Show when={Sentry.isEnabled}>
|
||||
{(_) => {
|
||||
const [reported, setReported] = createSignal(false)
|
||||
return (
|
||||
<Button
|
||||
size="large"
|
||||
disabled={reported()}
|
||||
onClick={() => {
|
||||
Sentry.captureException(props.error)
|
||||
setReported(true)
|
||||
}}
|
||||
>
|
||||
{language.t(reported() ? "error.page.action.reported" : "error.page.action.report")}
|
||||
</Button>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
<Show when={platform.checkUpdate}>
|
||||
<Show
|
||||
when={store.version}
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
import { sentryVitePlugin } from "@sentry/vite-plugin"
|
||||
import { defineConfig } from "vite"
|
||||
import desktopPlugin from "./vite"
|
||||
|
||||
const sentry =
|
||||
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && process.env.SENTRY_PROJECT
|
||||
? sentryVitePlugin({
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
org: process.env.SENTRY_ORG,
|
||||
project: process.env.SENTRY_PROJECT,
|
||||
telemetry: false,
|
||||
release: {
|
||||
name: process.env.SENTRY_RELEASE ?? process.env.VITE_SENTRY_RELEASE,
|
||||
},
|
||||
sourcemaps: {
|
||||
assets: "./dist/**",
|
||||
filesToDeleteAfterUpload: "./dist/**/*.map",
|
||||
},
|
||||
})
|
||||
: false
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [desktopPlugin] as any,
|
||||
plugins: [desktopPlugin, sentry] as any,
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
allowedHosts: true,
|
||||
@@ -10,6 +28,6 @@ export default defineConfig({
|
||||
},
|
||||
build: {
|
||||
target: "esnext",
|
||||
// sourcemap: true,
|
||||
sourcemap: true,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -141,7 +141,10 @@ export async function handler(
|
||||
)
|
||||
validateModelSettings(billingSource, authInfo)
|
||||
updateProviderKey(authInfo, providerInfo)
|
||||
logger.metric({ provider: providerInfo.id })
|
||||
logger.metric({
|
||||
provider: providerInfo.id,
|
||||
"provider.model": providerInfo.model,
|
||||
})
|
||||
|
||||
const startTimestamp = Date.now()
|
||||
const reqUrl = providerInfo.modifyUrl(providerInfo.api, isStream)
|
||||
@@ -149,12 +152,23 @@ export async function handler(
|
||||
providerInfo.modifyBody({
|
||||
...createBodyConverter(opts.format, providerInfo.format)(body),
|
||||
model: providerInfo.model,
|
||||
...providerInfo.payloadModifier,
|
||||
...Object.fromEntries(
|
||||
Object.entries(providerInfo.payloadMappings ?? {})
|
||||
.map(([k, v]) => [k, input.request.headers.get(v)])
|
||||
.filter(([_k, v]) => !!v),
|
||||
),
|
||||
...(() => {
|
||||
const replacer = (obj: Record<string, any>): Record<string, any> =>
|
||||
Object.fromEntries(
|
||||
Object.entries(obj).flatMap(([k, v]) => {
|
||||
if (Array.isArray(v)) return [[k, v]]
|
||||
if (typeof v === "object") return [[k, replacer(v)]]
|
||||
if (v === "$ip") return [[k, ip]]
|
||||
if (v === "$workspace") return authInfo?.workspaceID ? [[k, authInfo?.workspaceID]] : []
|
||||
if (v.startsWith("$header.")) {
|
||||
const headerValue = input.request.headers.get(v.slice(8))
|
||||
return headerValue ? [[k, headerValue]] : []
|
||||
}
|
||||
return [[k, v]]
|
||||
}),
|
||||
)
|
||||
return replacer(providerInfo.payloadModifier ?? {})
|
||||
})(),
|
||||
}),
|
||||
)
|
||||
logger.debug("REQUEST URL: " + reqUrl)
|
||||
@@ -514,7 +528,6 @@ export async function handler(
|
||||
reqModel,
|
||||
providerModel: modelProvider.model,
|
||||
adjustCacheUsage: providerProps.adjustCacheUsage,
|
||||
safetyIdentifier: modelProvider.safetyIdentifier ? ip : undefined,
|
||||
workspaceID: authInfo?.workspaceID,
|
||||
}
|
||||
if (format === "anthropic") return anthropicHelper(opts)
|
||||
|
||||
@@ -23,7 +23,7 @@ type Usage = {
|
||||
}
|
||||
}
|
||||
|
||||
export const oaCompatHelper: ProviderHelper = ({ adjustCacheUsage, safetyIdentifier }) => ({
|
||||
export const oaCompatHelper: ProviderHelper = ({ adjustCacheUsage }) => ({
|
||||
format: "oa-compat",
|
||||
modifyUrl: (providerApi: string) => providerApi + "/chat/completions",
|
||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
||||
@@ -34,7 +34,6 @@ export const oaCompatHelper: ProviderHelper = ({ adjustCacheUsage, safetyIdentif
|
||||
return {
|
||||
...body,
|
||||
...(body.stream ? { stream_options: { include_usage: true } } : {}),
|
||||
...(safetyIdentifier ? { safety_identifier: safetyIdentifier } : {}),
|
||||
}
|
||||
},
|
||||
createBinaryStreamDecoder: () => undefined,
|
||||
|
||||
@@ -18,10 +18,7 @@ export const openaiHelper: ProviderHelper = ({ workspaceID }) => ({
|
||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
||||
headers.set("authorization", `Bearer ${apiKey}`)
|
||||
},
|
||||
modifyBody: (body: Record<string, any>) => ({
|
||||
...body,
|
||||
...(workspaceID ? { safety_identifier: workspaceID } : {}),
|
||||
}),
|
||||
modifyBody: (body: Record<string, any>) => body,
|
||||
createBinaryStreamDecoder: () => undefined,
|
||||
streamSeparator: "\n\n",
|
||||
createUsageParser: () => {
|
||||
|
||||
@@ -37,7 +37,6 @@ export type ProviderHelper = (input: {
|
||||
reqModel: string
|
||||
providerModel: string
|
||||
adjustCacheUsage?: boolean
|
||||
safetyIdentifier?: string
|
||||
workspaceID?: string
|
||||
}) => {
|
||||
format: ZenData.Format
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -40,7 +40,6 @@ export namespace ZenData {
|
||||
disabled: z.boolean().optional(),
|
||||
storeModel: z.string().optional(),
|
||||
payloadModifier: z.record(z.string(), z.any()).optional(),
|
||||
safetyIdentifier: z.boolean().optional(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Config } from "effect"
|
||||
import { InstallationChannel } from "../installation/version"
|
||||
|
||||
function truthy(key: string) {
|
||||
const value = process.env[key]?.toLowerCase()
|
||||
@@ -10,6 +11,10 @@ function falsy(key: string) {
|
||||
return value === "false" || value === "0"
|
||||
}
|
||||
|
||||
// Channels that default to the new effect-httpapi server backend. The legacy
|
||||
// hono backend remains the default for stable (`prod`/`latest`) installs.
|
||||
const HTTPAPI_DEFAULT_ON_CHANNELS = new Set(["dev", "beta", "local"])
|
||||
|
||||
function number(key: string) {
|
||||
const value = process.env[key]
|
||||
if (!value) return undefined
|
||||
@@ -81,8 +86,16 @@ export const Flag = {
|
||||
OPENCODE_STRICT_CONFIG_DEPS: truthy("OPENCODE_STRICT_CONFIG_DEPS"),
|
||||
|
||||
OPENCODE_WORKSPACE_ID: process.env["OPENCODE_WORKSPACE_ID"],
|
||||
OPENCODE_EXPERIMENTAL_HTTPAPI: truthy("OPENCODE_EXPERIMENTAL_HTTPAPI"),
|
||||
// Defaults to true on dev/beta/local channels so internal users exercise the
|
||||
// new effect-httpapi server backend. Stable (`prod`/`latest`) installs stay
|
||||
// on the legacy hono backend until the rollout is complete. An explicit env
|
||||
// var ("true"/"1" or "false"/"0") always wins, providing an opt-in for
|
||||
// stable users and an escape hatch for dev/beta users.
|
||||
OPENCODE_EXPERIMENTAL_HTTPAPI:
|
||||
truthy("OPENCODE_EXPERIMENTAL_HTTPAPI") ||
|
||||
(!falsy("OPENCODE_EXPERIMENTAL_HTTPAPI") && HTTPAPI_DEFAULT_ON_CHANNELS.has(InstallationChannel)),
|
||||
OPENCODE_EXPERIMENTAL_WORKSPACES: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES"),
|
||||
OPENCODE_EXPERIMENTAL_EVENT_SYSTEM: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"),
|
||||
|
||||
// Evaluated at access time (not module load) because tests, the CLI, and
|
||||
// external tooling set these env vars at runtime.
|
||||
|
||||
@@ -11,6 +11,7 @@ const data = path.join(xdgData!, app)
|
||||
const cache = path.join(xdgCache!, app)
|
||||
const config = path.join(xdgConfig!, app)
|
||||
const state = path.join(xdgState!, app)
|
||||
const tmp = path.join(os.tmpdir(), app)
|
||||
|
||||
const paths = {
|
||||
get home() {
|
||||
@@ -22,6 +23,7 @@ const paths = {
|
||||
cache,
|
||||
config,
|
||||
state,
|
||||
tmp,
|
||||
}
|
||||
|
||||
export const Path = paths
|
||||
@@ -32,6 +34,7 @@ await Promise.all([
|
||||
fs.mkdir(Path.data, { recursive: true }),
|
||||
fs.mkdir(Path.config, { recursive: true }),
|
||||
fs.mkdir(Path.state, { recursive: true }),
|
||||
fs.mkdir(Path.tmp, { recursive: true }),
|
||||
fs.mkdir(Path.log, { recursive: true }),
|
||||
fs.mkdir(Path.bin, { recursive: true }),
|
||||
])
|
||||
@@ -44,6 +47,7 @@ export interface Interface {
|
||||
readonly cache: string
|
||||
readonly config: string
|
||||
readonly state: string
|
||||
readonly tmp: string
|
||||
readonly bin: string
|
||||
readonly log: string
|
||||
}
|
||||
@@ -55,6 +59,7 @@ export function make(input: Partial<Interface> = {}): Interface {
|
||||
cache: Path.cache,
|
||||
config: Flag.OPENCODE_CONFIG_DIR ?? Path.config,
|
||||
state: Path.state,
|
||||
tmp: Path.tmp,
|
||||
bin: Path.bin,
|
||||
log: Path.log,
|
||||
...input,
|
||||
|
||||
@@ -120,13 +120,17 @@ export const layer = Layer.effect(
|
||||
}
|
||||
})()
|
||||
|
||||
if (yield* afs.existsSafe(dir)) {
|
||||
if (yield* afs.existsSafe(path.join(dir, "node_modules", name))) {
|
||||
return resolveEntryPoint(name, path.join(dir, "node_modules", name))
|
||||
}
|
||||
|
||||
const tree = yield* reify({ dir, add: [pkg] })
|
||||
const first = tree.edgesOut.values().next().value?.to
|
||||
if (!first) return yield* new InstallFailedError({ add: [pkg], dir })
|
||||
if (!first) {
|
||||
const result = resolveEntryPoint(name, path.join(dir, "node_modules", name))
|
||||
if (Option.isSome(result.entrypoint)) return result
|
||||
return yield* new InstallFailedError({ add: [pkg], dir })
|
||||
}
|
||||
return resolveEntryPoint(first.name, first.path)
|
||||
}, Effect.scoped)
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export * as Log from "./log"
|
||||
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { createWriteStream } from "fs"
|
||||
|
||||
16
packages/core/test/global.test.ts
Normal file
16
packages/core/test/global.test.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import fs from "fs/promises"
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
|
||||
describe("global paths", () => {
|
||||
test("tmp path is under the system temp directory", () => {
|
||||
expect(Global.Path.tmp).toBe(path.join(os.tmpdir(), "opencode"))
|
||||
expect(Global.make().tmp).toBe(Global.Path.tmp)
|
||||
})
|
||||
|
||||
test("tmp path is created on module load", async () => {
|
||||
expect((await fs.stat(Global.Path.tmp)).isDirectory()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,12 @@
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { Effect, Layer, Option } from "effect"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
|
||||
import { tmpdir } from "./fixture/tmpdir"
|
||||
|
||||
const win = process.platform === "win32"
|
||||
@@ -15,6 +20,14 @@ const writePackage = (dir: string, pkg: Record<string, unknown>) =>
|
||||
}),
|
||||
)
|
||||
|
||||
const npmLayer = (cache: string) =>
|
||||
Npm.layer.pipe(
|
||||
Layer.provide(EffectFlock.layer),
|
||||
Layer.provide(AppFileSystem.layer),
|
||||
Layer.provide(Global.layerWith({ cache, state: path.join(cache, "state") })),
|
||||
Layer.provide(NodeFileSystem.layer),
|
||||
)
|
||||
|
||||
describe("Npm.sanitize", () => {
|
||||
test("keeps normal scoped package specs unchanged", () => {
|
||||
expect(Npm.sanitize("@opencode/acme")).toBe("@opencode/acme")
|
||||
@@ -29,6 +42,28 @@ describe("Npm.sanitize", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("Npm.add", () => {
|
||||
test("reifies when package cache directory exists without the package installed", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await fs.mkdir(path.join(tmp.path, "fixture-provider"))
|
||||
await writePackage(path.join(tmp.path, "fixture-provider"), {
|
||||
name: "fixture-provider",
|
||||
main: "index.js",
|
||||
})
|
||||
await Bun.write(path.join(tmp.path, "fixture-provider", "index.js"), "export const fixture = true\n")
|
||||
|
||||
const spec = `fixture-provider@file:${path.join(tmp.path, "fixture-provider")}`
|
||||
await fs.mkdir(path.join(tmp.path, "cache", "packages", Npm.sanitize(spec)), { recursive: true })
|
||||
|
||||
const entry = await Effect.gen(function* () {
|
||||
const npm = yield* Npm.Service
|
||||
return yield* npm.add(spec)
|
||||
}).pipe(Effect.scoped, Effect.provide(npmLayer(path.join(tmp.path, "cache"))), Effect.runPromise)
|
||||
|
||||
expect(Option.isSome(entry.entrypoint)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Npm.install", () => {
|
||||
test("respects omit from project .npmrc", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@tsconfig/bun/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noUncheckedIndexedAccess": false,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "@effect/language-service",
|
||||
"transform": "@effect/language-service/transform",
|
||||
"namespaceImportPackages": ["effect", "@effect/*"]
|
||||
}
|
||||
]
|
||||
"noUncheckedIndexedAccess": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { sentryVitePlugin } from "@sentry/vite-plugin"
|
||||
import { defineConfig } from "electron-vite"
|
||||
import appPlugin from "@opencode-ai/app/vite"
|
||||
import * as fs from "node:fs/promises"
|
||||
@@ -12,6 +13,23 @@ const OPENCODE_SERVER_DIST = "../opencode/dist/node"
|
||||
|
||||
const nodePtyPkg = `@lydell/node-pty-${process.platform}-${process.arch}`
|
||||
|
||||
const sentry =
|
||||
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && process.env.SENTRY_PROJECT
|
||||
? sentryVitePlugin({
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
org: process.env.SENTRY_ORG,
|
||||
project: process.env.SENTRY_PROJECT,
|
||||
telemetry: false,
|
||||
release: {
|
||||
name: process.env.SENTRY_RELEASE ?? process.env.VITE_SENTRY_RELEASE,
|
||||
},
|
||||
sourcemaps: {
|
||||
assets: "./out/renderer/**",
|
||||
filesToDeleteAfterUpload: "./out/renderer/**/*.map",
|
||||
},
|
||||
})
|
||||
: false
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
define: {
|
||||
@@ -61,13 +79,14 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
plugins: [appPlugin],
|
||||
plugins: [appPlugin, sentry],
|
||||
publicDir: "../../../app/public",
|
||||
root: "src/renderer",
|
||||
define: {
|
||||
"import.meta.env.VITE_OPENCODE_CHANNEL": JSON.stringify(channel),
|
||||
},
|
||||
build: {
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: "src/renderer/index.html",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"private": true,
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
@@ -38,6 +38,8 @@
|
||||
"@lydell/node-pty": "catalog:",
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@solid-primitives/i18n": "2.2.1",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@solidjs/meta": "catalog:",
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ServerConnection,
|
||||
useCommand,
|
||||
} from "@opencode-ai/app"
|
||||
import * as Sentry from "@sentry/solid"
|
||||
import type { AsyncStorage } from "@solid-primitives/storage"
|
||||
import { MemoryRouter } from "@solidjs/router"
|
||||
import { createEffect, createResource, onCleanup, onMount, Show } from "solid-js"
|
||||
@@ -29,6 +30,25 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||
throw new Error(t("error.dev.rootNotFound"))
|
||||
}
|
||||
|
||||
if (import.meta.env.VITE_SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||||
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT ?? import.meta.env.MODE,
|
||||
release: import.meta.env.VITE_SENTRY_RELEASE ?? `desktop-electron@${pkg.version}`,
|
||||
initialScope: {
|
||||
tags: {
|
||||
platform: "desktop-electron",
|
||||
},
|
||||
},
|
||||
integrations: (integrations) => {
|
||||
return integrations.filter(
|
||||
(i) =>
|
||||
i.name !== "Breadcrumbs" && !(import.meta.env.OPENCODE_CHANNEL === "prod" && i.name === "GlobalHandlers"),
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
void initI18n()
|
||||
|
||||
const deepLinkEvent = "opencode:deep-link"
|
||||
|
||||
@@ -26,13 +26,20 @@ const applyZoom = (next: number) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (!(OS_NAME === "macos" ? event.metaKey : event.ctrlKey)) return
|
||||
|
||||
let newZoom = webviewZoom()
|
||||
|
||||
if (event.key === "-") newZoom -= 0.2
|
||||
if (event.key === "=" || event.key === "+") newZoom += 0.2
|
||||
if (event.key === "0") newZoom = 1
|
||||
|
||||
applyZoom(clamp(newZoom))
|
||||
if (event.key === "-") {
|
||||
event.preventDefault()
|
||||
applyZoom(clamp(webviewZoom() - 0.2))
|
||||
return
|
||||
}
|
||||
if (event.key === "=" || event.key === "+") {
|
||||
event.preventDefault()
|
||||
applyZoom(clamp(webviewZoom() + 0.2))
|
||||
return
|
||||
}
|
||||
if (event.key === "0") {
|
||||
event.preventDefault()
|
||||
applyZoom(1)
|
||||
}
|
||||
})
|
||||
|
||||
export { webviewZoom }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -15,6 +15,7 @@
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@solid-primitives/i18n": "2.2.1",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@tauri-apps/api": "^2",
|
||||
@@ -35,6 +36,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/artifact": "4.0.0",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@types/bun": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
|
||||
9
packages/desktop/src/env.d.ts
vendored
Normal file
9
packages/desktop/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_SENTRY_DSN?: string
|
||||
readonly VITE_SENTRY_ENVIRONMENT?: string
|
||||
readonly VITE_SENTRY_RELEASE?: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ServerConnection,
|
||||
useCommand,
|
||||
} from "@opencode-ai/app"
|
||||
import * as Sentry from "@sentry/solid"
|
||||
import type { AsyncStorage } from "@solid-primitives/storage"
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||
import { readImage } from "@tauri-apps/plugin-clipboard-manager"
|
||||
|
||||
@@ -15,9 +15,9 @@ export default defineConfig({
|
||||
// Improves production stack traces
|
||||
keepNames: true,
|
||||
},
|
||||
// build: {
|
||||
// sourcemap: true,
|
||||
// },
|
||||
build: {
|
||||
sourcemap: true,
|
||||
},
|
||||
// 2. tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.14.30"
|
||||
version = "1.14.33"
|
||||
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.30/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.33/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.30/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.33/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.30/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.33/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.30/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.33/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.30/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.33/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE `session_message` (
|
||||
`id` text PRIMARY KEY,
|
||||
`session_id` text NOT NULL,
|
||||
`type` text NOT NULL,
|
||||
`time_created` integer NOT NULL,
|
||||
`time_updated` integer NOT NULL,
|
||||
`data` text NOT NULL,
|
||||
CONSTRAINT `fk_session_message_session_id_session_id_fk` FOREIGN KEY (`session_id`) REFERENCES `session`(`id`) ON DELETE CASCADE
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DROP INDEX IF EXISTS `session_entry_session_idx`;--> statement-breakpoint
|
||||
DROP INDEX IF EXISTS `session_entry_session_type_idx`;--> statement-breakpoint
|
||||
DROP INDEX IF EXISTS `session_entry_time_created_idx`;--> statement-breakpoint
|
||||
CREATE INDEX `session_message_session_idx` ON `session_message` (`session_id`);--> statement-breakpoint
|
||||
CREATE INDEX `session_message_session_type_idx` ON `session_message` (`session_id`,`type`);--> statement-breakpoint
|
||||
CREATE INDEX `session_message_time_created_idx` ON `session_message` (`time_created`);--> statement-breakpoint
|
||||
DROP TABLE `session_entry`;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"id": "aaa2ebeb-caa4-478d-8365-4fc595d16856",
|
||||
"prevIds": ["66cbe0d7-def0-451b-b88a-7608513a9b44"],
|
||||
"prevIds": ["61f807f9-6398-4067-be05-804acc2561bc"],
|
||||
"ddl": [
|
||||
{
|
||||
"name": "account_state",
|
||||
@@ -37,7 +37,7 @@
|
||||
"entityType": "tables"
|
||||
},
|
||||
{
|
||||
"name": "session_entry",
|
||||
"name": "session_message",
|
||||
"entityType": "tables"
|
||||
},
|
||||
{
|
||||
@@ -598,7 +598,7 @@
|
||||
"generated": null,
|
||||
"name": "id",
|
||||
"entityType": "columns",
|
||||
"table": "session_entry"
|
||||
"table": "session_message"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
@@ -608,7 +608,7 @@
|
||||
"generated": null,
|
||||
"name": "session_id",
|
||||
"entityType": "columns",
|
||||
"table": "session_entry"
|
||||
"table": "session_message"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
@@ -618,7 +618,7 @@
|
||||
"generated": null,
|
||||
"name": "type",
|
||||
"entityType": "columns",
|
||||
"table": "session_entry"
|
||||
"table": "session_message"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
@@ -628,7 +628,7 @@
|
||||
"generated": null,
|
||||
"name": "time_created",
|
||||
"entityType": "columns",
|
||||
"table": "session_entry"
|
||||
"table": "session_message"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
@@ -638,7 +638,7 @@
|
||||
"generated": null,
|
||||
"name": "time_updated",
|
||||
"entityType": "columns",
|
||||
"table": "session_entry"
|
||||
"table": "session_message"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
@@ -648,7 +648,7 @@
|
||||
"generated": null,
|
||||
"name": "data",
|
||||
"entityType": "columns",
|
||||
"table": "session_entry"
|
||||
"table": "session_message"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
@@ -1112,9 +1112,9 @@
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
"name": "fk_session_entry_session_id_session_id_fk",
|
||||
"name": "fk_session_message_session_id_session_id_fk",
|
||||
"entityType": "fks",
|
||||
"table": "session_entry"
|
||||
"table": "session_message"
|
||||
},
|
||||
{
|
||||
"columns": ["project_id"],
|
||||
@@ -1226,8 +1226,8 @@
|
||||
{
|
||||
"columns": ["id"],
|
||||
"nameExplicit": false,
|
||||
"name": "session_entry_pk",
|
||||
"table": "session_entry",
|
||||
"name": "session_message_pk",
|
||||
"table": "session_message",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
@@ -1322,9 +1322,9 @@
|
||||
"isUnique": false,
|
||||
"where": null,
|
||||
"origin": "manual",
|
||||
"name": "session_entry_session_idx",
|
||||
"name": "session_message_session_idx",
|
||||
"entityType": "indexes",
|
||||
"table": "session_entry"
|
||||
"table": "session_message"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
@@ -1340,9 +1340,9 @@
|
||||
"isUnique": false,
|
||||
"where": null,
|
||||
"origin": "manual",
|
||||
"name": "session_entry_session_type_idx",
|
||||
"name": "session_message_session_type_idx",
|
||||
"entityType": "indexes",
|
||||
"table": "session_entry"
|
||||
"table": "session_message"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
@@ -1354,9 +1354,9 @@
|
||||
"isUnique": false,
|
||||
"where": null,
|
||||
"origin": "manual",
|
||||
"name": "session_entry_time_created_idx",
|
||||
"name": "session_message_time_created_idx",
|
||||
"entityType": "indexes",
|
||||
"table": "session_entry"
|
||||
"table": "session_message"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `session` ADD `agent` text;--> statement-breakpoint
|
||||
ALTER TABLE `session` ADD `model` text;
|
||||
1439
packages/opencode/migration/20260501142318_next_venus/snapshot.json
Normal file
1439
packages/opencode/migration/20260501142318_next_venus/snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.33",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"prepare": "effect-language-service patch || true",
|
||||
"typecheck": "tsgo --noEmit",
|
||||
"test": "bun test --timeout 30000",
|
||||
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
|
||||
@@ -42,7 +41,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.4",
|
||||
"@effect/language-service": "0.84.2",
|
||||
"@octokit/webhooks-types": "7.6.1",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
|
||||
@@ -61,6 +61,7 @@ const createEmbeddedWebUIBundle = async () => {
|
||||
await $`bun run --cwd ${appDir} build`
|
||||
const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: dist })))
|
||||
.map((file) => file.replaceAll("\\", "/"))
|
||||
.filter((file) => !file.endsWith(".map"))
|
||||
.sort()
|
||||
const imports = files.map((file, i) => {
|
||||
const spec = path.relative(dir, path.join(dist, file)).replaceAll("\\", "/")
|
||||
|
||||
2014
packages/opencode/script/httpapi-exercise.ts
Normal file
2014
packages/opencode/script/httpapi-exercise.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,14 +12,16 @@ Plan for replacing instance Hono route implementations with Effect `HttpApi` whi
|
||||
|
||||
## Current State
|
||||
|
||||
- `OPENCODE_EXPERIMENTAL_HTTPAPI` gates the bridge. Default behavior still uses Hono.
|
||||
- The bridge mounts selected paths in `server/routes/instance/index.ts` before legacy Hono routes.
|
||||
- Legacy Hono routes remain for default behavior and for `hono-openapi` SDK generation.
|
||||
- `HttpApi` auth is independent of Hono auth.
|
||||
- `Authorization` is attached in each route module, not centrally wrapped in `server.ts`.
|
||||
- `OPENCODE_EXPERIMENTAL_HTTPAPI` selects the backend at server startup. Default is still `hono`.
|
||||
- `server/backend.ts` picks one of `effect-httpapi` or `hono`; `server.ts` builds either a pure Effect `HttpApi` web handler or the legacy Hono app accordingly. The earlier in-Hono "bridge" model has been replaced by this fork-at-startup.
|
||||
- Legacy Hono routes remain mounted for the `hono` backend and remain the source for `hono-openapi` SDK generation.
|
||||
- An Effect `HttpApi` OpenAPI surface exists (`OpenApi.fromApi(PublicApi)` in `cli/cmd/generate.ts --httpapi`, `OPENCODE_SDK_OPENAPI=httpapi` in `packages/sdk/js/script/build.ts`) but is opt-in. The default SDK generation is still Hono.
|
||||
- `httpapi/public.ts` carries the Hono-compat normalization for the Effect-generated OpenAPI surface (auth scheme strip, request-body required flag, optional `null` arms, `BadRequestError` / `NotFoundError` remap, `$ref` self-cycle fix, `auth_token` query injection). Today's Effect-generated SDK is not byte-identical to the Hono-generated SDK — see Phase 4.
|
||||
- Auth is centrally configured for the Effect backend via Effect `Config` (`refactor: use Effect config for HttpApi authorization`, `Fix HttpApi raw route authorization`) rather than re-attached in each route module.
|
||||
- Auth supports Basic auth and the legacy `auth_token` query parameter through `HttpApiSecurity.apiKey`.
|
||||
- Instance context is provided by `httpapi/server.ts` using `directory`, `workspace`, and `x-opencode-directory`.
|
||||
- `Observability.layer` is provided in the Effect route layer and deduplicated through the shared `memoMap`.
|
||||
- CORS middleware is wired into both backends (`feat(httpapi): add CORS middleware to instance routes`).
|
||||
|
||||
## Migration Rules
|
||||
|
||||
@@ -122,10 +124,19 @@ Keep large or stateful groups for later:
|
||||
|
||||
Hono routes cannot be deleted while `hono-openapi` is the source of SDK generation.
|
||||
|
||||
Status: the Effect `HttpApi` OpenAPI surface is **implemented and opt-in** (`bun dev generate --httpapi`, `OPENCODE_SDK_OPENAPI=httpapi`). Default SDK generation still uses Hono. `httpapi/public.ts` applies the Hono-compat normalization layer to the Effect output. Diff against the Hono-generated spec still shows real gaps that must be closed before the SDK can flip:
|
||||
|
||||
- Branded-type `pattern` constraints on ID schemas are not propagated to the Effect output (~169 missing).
|
||||
- Per-property `description` annotations are not propagated through `Schema.Struct` to the Effect output (~107 missing).
|
||||
- `Event.*` and `SyncEvent.*` component names use dotted form in Hono and PascalCase in Effect (~50 differences, breaks SDK type names).
|
||||
- Effect's component deduper emits numbered duplicates (`Session9`, `SyncEvent.session.updated.11`) that need a name-collision fix.
|
||||
- Cosmetic-only diffs (`additionalProperties: false`, `const` vs `enum`, MAX_SAFE_INTEGER `maximum`, `propertyNames`) can be normalized in `public.ts` if they would otherwise change SDK output.
|
||||
|
||||
Required before route deletion:
|
||||
|
||||
- Generate the public OpenAPI surface from Effect `HttpApi` for ported routes.
|
||||
- Close the diff above so Effect-generated SDK output matches the Hono-generated SDK output for every retained path.
|
||||
- Keep operation IDs, schemas, status codes, and SDK type names stable unless the change is intentional.
|
||||
- Flip `packages/sdk/js/script/build.ts` default to `httpapi` and regenerate.
|
||||
- Compare generated SDK output against `dev` for every route group deletion.
|
||||
- Remove Hono OpenAPI stubs only after Effect OpenAPI is the SDK source for those paths.
|
||||
|
||||
@@ -187,7 +198,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
|
||||
| `project` | `bridged` | list, current, git init, update |
|
||||
| `file` | `bridged` partial | find text/file/symbol, list/content/status |
|
||||
| `mcp` | `bridged` | status, add, OAuth, connect/disconnect |
|
||||
| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore |
|
||||
| `workspace` | `bridged` | adapter/list/status/create/remove/session-restore |
|
||||
| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose |
|
||||
| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list |
|
||||
| `session` | `bridged` | read, lifecycle, prompt, message/part mutations, revert, permission reply |
|
||||
@@ -279,7 +290,7 @@ This checklist tracks bridge parity only. Checked routes are available through t
|
||||
|
||||
### Workspace Routes
|
||||
|
||||
- [x] `GET /experimental/workspace/adaptor` - list workspace adaptors.
|
||||
- [x] `GET /experimental/workspace/adapter` - list workspace adapters.
|
||||
- [x] `POST /experimental/workspace` - create workspace.
|
||||
- [x] `GET /experimental/workspace` - list workspaces.
|
||||
- [x] `GET /experimental/workspace/status` - workspace status.
|
||||
@@ -365,25 +376,26 @@ Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays rev
|
||||
8. [x] Bridge session read routes: list, status, get, children, todo, diff, messages.
|
||||
9. [x] Bridge session lifecycle mutation routes: create, delete, update, fork, abort.
|
||||
10. [x] Bridge remaining session mutation and prompt routes.
|
||||
11. [ ] Replace event SSE with non-Hono Effect HTTP.
|
||||
12. [x] Replace pty websocket/control routes with non-Hono Effect HTTP.
|
||||
13. [x] Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer.
|
||||
14. [ ] Switch OpenAPI/SDK generation to Effect routes and compare SDK output.
|
||||
15. [ ] Flip ported JSON routes default-on, keep a short fallback, then delete replaced Hono route files.
|
||||
11. [ ] Replace event SSE with non-Hono Effect HTTP. The Effect backend has a raw Effect HTTP `httpapi/event.ts`; the Hono backend still uses `hono/streaming` `streamSSE`. Either port Hono `/event` to raw Effect HTTP for the fallback window, or skip and delete it together with Hono in step 15.
|
||||
12. [x] Replace pty websocket/control routes with non-Hono Effect HTTP for the Effect backend. Hono `pty.ts` remains in the Hono backend.
|
||||
13. [x] Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer for the Effect backend. Hono `tui.ts` remains in the Hono backend.
|
||||
14. [ ] Switch OpenAPI/SDK generation to Effect routes and compare SDK output. Effect path is implemented and opt-in via `--httpapi` / `OPENCODE_SDK_OPENAPI=httpapi`. Close the schema-shape gaps in `public.ts` (branded `pattern`, per-property `description`, `Event.*` / `SyncEvent.*` naming, dedup collisions), then flip `packages/sdk/js/script/build.ts` default.
|
||||
15. [ ] Flip `backend.ts` default from `hono` to `effect-httpapi`, keep `OPENCODE_EXPERIMENTAL_HTTPAPI` (or its inverse) as a short fallback flag, then delete replaced Hono route files.
|
||||
|
||||
## Checklist
|
||||
|
||||
- [x] Add first `HttpApi` JSON route slices.
|
||||
- [x] Bridge selected `HttpApi` routes into Hono behind `OPENCODE_EXPERIMENTAL_HTTPAPI`.
|
||||
- [x] Bridge selected `HttpApi` routes behind `OPENCODE_EXPERIMENTAL_HTTPAPI`. (Now backend-fork-at-startup rather than in-Hono path mounting.)
|
||||
- [x] Reuse existing Effect services in handlers.
|
||||
- [x] Provide auth, instance lookup, and observability in the Effect route layer.
|
||||
- [x] Attach auth middleware in route modules.
|
||||
- [x] Centralize auth via Effect `Config` for the Effect backend.
|
||||
- [x] Support `auth_token` as a query security scheme.
|
||||
- [x] Add bridge-level auth and instance tests.
|
||||
- [x] Complete exact Hono route inventory.
|
||||
- [x] Resolve implemented-but-unmounted route groups.
|
||||
- [x] Port remaining top-level JSON reads.
|
||||
- [ ] Generate SDK/OpenAPI from Effect routes.
|
||||
- [ ] Flip ported JSON routes to default-on with fallback.
|
||||
- [x] Implement Effect `HttpApi` OpenAPI generation behind `--httpapi` / `OPENCODE_SDK_OPENAPI=httpapi`.
|
||||
- [ ] Close Effect-vs-Hono OpenAPI schema-shape gaps and flip the SDK generator default.
|
||||
- [ ] Flip the runtime backend default from `hono` to `effect-httpapi`, with a short fallback flag.
|
||||
- [ ] Delete replaced Hono route implementations.
|
||||
- [ ] Replace SSE/websocket/streaming Hono routes with non-Hono implementations.
|
||||
- [ ] Replace SSE/websocket/streaming Hono routes with non-Hono implementations (or remove with the rest of Hono).
|
||||
|
||||
@@ -353,7 +353,7 @@ piecewise.
|
||||
- [ ] `src/cli/cmd/tui/event.ts`
|
||||
- [ ] `src/cli/ui.ts`
|
||||
- [ ] `src/command/index.ts`
|
||||
- [x] `src/control-plane/adaptors/worktree.ts`
|
||||
- [x] `src/control-plane/adapters/worktree.ts`
|
||||
- [x] `src/control-plane/types.ts`
|
||||
- [x] `src/control-plane/workspace.ts`
|
||||
- [ ] `src/file/index.ts`
|
||||
|
||||
@@ -51,6 +51,7 @@ import { LoadAPIKeyError } from "ai"
|
||||
import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
|
||||
import { applyPatch } from "diff"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { ShellID } from "@/tool/shell/id"
|
||||
|
||||
type ModeOption = { id: string; name: string; description?: string }
|
||||
type ModelOption = { modelId: string; name: string }
|
||||
@@ -129,7 +130,7 @@ async function sendUsageUpdate(
|
||||
})
|
||||
}
|
||||
|
||||
export async function init({ sdk: _sdk }: { sdk: OpencodeClient }) {
|
||||
export function init({ sdk: _sdk }: { sdk: OpencodeClient }) {
|
||||
return {
|
||||
create: (connection: AgentSideConnection, fullConfig: ACPConfig) => {
|
||||
return new Agent(connection, fullConfig)
|
||||
@@ -144,7 +145,7 @@ export class Agent implements ACPAgent {
|
||||
private sessionManager: ACPSessionManager
|
||||
private eventAbort = new AbortController()
|
||||
private eventStarted = false
|
||||
private bashSnapshots = new Map<string, string>()
|
||||
private shellSnapshots = new Map<string, string>()
|
||||
private toolStarts = new Set<string>()
|
||||
private permissionQueues = new Map<string, Promise<void>>()
|
||||
private permissionOptions: PermissionOption[] = [
|
||||
@@ -283,16 +284,16 @@ export class Agent implements ACPAgent {
|
||||
|
||||
switch (part.state.status) {
|
||||
case "pending":
|
||||
this.bashSnapshots.delete(part.callID)
|
||||
this.shellSnapshots.delete(part.callID)
|
||||
return
|
||||
|
||||
case "running":
|
||||
const output = this.bashOutput(part)
|
||||
const output = this.shellOutput(part)
|
||||
const content: ToolCallContent[] = []
|
||||
if (output) {
|
||||
const hash = Hash.fast(output)
|
||||
if (part.tool === "bash") {
|
||||
if (this.bashSnapshots.get(part.callID) === hash) {
|
||||
if (part.tool === ShellID.ToolID) {
|
||||
if (this.shellSnapshots.get(part.callID) === hash) {
|
||||
await this.connection
|
||||
.sessionUpdate({
|
||||
sessionId,
|
||||
@@ -311,7 +312,7 @@ export class Agent implements ACPAgent {
|
||||
})
|
||||
return
|
||||
}
|
||||
this.bashSnapshots.set(part.callID, hash)
|
||||
this.shellSnapshots.set(part.callID, hash)
|
||||
}
|
||||
content.push({
|
||||
type: "content",
|
||||
@@ -342,7 +343,7 @@ export class Agent implements ACPAgent {
|
||||
|
||||
case "completed": {
|
||||
this.toolStarts.delete(part.callID)
|
||||
this.bashSnapshots.delete(part.callID)
|
||||
this.shellSnapshots.delete(part.callID)
|
||||
const kind = toToolKind(part.tool)
|
||||
const content: ToolCallContent[] = [
|
||||
{
|
||||
@@ -423,7 +424,7 @@ export class Agent implements ACPAgent {
|
||||
}
|
||||
case "error":
|
||||
this.toolStarts.delete(part.callID)
|
||||
this.bashSnapshots.delete(part.callID)
|
||||
this.shellSnapshots.delete(part.callID)
|
||||
await this.connection
|
||||
.sessionUpdate({
|
||||
sessionId,
|
||||
@@ -837,10 +838,10 @@ export class Agent implements ACPAgent {
|
||||
await this.toolStart(sessionId, part)
|
||||
switch (part.state.status) {
|
||||
case "pending":
|
||||
this.bashSnapshots.delete(part.callID)
|
||||
this.shellSnapshots.delete(part.callID)
|
||||
break
|
||||
case "running":
|
||||
const output = this.bashOutput(part)
|
||||
const output = this.shellOutput(part)
|
||||
const runningContent: ToolCallContent[] = []
|
||||
if (output) {
|
||||
runningContent.push({
|
||||
@@ -871,7 +872,7 @@ export class Agent implements ACPAgent {
|
||||
break
|
||||
case "completed":
|
||||
this.toolStarts.delete(part.callID)
|
||||
this.bashSnapshots.delete(part.callID)
|
||||
this.shellSnapshots.delete(part.callID)
|
||||
const kind = toToolKind(part.tool)
|
||||
const content: ToolCallContent[] = [
|
||||
{
|
||||
@@ -951,7 +952,7 @@ export class Agent implements ACPAgent {
|
||||
break
|
||||
case "error":
|
||||
this.toolStarts.delete(part.callID)
|
||||
this.bashSnapshots.delete(part.callID)
|
||||
this.shellSnapshots.delete(part.callID)
|
||||
await this.connection
|
||||
.sessionUpdate({
|
||||
sessionId,
|
||||
@@ -1105,8 +1106,8 @@ export class Agent implements ACPAgent {
|
||||
}
|
||||
}
|
||||
|
||||
private bashOutput(part: ToolPart) {
|
||||
if (part.tool !== "bash") return
|
||||
private shellOutput(part: ToolPart) {
|
||||
if (part.tool !== ShellID.ToolID) return
|
||||
if (!("metadata" in part.state) || !part.state.metadata || typeof part.state.metadata !== "object") return
|
||||
const output = part.state.metadata["output"]
|
||||
if (typeof output !== "string") return
|
||||
@@ -1549,9 +1550,11 @@ export class Agent implements ACPAgent {
|
||||
|
||||
function toToolKind(toolName: string): ToolKind {
|
||||
const tool = toolName.toLocaleLowerCase()
|
||||
|
||||
switch (tool) {
|
||||
case "bash":
|
||||
case ShellID.ToolID:
|
||||
return "execute"
|
||||
|
||||
case "webfetch":
|
||||
return "fetch"
|
||||
|
||||
@@ -1576,6 +1579,7 @@ function toToolKind(toolName: string): ToolKind {
|
||||
|
||||
function toLocations(toolName: string, input: Record<string, any>): { path: string }[] {
|
||||
const tool = toolName.toLocaleLowerCase()
|
||||
|
||||
switch (tool) {
|
||||
case "read":
|
||||
case "edit":
|
||||
@@ -1584,7 +1588,7 @@ function toLocations(toolName: string, input: Record<string, any>): { path: stri
|
||||
case "glob":
|
||||
case "grep":
|
||||
return input["path"] ? [{ path: input["path"] }] : []
|
||||
case "bash":
|
||||
case ShellID.ToolID:
|
||||
return []
|
||||
default:
|
||||
return []
|
||||
|
||||
@@ -81,7 +81,11 @@ export const layer = Layer.effect(
|
||||
Effect.fn("Agent.state")(function* (ctx) {
|
||||
const cfg = yield* config.get()
|
||||
const skillDirs = yield* skill.dirs()
|
||||
const whitelistedDirs = [Truncate.GLOB, ...skillDirs.map((dir) => path.join(dir, "*"))]
|
||||
const whitelistedDirs = [
|
||||
Truncate.GLOB,
|
||||
path.join(Global.Path.tmp, "*"),
|
||||
...skillDirs.map((dir) => path.join(dir, "*")),
|
||||
]
|
||||
|
||||
const defaults = Permission.fromConfig({
|
||||
"*": "allow",
|
||||
|
||||
@@ -24,6 +24,7 @@ export function payloads() {
|
||||
.map(([type, def]) => {
|
||||
return z
|
||||
.object({
|
||||
id: z.string(),
|
||||
type: z.literal(type),
|
||||
properties: zodObject(def.properties),
|
||||
})
|
||||
@@ -39,6 +40,7 @@ export function effectPayloads() {
|
||||
.entries()
|
||||
.map(([type, def]) =>
|
||||
Schema.Struct({
|
||||
id: Schema.String,
|
||||
type: Schema.Literal(type),
|
||||
properties: def.properties,
|
||||
}).annotate({ identifier: `Event.${type}` }),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { EventEmitter } from "events"
|
||||
import { Identifier } from "@/id/id"
|
||||
|
||||
export type GlobalEvent = {
|
||||
directory?: string
|
||||
@@ -7,6 +8,15 @@ export type GlobalEvent = {
|
||||
payload: any
|
||||
}
|
||||
|
||||
export const GlobalBus = new EventEmitter<{
|
||||
class GlobalBusEmitter extends EventEmitter<{
|
||||
event: [GlobalEvent]
|
||||
}>()
|
||||
}> {
|
||||
override emit(eventName: "event", event: GlobalEvent): boolean {
|
||||
if (event.payload && typeof event.payload === "object" && !("id" in event.payload)) {
|
||||
event.payload.id = event.payload.syncEvent?.id ?? Identifier.create("evt", "ascending")
|
||||
}
|
||||
return super.emit(eventName, event)
|
||||
}
|
||||
}
|
||||
|
||||
export const GlobalBus = new GlobalBusEmitter()
|
||||
|
||||
@@ -5,6 +5,7 @@ import { BusEvent } from "./bus-event"
|
||||
import { GlobalBus } from "./global"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import { Identifier } from "@/id/id"
|
||||
|
||||
const log = Log.create({ service: "bus" })
|
||||
|
||||
@@ -18,6 +19,7 @@ export const InstanceDisposed = BusEvent.define(
|
||||
)
|
||||
|
||||
type Payload<D extends BusEvent.Definition = BusEvent.Definition> = {
|
||||
id: string
|
||||
type: D["type"]
|
||||
properties: BusProperties<D>
|
||||
}
|
||||
@@ -28,7 +30,11 @@ type State = {
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
readonly publish: <D extends BusEvent.Definition>(def: D, properties: BusProperties<D>) => Effect.Effect<void>
|
||||
readonly publish: <D extends BusEvent.Definition>(
|
||||
def: D,
|
||||
properties: BusProperties<D>,
|
||||
options?: { id?: string },
|
||||
) => Effect.Effect<void>
|
||||
readonly subscribe: <D extends BusEvent.Definition>(def: D) => Stream.Stream<Payload<D>>
|
||||
readonly subscribeAll: () => Stream.Stream<Payload>
|
||||
readonly subscribeCallback: <D extends BusEvent.Definition>(
|
||||
@@ -53,6 +59,7 @@ export const layer = Layer.effect(
|
||||
// Publish InstanceDisposed before shutting down so subscribers see it
|
||||
yield* PubSub.publish(wildcard, {
|
||||
type: InstanceDisposed.type,
|
||||
id: createID(),
|
||||
properties: { directory: ctx.directory },
|
||||
})
|
||||
yield* PubSub.shutdown(wildcard)
|
||||
@@ -77,10 +84,10 @@ export const layer = Layer.effect(
|
||||
})
|
||||
}
|
||||
|
||||
function publish<D extends BusEvent.Definition>(def: D, properties: BusProperties<D>) {
|
||||
function publish<D extends BusEvent.Definition>(def: D, properties: BusProperties<D>, options?: { id?: string }) {
|
||||
return Effect.gen(function* () {
|
||||
const s = yield* InstanceState.get(state)
|
||||
const payload: Payload = { type: def.type, properties }
|
||||
const payload: Payload = { id: options?.id ?? createID(), type: def.type, properties }
|
||||
log.info("publishing", { type: def.type })
|
||||
|
||||
const ps = s.typed.get(def.type)
|
||||
@@ -173,8 +180,16 @@ const { runPromise, runSync } = makeRuntime(Service, layer)
|
||||
|
||||
// runSync is safe here because the subscribe chain (InstanceState.get, PubSub.subscribe,
|
||||
// Scope.make, Effect.forkScoped) is entirely synchronous. If any step becomes async, this will throw.
|
||||
export async function publish<D extends BusEvent.Definition>(def: D, properties: BusProperties<D>) {
|
||||
return runPromise((svc) => svc.publish(def, properties))
|
||||
export function createID() {
|
||||
return Identifier.create("evt", "ascending")
|
||||
}
|
||||
|
||||
export async function publish<D extends BusEvent.Definition>(
|
||||
def: D,
|
||||
properties: BusProperties<D>,
|
||||
options?: { id?: string },
|
||||
) {
|
||||
return runPromise((svc) => svc.publish(def, properties, options))
|
||||
}
|
||||
|
||||
export function subscribe<D extends BusEvent.Definition>(def: D, callback: (event: Payload<D>) => unknown) {
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { InstanceBootstrap } from "../project/bootstrap"
|
||||
import { Instance } from "../project/instance"
|
||||
import { InstanceRuntime } from "../project/instance-runtime"
|
||||
import { WithInstance } from "../project/with-instance"
|
||||
|
||||
export async function bootstrap<T>(directory: string, cb: () => Promise<T>) {
|
||||
return Instance.provide({
|
||||
return WithInstance.provide({
|
||||
directory,
|
||||
init: () => AppRuntime.runPromise(InstanceBootstrap),
|
||||
fn: async () => {
|
||||
try {
|
||||
const result = await cb()
|
||||
return result
|
||||
} finally {
|
||||
await Instance.dispose()
|
||||
await InstanceRuntime.disposeInstance(Instance.current)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Duration, Effect, Match, Option } from "effect"
|
||||
import { UI } from "../ui"
|
||||
import { Account } from "@/account/account"
|
||||
import { AccountID, OrgID, PollExpired, type PollResult, type AccountError } from "@/account/schema"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import * as Prompt from "../effect/prompt"
|
||||
import open from "open"
|
||||
|
||||
@@ -172,60 +172,65 @@ const openEffect = Effect.fn("open")(function* () {
|
||||
yield* Prompt.outro("Opened " + url)
|
||||
})
|
||||
|
||||
export const LoginCommand = cmd({
|
||||
export const LoginCommand = effectCmd({
|
||||
command: "login <url>",
|
||||
describe: false,
|
||||
instance: false,
|
||||
builder: (yargs) =>
|
||||
yargs.positional("url", {
|
||||
describe: "server URL",
|
||||
type: "string",
|
||||
demandOption: true,
|
||||
}),
|
||||
async handler(args) {
|
||||
handler: Effect.fn("Cli.account.login")(function* (args) {
|
||||
UI.empty()
|
||||
await AppRuntime.runPromise(loginEffect(args.url))
|
||||
},
|
||||
yield* Effect.orDie(loginEffect(args.url))
|
||||
}),
|
||||
})
|
||||
|
||||
export const LogoutCommand = cmd({
|
||||
export const LogoutCommand = effectCmd({
|
||||
command: "logout [email]",
|
||||
describe: false,
|
||||
instance: false,
|
||||
builder: (yargs) =>
|
||||
yargs.positional("email", {
|
||||
describe: "account email to log out from",
|
||||
type: "string",
|
||||
}),
|
||||
async handler(args) {
|
||||
handler: Effect.fn("Cli.account.logout")(function* (args) {
|
||||
UI.empty()
|
||||
await AppRuntime.runPromise(logoutEffect(args.email))
|
||||
},
|
||||
yield* Effect.orDie(logoutEffect(args.email))
|
||||
}),
|
||||
})
|
||||
|
||||
export const SwitchCommand = cmd({
|
||||
export const SwitchCommand = effectCmd({
|
||||
command: "switch",
|
||||
describe: false,
|
||||
async handler() {
|
||||
instance: false,
|
||||
handler: Effect.fn("Cli.account.switch")(function* () {
|
||||
UI.empty()
|
||||
await AppRuntime.runPromise(switchEffect())
|
||||
},
|
||||
yield* Effect.orDie(switchEffect())
|
||||
}),
|
||||
})
|
||||
|
||||
export const OrgsCommand = cmd({
|
||||
export const OrgsCommand = effectCmd({
|
||||
command: "orgs",
|
||||
describe: false,
|
||||
async handler() {
|
||||
instance: false,
|
||||
handler: Effect.fn("Cli.account.orgs")(function* () {
|
||||
UI.empty()
|
||||
await AppRuntime.runPromise(orgsEffect())
|
||||
},
|
||||
yield* Effect.orDie(orgsEffect())
|
||||
}),
|
||||
})
|
||||
|
||||
export const OpenCommand = cmd({
|
||||
export const OpenCommand = effectCmd({
|
||||
command: "open",
|
||||
describe: false,
|
||||
async handler() {
|
||||
instance: false,
|
||||
handler: Effect.fn("Cli.account.open")(function* () {
|
||||
UI.empty()
|
||||
await AppRuntime.runPromise(openEffect())
|
||||
},
|
||||
yield* Effect.orDie(openEffect())
|
||||
}),
|
||||
})
|
||||
|
||||
export const ConsoleCommand = cmd({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { cmd } from "./cmd"
|
||||
import { Effect } from "effect"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
|
||||
import { ACP } from "@/acp/agent"
|
||||
import { Server } from "@/server/server"
|
||||
@@ -9,7 +9,7 @@ import { withNetworkOptions, resolveNetworkOptions } from "../network"
|
||||
|
||||
const log = Log.create({ service: "acp-command" })
|
||||
|
||||
export const AcpCommand = cmd({
|
||||
export const AcpCommand = effectCmd({
|
||||
command: "acp",
|
||||
describe: "start ACP (Agent Client Protocol) server",
|
||||
builder: (yargs) => {
|
||||
@@ -19,52 +19,53 @@ export const AcpCommand = cmd({
|
||||
default: process.cwd(),
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
handler: Effect.fn("Cli.acp")(function* (args) {
|
||||
process.env.OPENCODE_CLIENT = "acp"
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const opts = await resolveNetworkOptions(args)
|
||||
const server = await Server.listen(opts)
|
||||
const opts = yield* Effect.promise(() => resolveNetworkOptions(args))
|
||||
const server = yield* Effect.promise(() => Server.listen(opts))
|
||||
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: `http://${server.hostname}:${server.port}`,
|
||||
})
|
||||
|
||||
const input = new WritableStream<Uint8Array>({
|
||||
write(chunk) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
process.stdout.write(chunk, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
const output = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
process.stdin.on("data", (chunk: Buffer) => {
|
||||
controller.enqueue(new Uint8Array(chunk))
|
||||
})
|
||||
process.stdin.on("end", () => controller.close())
|
||||
process.stdin.on("error", (err) => controller.error(err))
|
||||
},
|
||||
})
|
||||
|
||||
const stream = ndJsonStream(input, output)
|
||||
const agent = await ACP.init({ sdk })
|
||||
|
||||
new AgentSideConnection((conn) => {
|
||||
return agent.create(conn, { sdk })
|
||||
}, stream)
|
||||
|
||||
log.info("setup connection")
|
||||
process.stdin.resume()
|
||||
await new Promise((resolve, reject) => {
|
||||
process.stdin.on("end", resolve)
|
||||
process.stdin.on("error", reject)
|
||||
})
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: `http://${server.hostname}:${server.port}`,
|
||||
})
|
||||
},
|
||||
|
||||
const input = new WritableStream<Uint8Array>({
|
||||
write(chunk) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
process.stdout.write(chunk, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
const output = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
process.stdin.on("data", (chunk: Buffer) => {
|
||||
controller.enqueue(new Uint8Array(chunk))
|
||||
})
|
||||
process.stdin.on("end", () => controller.close())
|
||||
process.stdin.on("error", (err) => controller.error(err))
|
||||
},
|
||||
})
|
||||
|
||||
const stream = ndJsonStream(input, output)
|
||||
const agent = ACP.init({ sdk })
|
||||
|
||||
new AgentSideConnection((conn) => {
|
||||
return agent.create(conn, { sdk })
|
||||
}, stream)
|
||||
|
||||
log.info("setup connection")
|
||||
process.stdin.resume()
|
||||
yield* Effect.promise(
|
||||
() =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
process.stdin.on("end", () => resolve())
|
||||
process.stdin.on("error", reject)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -10,8 +10,11 @@ import fs from "fs/promises"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import matter from "gray-matter"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { WithInstance } from "../../project/with-instance"
|
||||
import { EOL } from "os"
|
||||
import type { Argv } from "yargs"
|
||||
import { Effect } from "effect"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
|
||||
type AgentMode = "all" | "primary" | "subagent"
|
||||
|
||||
@@ -61,7 +64,7 @@ const AgentCreateCommand = cmd({
|
||||
describe: "model to use in the format of provider/model",
|
||||
}),
|
||||
async handler(args) {
|
||||
await Instance.provide({
|
||||
await WithInstance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
const cliPath = args.path
|
||||
@@ -232,28 +235,23 @@ const AgentCreateCommand = cmd({
|
||||
},
|
||||
})
|
||||
|
||||
const AgentListCommand = cmd({
|
||||
const AgentListCommand = effectCmd({
|
||||
command: "list",
|
||||
describe: "list all available agents",
|
||||
async handler() {
|
||||
await Instance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
const agents = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.list()))
|
||||
const sortedAgents = agents.sort((a, b) => {
|
||||
if (a.native !== b.native) {
|
||||
return a.native ? -1 : 1
|
||||
}
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
|
||||
for (const agent of sortedAgents) {
|
||||
process.stdout.write(`${agent.name} (${agent.mode})` + EOL)
|
||||
process.stdout.write(` ${JSON.stringify(agent.permission, null, 2)}` + EOL)
|
||||
}
|
||||
},
|
||||
handler: Effect.fn("Cli.agent.list")(function* () {
|
||||
const agents = yield* Agent.Service.use((svc) => svc.list())
|
||||
const sortedAgents = agents.sort((a, b) => {
|
||||
if (a.native !== b.native) {
|
||||
return a.native ? -1 : 1
|
||||
}
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
},
|
||||
|
||||
for (const agent of sortedAgents) {
|
||||
process.stdout.write(`${agent.name} (${agent.mode})` + EOL)
|
||||
process.stdout.write(` ${JSON.stringify(agent.permission, null, 2)}` + EOL)
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
export const AgentCommand = cmd({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CommandModule } from "yargs"
|
||||
|
||||
type WithDoubleDash<T> = T & { "--"?: string[] }
|
||||
export type WithDoubleDash<T> = T & { "--"?: string[] }
|
||||
|
||||
export function cmd<T, U>(input: CommandModule<T, WithDoubleDash<U>>) {
|
||||
return input
|
||||
|
||||
@@ -7,14 +7,13 @@ import { Session } from "@/session/session"
|
||||
import type { MessageV2 } from "../../../session/message-v2"
|
||||
import { MessageID, PartID } from "../../../session/schema"
|
||||
import { ToolRegistry } from "@/tool/registry"
|
||||
import { Instance } from "../../../project/instance"
|
||||
import { Permission } from "../../../permission"
|
||||
import { iife } from "../../../util/iife"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { cmd } from "../cmd"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { effectCmd, fail } from "../../effect-cmd"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import type { InstanceContext } from "@/project/instance"
|
||||
|
||||
export const AgentCommand = cmd({
|
||||
export const AgentCommand = effectCmd({
|
||||
command: "agent <name>",
|
||||
describe: "show agent configuration details",
|
||||
builder: (yargs) =>
|
||||
@@ -32,60 +31,60 @@ export const AgentCommand = cmd({
|
||||
type: "string",
|
||||
description: "Tool params as JSON or a JS object literal",
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const agentName = args.name as string
|
||||
const agent = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.get(agentName)))
|
||||
if (!agent) {
|
||||
process.stderr.write(
|
||||
`Agent ${agentName} not found, run '${basename(process.execPath)} agent list' to get an agent list` + EOL,
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
const availableTools = await getAvailableTools(agent)
|
||||
const resolvedTools = await resolveTools(agent, availableTools)
|
||||
const toolID = args.tool as string | undefined
|
||||
if (toolID) {
|
||||
const tool = availableTools.find((item) => item.id === toolID)
|
||||
if (!tool) {
|
||||
process.stderr.write(`Tool ${toolID} not found for agent ${agentName}` + EOL)
|
||||
process.exit(1)
|
||||
}
|
||||
if (resolvedTools[toolID] === false) {
|
||||
process.stderr.write(`Tool ${toolID} is disabled for agent ${agentName}` + EOL)
|
||||
process.exit(1)
|
||||
}
|
||||
const params = parseToolParams(args.params as string | undefined)
|
||||
const ctx = await createToolContext(agent)
|
||||
const result = await tool.execute(params, ctx)
|
||||
process.stdout.write(JSON.stringify({ tool: toolID, input: params, result }, null, 2) + EOL)
|
||||
return
|
||||
}
|
||||
|
||||
const output = {
|
||||
...agent,
|
||||
tools: resolvedTools,
|
||||
}
|
||||
process.stdout.write(JSON.stringify(output, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.agent")(function* (args) {
|
||||
const ctx = yield* InstanceRef
|
||||
if (!ctx) return
|
||||
return yield* run(args, ctx)
|
||||
}),
|
||||
})
|
||||
|
||||
async function getAvailableTools(agent: Agent.Info) {
|
||||
return AppRuntime.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const provider = yield* Provider.Service
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const model = agent.model ?? (yield* provider.defaultModel())
|
||||
return yield* registry.tools({
|
||||
...model,
|
||||
agent,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
const run = Effect.fn("Cli.debug.agent.body")(function* (
|
||||
args: { name: string; tool?: string; params?: string },
|
||||
ctx: InstanceContext,
|
||||
) {
|
||||
const agentName = args.name
|
||||
const agent = yield* Agent.Service.use((svc) => svc.get(agentName))
|
||||
if (!agent) {
|
||||
process.stderr.write(
|
||||
`Agent ${agentName} not found, run '${basename(process.execPath)} agent list' to get an agent list` + EOL,
|
||||
)
|
||||
return yield* fail("", 1)
|
||||
}
|
||||
const availableTools = yield* getAvailableTools(agent)
|
||||
const resolvedTools = resolveTools(agent, availableTools)
|
||||
const toolID = args.tool
|
||||
if (toolID) {
|
||||
const tool = availableTools.find((item) => item.id === toolID)
|
||||
if (!tool) {
|
||||
process.stderr.write(`Tool ${toolID} not found for agent ${agentName}` + EOL)
|
||||
return yield* fail("", 1)
|
||||
}
|
||||
if (resolvedTools[toolID] === false) {
|
||||
process.stderr.write(`Tool ${toolID} is disabled for agent ${agentName}` + EOL)
|
||||
return yield* fail("", 1)
|
||||
}
|
||||
const params = parseToolParams(args.params)
|
||||
const toolCtx = yield* createToolContext(agent, ctx)
|
||||
const result = yield* tool.execute(params, toolCtx)
|
||||
process.stdout.write(JSON.stringify({ tool: toolID, input: params, result }, null, 2) + EOL)
|
||||
return
|
||||
}
|
||||
|
||||
async function resolveTools(agent: Agent.Info, availableTools: Awaited<ReturnType<typeof getAvailableTools>>) {
|
||||
const output = {
|
||||
...agent,
|
||||
tools: resolvedTools,
|
||||
}
|
||||
process.stdout.write(JSON.stringify(output, null, 2) + EOL)
|
||||
})
|
||||
|
||||
const getAvailableTools = Effect.fn("Cli.debug.agent.getAvailableTools")(function* (agent: Agent.Info) {
|
||||
const provider = yield* Provider.Service
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const model = agent.model ?? (yield* provider.defaultModel())
|
||||
return yield* registry.tools({ ...model, agent })
|
||||
})
|
||||
|
||||
function resolveTools(agent: Agent.Info, availableTools: { id: string }[]) {
|
||||
const disabled = Permission.disabled(
|
||||
availableTools.map((tool) => tool.id),
|
||||
agent.permission,
|
||||
@@ -123,50 +122,38 @@ function parseToolParams(input?: string) {
|
||||
return parsed as Record<string, unknown>
|
||||
}
|
||||
|
||||
async function createToolContext(agent: Agent.Info) {
|
||||
const { session, messageID } = await AppRuntime.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const session = yield* Session.Service
|
||||
const result = yield* session.create({ title: `Debug tool run (${agent.name})` })
|
||||
const messageID = MessageID.ascending()
|
||||
const model = agent.model
|
||||
? agent.model
|
||||
: yield* Effect.gen(function* () {
|
||||
const provider = yield* Provider.Service
|
||||
return yield* provider.defaultModel()
|
||||
})
|
||||
const now = Date.now()
|
||||
const message: MessageV2.Assistant = {
|
||||
id: messageID,
|
||||
sessionID: result.id,
|
||||
role: "assistant",
|
||||
time: {
|
||||
created: now,
|
||||
},
|
||||
parentID: messageID,
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
mode: "debug",
|
||||
agent: agent.name,
|
||||
path: {
|
||||
cwd: Instance.directory,
|
||||
root: Instance.worktree,
|
||||
},
|
||||
cost: 0,
|
||||
tokens: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
reasoning: 0,
|
||||
cache: {
|
||||
read: 0,
|
||||
write: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
yield* session.updateMessage(message)
|
||||
return { session: result, messageID }
|
||||
}),
|
||||
)
|
||||
const createToolContext = Effect.fn("Cli.debug.agent.createToolContext")(function* (
|
||||
agent: Agent.Info,
|
||||
ctx: InstanceContext,
|
||||
) {
|
||||
const sessionSvc = yield* Session.Service
|
||||
const session = yield* sessionSvc.create({ title: `Debug tool run (${agent.name})` })
|
||||
const messageID = MessageID.ascending()
|
||||
const model = agent.model
|
||||
? agent.model
|
||||
: yield* Effect.gen(function* () {
|
||||
const provider = yield* Provider.Service
|
||||
return yield* provider.defaultModel()
|
||||
})
|
||||
const now = Date.now()
|
||||
const message: MessageV2.Assistant = {
|
||||
id: messageID,
|
||||
sessionID: session.id,
|
||||
role: "assistant",
|
||||
time: { created: now },
|
||||
parentID: messageID,
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
mode: "debug",
|
||||
agent: agent.name,
|
||||
path: {
|
||||
cwd: ctx.directory,
|
||||
root: ctx.worktree,
|
||||
},
|
||||
cost: 0,
|
||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
}
|
||||
yield* sessionSvc.updateMessage(message)
|
||||
|
||||
const ruleset = Permission.merge(agent.permission, session.permission ?? [])
|
||||
|
||||
@@ -189,4 +176,4 @@ async function createToolContext(agent: Agent.Info) {
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { EOL } from "os"
|
||||
import { Effect } from "effect"
|
||||
import { Config } from "@/config/config"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { cmd } from "../cmd"
|
||||
import { effectCmd } from "../../effect-cmd"
|
||||
|
||||
export const ConfigCommand = cmd({
|
||||
export const ConfigCommand = effectCmd({
|
||||
command: "config",
|
||||
describe: "show resolved configuration",
|
||||
builder: (yargs) => yargs,
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.get()))
|
||||
process.stdout.write(JSON.stringify(config, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.config")(function* () {
|
||||
const config = yield* Config.Service.use((cfg) => cfg.get())
|
||||
process.stdout.write(JSON.stringify(config, null, 2) + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { EOL } from "os"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Effect } from "effect"
|
||||
import { File } from "../../../file"
|
||||
import { Ripgrep } from "@/file/ripgrep"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { effectCmd } from "../../effect-cmd"
|
||||
import { cmd } from "../cmd"
|
||||
|
||||
const FileSearchCommand = cmd({
|
||||
const FileSearchCommand = effectCmd({
|
||||
command: "search <query>",
|
||||
describe: "search files by query",
|
||||
builder: (yargs) =>
|
||||
@@ -14,15 +14,13 @@ const FileSearchCommand = cmd({
|
||||
demandOption: true,
|
||||
description: "Search query",
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const results = await AppRuntime.runPromise(File.Service.use((svc) => svc.search({ query: args.query })))
|
||||
process.stdout.write(results.join(EOL) + EOL)
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.file.search")(function* (args) {
|
||||
const results = yield* File.Service.use((svc) => svc.search({ query: args.query }))
|
||||
process.stdout.write(results.join(EOL) + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
const FileReadCommand = cmd({
|
||||
const FileReadCommand = effectCmd({
|
||||
command: "read <path>",
|
||||
describe: "read file contents as JSON",
|
||||
builder: (yargs) =>
|
||||
@@ -31,27 +29,23 @@ const FileReadCommand = cmd({
|
||||
demandOption: true,
|
||||
description: "File path to read",
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const content = await AppRuntime.runPromise(File.Service.use((svc) => svc.read(args.path)))
|
||||
process.stdout.write(JSON.stringify(content, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.file.read")(function* (args) {
|
||||
const content = yield* File.Service.use((svc) => svc.read(args.path))
|
||||
process.stdout.write(JSON.stringify(content, null, 2) + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
const FileStatusCommand = cmd({
|
||||
const FileStatusCommand = effectCmd({
|
||||
command: "status",
|
||||
describe: "show file status information",
|
||||
builder: (yargs) => yargs,
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const status = await AppRuntime.runPromise(File.Service.use((svc) => svc.status()))
|
||||
process.stdout.write(JSON.stringify(status, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.file.status")(function* () {
|
||||
const status = yield* File.Service.use((svc) => svc.status())
|
||||
process.stdout.write(JSON.stringify(status, null, 2) + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
const FileListCommand = cmd({
|
||||
const FileListCommand = effectCmd({
|
||||
command: "list <path>",
|
||||
describe: "list files in a directory",
|
||||
builder: (yargs) =>
|
||||
@@ -60,15 +54,13 @@ const FileListCommand = cmd({
|
||||
demandOption: true,
|
||||
description: "File path to list",
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const files = await AppRuntime.runPromise(File.Service.use((svc) => svc.list(args.path)))
|
||||
process.stdout.write(JSON.stringify(files, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.file.list")(function* (args) {
|
||||
const files = yield* File.Service.use((svc) => svc.list(args.path))
|
||||
process.stdout.write(JSON.stringify(files, null, 2) + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
const FileTreeCommand = cmd({
|
||||
const FileTreeCommand = effectCmd({
|
||||
command: "tree [dir]",
|
||||
describe: "show directory tree",
|
||||
builder: (yargs) =>
|
||||
@@ -77,12 +69,10 @@ const FileTreeCommand = cmd({
|
||||
description: "Directory to tree",
|
||||
default: process.cwd(),
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const tree = await AppRuntime.runPromise(Ripgrep.Service.use((svc) => svc.tree({ cwd: args.dir, limit: 200 })))
|
||||
console.log(JSON.stringify(tree, null, 2))
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.file.tree")(function* (args) {
|
||||
const tree = yield* Effect.orDie(Ripgrep.Service.use((svc) => svc.tree({ cwd: args.dir, limit: 200 })))
|
||||
console.log(JSON.stringify(tree, null, 2))
|
||||
}),
|
||||
})
|
||||
|
||||
export const FileCommand = cmd({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { Duration, Effect } from "effect"
|
||||
import { effectCmd } from "../../effect-cmd"
|
||||
import { cmd } from "../cmd"
|
||||
import { ConfigCommand } from "./config"
|
||||
import { FileCommand } from "./file"
|
||||
@@ -26,19 +27,19 @@ export const DebugCommand = cmd({
|
||||
.command(StartupCommand)
|
||||
.command(AgentCommand)
|
||||
.command(PathsCommand)
|
||||
.command({
|
||||
command: "wait",
|
||||
describe: "wait indefinitely (for debugging)",
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1_000 * 60 * 60 * 24))
|
||||
})
|
||||
},
|
||||
})
|
||||
.command(WaitCommand)
|
||||
.demandCommand(),
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
const WaitCommand = effectCmd({
|
||||
command: "wait",
|
||||
describe: "wait indefinitely (for debugging)",
|
||||
handler: Effect.fn("Cli.debug.wait")(function* () {
|
||||
yield* Effect.sleep(Duration.days(1))
|
||||
}),
|
||||
})
|
||||
|
||||
const PathsCommand = cmd({
|
||||
command: "paths",
|
||||
describe: "show global paths (data, config, cache, state)",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { LSP } from "@/lsp/lsp"
|
||||
import { AppRuntime } from "../../../effect/app-runtime"
|
||||
import { Effect } from "effect"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { effectCmd } from "../../effect-cmd"
|
||||
import { cmd } from "../cmd"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { EOL } from "os"
|
||||
@@ -14,47 +13,39 @@ export const LSPCommand = cmd({
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
const DiagnosticsCommand = cmd({
|
||||
const DiagnosticsCommand = effectCmd({
|
||||
command: "diagnostics <file>",
|
||||
describe: "get diagnostics for a file",
|
||||
builder: (yargs) => yargs.positional("file", { type: "string", demandOption: true }),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const out = await AppRuntime.runPromise(
|
||||
LSP.Service.use((lsp) =>
|
||||
Effect.gen(function* () {
|
||||
yield* lsp.touchFile(args.file, "full")
|
||||
return yield* lsp.diagnostics()
|
||||
}),
|
||||
),
|
||||
)
|
||||
process.stdout.write(JSON.stringify(out, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.lsp.diagnostics")(function* (args) {
|
||||
const out = yield* LSP.Service.use((lsp) =>
|
||||
Effect.gen(function* () {
|
||||
yield* lsp.touchFile(args.file, "full")
|
||||
return yield* lsp.diagnostics()
|
||||
}),
|
||||
)
|
||||
process.stdout.write(JSON.stringify(out, null, 2) + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
export const SymbolsCommand = cmd({
|
||||
export const SymbolsCommand = effectCmd({
|
||||
command: "symbols <query>",
|
||||
describe: "search workspace symbols",
|
||||
builder: (yargs) => yargs.positional("query", { type: "string", demandOption: true }),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
using _ = Log.Default.time("symbols")
|
||||
const results = await AppRuntime.runPromise(LSP.Service.use((lsp) => lsp.workspaceSymbol(args.query)))
|
||||
process.stdout.write(JSON.stringify(results, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.lsp.symbols")(function* (args) {
|
||||
using _ = Log.Default.time("symbols")
|
||||
const results = yield* LSP.Service.use((lsp) => lsp.workspaceSymbol(args.query))
|
||||
process.stdout.write(JSON.stringify(results, null, 2) + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
export const DocumentSymbolsCommand = cmd({
|
||||
export const DocumentSymbolsCommand = effectCmd({
|
||||
command: "document-symbols <uri>",
|
||||
describe: "get symbols from a document",
|
||||
builder: (yargs) => yargs.positional("uri", { type: "string", demandOption: true }),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
using _ = Log.Default.time("document-symbols")
|
||||
const results = await AppRuntime.runPromise(LSP.Service.use((lsp) => lsp.documentSymbol(args.uri)))
|
||||
process.stdout.write(JSON.stringify(results, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.lsp.documentSymbols")(function* (args) {
|
||||
using _ = Log.Default.time("document-symbols")
|
||||
const results = yield* LSP.Service.use((lsp) => lsp.documentSymbol(args.uri))
|
||||
process.stdout.write(JSON.stringify(results, null, 2) + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { EOL } from "os"
|
||||
import { Effect, Stream } from "effect"
|
||||
import { AppRuntime } from "../../../effect/app-runtime"
|
||||
import { Ripgrep } from "../../../file/ripgrep"
|
||||
import { Instance } from "../../../project/instance"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { effectCmd } from "../../effect-cmd"
|
||||
import { cmd } from "../cmd"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
|
||||
export const RipgrepCommand = cmd({
|
||||
command: "rg",
|
||||
@@ -13,24 +12,22 @@ export const RipgrepCommand = cmd({
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
const TreeCommand = cmd({
|
||||
const TreeCommand = effectCmd({
|
||||
command: "tree",
|
||||
describe: "show file tree using ripgrep",
|
||||
builder: (yargs) =>
|
||||
yargs.option("limit", {
|
||||
type: "number",
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const tree = await AppRuntime.runPromise(
|
||||
Ripgrep.Service.use((svc) => svc.tree({ cwd: Instance.directory, limit: args.limit })),
|
||||
)
|
||||
process.stdout.write(tree + EOL)
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.rg.tree")(function* (args) {
|
||||
const ctx = yield* InstanceRef
|
||||
if (!ctx) return
|
||||
const tree = yield* Effect.orDie(Ripgrep.Service.use((svc) => svc.tree({ cwd: ctx.directory, limit: args.limit })))
|
||||
process.stdout.write(tree + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
const FilesCommand = cmd({
|
||||
const FilesCommand = effectCmd({
|
||||
command: "files",
|
||||
describe: "list files using ripgrep",
|
||||
builder: (yargs) =>
|
||||
@@ -47,29 +44,26 @@ const FilesCommand = cmd({
|
||||
type: "number",
|
||||
description: "Limit number of results",
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const files = await AppRuntime.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const rg = yield* Ripgrep.Service
|
||||
return yield* rg
|
||||
.files({
|
||||
cwd: Instance.directory,
|
||||
glob: args.glob ? [args.glob] : undefined,
|
||||
})
|
||||
.pipe(
|
||||
Stream.take(args.limit ?? Infinity),
|
||||
Stream.runCollect,
|
||||
Effect.map((c) => [...c]),
|
||||
)
|
||||
}),
|
||||
handler: Effect.fn("Cli.debug.rg.files")(function* (args) {
|
||||
const ctx = yield* InstanceRef
|
||||
if (!ctx) return
|
||||
const rg = yield* Ripgrep.Service
|
||||
const files = yield* rg
|
||||
.files({
|
||||
cwd: ctx.directory,
|
||||
glob: args.glob ? [args.glob] : undefined,
|
||||
})
|
||||
.pipe(
|
||||
Stream.take(args.limit ?? Infinity),
|
||||
Stream.runCollect,
|
||||
Effect.map((c) => [...c]),
|
||||
Effect.orDie,
|
||||
)
|
||||
process.stdout.write(files.join(EOL) + EOL)
|
||||
})
|
||||
},
|
||||
process.stdout.write(files.join(EOL) + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
const SearchCommand = cmd({
|
||||
const SearchCommand = effectCmd({
|
||||
command: "search <pattern>",
|
||||
describe: "search file contents using ripgrep",
|
||||
builder: (yargs) =>
|
||||
@@ -87,19 +81,19 @@ const SearchCommand = cmd({
|
||||
type: "number",
|
||||
description: "Limit number of results",
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const results = await AppRuntime.runPromise(
|
||||
Ripgrep.Service.use((svc) =>
|
||||
svc.search({
|
||||
cwd: Instance.directory,
|
||||
pattern: args.pattern,
|
||||
glob: args.glob as string[] | undefined,
|
||||
limit: args.limit,
|
||||
}),
|
||||
),
|
||||
)
|
||||
process.stdout.write(JSON.stringify(results.items, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.rg.search")(function* (args) {
|
||||
const ctx = yield* InstanceRef
|
||||
if (!ctx) return
|
||||
const results = yield* Effect.orDie(
|
||||
Ripgrep.Service.use((svc) =>
|
||||
svc.search({
|
||||
cwd: ctx.directory,
|
||||
pattern: args.pattern,
|
||||
glob: args.glob as string[] | undefined,
|
||||
limit: args.limit,
|
||||
}),
|
||||
),
|
||||
)
|
||||
process.stdout.write(JSON.stringify(results.items, null, 2) + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
import { EOL } from "os"
|
||||
import { Effect } from "effect"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Skill } from "../../../skill"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { cmd } from "../cmd"
|
||||
import { effectCmd } from "../../effect-cmd"
|
||||
|
||||
export const SkillCommand = cmd({
|
||||
export const SkillCommand = effectCmd({
|
||||
command: "skill",
|
||||
describe: "list all available skills",
|
||||
builder: (yargs) => yargs,
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const skills = await AppRuntime.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const skill = yield* Skill.Service
|
||||
return yield* skill.all()
|
||||
}),
|
||||
)
|
||||
process.stdout.write(JSON.stringify(skills, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.skill")(function* () {
|
||||
const skill = yield* Skill.Service
|
||||
const skills = yield* skill.all()
|
||||
process.stdout.write(JSON.stringify(skills, null, 2) + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Effect } from "effect"
|
||||
import { Snapshot } from "../../../snapshot"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { effectCmd } from "../../effect-cmd"
|
||||
import { cmd } from "../cmd"
|
||||
|
||||
export const SnapshotCommand = cmd({
|
||||
@@ -10,17 +10,16 @@ export const SnapshotCommand = cmd({
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
const TrackCommand = cmd({
|
||||
const TrackCommand = effectCmd({
|
||||
command: "track",
|
||||
describe: "track current snapshot state",
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
console.log(await AppRuntime.runPromise(Snapshot.Service.use((svc) => svc.track())))
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.snapshot.track")(function* () {
|
||||
const out = yield* Snapshot.Service.use((svc) => svc.track())
|
||||
console.log(out)
|
||||
}),
|
||||
})
|
||||
|
||||
const PatchCommand = cmd({
|
||||
const PatchCommand = effectCmd({
|
||||
command: "patch <hash>",
|
||||
describe: "show patch for a snapshot hash",
|
||||
builder: (yargs) =>
|
||||
@@ -29,14 +28,13 @@ const PatchCommand = cmd({
|
||||
description: "hash",
|
||||
demandOption: true,
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
console.log(await AppRuntime.runPromise(Snapshot.Service.use((svc) => svc.patch(args.hash))))
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.snapshot.patch")(function* (args) {
|
||||
const out = yield* Snapshot.Service.use((svc) => svc.patch(args.hash))
|
||||
console.log(out)
|
||||
}),
|
||||
})
|
||||
|
||||
const DiffCommand = cmd({
|
||||
const DiffCommand = effectCmd({
|
||||
command: "diff <hash>",
|
||||
describe: "show diff for a snapshot hash",
|
||||
builder: (yargs) =>
|
||||
@@ -45,9 +43,8 @@ const DiffCommand = cmd({
|
||||
description: "hash",
|
||||
demandOption: true,
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
console.log(await AppRuntime.runPromise(Snapshot.Service.use((svc) => svc.diff(args.hash))))
|
||||
})
|
||||
},
|
||||
handler: Effect.fn("Cli.debug.snapshot.diff")(function* (args) {
|
||||
const out = yield* Snapshot.Service.use((svc) => svc.diff(args.hash))
|
||||
console.log(out)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { Session } from "@/session/session"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { SessionID } from "../../session/schema"
|
||||
import { cmd } from "./cmd"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { effectCmd, fail } from "../effect-cmd"
|
||||
import { UI } from "../ui"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import { EOL } from "os"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Effect } from "effect"
|
||||
|
||||
function redact(kind: string, id: string, value: string) {
|
||||
return value.trim() ? `[redacted:${kind}:${id}]` : value
|
||||
@@ -220,11 +218,11 @@ function sanitize(data: { info: Session.Info; messages: MessageV2.WithParts[] })
|
||||
}
|
||||
}
|
||||
|
||||
export const ExportCommand = cmd({
|
||||
export const ExportCommand = effectCmd({
|
||||
command: "export [sessionID]",
|
||||
describe: "export session data as JSON",
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.positional("sessionID", {
|
||||
describe: "session id to export",
|
||||
type: "string",
|
||||
@@ -232,75 +230,62 @@ export const ExportCommand = cmd({
|
||||
.option("sanitize", {
|
||||
describe: "redact sensitive transcript and file data",
|
||||
type: "boolean",
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
let sessionID = args.sessionID ? SessionID.make(args.sessionID) : undefined
|
||||
process.stderr.write(`Exporting session: ${sessionID ?? "latest"}\n`)
|
||||
|
||||
if (!sessionID) {
|
||||
UI.empty()
|
||||
prompts.intro("Export session", {
|
||||
output: process.stderr,
|
||||
})
|
||||
|
||||
const sessions = []
|
||||
for await (const session of Session.list()) {
|
||||
sessions.push(session)
|
||||
}
|
||||
|
||||
if (sessions.length === 0) {
|
||||
prompts.log.error("No sessions found", {
|
||||
output: process.stderr,
|
||||
})
|
||||
prompts.outro("Done", {
|
||||
output: process.stderr,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
sessions.sort((a, b) => b.time.updated - a.time.updated)
|
||||
|
||||
const selectedSession = await prompts.autocomplete({
|
||||
message: "Select session to export",
|
||||
maxItems: 10,
|
||||
options: sessions.map((session) => ({
|
||||
label: session.title,
|
||||
value: session.id,
|
||||
hint: `${new Date(session.time.updated).toLocaleString()} • ${session.id.slice(-8)}`,
|
||||
})),
|
||||
output: process.stderr,
|
||||
})
|
||||
|
||||
if (prompts.isCancel(selectedSession)) {
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
|
||||
sessionID = selectedSession
|
||||
|
||||
prompts.outro("Exporting session...", {
|
||||
output: process.stderr,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const sessionInfo = await AppRuntime.runPromise(Session.Service.use((svc) => svc.get(sessionID!)))
|
||||
const messages = await AppRuntime.runPromise(
|
||||
Session.Service.use((svc) => svc.messages({ sessionID: sessionInfo.id })),
|
||||
)
|
||||
|
||||
const exportData = {
|
||||
info: sessionInfo,
|
||||
messages,
|
||||
}
|
||||
|
||||
process.stdout.write(JSON.stringify(args.sanitize ? sanitize(exportData) : exportData, null, 2))
|
||||
process.stdout.write(EOL)
|
||||
} catch {
|
||||
UI.error(`Session not found: ${sessionID!}`)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
handler: Effect.fn("Cli.export")(function* (args) {
|
||||
return yield* run(args)
|
||||
}),
|
||||
})
|
||||
|
||||
const run = Effect.fn("Cli.export.body")(function* (args: { sessionID?: string; sanitize?: boolean }) {
|
||||
const svc = yield* Session.Service
|
||||
let sessionID = args.sessionID ? SessionID.make(args.sessionID) : undefined
|
||||
process.stderr.write(`Exporting session: ${sessionID ?? "latest"}\n`)
|
||||
|
||||
if (!sessionID) {
|
||||
UI.empty()
|
||||
prompts.intro("Export session", { output: process.stderr })
|
||||
|
||||
const sessions = yield* svc.list()
|
||||
|
||||
if (sessions.length === 0) {
|
||||
prompts.log.error("No sessions found", { output: process.stderr })
|
||||
prompts.outro("Done", { output: process.stderr })
|
||||
return
|
||||
}
|
||||
|
||||
sessions.sort((a, b) => b.time.updated - a.time.updated)
|
||||
|
||||
const selectedSession = yield* Effect.promise(() =>
|
||||
prompts.autocomplete({
|
||||
message: "Select session to export",
|
||||
maxItems: 10,
|
||||
options: sessions.map((session) => ({
|
||||
label: session.title,
|
||||
value: session.id,
|
||||
hint: `${new Date(session.time.updated).toLocaleString()} • ${session.id.slice(-8)}`,
|
||||
})),
|
||||
output: process.stderr,
|
||||
}),
|
||||
)
|
||||
|
||||
if (prompts.isCancel(selectedSession)) {
|
||||
return yield* Effect.die(new UI.CancelledError())
|
||||
}
|
||||
|
||||
sessionID = selectedSession
|
||||
|
||||
prompts.outro("Exporting session...", { output: process.stderr })
|
||||
}
|
||||
|
||||
// Match legacy try/catch — catches both typed failures and defects
|
||||
// (Session.Service.get throws NotFoundError as a defect, not a typed E).
|
||||
return yield* Effect.gen(function* () {
|
||||
const sessionInfo = yield* svc.get(sessionID!)
|
||||
const messages = yield* svc.messages({ sessionID: sessionInfo.id })
|
||||
|
||||
const exportData = { info: sessionInfo, messages }
|
||||
|
||||
process.stdout.write(JSON.stringify(args.sanitize ? sanitize(exportData) : exportData, null, 2))
|
||||
process.stdout.write(EOL)
|
||||
}).pipe(Effect.catchCause(() => fail(`Session not found: ${sessionID!}`)))
|
||||
})
|
||||
|
||||
@@ -18,9 +18,9 @@ import type {
|
||||
} from "@octokit/webhooks-types"
|
||||
import { UI } from "../ui"
|
||||
import { cmd } from "./cmd"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import { ModelsDev } from "@/provider/models"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { SessionShare } from "@/share/session"
|
||||
import { Session } from "@/session/session"
|
||||
import type { SessionID } from "../../session/schema"
|
||||
@@ -199,191 +199,192 @@ export const GithubCommand = cmd({
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
export const GithubInstallCommand = cmd({
|
||||
export const GithubInstallCommand = effectCmd({
|
||||
command: "install",
|
||||
describe: "install the GitHub agent",
|
||||
async handler() {
|
||||
await Instance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
{
|
||||
UI.empty()
|
||||
prompts.intro("Install GitHub agent")
|
||||
const app = await getAppInfo()
|
||||
await installGitHubApp()
|
||||
handler: Effect.fn("Cli.github.install")(function* () {
|
||||
const maybeCtx = yield* InstanceRef
|
||||
if (!maybeCtx) return yield* Effect.die("InstanceRef not provided")
|
||||
const ctx = maybeCtx
|
||||
yield* Effect.promise(async () => {
|
||||
{
|
||||
UI.empty()
|
||||
prompts.intro("Install GitHub agent")
|
||||
const app = await getAppInfo()
|
||||
await installGitHubApp()
|
||||
|
||||
const providers = await ModelsDev.get().then((p) => {
|
||||
// TODO: add guide for copilot, for now just hide it
|
||||
delete p["github-copilot"]
|
||||
return p
|
||||
const providers = await AppRuntime.runPromise(ModelsDev.Service.use((s) => s.get())).then((p) => {
|
||||
// TODO: add guide for copilot, for now just hide it
|
||||
delete p["github-copilot"]
|
||||
return p
|
||||
})
|
||||
|
||||
const provider = await promptProvider()
|
||||
const model = await promptModel()
|
||||
//const key = await promptKey()
|
||||
|
||||
await addWorkflowFiles()
|
||||
printNextSteps()
|
||||
|
||||
function printNextSteps() {
|
||||
let step2
|
||||
if (provider === "amazon-bedrock") {
|
||||
step2 =
|
||||
"Configure OIDC in AWS - https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services"
|
||||
} else {
|
||||
step2 = [
|
||||
` 2. Add the following secrets in org or repo (${app.owner}/${app.repo}) settings`,
|
||||
"",
|
||||
...providers[provider].env.map((e) => ` - ${e}`),
|
||||
].join("\n")
|
||||
}
|
||||
|
||||
prompts.outro(
|
||||
[
|
||||
"Next steps:",
|
||||
"",
|
||||
` 1. Commit the \`${WORKFLOW_FILE}\` file and push`,
|
||||
step2,
|
||||
"",
|
||||
" 3. Go to a GitHub issue and comment `/oc summarize` to see the agent in action",
|
||||
"",
|
||||
" Learn more about the GitHub agent - https://opencode.ai/docs/github/#usage-examples",
|
||||
].join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
async function getAppInfo() {
|
||||
const project = ctx.project
|
||||
if (project.vcs !== "git") {
|
||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
|
||||
// Get repo info
|
||||
const info = await AppRuntime.runPromise(
|
||||
Git.Service.use((git) => git.run(["remote", "get-url", "origin"], { cwd: ctx.worktree })),
|
||||
).then((x) => x.text().trim())
|
||||
const parsed = parseGitHubRemote(info)
|
||||
if (!parsed) {
|
||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
return { owner: parsed.owner, repo: parsed.repo, root: ctx.worktree }
|
||||
}
|
||||
|
||||
async function promptProvider() {
|
||||
const priority: Record<string, number> = {
|
||||
opencode: 0,
|
||||
anthropic: 1,
|
||||
openai: 2,
|
||||
google: 3,
|
||||
}
|
||||
let provider = await prompts.select({
|
||||
message: "Select provider",
|
||||
maxItems: 8,
|
||||
options: pipe(
|
||||
providers,
|
||||
values(),
|
||||
sortBy(
|
||||
(x) => priority[x.id] ?? 99,
|
||||
(x) => x.name ?? x.id,
|
||||
),
|
||||
map((x) => ({
|
||||
label: x.name,
|
||||
value: x.id,
|
||||
hint: priority[x.id] === 0 ? "recommended" : undefined,
|
||||
})),
|
||||
),
|
||||
})
|
||||
|
||||
const provider = await promptProvider()
|
||||
const model = await promptModel()
|
||||
//const key = await promptKey()
|
||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||
|
||||
await addWorkflowFiles()
|
||||
printNextSteps()
|
||||
return provider
|
||||
}
|
||||
|
||||
function printNextSteps() {
|
||||
let step2
|
||||
if (provider === "amazon-bedrock") {
|
||||
step2 =
|
||||
"Configure OIDC in AWS - https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services"
|
||||
} else {
|
||||
step2 = [
|
||||
` 2. Add the following secrets in org or repo (${app.owner}/${app.repo}) settings`,
|
||||
"",
|
||||
...providers[provider].env.map((e) => ` - ${e}`),
|
||||
].join("\n")
|
||||
async function promptModel() {
|
||||
const providerData = providers[provider]!
|
||||
|
||||
const model = await prompts.select({
|
||||
message: "Select model",
|
||||
maxItems: 8,
|
||||
options: pipe(
|
||||
providerData.models,
|
||||
values(),
|
||||
sortBy((x) => x.name ?? x.id),
|
||||
map((x) => ({
|
||||
label: x.name ?? x.id,
|
||||
value: x.id,
|
||||
})),
|
||||
),
|
||||
})
|
||||
|
||||
if (prompts.isCancel(model)) throw new UI.CancelledError()
|
||||
return model
|
||||
}
|
||||
|
||||
async function installGitHubApp() {
|
||||
const s = prompts.spinner()
|
||||
s.start("Installing GitHub app")
|
||||
|
||||
// Get installation
|
||||
const installation = await getInstallation()
|
||||
if (installation) return s.stop("GitHub app already installed")
|
||||
|
||||
// Open browser
|
||||
const url = "https://github.com/apps/opencode-agent"
|
||||
const command =
|
||||
process.platform === "darwin"
|
||||
? `open "${url}"`
|
||||
: process.platform === "win32"
|
||||
? `start "" "${url}"`
|
||||
: `xdg-open "${url}"`
|
||||
|
||||
exec(command, (error) => {
|
||||
if (error) {
|
||||
prompts.log.warn(`Could not open browser. Please visit: ${url}`)
|
||||
}
|
||||
})
|
||||
|
||||
prompts.outro(
|
||||
[
|
||||
"Next steps:",
|
||||
"",
|
||||
` 1. Commit the \`${WORKFLOW_FILE}\` file and push`,
|
||||
step2,
|
||||
"",
|
||||
" 3. Go to a GitHub issue and comment `/oc summarize` to see the agent in action",
|
||||
"",
|
||||
" Learn more about the GitHub agent - https://opencode.ai/docs/github/#usage-examples",
|
||||
].join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
async function getAppInfo() {
|
||||
const project = Instance.project
|
||||
if (project.vcs !== "git") {
|
||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
|
||||
// Get repo info
|
||||
const info = await AppRuntime.runPromise(
|
||||
Git.Service.use((git) => git.run(["remote", "get-url", "origin"], { cwd: Instance.worktree })),
|
||||
).then((x) => x.text().trim())
|
||||
const parsed = parseGitHubRemote(info)
|
||||
if (!parsed) {
|
||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
return { owner: parsed.owner, repo: parsed.repo, root: Instance.worktree }
|
||||
}
|
||||
|
||||
async function promptProvider() {
|
||||
const priority: Record<string, number> = {
|
||||
opencode: 0,
|
||||
anthropic: 1,
|
||||
openai: 2,
|
||||
google: 3,
|
||||
}
|
||||
let provider = await prompts.select({
|
||||
message: "Select provider",
|
||||
maxItems: 8,
|
||||
options: pipe(
|
||||
providers,
|
||||
values(),
|
||||
sortBy(
|
||||
(x) => priority[x.id] ?? 99,
|
||||
(x) => x.name ?? x.id,
|
||||
),
|
||||
map((x) => ({
|
||||
label: x.name,
|
||||
value: x.id,
|
||||
hint: priority[x.id] === 0 ? "recommended" : undefined,
|
||||
})),
|
||||
),
|
||||
})
|
||||
|
||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
async function promptModel() {
|
||||
const providerData = providers[provider]!
|
||||
|
||||
const model = await prompts.select({
|
||||
message: "Select model",
|
||||
maxItems: 8,
|
||||
options: pipe(
|
||||
providerData.models,
|
||||
values(),
|
||||
sortBy((x) => x.name ?? x.id),
|
||||
map((x) => ({
|
||||
label: x.name ?? x.id,
|
||||
value: x.id,
|
||||
})),
|
||||
),
|
||||
})
|
||||
|
||||
if (prompts.isCancel(model)) throw new UI.CancelledError()
|
||||
return model
|
||||
}
|
||||
|
||||
async function installGitHubApp() {
|
||||
const s = prompts.spinner()
|
||||
s.start("Installing GitHub app")
|
||||
|
||||
// Get installation
|
||||
// Wait for installation
|
||||
s.message("Waiting for GitHub app to be installed")
|
||||
const MAX_RETRIES = 120
|
||||
let retries = 0
|
||||
do {
|
||||
const installation = await getInstallation()
|
||||
if (installation) return s.stop("GitHub app already installed")
|
||||
if (installation) break
|
||||
|
||||
// Open browser
|
||||
const url = "https://github.com/apps/opencode-agent"
|
||||
const command =
|
||||
process.platform === "darwin"
|
||||
? `open "${url}"`
|
||||
: process.platform === "win32"
|
||||
? `start "" "${url}"`
|
||||
: `xdg-open "${url}"`
|
||||
|
||||
exec(command, (error) => {
|
||||
if (error) {
|
||||
prompts.log.warn(`Could not open browser. Please visit: ${url}`)
|
||||
}
|
||||
})
|
||||
|
||||
// Wait for installation
|
||||
s.message("Waiting for GitHub app to be installed")
|
||||
const MAX_RETRIES = 120
|
||||
let retries = 0
|
||||
do {
|
||||
const installation = await getInstallation()
|
||||
if (installation) break
|
||||
|
||||
if (retries > MAX_RETRIES) {
|
||||
s.stop(
|
||||
`Failed to detect GitHub app installation. Make sure to install the app for the \`${app.owner}/${app.repo}\` repository.`,
|
||||
)
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
|
||||
retries++
|
||||
await sleep(1000)
|
||||
} while (true) // oxlint-disable-line no-constant-condition
|
||||
|
||||
s.stop("Installed GitHub app")
|
||||
|
||||
async function getInstallation() {
|
||||
return await fetch(
|
||||
`https://api.opencode.ai/get_github_app_installation?owner=${app.owner}&repo=${app.repo}`,
|
||||
if (retries > MAX_RETRIES) {
|
||||
s.stop(
|
||||
`Failed to detect GitHub app installation. Make sure to install the app for the \`${app.owner}/${app.repo}\` repository.`,
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((data) => data.installation)
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
|
||||
retries++
|
||||
await sleep(1000)
|
||||
} while (true) // oxlint-disable-line no-constant-condition
|
||||
|
||||
s.stop("Installed GitHub app")
|
||||
|
||||
async function getInstallation() {
|
||||
return await fetch(
|
||||
`https://api.opencode.ai/get_github_app_installation?owner=${app.owner}&repo=${app.repo}`,
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((data) => data.installation)
|
||||
}
|
||||
}
|
||||
|
||||
async function addWorkflowFiles() {
|
||||
const envStr =
|
||||
provider === "amazon-bedrock"
|
||||
? ""
|
||||
: `\n env:${providers[provider].env.map((e) => `\n ${e}: \${{ secrets.${e} }}`).join("")}`
|
||||
async function addWorkflowFiles() {
|
||||
const envStr =
|
||||
provider === "amazon-bedrock"
|
||||
? ""
|
||||
: `\n env:${providers[provider].env.map((e) => `\n ${e}: \${{ secrets.${e} }}`).join("")}`
|
||||
|
||||
await Filesystem.write(
|
||||
path.join(app.root, WORKFLOW_FILE),
|
||||
`name: opencode
|
||||
await Filesystem.write(
|
||||
path.join(app.root, WORKFLOW_FILE),
|
||||
`name: opencode
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
@@ -414,17 +415,16 @@ jobs:
|
||||
uses: anomalyco/opencode/github@latest${envStr}
|
||||
with:
|
||||
model: ${provider}/${model}`,
|
||||
)
|
||||
)
|
||||
|
||||
prompts.log.success(`Added workflow file: "${WORKFLOW_FILE}"`)
|
||||
}
|
||||
prompts.log.success(`Added workflow file: "${WORKFLOW_FILE}"`)
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
export const GithubRunCommand = cmd({
|
||||
export const GithubRunCommand = effectCmd({
|
||||
command: "run",
|
||||
describe: "run the GitHub agent",
|
||||
builder: (yargs) =>
|
||||
@@ -437,8 +437,10 @@ export const GithubRunCommand = cmd({
|
||||
type: "string",
|
||||
describe: "GitHub personal access token (github_pat_********)",
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
handler: Effect.fn("Cli.github.run")(function* (args) {
|
||||
const ctx = yield* InstanceRef
|
||||
if (!ctx) return yield* Effect.die("InstanceRef not provided")
|
||||
yield* Effect.promise(async () => {
|
||||
const isMock = args.token || args.event
|
||||
|
||||
const context = isMock ? (JSON.parse(args.event!) as Context) : github.context
|
||||
@@ -501,21 +503,21 @@ export const GithubRunCommand = cmd({
|
||||
: "issue"
|
||||
: undefined
|
||||
const gitText = async (args: string[]) => {
|
||||
const result = await AppRuntime.runPromise(Git.Service.use((git) => git.run(args, { cwd: Instance.worktree })))
|
||||
const result = await AppRuntime.runPromise(Git.Service.use((git) => git.run(args, { cwd: ctx.worktree })))
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Process.RunFailedError(["git", ...args], result.exitCode, result.stdout, result.stderr)
|
||||
}
|
||||
return result.text().trim()
|
||||
}
|
||||
const gitRun = async (args: string[]) => {
|
||||
const result = await AppRuntime.runPromise(Git.Service.use((git) => git.run(args, { cwd: Instance.worktree })))
|
||||
const result = await AppRuntime.runPromise(Git.Service.use((git) => git.run(args, { cwd: ctx.worktree })))
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Process.RunFailedError(["git", ...args], result.exitCode, result.stdout, result.stderr)
|
||||
}
|
||||
return result
|
||||
}
|
||||
const gitStatus = (args: string[]) =>
|
||||
AppRuntime.runPromise(Git.Service.use((git) => git.run(args, { cwd: Instance.worktree })))
|
||||
AppRuntime.runPromise(Git.Service.use((git) => git.run(args, { cwd: ctx.worktree })))
|
||||
const commitChanges = async (summary: string, actor?: string) => {
|
||||
const args = ["commit", "-m", summary]
|
||||
if (actor) args.push("-m", `Co-authored-by: ${actor} <${actor}@users.noreply.github.com>`)
|
||||
@@ -879,7 +881,7 @@ export const GithubRunCommand = cmd({
|
||||
function subscribeSessionEvents() {
|
||||
const TOOL: Record<string, [string, string]> = {
|
||||
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
|
||||
bash: ["Bash", UI.Style.TEXT_DANGER_BOLD],
|
||||
bash: ["Shell", UI.Style.TEXT_DANGER_BOLD],
|
||||
edit: ["Edit", UI.Style.TEXT_SUCCESS_BOLD],
|
||||
glob: ["Glob", UI.Style.TEXT_INFO_BOLD],
|
||||
grep: ["Grep", UI.Style.TEXT_INFO_BOLD],
|
||||
@@ -1645,5 +1647,5 @@ query($owner: String!, $repo: String!, $number: Int!) {
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import type { Argv } from "yargs"
|
||||
import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2"
|
||||
import { Session } from "@/session/session"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { cmd } from "./cmd"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { CliError, effectCmd } from "../effect-cmd"
|
||||
import { Database } from "@/storage/db"
|
||||
import { SessionTable, MessageTable, PartTable } from "../../session/session.sql"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { ShareNext } from "@/share/share-next"
|
||||
import { EOL } from "os"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Schema } from "effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
|
||||
const decodeMessageInfo = Schema.decodeUnknownSync(MessageV2.Info)
|
||||
const decodePart = Schema.decodeUnknownSync(MessageV2.Part)
|
||||
@@ -78,135 +75,143 @@ export function transformShareData(shareData: ShareData[]): {
|
||||
}
|
||||
}
|
||||
|
||||
export const ImportCommand = cmd({
|
||||
type ExportData = { info: SDKSession; messages: Array<{ info: Message; parts: Part[] }> }
|
||||
|
||||
export const ImportCommand = effectCmd({
|
||||
command: "import <file>",
|
||||
describe: "import session data from JSON file or URL",
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs.positional("file", {
|
||||
builder: (yargs) =>
|
||||
yargs.positional("file", {
|
||||
describe: "path to JSON file or share URL",
|
||||
type: "string",
|
||||
demandOption: true,
|
||||
}),
|
||||
handler: Effect.fn("Cli.import")(function* (args) {
|
||||
const ctx = yield* InstanceRef
|
||||
if (!ctx) return yield* Effect.die("InstanceRef not provided")
|
||||
return yield* runImport(args.file, ctx.project.id)
|
||||
}),
|
||||
})
|
||||
|
||||
const runImport = Effect.fn("Cli.import.body")(function* (file: string, projectID: string) {
|
||||
const share = yield* ShareNext.Service
|
||||
|
||||
let exportData: ExportData | undefined
|
||||
|
||||
const isUrl = file.startsWith("http://") || file.startsWith("https://")
|
||||
|
||||
if (isUrl) {
|
||||
const slug = parseShareUrl(file)
|
||||
if (!slug) {
|
||||
const baseUrl = yield* Effect.orDie(share.url())
|
||||
process.stdout.write(`Invalid URL format. Expected: ${baseUrl}/share/<slug>`)
|
||||
process.stdout.write(EOL)
|
||||
return
|
||||
}
|
||||
|
||||
const baseUrl = new URL(file).origin
|
||||
const req = yield* Effect.orDie(share.request())
|
||||
const headers = shouldAttachShareAuthHeaders(file, req.baseUrl) ? req.headers : {}
|
||||
|
||||
const tryFetch = (url: string) =>
|
||||
Effect.tryPromise({
|
||||
try: () => fetch(url, { headers }),
|
||||
catch: (e) =>
|
||||
new CliError({
|
||||
message: `Failed to fetch share data: ${e instanceof Error ? e.message : String(e)}`,
|
||||
}),
|
||||
})
|
||||
|
||||
const dataPath = req.api.data(slug)
|
||||
let response = yield* tryFetch(`${baseUrl}${dataPath}`)
|
||||
|
||||
if (!response.ok && dataPath !== `/api/share/${slug}/data`) {
|
||||
response = yield* tryFetch(`${baseUrl}/api/share/${slug}/data`)
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
process.stdout.write(`Failed to fetch share data: ${response.statusText}`)
|
||||
process.stdout.write(EOL)
|
||||
return
|
||||
}
|
||||
|
||||
const shareData = yield* Effect.tryPromise({
|
||||
try: () => response.json() as Promise<ShareData[]>,
|
||||
catch: () => new CliError({ message: "Share data was not valid JSON" }),
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
let exportData:
|
||||
| {
|
||||
info: SDKSession
|
||||
messages: Array<{
|
||||
info: Message
|
||||
parts: Part[]
|
||||
}>
|
||||
}
|
||||
| undefined
|
||||
const transformed = transformShareData(shareData)
|
||||
|
||||
const isUrl = args.file.startsWith("http://") || args.file.startsWith("https://")
|
||||
if (!transformed) {
|
||||
process.stdout.write(`Share not found or empty: ${slug}`)
|
||||
process.stdout.write(EOL)
|
||||
return
|
||||
}
|
||||
|
||||
if (isUrl) {
|
||||
const slug = parseShareUrl(args.file)
|
||||
if (!slug) {
|
||||
const baseUrl = await AppRuntime.runPromise(ShareNext.Service.use((svc) => svc.url()))
|
||||
process.stdout.write(`Invalid URL format. Expected: ${baseUrl}/share/<slug>`)
|
||||
process.stdout.write(EOL)
|
||||
return
|
||||
}
|
||||
exportData = transformed
|
||||
} else {
|
||||
exportData = yield* Effect.promise(() =>
|
||||
Filesystem.readJson<NonNullable<typeof exportData>>(file).catch(() => undefined),
|
||||
)
|
||||
if (!exportData) {
|
||||
process.stdout.write(`File not found: ${file}`)
|
||||
process.stdout.write(EOL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const parsed = new URL(args.file)
|
||||
const baseUrl = parsed.origin
|
||||
const req = await AppRuntime.runPromise(ShareNext.Service.use((svc) => svc.request()))
|
||||
const headers = shouldAttachShareAuthHeaders(args.file, req.baseUrl) ? req.headers : {}
|
||||
if (!exportData) {
|
||||
process.stdout.write(`Failed to read session data`)
|
||||
process.stdout.write(EOL)
|
||||
return
|
||||
}
|
||||
|
||||
const dataPath = req.api.data(slug)
|
||||
let response = await fetch(`${baseUrl}${dataPath}`, {
|
||||
headers,
|
||||
const info = Schema.decodeUnknownSync(Session.Info)({
|
||||
...exportData.info,
|
||||
projectID,
|
||||
}) as Session.Info
|
||||
const row = Session.toRow(info)
|
||||
Database.use((db) =>
|
||||
db
|
||||
.insert(SessionTable)
|
||||
.values(row)
|
||||
.onConflictDoUpdate({ target: SessionTable.id, set: { project_id: row.project_id } })
|
||||
.run(),
|
||||
)
|
||||
|
||||
for (const msg of exportData.messages) {
|
||||
const msgInfo = decodeMessageInfo(msg.info) as MessageV2.Info
|
||||
const { id, sessionID: _, ...msgData } = msgInfo
|
||||
Database.use((db) =>
|
||||
db
|
||||
.insert(MessageTable)
|
||||
.values({
|
||||
id,
|
||||
session_id: row.id,
|
||||
time_created: msgInfo.time?.created ?? Date.now(),
|
||||
data: msgData,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.run(),
|
||||
)
|
||||
|
||||
if (!response.ok && dataPath !== `/api/share/${slug}/data`) {
|
||||
response = await fetch(`${baseUrl}/api/share/${slug}/data`, {
|
||||
headers,
|
||||
})
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
process.stdout.write(`Failed to fetch share data: ${response.statusText}`)
|
||||
process.stdout.write(EOL)
|
||||
return
|
||||
}
|
||||
|
||||
const shareData: ShareData[] = await response.json()
|
||||
const transformed = transformShareData(shareData)
|
||||
|
||||
if (!transformed) {
|
||||
process.stdout.write(`Share not found or empty: ${slug}`)
|
||||
process.stdout.write(EOL)
|
||||
return
|
||||
}
|
||||
|
||||
exportData = transformed
|
||||
} else {
|
||||
exportData = await Filesystem.readJson<NonNullable<typeof exportData>>(args.file).catch(() => undefined)
|
||||
if (!exportData) {
|
||||
process.stdout.write(`File not found: ${args.file}`)
|
||||
process.stdout.write(EOL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (!exportData) {
|
||||
process.stdout.write(`Failed to read session data`)
|
||||
process.stdout.write(EOL)
|
||||
return
|
||||
}
|
||||
|
||||
const info = Schema.decodeUnknownSync(Session.Info)({
|
||||
...exportData.info,
|
||||
projectID: Instance.project.id,
|
||||
}) as Session.Info
|
||||
const row = Session.toRow(info)
|
||||
for (const part of msg.parts) {
|
||||
const partInfo = decodePart(part) as MessageV2.Part
|
||||
const { id: partId, sessionID: _s, messageID, ...partData } = partInfo
|
||||
Database.use((db) =>
|
||||
db
|
||||
.insert(SessionTable)
|
||||
.values(row)
|
||||
.onConflictDoUpdate({ target: SessionTable.id, set: { project_id: row.project_id } })
|
||||
.insert(PartTable)
|
||||
.values({
|
||||
id: partId,
|
||||
message_id: messageID,
|
||||
session_id: row.id,
|
||||
data: partData,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.run(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for (const msg of exportData.messages) {
|
||||
const msgInfo = decodeMessageInfo(msg.info) as MessageV2.Info
|
||||
const { id, sessionID: _, ...msgData } = msgInfo
|
||||
Database.use((db) =>
|
||||
db
|
||||
.insert(MessageTable)
|
||||
.values({
|
||||
id,
|
||||
session_id: row.id,
|
||||
time_created: msgInfo.time?.created ?? Date.now(),
|
||||
data: msgData,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.run(),
|
||||
)
|
||||
|
||||
for (const part of msg.parts) {
|
||||
const partInfo = decodePart(part) as MessageV2.Part
|
||||
const { id: partId, sessionID: _s, messageID, ...partData } = partInfo
|
||||
Database.use((db) =>
|
||||
db
|
||||
.insert(PartTable)
|
||||
.values({
|
||||
id: partId,
|
||||
message_id: messageID,
|
||||
session_id: row.id,
|
||||
data: partData,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.run(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
process.stdout.write(`Imported session: ${exportData.info.id}`)
|
||||
process.stdout.write(EOL)
|
||||
})
|
||||
},
|
||||
process.stdout.write(`Imported session: ${exportData.info.id}`)
|
||||
process.stdout.write(EOL)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { cmd } from "./cmd"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import { Cause } from "effect"
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
||||
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
|
||||
@@ -10,6 +12,7 @@ import { McpOAuthProvider } from "../../mcp/oauth-provider"
|
||||
import { Config } from "@/config/config"
|
||||
import { ConfigMCP } from "../../config/mcp"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { WithInstance } from "../../project/with-instance"
|
||||
import { Installation } from "../../installation"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import path from "path"
|
||||
@@ -64,35 +67,31 @@ function oauthServers(config: Config.Info) {
|
||||
)
|
||||
}
|
||||
|
||||
async function listState() {
|
||||
return AppRuntime.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const cfg = yield* Config.Service
|
||||
const mcp = yield* MCP.Service
|
||||
const config = yield* cfg.get()
|
||||
const statuses = yield* mcp.status()
|
||||
const stored = yield* Effect.all(
|
||||
Object.fromEntries(configuredServers(config).map(([name]) => [name, mcp.hasStoredTokens(name)])),
|
||||
{ concurrency: "unbounded" },
|
||||
)
|
||||
return { config, statuses, stored }
|
||||
}),
|
||||
)
|
||||
function listState() {
|
||||
return Effect.gen(function* () {
|
||||
const cfg = yield* Config.Service
|
||||
const mcp = yield* MCP.Service
|
||||
const config = yield* cfg.get()
|
||||
const statuses = yield* mcp.status()
|
||||
const stored = yield* Effect.all(
|
||||
Object.fromEntries(configuredServers(config).map(([name]) => [name, mcp.hasStoredTokens(name)])),
|
||||
{ concurrency: "unbounded" },
|
||||
)
|
||||
return { config, statuses, stored }
|
||||
})
|
||||
}
|
||||
|
||||
async function authState() {
|
||||
return AppRuntime.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const cfg = yield* Config.Service
|
||||
const mcp = yield* MCP.Service
|
||||
const config = yield* cfg.get()
|
||||
const auth = yield* Effect.all(
|
||||
Object.fromEntries(oauthServers(config).map(([name]) => [name, mcp.getAuthStatus(name)])),
|
||||
{ concurrency: "unbounded" },
|
||||
)
|
||||
return { config, auth }
|
||||
}),
|
||||
)
|
||||
function authState() {
|
||||
return Effect.gen(function* () {
|
||||
const cfg = yield* Config.Service
|
||||
const mcp = yield* MCP.Service
|
||||
const config = yield* cfg.get()
|
||||
const auth = yield* Effect.all(
|
||||
Object.fromEntries(oauthServers(config).map(([name]) => [name, mcp.getAuthStatus(name)])),
|
||||
{ concurrency: "unbounded" },
|
||||
)
|
||||
return { config, auth }
|
||||
})
|
||||
}
|
||||
|
||||
export const McpCommand = cmd({
|
||||
@@ -109,73 +108,68 @@ export const McpCommand = cmd({
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
export const McpListCommand = cmd({
|
||||
export const McpListCommand = effectCmd({
|
||||
command: "list",
|
||||
aliases: ["ls"],
|
||||
describe: "list MCP servers and their status",
|
||||
async handler() {
|
||||
await Instance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
UI.empty()
|
||||
prompts.intro("MCP Servers")
|
||||
handler: Effect.fn("Cli.mcp.list")(function* () {
|
||||
UI.empty()
|
||||
prompts.intro("MCP Servers")
|
||||
|
||||
const { config, statuses, stored } = await listState()
|
||||
const servers = configuredServers(config)
|
||||
const { config, statuses, stored } = yield* listState()
|
||||
const servers = configuredServers(config)
|
||||
|
||||
if (servers.length === 0) {
|
||||
prompts.log.warn("No MCP servers configured")
|
||||
prompts.outro("Add servers with: opencode mcp add")
|
||||
return
|
||||
if (servers.length === 0) {
|
||||
prompts.log.warn("No MCP servers configured")
|
||||
prompts.outro("Add servers with: opencode mcp add")
|
||||
return
|
||||
}
|
||||
|
||||
for (const [name, serverConfig] of servers) {
|
||||
const status = statuses[name]
|
||||
const hasOAuth = isMcpRemote(serverConfig) && !!serverConfig.oauth
|
||||
const hasStoredTokens = stored[name]
|
||||
|
||||
let statusIcon: string
|
||||
let statusText: string
|
||||
let hint = ""
|
||||
|
||||
if (!status) {
|
||||
statusIcon = "○"
|
||||
statusText = "not initialized"
|
||||
} else if (status.status === "connected") {
|
||||
statusIcon = "✓"
|
||||
statusText = "connected"
|
||||
if (hasOAuth && hasStoredTokens) {
|
||||
hint = " (OAuth)"
|
||||
}
|
||||
} else if (status.status === "disabled") {
|
||||
statusIcon = "○"
|
||||
statusText = "disabled"
|
||||
} else if (status.status === "needs_auth") {
|
||||
statusIcon = "⚠"
|
||||
statusText = "needs authentication"
|
||||
} else if (status.status === "needs_client_registration") {
|
||||
statusIcon = "✗"
|
||||
statusText = "needs client registration"
|
||||
hint = "\n " + status.error
|
||||
} else {
|
||||
statusIcon = "✗"
|
||||
statusText = "failed"
|
||||
hint = "\n " + status.error
|
||||
}
|
||||
|
||||
for (const [name, serverConfig] of servers) {
|
||||
const status = statuses[name]
|
||||
const hasOAuth = isMcpRemote(serverConfig) && !!serverConfig.oauth
|
||||
const hasStoredTokens = stored[name]
|
||||
const typeHint = serverConfig.type === "remote" ? serverConfig.url : serverConfig.command.join(" ")
|
||||
prompts.log.info(
|
||||
`${statusIcon} ${name} ${UI.Style.TEXT_DIM}${statusText}${hint}\n ${UI.Style.TEXT_DIM}${typeHint}`,
|
||||
)
|
||||
}
|
||||
|
||||
let statusIcon: string
|
||||
let statusText: string
|
||||
let hint = ""
|
||||
|
||||
if (!status) {
|
||||
statusIcon = "○"
|
||||
statusText = "not initialized"
|
||||
} else if (status.status === "connected") {
|
||||
statusIcon = "✓"
|
||||
statusText = "connected"
|
||||
if (hasOAuth && hasStoredTokens) {
|
||||
hint = " (OAuth)"
|
||||
}
|
||||
} else if (status.status === "disabled") {
|
||||
statusIcon = "○"
|
||||
statusText = "disabled"
|
||||
} else if (status.status === "needs_auth") {
|
||||
statusIcon = "⚠"
|
||||
statusText = "needs authentication"
|
||||
} else if (status.status === "needs_client_registration") {
|
||||
statusIcon = "✗"
|
||||
statusText = "needs client registration"
|
||||
hint = "\n " + status.error
|
||||
} else {
|
||||
statusIcon = "✗"
|
||||
statusText = "failed"
|
||||
hint = "\n " + status.error
|
||||
}
|
||||
|
||||
const typeHint = serverConfig.type === "remote" ? serverConfig.url : serverConfig.command.join(" ")
|
||||
prompts.log.info(
|
||||
`${statusIcon} ${name} ${UI.Style.TEXT_DIM}${statusText}${hint}\n ${UI.Style.TEXT_DIM}${typeHint}`,
|
||||
)
|
||||
}
|
||||
|
||||
prompts.outro(`${servers.length} server(s)`)
|
||||
},
|
||||
})
|
||||
},
|
||||
prompts.outro(`${servers.length} server(s)`)
|
||||
}),
|
||||
})
|
||||
|
||||
export const McpAuthCommand = cmd({
|
||||
export const McpAuthCommand = effectCmd({
|
||||
command: "auth [name]",
|
||||
describe: "authenticate with an OAuth-enabled MCP server",
|
||||
builder: (yargs) =>
|
||||
@@ -185,98 +179,98 @@ export const McpAuthCommand = cmd({
|
||||
type: "string",
|
||||
})
|
||||
.command(McpAuthListCommand),
|
||||
async handler(args) {
|
||||
await Instance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
UI.empty()
|
||||
prompts.intro("MCP OAuth Authentication")
|
||||
handler: Effect.fn("Cli.mcp.auth")(function* (args) {
|
||||
UI.empty()
|
||||
prompts.intro("MCP OAuth Authentication")
|
||||
|
||||
const { config, auth } = await authState()
|
||||
const mcpServers = config.mcp ?? {}
|
||||
const servers = oauthServers(config)
|
||||
const { config, auth } = yield* authState()
|
||||
const mcpServers = config.mcp ?? {}
|
||||
const servers = oauthServers(config)
|
||||
|
||||
if (servers.length === 0) {
|
||||
prompts.log.warn("No OAuth-capable MCP servers configured")
|
||||
prompts.log.info("Remote MCP servers support OAuth by default. Add a remote server in opencode.json:")
|
||||
prompts.log.info(`
|
||||
if (servers.length === 0) {
|
||||
prompts.log.warn("No OAuth-capable MCP servers configured")
|
||||
prompts.log.info("Remote MCP servers support OAuth by default. Add a remote server in opencode.json:")
|
||||
prompts.log.info(`
|
||||
"mcp": {
|
||||
"my-server": {
|
||||
"type": "remote",
|
||||
"url": "https://example.com/mcp"
|
||||
}
|
||||
}`)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
let serverName = args.name
|
||||
if (!serverName) {
|
||||
// Build options with auth status
|
||||
const options = servers.map(([name, cfg]) => {
|
||||
const authStatus = auth[name]
|
||||
const icon = getAuthStatusIcon(authStatus)
|
||||
const statusText = getAuthStatusText(authStatus)
|
||||
const url = cfg.url
|
||||
return {
|
||||
label: `${icon} ${name} (${statusText})`,
|
||||
value: name,
|
||||
hint: url,
|
||||
}
|
||||
})
|
||||
|
||||
let serverName = args.name
|
||||
if (!serverName) {
|
||||
// Build options with auth status
|
||||
const options = servers.map(([name, cfg]) => {
|
||||
const authStatus = auth[name]
|
||||
const icon = getAuthStatusIcon(authStatus)
|
||||
const statusText = getAuthStatusText(authStatus)
|
||||
const url = cfg.url
|
||||
return {
|
||||
label: `${icon} ${name} (${statusText})`,
|
||||
value: name,
|
||||
hint: url,
|
||||
}
|
||||
})
|
||||
const selected = yield* Effect.promise(() =>
|
||||
prompts.select({
|
||||
message: "Select MCP server to authenticate",
|
||||
options,
|
||||
}),
|
||||
)
|
||||
if (prompts.isCancel(selected)) throw new UI.CancelledError()
|
||||
serverName = selected
|
||||
}
|
||||
|
||||
const selected = await prompts.select({
|
||||
message: "Select MCP server to authenticate",
|
||||
options,
|
||||
})
|
||||
if (prompts.isCancel(selected)) throw new UI.CancelledError()
|
||||
serverName = selected
|
||||
}
|
||||
const serverConfig = mcpServers[serverName]
|
||||
if (!serverConfig) {
|
||||
prompts.log.error(`MCP server not found: ${serverName}`)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
const serverConfig = mcpServers[serverName]
|
||||
if (!serverConfig) {
|
||||
prompts.log.error(`MCP server not found: ${serverName}`)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
if (!isMcpRemote(serverConfig) || serverConfig.oauth === false) {
|
||||
prompts.log.error(`MCP server ${serverName} is not an OAuth-capable remote server`)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
if (!isMcpRemote(serverConfig) || serverConfig.oauth === false) {
|
||||
prompts.log.error(`MCP server ${serverName} is not an OAuth-capable remote server`)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
// Check if already authenticated
|
||||
const authStatus = auth[serverName] ?? (yield* MCP.Service.use((mcp) => mcp.getAuthStatus(serverName)))
|
||||
if (authStatus === "authenticated") {
|
||||
const confirm = yield* Effect.promise(() =>
|
||||
prompts.confirm({
|
||||
message: `${serverName} already has valid credentials. Re-authenticate?`,
|
||||
}),
|
||||
)
|
||||
if (prompts.isCancel(confirm) || !confirm) {
|
||||
prompts.outro("Cancelled")
|
||||
return
|
||||
}
|
||||
} else if (authStatus === "expired") {
|
||||
prompts.log.warn(`${serverName} has expired credentials. Re-authenticating...`)
|
||||
}
|
||||
|
||||
// Check if already authenticated
|
||||
const authStatus =
|
||||
auth[serverName] ?? (await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.getAuthStatus(serverName))))
|
||||
if (authStatus === "authenticated") {
|
||||
const confirm = await prompts.confirm({
|
||||
message: `${serverName} already has valid credentials. Re-authenticate?`,
|
||||
})
|
||||
if (prompts.isCancel(confirm) || !confirm) {
|
||||
prompts.outro("Cancelled")
|
||||
return
|
||||
}
|
||||
} else if (authStatus === "expired") {
|
||||
prompts.log.warn(`${serverName} has expired credentials. Re-authenticating...`)
|
||||
}
|
||||
const spinner = prompts.spinner()
|
||||
spinner.start("Starting OAuth flow...")
|
||||
|
||||
const spinner = prompts.spinner()
|
||||
spinner.start("Starting OAuth flow...")
|
||||
|
||||
// Subscribe to browser open failure events to show URL for manual opening
|
||||
const unsubscribe = Bus.subscribe(MCP.BrowserOpenFailed, (evt) => {
|
||||
if (evt.properties.mcpName === serverName) {
|
||||
spinner.stop("Could not open browser automatically")
|
||||
prompts.log.warn("Please open this URL in your browser to authenticate:")
|
||||
prompts.log.info(evt.properties.url)
|
||||
spinner.start("Waiting for authorization...")
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
const status = await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.authenticate(serverName)))
|
||||
// Subscribe to browser open failure events to show URL for manual opening
|
||||
const unsubscribe = Bus.subscribe(MCP.BrowserOpenFailed, (evt) => {
|
||||
if (evt.properties.mcpName === serverName) {
|
||||
spinner.stop("Could not open browser automatically")
|
||||
prompts.log.warn("Please open this URL in your browser to authenticate:")
|
||||
prompts.log.info(evt.properties.url)
|
||||
spinner.start("Waiting for authorization...")
|
||||
}
|
||||
})
|
||||
|
||||
yield* MCP.Service.use((mcp) => mcp.authenticate(serverName)).pipe(
|
||||
Effect.tap((status) =>
|
||||
Effect.sync(() => {
|
||||
if (status.status === "connected") {
|
||||
spinner.stop("Authentication successful!")
|
||||
} else if (status.status === "needs_client_registration") {
|
||||
@@ -300,55 +294,53 @@ export const McpAuthCommand = cmd({
|
||||
} else {
|
||||
spinner.stop("Unexpected status: " + status.status, 1)
|
||||
}
|
||||
} catch (error) {
|
||||
}),
|
||||
),
|
||||
Effect.catchCause((cause) =>
|
||||
Effect.sync(() => {
|
||||
spinner.stop("Authentication failed", 1)
|
||||
const error = Cause.squash(cause)
|
||||
prompts.log.error(error instanceof Error ? error.message : String(error))
|
||||
} finally {
|
||||
unsubscribe()
|
||||
}
|
||||
}),
|
||||
),
|
||||
Effect.ensuring(Effect.sync(() => unsubscribe())),
|
||||
)
|
||||
|
||||
prompts.outro("Done")
|
||||
},
|
||||
})
|
||||
},
|
||||
prompts.outro("Done")
|
||||
}),
|
||||
})
|
||||
|
||||
export const McpAuthListCommand = cmd({
|
||||
export const McpAuthListCommand = effectCmd({
|
||||
command: "list",
|
||||
aliases: ["ls"],
|
||||
describe: "list OAuth-capable MCP servers and their auth status",
|
||||
async handler() {
|
||||
await Instance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
UI.empty()
|
||||
prompts.intro("MCP OAuth Status")
|
||||
handler: Effect.fn("Cli.mcp.auth.list")(function* () {
|
||||
UI.empty()
|
||||
prompts.intro("MCP OAuth Status")
|
||||
|
||||
const { config, auth } = await authState()
|
||||
const servers = oauthServers(config)
|
||||
const { config, auth } = yield* authState()
|
||||
const servers = oauthServers(config)
|
||||
|
||||
if (servers.length === 0) {
|
||||
prompts.log.warn("No OAuth-capable MCP servers configured")
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
if (servers.length === 0) {
|
||||
prompts.log.warn("No OAuth-capable MCP servers configured")
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
for (const [name, serverConfig] of servers) {
|
||||
const authStatus = auth[name]
|
||||
const icon = getAuthStatusIcon(authStatus)
|
||||
const statusText = getAuthStatusText(authStatus)
|
||||
const url = serverConfig.url
|
||||
for (const [name, serverConfig] of servers) {
|
||||
const authStatus = auth[name]
|
||||
const icon = getAuthStatusIcon(authStatus)
|
||||
const statusText = getAuthStatusText(authStatus)
|
||||
const url = serverConfig.url
|
||||
|
||||
prompts.log.info(`${icon} ${name} ${UI.Style.TEXT_DIM}${statusText}\n ${UI.Style.TEXT_DIM}${url}`)
|
||||
}
|
||||
prompts.log.info(`${icon} ${name} ${UI.Style.TEXT_DIM}${statusText}\n ${UI.Style.TEXT_DIM}${url}`)
|
||||
}
|
||||
|
||||
prompts.outro(`${servers.length} OAuth-capable server(s)`)
|
||||
},
|
||||
})
|
||||
},
|
||||
prompts.outro(`${servers.length} OAuth-capable server(s)`)
|
||||
}),
|
||||
})
|
||||
|
||||
export const McpLogoutCommand = cmd({
|
||||
export const McpLogoutCommand = effectCmd({
|
||||
command: "logout [name]",
|
||||
describe: "remove OAuth credentials for an MCP server",
|
||||
builder: (yargs) =>
|
||||
@@ -356,57 +348,54 @@ export const McpLogoutCommand = cmd({
|
||||
describe: "name of the MCP server",
|
||||
type: "string",
|
||||
}),
|
||||
async handler(args) {
|
||||
await Instance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
UI.empty()
|
||||
prompts.intro("MCP OAuth Logout")
|
||||
handler: Effect.fn("Cli.mcp.logout")(function* (args) {
|
||||
UI.empty()
|
||||
prompts.intro("MCP OAuth Logout")
|
||||
|
||||
const credentials = await AppRuntime.runPromise(McpAuth.Service.use((auth) => auth.all()))
|
||||
const serverNames = Object.keys(credentials)
|
||||
const credentials = yield* McpAuth.Service.use((auth) => auth.all())
|
||||
const serverNames = Object.keys(credentials)
|
||||
|
||||
if (serverNames.length === 0) {
|
||||
prompts.log.warn("No MCP OAuth credentials stored")
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
if (serverNames.length === 0) {
|
||||
prompts.log.warn("No MCP OAuth credentials stored")
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
let serverName = args.name
|
||||
if (!serverName) {
|
||||
const selected = await prompts.select({
|
||||
message: "Select MCP server to logout",
|
||||
options: serverNames.map((name) => {
|
||||
const entry = credentials[name]
|
||||
const hasTokens = !!entry.tokens
|
||||
const hasClient = !!entry.clientInfo
|
||||
let hint = ""
|
||||
if (hasTokens && hasClient) hint = "tokens + client"
|
||||
else if (hasTokens) hint = "tokens"
|
||||
else if (hasClient) hint = "client registration"
|
||||
return {
|
||||
label: name,
|
||||
value: name,
|
||||
hint,
|
||||
}
|
||||
}),
|
||||
})
|
||||
if (prompts.isCancel(selected)) throw new UI.CancelledError()
|
||||
serverName = selected
|
||||
}
|
||||
let serverName = args.name
|
||||
if (!serverName) {
|
||||
const selected = yield* Effect.promise(() =>
|
||||
prompts.select({
|
||||
message: "Select MCP server to logout",
|
||||
options: serverNames.map((name) => {
|
||||
const entry = credentials[name]
|
||||
const hasTokens = !!entry.tokens
|
||||
const hasClient = !!entry.clientInfo
|
||||
let hint = ""
|
||||
if (hasTokens && hasClient) hint = "tokens + client"
|
||||
else if (hasTokens) hint = "tokens"
|
||||
else if (hasClient) hint = "client registration"
|
||||
return {
|
||||
label: name,
|
||||
value: name,
|
||||
hint,
|
||||
}
|
||||
}),
|
||||
}),
|
||||
)
|
||||
if (prompts.isCancel(selected)) throw new UI.CancelledError()
|
||||
serverName = selected
|
||||
}
|
||||
|
||||
if (!credentials[serverName]) {
|
||||
prompts.log.error(`No credentials found for: ${serverName}`)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
if (!credentials[serverName]) {
|
||||
prompts.log.error(`No credentials found for: ${serverName}`)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.removeAuth(serverName)))
|
||||
prompts.log.success(`Removed OAuth credentials for ${serverName}`)
|
||||
prompts.outro("Done")
|
||||
},
|
||||
})
|
||||
},
|
||||
yield* MCP.Service.use((mcp) => mcp.removeAuth(serverName))
|
||||
prompts.log.success(`Removed OAuth credentials for ${serverName}`)
|
||||
prompts.outro("Done")
|
||||
}),
|
||||
})
|
||||
|
||||
async function resolveConfigPath(baseDir: string, global = false) {
|
||||
@@ -448,7 +437,7 @@ export const McpAddCommand = cmd({
|
||||
command: "add",
|
||||
describe: "add an MCP server",
|
||||
async handler() {
|
||||
await Instance.provide({
|
||||
await WithInstance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
UI.empty()
|
||||
@@ -618,7 +607,7 @@ export const McpDebugCommand = cmd({
|
||||
demandOption: true,
|
||||
}),
|
||||
async handler(args) {
|
||||
await Instance.provide({
|
||||
await WithInstance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
UI.empty()
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { EOL } from "os"
|
||||
import { Effect } from "effect"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { ProviderID } from "../../provider/schema"
|
||||
import { ModelsDev } from "@/provider/models"
|
||||
import { cmd } from "./cmd"
|
||||
import { effectCmd, fail } from "../effect-cmd"
|
||||
import { UI } from "../ui"
|
||||
import { EOL } from "os"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Effect } from "effect"
|
||||
|
||||
export const ModelsCommand = cmd({
|
||||
export const ModelsCommand = effectCmd({
|
||||
command: "models [provider]",
|
||||
describe: "list all available models",
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.positional("provider", {
|
||||
describe: "provider ID to filter models by",
|
||||
type: "string",
|
||||
@@ -26,63 +23,44 @@ export const ModelsCommand = cmd({
|
||||
.option("refresh", {
|
||||
describe: "refresh the models cache from models.dev",
|
||||
type: "boolean",
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
}),
|
||||
handler: Effect.fn("Cli.models")(function* (args) {
|
||||
if (args.refresh) {
|
||||
await ModelsDev.refresh(true)
|
||||
yield* ModelsDev.Service.use((s) => s.refresh(true))
|
||||
UI.println(UI.Style.TEXT_SUCCESS_BOLD + "Models cache refreshed" + UI.Style.TEXT_NORMAL)
|
||||
}
|
||||
|
||||
await Instance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
await AppRuntime.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* Provider.Service
|
||||
const providers = yield* svc.list()
|
||||
const provider = yield* Provider.Service
|
||||
const providers = yield* provider.list()
|
||||
|
||||
const print = (providerID: ProviderID, verbose?: boolean) => {
|
||||
const provider = providers[providerID]
|
||||
const sorted = Object.entries(provider.models).sort(([a], [b]) => a.localeCompare(b))
|
||||
for (const [modelID, model] of sorted) {
|
||||
process.stdout.write(`${providerID}/${modelID}`)
|
||||
process.stdout.write(EOL)
|
||||
if (verbose) {
|
||||
process.stdout.write(JSON.stringify(model, null, 2))
|
||||
process.stdout.write(EOL)
|
||||
}
|
||||
}
|
||||
}
|
||||
const print = (providerID: ProviderID, verbose?: boolean) => {
|
||||
const p = providers[providerID]
|
||||
const sorted = Object.entries(p.models).sort(([a], [b]) => a.localeCompare(b))
|
||||
for (const [modelID, model] of sorted) {
|
||||
process.stdout.write(`${providerID}/${modelID}`)
|
||||
process.stdout.write(EOL)
|
||||
if (verbose) {
|
||||
process.stdout.write(JSON.stringify(model, null, 2))
|
||||
process.stdout.write(EOL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (args.provider) {
|
||||
const providerID = ProviderID.make(args.provider)
|
||||
const provider = providers[providerID]
|
||||
if (!provider) {
|
||||
yield* Effect.sync(() => UI.error(`Provider not found: ${args.provider}`))
|
||||
return
|
||||
}
|
||||
if (args.provider) {
|
||||
const providerID = ProviderID.make(args.provider)
|
||||
if (!providers[providerID]) return yield* fail(`Provider not found: ${args.provider}`)
|
||||
print(providerID, args.verbose)
|
||||
return
|
||||
}
|
||||
|
||||
yield* Effect.sync(() => print(providerID, args.verbose))
|
||||
return
|
||||
}
|
||||
|
||||
const ids = Object.keys(providers).sort((a, b) => {
|
||||
const aIsOpencode = a.startsWith("opencode")
|
||||
const bIsOpencode = b.startsWith("opencode")
|
||||
if (aIsOpencode && !bIsOpencode) return -1
|
||||
if (!aIsOpencode && bIsOpencode) return 1
|
||||
return a.localeCompare(b)
|
||||
})
|
||||
|
||||
yield* Effect.sync(() => {
|
||||
for (const providerID of ids) {
|
||||
print(ProviderID.make(providerID), args.verbose)
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
},
|
||||
const ids = Object.keys(providers).sort((a, b) => {
|
||||
const aIsOpencode = a.startsWith("opencode")
|
||||
const bIsOpencode = b.startsWith("opencode")
|
||||
if (aIsOpencode && !bIsOpencode) return -1
|
||||
if (!aIsOpencode && bIsOpencode) return 1
|
||||
return a.localeCompare(b)
|
||||
})
|
||||
},
|
||||
|
||||
for (const providerID of ids) print(ProviderID.make(providerID), args.verbose)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { intro, log, outro, spinner } from "@clack/prompts"
|
||||
import type { Argv } from "yargs"
|
||||
import { Effect } from "effect"
|
||||
|
||||
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/filesystem"
|
||||
import { Process } from "@/util/process"
|
||||
import { UI } from "../ui"
|
||||
import { cmd } from "./cmd"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
|
||||
type Spin = {
|
||||
start: (msg: string) => void
|
||||
@@ -175,12 +175,12 @@ export function createPlugTask(input: PlugInput, dep: PlugDeps = defaultPlugDeps
|
||||
}
|
||||
}
|
||||
|
||||
export const PluginCommand = cmd({
|
||||
export const PluginCommand = effectCmd({
|
||||
command: "plugin <module>",
|
||||
aliases: ["plug"],
|
||||
describe: "install plugin and update config",
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.positional("module", {
|
||||
type: "string",
|
||||
describe: "npm module name",
|
||||
@@ -196,9 +196,8 @@ export const PluginCommand = cmd({
|
||||
type: "boolean",
|
||||
default: false,
|
||||
describe: "replace existing plugin version",
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
}),
|
||||
handler: Effect.fn("Cli.plug")(function* (args) {
|
||||
const mod = String(args.module ?? "").trim()
|
||||
if (!mod) {
|
||||
UI.error("module is required")
|
||||
@@ -214,20 +213,18 @@ export const PluginCommand = cmd({
|
||||
global: Boolean(args.global),
|
||||
force: Boolean(args.force),
|
||||
})
|
||||
let ok = true
|
||||
|
||||
await Instance.provide({
|
||||
directory: process.cwd(),
|
||||
fn: async () => {
|
||||
ok = await run({
|
||||
vcs: Instance.project.vcs,
|
||||
worktree: Instance.worktree,
|
||||
directory: Instance.directory,
|
||||
})
|
||||
},
|
||||
})
|
||||
const ctx = yield* InstanceRef
|
||||
if (!ctx) return
|
||||
const ok = yield* Effect.promise(() =>
|
||||
run({
|
||||
vcs: ctx.project.vcs,
|
||||
worktree: ctx.worktree,
|
||||
directory: ctx.directory,
|
||||
}),
|
||||
)
|
||||
|
||||
outro("Done")
|
||||
if (!ok) process.exitCode = 1
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Effect } from "effect"
|
||||
import { UI } from "../ui"
|
||||
import { cmd } from "./cmd"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { effectCmd, fail } from "../effect-cmd"
|
||||
import { Git } from "@/git"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { Process } from "@/util/process"
|
||||
|
||||
export const PrCommand = cmd({
|
||||
export const PrCommand = effectCmd({
|
||||
command: "pr <number>",
|
||||
describe: "fetch and checkout a GitHub PR branch, then run opencode",
|
||||
builder: (yargs) =>
|
||||
@@ -14,125 +14,102 @@ export const PrCommand = cmd({
|
||||
describe: "PR number to checkout",
|
||||
demandOption: true,
|
||||
}),
|
||||
async handler(args) {
|
||||
await Instance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
const project = Instance.project
|
||||
if (project.vcs !== "git") {
|
||||
UI.error("Could not find git repository. Please run this command from a git repository.")
|
||||
process.exit(1)
|
||||
handler: Effect.fn("Cli.pr")(function* (args) {
|
||||
const ctx = yield* InstanceRef
|
||||
if (!ctx) return yield* fail("Could not load instance context")
|
||||
if (ctx.project.vcs !== "git") {
|
||||
return yield* fail("Could not find git repository. Please run this command from a git repository.")
|
||||
}
|
||||
|
||||
const git = yield* Git.Service
|
||||
const worktree = ctx.worktree
|
||||
|
||||
const prNumber = args.number
|
||||
const localBranchName = `pr/${prNumber}`
|
||||
UI.println(`Fetching and checking out PR #${prNumber}...`)
|
||||
|
||||
const checkout = yield* Effect.promise(() =>
|
||||
Process.run(["gh", "pr", "checkout", `${prNumber}`, "--branch", localBranchName, "--force"], { nothrow: true }),
|
||||
)
|
||||
if (checkout.code !== 0) {
|
||||
return yield* fail(`Failed to checkout PR #${prNumber}. Make sure you have gh CLI installed and authenticated.`)
|
||||
}
|
||||
|
||||
const prInfoResult = yield* Effect.promise(() =>
|
||||
Process.text(
|
||||
[
|
||||
"gh",
|
||||
"pr",
|
||||
"view",
|
||||
`${prNumber}`,
|
||||
"--json",
|
||||
"headRepository,headRepositoryOwner,isCrossRepository,headRefName,body",
|
||||
],
|
||||
{ nothrow: true },
|
||||
),
|
||||
)
|
||||
|
||||
let sessionId: string | undefined
|
||||
|
||||
if (prInfoResult.code === 0 && prInfoResult.text.trim()) {
|
||||
const prInfo = JSON.parse(prInfoResult.text)
|
||||
|
||||
if (prInfo?.isCrossRepository && prInfo.headRepository && prInfo.headRepositoryOwner) {
|
||||
const forkOwner = prInfo.headRepositoryOwner.login
|
||||
const forkName = prInfo.headRepository.name
|
||||
const remoteName = forkOwner
|
||||
|
||||
const remotes = (yield* git.run(["remote"], { cwd: worktree })).text().trim()
|
||||
if (!remotes.split("\n").includes(remoteName)) {
|
||||
yield* git.run(["remote", "add", remoteName, `https://github.com/${forkOwner}/${forkName}.git`], {
|
||||
cwd: worktree,
|
||||
})
|
||||
UI.println(`Added fork remote: ${remoteName}`)
|
||||
}
|
||||
|
||||
const prNumber = args.number
|
||||
const localBranchName = `pr/${prNumber}`
|
||||
UI.println(`Fetching and checking out PR #${prNumber}...`)
|
||||
yield* git.run(["branch", `--set-upstream-to=${remoteName}/${prInfo.headRefName}`, localBranchName], {
|
||||
cwd: worktree,
|
||||
})
|
||||
}
|
||||
|
||||
// Use gh pr checkout with custom branch name
|
||||
const result = await Process.run(
|
||||
["gh", "pr", "checkout", `${prNumber}`, "--branch", localBranchName, "--force"],
|
||||
{
|
||||
nothrow: true,
|
||||
},
|
||||
)
|
||||
if (prInfo?.body) {
|
||||
const sessionMatch = prInfo.body.match(/https:\/\/opncd\.ai\/s\/([a-zA-Z0-9_-]+)/)
|
||||
if (sessionMatch) {
|
||||
const sessionUrl = sessionMatch[0]
|
||||
UI.println(`Found opencode session: ${sessionUrl}`)
|
||||
UI.println(`Importing session...`)
|
||||
|
||||
if (result.code !== 0) {
|
||||
UI.error(`Failed to checkout PR #${prNumber}. Make sure you have gh CLI installed and authenticated.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Fetch PR info for fork handling and session link detection
|
||||
const prInfoResult = await Process.text(
|
||||
[
|
||||
"gh",
|
||||
"pr",
|
||||
"view",
|
||||
`${prNumber}`,
|
||||
"--json",
|
||||
"headRepository,headRepositoryOwner,isCrossRepository,headRefName,body",
|
||||
],
|
||||
{ nothrow: true },
|
||||
)
|
||||
|
||||
let sessionId: string | undefined
|
||||
|
||||
if (prInfoResult.code === 0) {
|
||||
const prInfoText = prInfoResult.text
|
||||
if (prInfoText.trim()) {
|
||||
const prInfo = JSON.parse(prInfoText)
|
||||
|
||||
// Handle fork PRs
|
||||
if (prInfo && prInfo.isCrossRepository && prInfo.headRepository && prInfo.headRepositoryOwner) {
|
||||
const forkOwner = prInfo.headRepositoryOwner.login
|
||||
const forkName = prInfo.headRepository.name
|
||||
const remoteName = forkOwner
|
||||
|
||||
// Check if remote already exists
|
||||
const remotes = await AppRuntime.runPromise(
|
||||
Git.Service.use((git) => git.run(["remote"], { cwd: Instance.worktree })),
|
||||
).then((x) => x.text().trim())
|
||||
if (!remotes.split("\n").includes(remoteName)) {
|
||||
await AppRuntime.runPromise(
|
||||
Git.Service.use((git) =>
|
||||
git.run(["remote", "add", remoteName, `https://github.com/${forkOwner}/${forkName}.git`], {
|
||||
cwd: Instance.worktree,
|
||||
}),
|
||||
),
|
||||
)
|
||||
UI.println(`Added fork remote: ${remoteName}`)
|
||||
}
|
||||
|
||||
// Set upstream to the fork so pushes go there
|
||||
const headRefName = prInfo.headRefName
|
||||
await AppRuntime.runPromise(
|
||||
Git.Service.use((git) =>
|
||||
git.run(["branch", `--set-upstream-to=${remoteName}/${headRefName}`, localBranchName], {
|
||||
cwd: Instance.worktree,
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Check for opencode session link in PR body
|
||||
if (prInfo && prInfo.body) {
|
||||
const sessionMatch = prInfo.body.match(/https:\/\/opncd\.ai\/s\/([a-zA-Z0-9_-]+)/)
|
||||
if (sessionMatch) {
|
||||
const sessionUrl = sessionMatch[0]
|
||||
UI.println(`Found opencode session: ${sessionUrl}`)
|
||||
UI.println(`Importing session...`)
|
||||
|
||||
const importResult = await Process.text(["opencode", "import", sessionUrl], {
|
||||
nothrow: true,
|
||||
})
|
||||
if (importResult.code === 0) {
|
||||
const importOutput = importResult.text.trim()
|
||||
// Extract session ID from the output (format: "Imported session: <session-id>")
|
||||
const sessionIdMatch = importOutput.match(/Imported session: ([a-zA-Z0-9_-]+)/)
|
||||
if (sessionIdMatch) {
|
||||
sessionId = sessionIdMatch[1]
|
||||
UI.println(`Session imported: ${sessionId}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
const importResult = yield* Effect.promise(() =>
|
||||
Process.text(["opencode", "import", sessionUrl], { nothrow: true }),
|
||||
)
|
||||
if (importResult.code === 0) {
|
||||
const sessionIdMatch = importResult.text.trim().match(/Imported session: ([a-zA-Z0-9_-]+)/)
|
||||
if (sessionIdMatch) {
|
||||
sessionId = sessionIdMatch[1]
|
||||
UI.println(`Session imported: ${sessionId}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UI.println(`Successfully checked out PR #${prNumber} as branch '${localBranchName}'`)
|
||||
UI.println()
|
||||
UI.println("Starting opencode...")
|
||||
UI.println()
|
||||
UI.println(`Successfully checked out PR #${prNumber} as branch '${localBranchName}'`)
|
||||
UI.println()
|
||||
UI.println("Starting opencode...")
|
||||
UI.println()
|
||||
|
||||
const opencodeArgs = sessionId ? ["-s", sessionId] : []
|
||||
const opencodeProcess = Process.spawn(["opencode", ...opencodeArgs], {
|
||||
const opencodeArgs = sessionId ? ["-s", sessionId] : []
|
||||
const code = yield* Effect.promise(
|
||||
() =>
|
||||
Process.spawn(["opencode", ...opencodeArgs], {
|
||||
stdin: "inherit",
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
cwd: process.cwd(),
|
||||
})
|
||||
const code = await opencodeProcess.exited
|
||||
if (code !== 0) throw new Error(`opencode exited with code ${code}`)
|
||||
},
|
||||
})
|
||||
},
|
||||
}).exited,
|
||||
)
|
||||
// Match legacy throw semantics — propagate as a defect so the top-level
|
||||
// index.ts catch handles it identically (exit 1, "Unexpected error" banner).
|
||||
if (code !== 0) return yield* Effect.die(new Error(`opencode exited with code ${code}`))
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -4,13 +4,16 @@ import { cmd } from "./cmd"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import { UI } from "../ui"
|
||||
import { ModelsDev } from "@/provider/models"
|
||||
|
||||
const getModels = () => AppRuntime.runPromise(ModelsDev.Service.use((s) => s.get()))
|
||||
const refreshModels = () => AppRuntime.runPromise(ModelsDev.Service.use((s) => s.refresh(true)))
|
||||
import { map, pipe, sortBy, values } from "remeda"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import { Config } from "@/config/config"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Plugin } from "../../plugin"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { WithInstance } from "../../project/with-instance"
|
||||
import type { Hooks } from "@opencode-ai/plugin"
|
||||
import { Process } from "@/util/process"
|
||||
import { text } from "node:stream/consumers"
|
||||
@@ -245,7 +248,7 @@ export const ProvidersListCommand = cmd({
|
||||
return Object.entries(yield* auth.all())
|
||||
}),
|
||||
)
|
||||
const database = await ModelsDev.get()
|
||||
const database = await getModels()
|
||||
|
||||
for (const [providerID, result] of results) {
|
||||
const name = database[providerID]?.name || providerID
|
||||
@@ -300,7 +303,7 @@ export const ProvidersLoginCommand = cmd({
|
||||
type: "string",
|
||||
}),
|
||||
async handler(args) {
|
||||
await Instance.provide({
|
||||
await WithInstance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
UI.empty()
|
||||
@@ -334,14 +337,14 @@ export const ProvidersLoginCommand = cmd({
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
await ModelsDev.refresh(true).catch(() => {})
|
||||
await refreshModels().catch(() => {})
|
||||
|
||||
const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.get()))
|
||||
|
||||
const disabled = new Set(config.disabled_providers ?? [])
|
||||
const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
|
||||
|
||||
const providers = await ModelsDev.get().then((x) => {
|
||||
const providers = await getModels().then((x) => {
|
||||
const filtered: Record<string, (typeof x)[string]> = {}
|
||||
for (const [key, value] of Object.entries(x)) {
|
||||
if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) {
|
||||
@@ -505,7 +508,7 @@ export const ProvidersLogoutCommand = cmd({
|
||||
prompts.log.error("No credentials found")
|
||||
return
|
||||
}
|
||||
const database = await ModelsDev.get()
|
||||
const database = await getModels()
|
||||
const selected = await prompts.select({
|
||||
message: "Select provider",
|
||||
options: credentials.map(([key, value]) => ({
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user