Compare commits

...
Author SHA1 Message Date
Kit Langton 68ff83a98d refactor(opencode): simplify database effect reset lifecycle 2026-05-02 23:23:32 -04:00
Kit Langton fd4887d45d refactor(effect-drizzle-sqlite): simplify single-row reads 2026-05-02 23:23:32 -04:00
Kit Langton 25e546c837 refactor(effect-drizzle-sqlite): use Effectable prototype 2026-05-02 23:23:32 -04:00
Kit Langton f948a1e3b0 test(opencode): pin lifecycle invariants for DatabaseEffect + managed-runtime
Adds regression tests for the two non-obvious invariants enforced by the
Effect-Drizzle integration:

- packages/opencode/test/storage/db-effect.test.ts pins that
  DatabaseEffect.layer rebuilds a fresh handle after Database.close + dispose,
  and demonstrates the shared-memoMap poisoning that resetDatabase prevents
  by disposing every DB-consuming runtime before closing the SQLite handle.

- packages/opencode/test/effect/managed-runtime.test.ts pins makeManagedRuntime
  dispose semantics and the lazy.resetIf compare-and-reset guard so a
  rebuilt instance is never clobbered by a stale dispose.
2026-05-02 23:23:32 -04:00
Kit Langton 46996e5a67 refactor(opencode): extract managed-runtime helper, prune adapter dead code
- Extract makeManagedRuntime() to src/effect/managed-runtime.ts so AppRuntime
  and BootstrapRuntime stop duplicating the lazy ManagedRuntime + dispose
  pattern, and document the shared-memoMap dispose ordering invariant.
- Add lazy.resetIf(expected) and use it in 3 compare-and-reset call sites
  (db.close, AppRuntime.dispose, disposeWebHandler).
- Drop dead `filename` option from EffectDrizzleSqlite MakeConfig.
- Drop redundant `patched` IIFE flag (patchClass is already idempotent).
- Add module-load assertion that Effect's protocol keys are present so a
  silent breakage on an Effect upgrade becomes a loud failure at import.
- Collapse share-next test `live()` into the wider `wired()` factory.
- Document lifecycle constraint in db-effect.ts and test/fixture/db.ts.
2026-05-02 23:23:32 -04:00
Kit Langton 322bb01257 refactor(opencode): unify drizzle client through effect adapter
The Effect adapter is now the only Drizzle wrapper over the bun:sqlite
handle. Database.Client owns the lifecycle, DatabaseEffect.Service just
exposes that handle. Removes acquire/release ref counting that was
ceremony around the same module-level singleton.
2026-05-02 23:23:32 -04:00
Kit Langton aaa42cca07 fix(opencode): own effect sqlite lifecycle in layers 2026-05-02 23:23:32 -04:00
Kit Langton 8e2c15214e fix(opencode): refresh effect sqlite client after reset 2026-05-02 23:23:32 -04:00
Kit Langton 4faa6c64d6 feat(opencode): pilot effect sqlite database service 2026-05-02 23:23:32 -04:00
Kit Langton f6f6cd0515 fix(effect-drizzle-sqlite): simplify sqlite adapter 2026-05-02 23:23:32 -04:00
Kit Langton efcbc153ee test(effect-drizzle-sqlite): cover transaction edge cases 2026-05-02 23:23:32 -04:00
Kit Langton e4ae265d8f fix(effect-drizzle-sqlite): support pipeable transactions 2026-05-02 23:23:32 -04:00
Kit Langton 89efce865d feat(effect-drizzle-sqlite): add sqlite adapter 2026-05-02 23:23:31 -04:00
Kit Langton 0e13279545 refactor(cli): convert agent / providers / mcp to effectCmd (#25525) 2026-05-02 23:22:44 -04:00
Kit Langton 5f03d892c0 fix(httpapi): pagination Link header echoes request host (#25527) 2026-05-02 23:19:33 -04:00
Kit Langton bdabb102fe refactor(cli/stats): Stage 4 — fully Effect-native body (#25523) 2026-05-02 23:08:26 -04:00
Kit Langton a79a6594b0 chore: bump Effect beta (#25524) 2026-05-02 23:08:13 -04:00
opencode-agent[bot] a3d282a4c2 chore: generate 2026-05-03 03:04:40 +00:00
Kit Langton db24f89313 refactor(cli): convert mcp list, auth, auth list, logout to effectCmd (#25521) 2026-05-03 03:03:32 +00:00
opencode-agent[bot] 31cb0bfa4f chore: generate 2026-05-03 02:54:20 +00:00
Kit Langton af9fdf0a1c refactor(cli): convert github subcommands to effectCmd (#25522) 2026-05-02 22:53:20 -04:00
Youssef Achy be88cd5cb9 chore(opencode): exclude .map files from CLI binary build (#25500) 2026-05-02 22:52:32 -04:00
Luke Parker b4cc7d13b6 fix(desktop): limit zoom handler to zoom keys (#25516) 2026-05-03 02:44:52 +00:00
Aiden Cline 0ba013f8de chore: rm log statement (#25470) 2026-05-02 21:43:48 -05:00
Kit Langton 0956b15c52 refactor(acp): drop async from synchronous ACP.init (#25520) 2026-05-02 22:38:44 -04:00
opencode-agent[bot] 61150f6391 chore: generate 2026-05-03 02:36:41 +00:00
Kit Langton 7409dcc6bd refactor(cli): convert run command to effectCmd (#25519) 2026-05-02 22:35:20 -04:00
Kit Langton 2829943ad1 refactor(cli): convert debug wait, agent list, acp to effectCmd (#25518) 2026-05-02 22:31:20 -04:00
Kit Langton c4311dda31 feat(cli): allow effectCmd instance to be a function of args (#25517) 2026-05-03 02:27:41 +00:00
Kit Langton ad05a46d74 refactor(lifecycle): bootstrap as pure orchestration (#25510) 2026-05-02 22:26:54 -04:00
opencode-agent[bot] a6cadba814 chore: generate 2026-05-03 02:10:52 +00:00
Dax a3bc5d35b0 Refactor v2 session events as schemas (#24512) 2026-05-02 22:09:48 -04:00
Kit Langton 1409a0715c refactor(cli): convert web + account to effectCmd (instance: false) (#25512) 2026-05-02 21:59:35 -04:00
Kit Langton e98c291866 feat(cli): add instance: false opt-out to effectCmd (#25507) 2026-05-03 01:44:06 +00:00
Kit Langton e709dc34fb feat: default HTTP API backend to on for dev/beta channels 2026-05-02 20:43:23 -04:00
opencode-agent[bot] 9293cddb3a chore: generate 2026-05-03 00:43:16 +00:00
Kit Langton 68b3448b09 refactor(cli): drop redundant explicit Effect.ensuring(store.dispose) (#25503) 2026-05-02 20:42:09 -04:00
opencode-agent[bot] 80f2b13a55 chore: generate 2026-05-03 00:40:21 +00:00
Kit Langton 7d91d3b1ed Normalize instance lifecycle wiring (#25501) 2026-05-02 20:39:20 -04:00
opencode-agent[bot] a6464062b7 chore: generate 2026-05-03 00:32:24 +00:00
Kit Langton fd01dc9c89 test(httpapi): add route exerciser 2026-05-02 20:31:21 -04:00
opencode-agent[bot] d10fb88b66 chore: generate 2026-05-03 00:10:53 +00:00
Luke Parker 6b68b1020e docs: clarify LSP and formatter opt-in config (#25502) 2026-05-03 00:09:50 +00:00
Kit Langton 85bb9007ba feat(cli): auto-dispose InstanceContext after effectCmd handlers (#25481) 2026-05-02 19:54:13 -04:00
opencode-agent[bot] 9bef88e3b0 chore: generate 2026-05-02 23:34:40 +00:00
Kit Langton f98053c34e fix(instance): run bootstrap from instance store (#25475) 2026-05-02 19:33:38 -04:00
opencode-agent[bot] 36007aecf4 chore: generate 2026-05-02 23:23:53 +00:00
Kit Langton 4de44bbbef refactor(cli): convert debug subcommands to effectCmd (#25479) 2026-05-02 19:22:51 -04:00
opencode-agent[bot] 9d03d4419e chore: generate 2026-05-02 23:20:15 +00:00
Kit Langton 7ab1c1c74a refactor(cli): convert debug agent command to effectCmd (#25485) 2026-05-02 19:19:06 -04:00
Luke Parker 3f459819ba feat: refactor bash tool with shell-aware prompts for bash, pwsh+powershell, and cmd (#20039) 2026-05-03 09:18:48 +10:00
Kit Langton 1986a6e817 refactor(cli): convert session subcommands to effectCmd (#25483) 2026-05-02 18:15:28 -04:00
opencode-agent[bot] dfe1325fca chore: generate 2026-05-02 22:02:14 +00:00
Kit Langton c1686c6ddc refactor(cli): convert stats command to effectCmd (#25474) 2026-05-02 18:01:06 -04:00
Kit Langton 79b6ce5db4 refactor(cli): convert import command to effectCmd (#25467) 2026-05-02 21:56:32 +00:00
Kit Langton 0c816eb4b1 refactor(cli): convert plugin command to effectCmd (#25473) 2026-05-02 17:55:13 -04:00
Kit Langton e318e173d8 refactor(cli): convert export command to effectCmd (#25471) 2026-05-02 17:45:41 -04:00
opencode-agent[bot] b314781a1a chore: generate 2026-05-02 21:02:46 +00:00
Kit Langton 8396d6b016 refactor(cli): convert pr command to effectCmd (#25465) 2026-05-02 17:01:46 -04:00
opencode 43e20874f4 sync release versions for v1.14.33 2026-05-02 19:53:06 +00:00
opencode-agent[bot] c444e971b0 chore: generate 2026-05-02 19:27:24 +00:00
HyeokjaeLeeandDax Raad 430bde9e9b fix(instance): restore InstanceBootstrap init parameter for non-Effec… (#25449)
Co-authored-by: Dax Raad <d@ironbay.co>
2026-05-02 15:26:30 -04:00
Kit Langton 05b82a6a30 refactor(cli): drop ModelsDev Promise compat shim (#25460) 2026-05-02 15:11:01 -04:00
Kit Langton 6cd02c05c2 fix(telemetry): emit Tool.execute span for MCP and plugin tools (#25452) 2026-05-02 14:49:56 -04:00
opencode-agent[bot] b3a7513765 chore: generate 2026-05-02 18:00:11 +00:00
Kit Langton f8738c9002 feat(models): effectify ModelsDev as Service (#25434) 2026-05-02 13:59:08 -04:00
Aiden Cline b460db15d7 tweak: allow read tool to accept offset of 0 (#25431) 2026-05-02 11:12:07 -05:00
opencode-agent[bot] ff4779ca11 chore: generate 2026-05-02 16:09:04 +00:00
Kit Langton 146ff8ad85 feat(cli): add effectCmd wrapper + convert models command (#25429) 2026-05-02 12:08:04 -04:00
OpeOginni 0d0ec7dc46 docs: CLI docs for current commands and flags (#25399) 2026-05-02 11:07:22 -05:00
Jérôme Benoit 1ea6e6cd4b fix(nix): remove stale packages/shared filter (#24930) 2026-05-02 10:49:51 -05:00
opencode-agent[bot] 96061222d2 chore: generate 2026-05-02 15:45:21 +00:00
Kit Langton 3b9155714d Delete Instance.dispose and Instance.reload (#25427) 2026-05-02 11:44:16 -04:00
opencode 7371db5cc6 sync release versions for v1.14.32 2026-05-02 15:34:12 +00:00
Kit Langton b09b7d28b8 refactor(instance-store): consolidate dispose helpers (#25424) 2026-05-02 11:21:40 -04:00
opencode-agent[bot] 31ed4602e1 chore: update nix node_modules hashes 2026-05-02 15:16:12 +00:00
Sebastian 6a76346734 upgrade opentui to 0.2.2 (#25420) 2026-05-02 15:01:53 +00:00
Kit Langton 78b3000031 fix(tui): keep shell-mode prompt editable (#25419) 2026-05-02 10:56:27 -04:00
Kit Langton 4c4860fb24 Replace Instance.disposeAll/load with fixture helper (#25418) 2026-05-02 10:56:15 -04:00
Kit Langton 5242a1c6b4 fix(httpapi): install Instance ALS for adapter Promise bridge (#25417) 2026-05-02 10:49:44 -04:00
Kit Langton 075f876e6f fix(httpapi): re-land workspace create payload accepts missing extra (#25412) 2026-05-02 09:35:39 -04:00
opencode-agent[bot] a849812e9f chore: generate 2026-05-02 13:09:14 +00:00
Kit Langton d99dde6306 Migrate test inits from Promise to Effect (#25377) 2026-05-02 09:07:59 -04:00
opencode-agent[bot] becf57ee6a chore: generate 2026-05-02 04:03:59 +00:00
Kit Langton f33aec1139 Convert LoadInput.init to Effect + extract InstanceBootstrap as a Service (#25376) 2026-05-02 00:02:52 -04:00
Kit Langton 1571933096 Drop ALS fallbacks from containsPath and workspace routing (#25374) 2026-05-02 03:06:22 +00:00
Kit Langton 160928a9a9 Extract InstanceStore.provide helper (#25372) 2026-05-01 22:42:03 -04:00
opencode-agent[bot] d297c29f22 chore: generate 2026-05-02 02:19:48 +00:00
Kit Langton 0b498dd448 fix(httpapi): preserve OpenAPI parameter parity (#25291) 2026-05-01 22:18:52 -04:00
Kit Langton cec9c6122a Move instance loading into Effect service (#25277) 2026-05-01 22:18:06 -04:00
Zeke SikelianosandAiden Cline 51e310c9ce fix(read): prevent unsupported image formats from being sending to provider (#21114)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-05-01 18:14:22 -05:00
Aiden Cline 478156456e core: fix npm package detection to properly handle cached directories without installed packages (#25354) 2026-05-01 15:49:14 -05:00
opencode-agent[bot] 6252412d94 chore: generate 2026-05-01 20:03:10 +00:00
Dax Raad c2609cbf04 core: allow agents to access global tmp directory without permission prompts
Agents can now create temporary files in the global tmp directory without
triggering external_directory permission prompts. This enables agents to
freely use temporary storage for intermediate files during builds and
other operations.
2026-05-01 15:35:45 -04:00
github-actions[bot] 2115df57bf Update VOUCHED list
https://github.com/anomalyco/opencode/issues/25288#issuecomment-4360290197
2026-05-01 16:16:45 +00:00
Aiden Cline 29ec07700c fix: bedrock reasoning issue (#25303) 2026-05-01 11:15:17 -05:00
Frank bcae852d28 zen: remove hardcoded safety identifier 2026-05-01 11:12:28 -04:00
Kit Langton 16ddf5f559 fix(session): use finite archived timestamp schema (#25275) 2026-05-01 11:57:03 +00:00
Kit Langton 8c79c58c4d refactor: rename workspace adapters (#25272) 2026-05-01 07:36:52 -04:00
luo jiyin 97ed9ba624 fix: correct documentation typos (#25260) 2026-05-01 12:05:06 +02:00
Simon Klee a6b6395c8a fix(tui): gate logo subpixel rendering on truecolor support (#25265) 2026-05-01 11:33:44 +02:00
opencode 21f8027ef7 sync release versions for v1.14.31 2026-05-01 06:13:48 +00:00
Brendan Allan a5aa72bd7d fix: update provider store after loading providers in bootstrap (#25236) 2026-05-01 13:39:22 +08:00
Aiden Cline 563177c6ac fix: fix issue if tool returned image and empty text and it caused api errors (#25241) 2026-05-01 00:13:03 -05:00
opencode-agent[bot] 4eae8ec037 chore: generate 2026-05-01 04:21:51 +00:00
Aiden Cline 08895c396e docs: fix tui and keybinds documentation (#25233) 2026-04-30 23:20:54 -05:00
opencode-agent[bot] 4e451a4b0f chore: update nix node_modules hashes 2026-05-01 04:07:58 +00:00
Brendan AllanandJay V 163290bcf0 desktop: sentry integration (#15300)
Co-authored-by: Jay V <air@live.ca>
2026-05-01 11:56:31 +08:00
Aiden Cline c68c33d4fe docs: remove deprecated modes.mdx pages (#25227) 2026-04-30 22:49:32 -05:00
Dax Raad 3615d8e226 core: clarify that temp directory already exists for AI agents
The bash tool description now explicitly states that the temp directory has already been created and exists, preventing agents from unnecessarily trying to create it before use.
2026-04-30 23:48:48 -04:00
Dax 2283979199 Preapprove agent tmp directory access (#25226) 2026-04-30 23:47:15 -04:00
Aiden Cline 33f7f593ee fix: tui list jank issue (#25219) 2026-04-30 22:45:41 -05:00
opencode-agent[bot] 461e7345b3 chore: update nix node_modules hashes 2026-05-01 03:32:55 +00:00
opencode-agent[bot] 6bd91c68e8 chore: generate 2026-05-01 03:22:36 +00:00
Dax Raad ff55a40749 core: remove @effect/language-service plugin and optimize hot path type performance
- Removed @effect/language-service from both packages/core and packages/opencode tsconfig files and dependencies

- Wrapped mergeDeep calls in config loading and LLM streaming to avoid expensive remeda conditional merge type instantiations in hot paths

- Narrowed Drizzle migrate() overload signature to avoid expensive variance checks during database initialization

These changes reduce TypeScript type-checking overhead and improve startup and runtime performance for config loading, LLM streaming, and database migrations.
2026-04-30 23:21:05 -04:00
opencode-agent[bot] 8b56d77ea1 chore: generate 2026-05-01 03:02:15 +00:00
Kit Langton dd3aa96730 test(httpapi): cover more safe GET parity (#25217) 2026-04-30 23:01:11 -04:00
Kit Langton 8b56d1712f refactor(session): pass project to list (#25215) 2026-04-30 23:00:59 -04:00
Kit Langton 3c24d22d42 fix(httpapi): omit absent optional response fields (#25214) 2026-05-01 02:38:32 +00:00
Kit Langton 4c70ea28d2 fix(tui): scope Zed editor context to containing workspaces (#25211) 2026-04-30 22:33:39 -04:00
Kit Langton 5ba68a28c0 refactor(httpapi): scope async prompt fiber (#25213) 2026-04-30 22:33:02 -04:00
opencode-agent[bot] bce4def2db chore: generate 2026-05-01 02:26:56 +00:00
Kit Langton 3544ea0244 refactor(httpapi): drop session prompt bridge (#25210) 2026-04-30 22:25:52 -04:00
opencode-agent[bot] 6434918794 chore: generate 2026-05-01 01:46:55 +00:00
Kit Langton 5984d917dc refactor(session): yield instance context in system prompt (#25207) 2026-04-30 21:45:48 -04:00
Kit Langton c2a97a7a6c refactor(file): yield instance context in watcher (#25205) 2026-04-30 21:45:21 -04:00
Kit Langton a083c88e87 refactor(sync): capture instance context for publish (#25206) 2026-04-30 21:45:02 -04:00
Kit Langton ce3b0988c4 refactor(project): yield instance context in bootstrap (#25204) 2026-04-30 21:44:52 -04:00
Kit Langton e8a194a2bb test(effect): stabilize runner active shell check (#25203) 2026-04-30 21:36:19 -04:00
Kit Langton 8aa8798e07 refactor(session): yield instance context in llm (#25200) 2026-04-30 21:29:28 -04:00
opencode-agent[bot] 6d4629b566 chore: generate 2026-05-01 01:28:37 +00:00
OpeOginni a9d399699e fix(desktop): Prevent Model response Interruption when opening settings dialog (#25114) 2026-05-01 01:27:38 +00:00
Kit Langton bc805b3001 Pass CORS options to HttpApi backend (#25201) 2026-04-30 21:26:32 -04:00
392 changed files with 27342 additions and 18591 deletions
+1
View File
@@ -32,6 +32,7 @@ rekram1-node
-ricardo-m-l -ricardo-m-l
-robinmordasiewicz -robinmordasiewicz
rubdos rubdos
-saisharan0103 spamming ai prs
shantur shantur
simonklee simonklee
-spider-yamet clawdbot/llm psychosis, spam pinging the team -spider-yamet clawdbot/llm psychosis, spam pinging the team
+6
View File
@@ -36,3 +36,9 @@ jobs:
PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }} PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }} PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }} 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
View File
@@ -494,6 +494,13 @@ jobs:
working-directory: packages/desktop-electron working-directory: packages/desktop-electron
env: env:
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }} 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 - name: Package and publish
if: needs.version.outputs.release if: needs.version.outputs.release
+1 -1
View File
@@ -132,7 +132,7 @@ It's very similar to Claude Code in terms of capability. Here are the key differ
- 100% open source - 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. - 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 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. - 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.
+163 -62
View File
@@ -29,12 +29,13 @@
}, },
"packages/app": { "packages/app": {
"name": "@opencode-ai/app", "name": "@opencode-ai/app",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
"@opencode-ai/ui": "workspace:*", "@opencode-ai/ui": "workspace:*",
"@sentry/solid": "catalog:",
"@shikijs/transformers": "3.9.2", "@shikijs/transformers": "3.9.2",
"@solid-primitives/active-element": "2.1.3", "@solid-primitives/active-element": "2.1.3",
"@solid-primitives/audio": "1.4.2", "@solid-primitives/audio": "1.4.2",
@@ -69,6 +70,7 @@
"devDependencies": { "devDependencies": {
"@happy-dom/global-registrator": "20.0.11", "@happy-dom/global-registrator": "20.0.11",
"@playwright/test": "catalog:", "@playwright/test": "catalog:",
"@sentry/vite-plugin": "catalog:",
"@tailwindcss/vite": "catalog:", "@tailwindcss/vite": "catalog:",
"@tsconfig/bun": "1.0.9", "@tsconfig/bun": "1.0.9",
"@types/bun": "catalog:", "@types/bun": "catalog:",
@@ -83,7 +85,7 @@
}, },
"packages/console/app": { "packages/console/app": {
"name": "@opencode-ai/console-app", "name": "@opencode-ai/console-app",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@cloudflare/vite-plugin": "1.15.2", "@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1", "@ibm/plex": "6.4.1",
@@ -117,7 +119,7 @@
}, },
"packages/console/core": { "packages/console/core": {
"name": "@opencode-ai/console-core", "name": "@opencode-ai/console-core",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@aws-sdk/client-sts": "3.782.0", "@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1", "@jsx-email/render": "1.1.1",
@@ -144,7 +146,7 @@
}, },
"packages/console/function": { "packages/console/function": {
"name": "@opencode-ai/console-function", "name": "@opencode-ai/console-function",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@ai-sdk/anthropic": "3.0.64", "@ai-sdk/anthropic": "3.0.64",
"@ai-sdk/openai": "3.0.48", "@ai-sdk/openai": "3.0.48",
@@ -168,7 +170,7 @@
}, },
"packages/console/mail": { "packages/console/mail": {
"name": "@opencode-ai/console-mail", "name": "@opencode-ai/console-mail",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@jsx-email/all": "2.2.3", "@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3", "@jsx-email/cli": "1.4.3",
@@ -192,7 +194,7 @@
}, },
"packages/core": { "packages/core": {
"name": "@opencode-ai/core", "name": "@opencode-ai/core",
"version": "1.14.30", "version": "1.14.33",
"bin": { "bin": {
"opencode": "./bin/opencode", "opencode": "./bin/opencode",
}, },
@@ -226,10 +228,11 @@
}, },
"packages/desktop": { "packages/desktop": {
"name": "@opencode-ai/desktop", "name": "@opencode-ai/desktop",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@opencode-ai/app": "workspace:*", "@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*", "@opencode-ai/ui": "workspace:*",
"@sentry/solid": "catalog:",
"@solid-primitives/i18n": "2.2.1", "@solid-primitives/i18n": "2.2.1",
"@solid-primitives/storage": "catalog:", "@solid-primitives/storage": "catalog:",
"@solidjs/meta": "catalog:", "@solidjs/meta": "catalog:",
@@ -250,6 +253,7 @@
}, },
"devDependencies": { "devDependencies": {
"@actions/artifact": "4.0.0", "@actions/artifact": "4.0.0",
"@sentry/vite-plugin": "catalog:",
"@tauri-apps/cli": "^2", "@tauri-apps/cli": "^2",
"@types/bun": "catalog:", "@types/bun": "catalog:",
"@typescript/native-preview": "catalog:", "@typescript/native-preview": "catalog:",
@@ -259,7 +263,7 @@
}, },
"packages/desktop-electron": { "packages/desktop-electron": {
"name": "@opencode-ai/desktop-electron", "name": "@opencode-ai/desktop-electron",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"drizzle-orm": "catalog:", "drizzle-orm": "catalog:",
"effect": "catalog:", "effect": "catalog:",
@@ -275,6 +279,8 @@
"@lydell/node-pty": "catalog:", "@lydell/node-pty": "catalog:",
"@opencode-ai/app": "workspace:*", "@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*", "@opencode-ai/ui": "workspace:*",
"@sentry/solid": "catalog:",
"@sentry/vite-plugin": "catalog:",
"@solid-primitives/i18n": "2.2.1", "@solid-primitives/i18n": "2.2.1",
"@solid-primitives/storage": "catalog:", "@solid-primitives/storage": "catalog:",
"@solidjs/meta": "catalog:", "@solidjs/meta": "catalog:",
@@ -301,9 +307,22 @@
"@lydell/node-pty-win32-x64": "1.2.0-beta.10", "@lydell/node-pty-win32-x64": "1.2.0-beta.10",
}, },
}, },
"packages/effect-drizzle-sqlite": {
"name": "@opencode-ai/effect-drizzle-sqlite",
"version": "0.0.0",
"dependencies": {
"drizzle-orm": "catalog:",
"effect": "catalog:",
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
},
},
"packages/enterprise": { "packages/enterprise": {
"name": "@opencode-ai/enterprise", "name": "@opencode-ai/enterprise",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
"@opencode-ai/ui": "workspace:*", "@opencode-ai/ui": "workspace:*",
@@ -332,7 +351,7 @@
}, },
"packages/function": { "packages/function": {
"name": "@opencode-ai/function", "name": "@opencode-ai/function",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@octokit/auth-app": "8.0.1", "@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:", "@octokit/rest": "catalog:",
@@ -348,7 +367,7 @@
}, },
"packages/opencode": { "packages/opencode": {
"name": "opencode", "name": "opencode",
"version": "1.14.30", "version": "1.14.33",
"bin": { "bin": {
"opencode": "./bin/opencode", "opencode": "./bin/opencode",
}, },
@@ -390,6 +409,7 @@
"@octokit/graphql": "9.0.2", "@octokit/graphql": "9.0.2",
"@octokit/rest": "catalog:", "@octokit/rest": "catalog:",
"@openauthjs/openauth": "catalog:", "@openauthjs/openauth": "catalog:",
"@opencode-ai/effect-drizzle-sqlite": "workspace:*",
"@opencode-ai/plugin": "workspace:*", "@opencode-ai/plugin": "workspace:*",
"@opencode-ai/script": "workspace:*", "@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
@@ -456,7 +476,6 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.28.4", "@babel/core": "7.28.4",
"@effect/language-service": "0.84.2",
"@octokit/webhooks-types": "7.6.1", "@octokit/webhooks-types": "7.6.1",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
"@opencode-ai/script": "workspace:*", "@opencode-ai/script": "workspace:*",
@@ -491,7 +510,7 @@
}, },
"packages/plugin": { "packages/plugin": {
"name": "@opencode-ai/plugin", "name": "@opencode-ai/plugin",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
"effect": "catalog:", "effect": "catalog:",
@@ -506,8 +525,8 @@
"typescript": "catalog:", "typescript": "catalog:",
}, },
"peerDependencies": { "peerDependencies": {
"@opentui/core": ">=0.2.0", "@opentui/core": ">=0.2.2",
"@opentui/solid": ">=0.2.0", "@opentui/solid": ">=0.2.2",
}, },
"optionalPeers": [ "optionalPeers": [
"@opentui/core", "@opentui/core",
@@ -526,7 +545,7 @@
}, },
"packages/sdk/js": { "packages/sdk/js": {
"name": "@opencode-ai/sdk", "name": "@opencode-ai/sdk",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"cross-spawn": "catalog:", "cross-spawn": "catalog:",
}, },
@@ -541,7 +560,7 @@
}, },
"packages/slack": { "packages/slack": {
"name": "@opencode-ai/slack", "name": "@opencode-ai/slack",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1", "@slack/bolt": "^3.17.1",
@@ -576,7 +595,7 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@opencode-ai/ui", "name": "@opencode-ai/ui",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
@@ -625,7 +644,7 @@
}, },
"packages/web": { "packages/web": {
"name": "@opencode-ai/web", "name": "@opencode-ai/web",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@astrojs/cloudflare": "12.6.3", "@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1", "@astrojs/markdown-remark": "6.3.1",
@@ -685,10 +704,12 @@
"@npmcli/arborist": "9.4.0", "@npmcli/arborist": "9.4.0",
"@octokit/rest": "22.0.0", "@octokit/rest": "22.0.0",
"@openauthjs/openauth": "0.0.0-20250322224806", "@openauthjs/openauth": "0.0.0-20250322224806",
"@opentui/core": "0.2.0", "@opentui/core": "0.2.2",
"@opentui/solid": "0.2.0", "@opentui/solid": "0.2.2",
"@pierre/diffs": "1.1.0-beta.18", "@pierre/diffs": "1.1.0-beta.18",
"@playwright/test": "1.59.1", "@playwright/test": "1.59.1",
"@sentry/solid": "10.36.0",
"@sentry/vite-plugin": "4.6.0",
"@solid-primitives/storage": "4.3.3", "@solid-primitives/storage": "4.3.3",
"@solidjs/meta": "0.29.4", "@solidjs/meta": "0.29.4",
"@solidjs/router": "0.15.4", "@solidjs/router": "0.15.4",
@@ -708,7 +729,7 @@
"dompurify": "3.3.1", "dompurify": "3.3.1",
"drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-kit": "1.0.0-beta.19-d95b7a4",
"drizzle-orm": "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", "fuzzysort": "3.1.0",
"hono": "4.10.7", "hono": "4.10.7",
"hono-openapi": "1.1.2", "hono-openapi": "1.1.2",
@@ -1069,8 +1090,6 @@
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="], "@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/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=="], "@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=="],
@@ -1567,6 +1586,8 @@
"@opencode-ai/desktop-electron": ["@opencode-ai/desktop-electron@workspace:packages/desktop-electron"], "@opencode-ai/desktop-electron": ["@opencode-ai/desktop-electron@workspace:packages/desktop-electron"],
"@opencode-ai/effect-drizzle-sqlite": ["@opencode-ai/effect-drizzle-sqlite@workspace:packages/effect-drizzle-sqlite"],
"@opencode-ai/enterprise": ["@opencode-ai/enterprise@workspace:packages/enterprise"], "@opencode-ai/enterprise": ["@opencode-ai/enterprise@workspace:packages/enterprise"],
"@opencode-ai/function": ["@opencode-ai/function@workspace:packages/function"], "@opencode-ai/function": ["@opencode-ai/function@workspace:packages/function"],
@@ -1613,21 +1634,21 @@
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], "@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=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
@@ -1959,6 +1980,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=="], "@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/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=="], "@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 +2784,21 @@
"builder-util-runtime": ["builder-util-runtime@9.5.1", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ=="], "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-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-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=="], "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
@@ -3035,7 +3094,7 @@
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], "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=="], "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
@@ -3243,7 +3302,7 @@
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], "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=="], "finity": ["finity@0.5.4", "", {}, "sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA=="],
@@ -3737,7 +3796,7 @@
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], "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=="], "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
@@ -4141,7 +4200,7 @@
"p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], "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=="], "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="],
@@ -4161,7 +4220,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=="], "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=="], "param-case": ["param-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A=="],
@@ -4191,7 +4250,7 @@
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], "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=="], "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="],
@@ -4951,7 +5010,7 @@
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], "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=="], "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 +5118,9 @@
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], "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=="], "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
@@ -5595,6 +5656,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=="], "@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=="], "@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=="], "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
@@ -5611,6 +5674,16 @@
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@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-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=="], "@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 +5738,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=="], "@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/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=="], "@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 +5924,6 @@
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "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=="], "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=="], "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 +6034,7 @@
"ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "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=="], "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
@@ -5973,6 +6046,8 @@
"pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="], "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=="], "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=="], "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
@@ -6065,12 +6140,16 @@
"type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "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=="], "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=="], "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=="], "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=="], "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 +6648,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=="], "@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/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=="], "@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
@@ -6591,6 +6680,8 @@
"@standard-community/standard-openapi/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@standard-community/standard-openapi/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@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=="], "@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=="], "@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
@@ -6725,7 +6816,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/@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=="], "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 +6828,12 @@
"ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "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=="], "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=="], "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=="], "readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
@@ -6767,6 +6862,8 @@
"type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "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/@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=="], "vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
@@ -6991,6 +7088,12 @@
"@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], "@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"@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=="], "@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=="], "@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 +7176,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=="], "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=="], "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=="], "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/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=="], "pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
@@ -7099,6 +7194,8 @@
"tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], "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/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=="], "@astrojs/check/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
@@ -7153,6 +7250,8 @@
"@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "@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": ["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=="], "@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 +7278,8 @@
"opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], "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=="], "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=="], "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+4 -4
View File
@@ -1,8 +1,8 @@
{ {
"nodeModules": { "nodeModules": {
"x86_64-linux": "sha256-cBfg4pJ4mjsfS4MFFASBaZZykArgIoeo/3woOcSGy1U=", "x86_64-linux": "sha256-SLWRe4uPSRWgU+NPa1BywmrUtNVIC0Oy2mjmxclxk+s=",
"aarch64-linux": "sha256-Q6cqUwfqbscdrPW0uHcfshhQINjJi0HiyURMSdOOCf4=", "aarch64-linux": "sha256-toHEeIqMzrmThoV0B52juGKm4pa/aJN3gBFFtrSZp2Q=",
"aarch64-darwin": "sha256-1AtfsD1D9YxWSEsecPJF9XsvsxsWTtVtkP5l6UW43og=", "aarch64-darwin": "sha256-lYUsUxq5zR2RXjqZTEdjduOncnlwvTlxDJVKWXJuKPY=",
"x86_64-darwin": "sha256-YS5/8YTf9LymAUbjXVrGDfxtKVJrpZbPnnCtsGHSHoU=" "x86_64-darwin": "sha256-77XmuEYqGwb1mkEHfnghq1VtukFTneohA0FW6WDOk1U="
} }
} }
-1
View File
@@ -55,7 +55,6 @@ stdenvNoCC.mkDerivation {
--filter './packages/opencode' \ --filter './packages/opencode' \
--filter './packages/desktop' \ --filter './packages/desktop' \
--filter './packages/app' \ --filter './packages/app' \
--filter './packages/shared' \
--frozen-lockfile \ --frozen-lockfile \
--ignore-scripts \ --ignore-scripts \
--no-progress --no-progress
+5 -3
View File
@@ -34,8 +34,8 @@
"@types/cross-spawn": "6.0.6", "@types/cross-spawn": "6.0.6",
"@octokit/rest": "22.0.0", "@octokit/rest": "22.0.0",
"@hono/zod-validator": "0.4.2", "@hono/zod-validator": "0.4.2",
"@opentui/core": "0.2.0", "@opentui/core": "0.2.2",
"@opentui/solid": "0.2.0", "@opentui/solid": "0.2.2",
"ulid": "3.0.1", "ulid": "3.0.1",
"@kobalte/core": "0.13.11", "@kobalte/core": "0.13.11",
"@types/luxon": "3.7.1", "@types/luxon": "3.7.1",
@@ -53,7 +53,7 @@
"dompurify": "3.3.1", "dompurify": "3.3.1",
"drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-kit": "1.0.0-beta.19-d95b7a4",
"drizzle-orm": "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", "ai": "6.0.168",
"cross-spawn": "7.0.6", "cross-spawn": "7.0.6",
"hono": "4.10.7", "hono": "4.10.7",
@@ -77,6 +77,8 @@
"@solidjs/meta": "0.29.4", "@solidjs/meta": "0.29.4",
"@solidjs/router": "0.15.4", "@solidjs/router": "0.15.4",
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020", "@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", "solid-js": "1.9.10",
"vite-plugin-solid": "2.11.10", "vite-plugin-solid": "2.11.10",
"@lydell/node-pty": "1.2.0-beta.10" "@lydell/node-pty": "1.2.0-beta.10"
+3 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/app", "name": "@opencode-ai/app",
"version": "1.14.30", "version": "1.14.33",
"description": "", "description": "",
"type": "module", "type": "module",
"exports": { "exports": {
@@ -27,6 +27,7 @@
"devDependencies": { "devDependencies": {
"@happy-dom/global-registrator": "20.0.11", "@happy-dom/global-registrator": "20.0.11",
"@playwright/test": "catalog:", "@playwright/test": "catalog:",
"@sentry/vite-plugin": "catalog:",
"@tailwindcss/vite": "catalog:", "@tailwindcss/vite": "catalog:",
"@tsconfig/bun": "1.0.9", "@tsconfig/bun": "1.0.9",
"@types/bun": "catalog:", "@types/bun": "catalog:",
@@ -40,6 +41,7 @@
}, },
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@sentry/solid": "catalog:",
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
"@opencode-ai/ui": "workspace:*", "@opencode-ai/ui": "workspace:*",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
+14 -6
View File
@@ -1,4 +1,5 @@
import "@/index.css" import "@/index.css"
import * as Sentry from "@sentry/solid"
import { I18nProvider } from "@opencode-ai/ui/context" import { I18nProvider } from "@opencode-ai/ui/context"
import { DialogProvider } from "@opencode-ai/ui/context/dialog" import { DialogProvider } from "@opencode-ai/ui/context/dialog"
import { FileComponentProvider } from "@opencode-ai/ui/context/file" import { FileComponentProvider } from "@opencode-ai/ui/context/file"
@@ -148,12 +149,19 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
> >
<LanguageProvider locale={props.locale}> <LanguageProvider locale={props.locale}>
<UiI18nBridge> <UiI18nBridge>
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}> <ErrorBoundary
<DialogProvider> fallback={(error) => {
<MarkedProvider> Sentry.captureException(error)
<FileComponentProvider component={File}>{props.children}</FileComponentProvider> return <ErrorPage error={error} />
</MarkedProvider> }}
</DialogProvider> >
<QueryProvider>
<DialogProvider>
<MarkedProvider>
<FileComponentProvider component={File}>{props.children}</FileComponentProvider>
</MarkedProvider>
</DialogProvider>
</QueryProvider>
</ErrorBoundary> </ErrorBoundary>
</UiI18nBridge> </UiI18nBridge>
</LanguageProvider> </LanguageProvider>
@@ -329,6 +329,7 @@ export const SettingsGeneral: Component = () => {
label={(o) => o.label} label={(o) => o.label}
onSelect={(option) => { onSelect={(option) => {
if (!option) return if (!option) return
if (option.value === currentShell()) return
globalSync.updateConfig({ shell: option.value }) globalSync.updateConfig({ shell: option.value })
}} }}
variant="secondary" variant="secondary"
+3
View File
@@ -204,6 +204,9 @@ function createGlobalSync() {
}, },
translate: language.t, translate: language.t,
getSdk: sdkFor, getSdk: sdkFor,
global: {
provider: globalStore.provider,
},
}) })
async function loadSessions(directory: string) { 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 const seededPath = input.global.path.directory === input.directory ? input.global.path : undefined
if (seededProject) input.setStore("project", seededProject) if (seededProject) input.setStore("project", seededProject)
if (seededPath) input.setStore("path", seededPath) 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) { if (Object.keys(input.store.config).length === 0 && Object.keys(input.global.config).length > 0) {
input.setStore("config", reconcile(input.global.config, { merge: false })) input.setStore("config", reconcile(input.global.config, { merge: false }))
} }
@@ -23,6 +23,7 @@ describe("createChildStoreManager", () => {
onDispose() {}, onDispose() {},
translate: (key) => key, translate: (key) => key,
getSdk: () => null!, getSdk: () => null!,
global: { provider: null! },
}) })
Array.from({ length: 30 }, (_, index) => `/pinned-${index}`).forEach((directory) => { Array.from({ length: 30 }, (_, index) => `/pinned-${index}`).forEach((directory) => {
@@ -1,7 +1,7 @@
import { createRoot, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js" import { createRoot, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js"
import { createStore, type SetStoreFunction, type Store } from "solid-js/store" import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
import { Persist, persisted } from "@/utils/persist" 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 { import {
DIR_IDLE_TTL_MS, DIR_IDLE_TTL_MS,
MAX_DIR_STORES, MAX_DIR_STORES,
@@ -27,6 +27,9 @@ export function createChildStoreManager(input: {
onDispose: (directory: string) => void onDispose: (directory: string) => void
translate: (key: string, vars?: Record<string, string | number>) => string translate: (key: string, vars?: Record<string, string | number>) => string
getSdk: (directory: string) => OpencodeClient getSdk: (directory: string) => OpencodeClient
global: {
provider: ProviderListResponse
}
}) { }) {
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {} const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
const vcsCache = new Map<string, VcsCache>() const vcsCache = new Map<string, VcsCache>()
@@ -189,7 +192,13 @@ export function createChildStoreManager(input: {
get provider_ready() { get provider_ready() {
return !providerQuery.isLoading 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: {}, config: {},
get path() { get path() {
if (pathQuery.isLoading || !pathQuery.data) if (pathQuery.isLoading || !pathQuery.data)
+20
View File
@@ -1,5 +1,6 @@
// @refresh reload // @refresh reload
import * as Sentry from "@sentry/solid"
import { render } from "solid-js/web" import { render } from "solid-js/web"
import { AppBaseProviders, AppInterface } from "@/app" import { AppBaseProviders, AppInterface } from "@/app"
import { type Platform, PlatformProvider } from "@/context/platform" import { type Platform, PlatformProvider } from "@/context/platform"
@@ -125,6 +126,25 @@ const platform: Platform = {
setDefaultServer: writeDefaultServerUrl, 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) { if (root instanceof HTMLElement) {
const server: ServerConnection.Http = { type: "http", http: { url: getCurrentUrl() } } const server: ServerConnection.Http = { type: "http", http: { url: getCurrentUrl() } }
render( render(
+4
View File
@@ -2,6 +2,10 @@ interface ImportMetaEnv {
readonly VITE_OPENCODE_SERVER_HOST: string readonly VITE_OPENCODE_SERVER_HOST: string
readonly VITE_OPENCODE_SERVER_PORT: string readonly VITE_OPENCODE_SERVER_PORT: string
readonly VITE_OPENCODE_CHANNEL?: "dev" | "beta" | "prod" readonly VITE_OPENCODE_CHANNEL?: "dev" | "beta" | "prod"
readonly VITE_SENTRY_DSN?: string
readonly VITE_SENTRY_ENVIRONMENT?: string
readonly VITE_SENTRY_RELEASE?: string
} }
interface ImportMeta { interface ImportMeta {
+2
View File
@@ -402,6 +402,8 @@ export const dict = {
"error.page.description": "حدث خطأ أثناء تحميل التطبيق.", "error.page.description": "حدث خطأ أثناء تحميل التطبيق.",
"error.page.details.label": "تفاصيل الخطأ", "error.page.details.label": "تفاصيل الخطأ",
"error.page.action.restart": "إعادة تشغيل", "error.page.action.restart": "إعادة تشغيل",
"error.page.action.report": "الإبلاغ عن الخطأ",
"error.page.action.reported": "تم الإبلاغ عن الخطأ",
"error.page.action.checking": "جارٍ التحقق...", "error.page.action.checking": "جارٍ التحقق...",
"error.page.action.checkUpdates": "التحقق من وجود تحديثات", "error.page.action.checkUpdates": "التحقق من وجود تحديثات",
"error.page.action.updateTo": "تحديث إلى {{version}}", "error.page.action.updateTo": "تحديث إلى {{version}}",
+2
View File
@@ -403,6 +403,8 @@ export const dict = {
"error.page.description": "Ocorreu um erro ao carregar a aplicação.", "error.page.description": "Ocorreu um erro ao carregar a aplicação.",
"error.page.details.label": "Detalhes do Erro", "error.page.details.label": "Detalhes do Erro",
"error.page.action.restart": "Reiniciar", "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.checking": "Verificando...",
"error.page.action.checkUpdates": "Verificar atualizações", "error.page.action.checkUpdates": "Verificar atualizações",
"error.page.action.updateTo": "Atualizar para {{version}}", "error.page.action.updateTo": "Atualizar para {{version}}",
+2
View File
@@ -449,6 +449,8 @@ export const dict = {
"error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.", "error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.",
"error.page.details.label": "Detalji greške", "error.page.details.label": "Detalji greške",
"error.page.action.restart": "Restartuj", "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.checking": "Provjera...",
"error.page.action.checkUpdates": "Provjeri ažuriranja", "error.page.action.checkUpdates": "Provjeri ažuriranja",
"error.page.action.updateTo": "Ažuriraj na {{version}}", "error.page.action.updateTo": "Ažuriraj na {{version}}",
+2
View File
@@ -446,6 +446,8 @@ export const dict = {
"error.page.description": "Der opstod en fejl under indlæsning af applikationen.", "error.page.description": "Der opstod en fejl under indlæsning af applikationen.",
"error.page.details.label": "Fejldetaljer", "error.page.details.label": "Fejldetaljer",
"error.page.action.restart": "Genstart", "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.checking": "Tjekker...",
"error.page.action.checkUpdates": "Tjek for opdateringer", "error.page.action.checkUpdates": "Tjek for opdateringer",
"error.page.action.updateTo": "Opdater til {{version}}", "error.page.action.updateTo": "Opdater til {{version}}",
+2
View File
@@ -410,6 +410,8 @@ export const dict = {
"error.page.description": "Beim Laden der Anwendung ist ein Fehler aufgetreten.", "error.page.description": "Beim Laden der Anwendung ist ein Fehler aufgetreten.",
"error.page.details.label": "Fehlerdetails", "error.page.details.label": "Fehlerdetails",
"error.page.action.restart": "Neustart", "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.checking": "Prüfen...",
"error.page.action.checkUpdates": "Nach Updates suchen", "error.page.action.checkUpdates": "Nach Updates suchen",
"error.page.action.updateTo": "Auf {{version}} aktualisieren", "error.page.action.updateTo": "Auf {{version}} aktualisieren",
+2
View File
@@ -465,6 +465,8 @@ export const dict = {
"error.page.description": "An error occurred while loading the application.", "error.page.description": "An error occurred while loading the application.",
"error.page.details.label": "Error Details", "error.page.details.label": "Error Details",
"error.page.action.restart": "Restart", "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.checking": "Checking...",
"error.page.action.checkUpdates": "Check for updates", "error.page.action.checkUpdates": "Check for updates",
"error.page.action.updateTo": "Update to {{version}}", "error.page.action.updateTo": "Update to {{version}}",
+2
View File
@@ -449,6 +449,8 @@ export const dict = {
"error.page.description": "Ocurrió un error al cargar la aplicación.", "error.page.description": "Ocurrió un error al cargar la aplicación.",
"error.page.details.label": "Detalles del error", "error.page.details.label": "Detalles del error",
"error.page.action.restart": "Reiniciar", "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.checking": "Comprobando...",
"error.page.action.checkUpdates": "Buscar actualizaciones", "error.page.action.checkUpdates": "Buscar actualizaciones",
"error.page.action.updateTo": "Actualizar a {{version}}", "error.page.action.updateTo": "Actualizar a {{version}}",
+2
View File
@@ -406,6 +406,8 @@ export const dict = {
"error.page.description": "Une erreur s'est produite lors du chargement de l'application.", "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.details.label": "Détails de l'erreur",
"error.page.action.restart": "Redémarrer", "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.checking": "Vérification...",
"error.page.action.checkUpdates": "Vérifier les mises à jour", "error.page.action.checkUpdates": "Vérifier les mises à jour",
"error.page.action.updateTo": "Mettre à jour vers {{version}}", "error.page.action.updateTo": "Mettre à jour vers {{version}}",
+2
View File
@@ -402,6 +402,8 @@ export const dict = {
"error.page.description": "アプリケーションの読み込み中にエラーが発生しました。", "error.page.description": "アプリケーションの読み込み中にエラーが発生しました。",
"error.page.details.label": "エラー詳細", "error.page.details.label": "エラー詳細",
"error.page.action.restart": "再起動", "error.page.action.restart": "再起動",
"error.page.action.report": "エラーを報告",
"error.page.action.reported": "エラーを報告しました",
"error.page.action.checking": "確認中...", "error.page.action.checking": "確認中...",
"error.page.action.checkUpdates": "アップデートを確認", "error.page.action.checkUpdates": "アップデートを確認",
"error.page.action.updateTo": "{{version}}にアップデート", "error.page.action.updateTo": "{{version}}にアップデート",
+2
View File
@@ -401,6 +401,8 @@ export const dict = {
"error.page.description": "애플리케이션을 로드하는 동안 오류가 발생했습니다.", "error.page.description": "애플리케이션을 로드하는 동안 오류가 발생했습니다.",
"error.page.details.label": "오류 세부 정보", "error.page.details.label": "오류 세부 정보",
"error.page.action.restart": "다시 시작", "error.page.action.restart": "다시 시작",
"error.page.action.report": "오류 신고",
"error.page.action.reported": "오류가 신고됨",
"error.page.action.checking": "확인 중...", "error.page.action.checking": "확인 중...",
"error.page.action.checkUpdates": "업데이트 확인", "error.page.action.checkUpdates": "업데이트 확인",
"error.page.action.updateTo": "{{version}} 버전으로 업데이트", "error.page.action.updateTo": "{{version}} 버전으로 업데이트",
+2
View File
@@ -450,6 +450,8 @@ export const dict = {
"error.page.description": "Det oppstod en feil under lasting av applikasjonen.", "error.page.description": "Det oppstod en feil under lasting av applikasjonen.",
"error.page.details.label": "Feildetaljer", "error.page.details.label": "Feildetaljer",
"error.page.action.restart": "Start på nytt", "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.checking": "Sjekker...",
"error.page.action.checkUpdates": "Se etter oppdateringer", "error.page.action.checkUpdates": "Se etter oppdateringer",
"error.page.action.updateTo": "Oppdater til {{version}}", "error.page.action.updateTo": "Oppdater til {{version}}",
+2
View File
@@ -403,6 +403,8 @@ export const dict = {
"error.page.description": "Wystąpił błąd podczas ładowania aplikacji.", "error.page.description": "Wystąpił błąd podczas ładowania aplikacji.",
"error.page.details.label": "Szczegóły błędu", "error.page.details.label": "Szczegóły błędu",
"error.page.action.restart": "Restartuj", "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.checking": "Sprawdzanie...",
"error.page.action.checkUpdates": "Sprawdź aktualizacje", "error.page.action.checkUpdates": "Sprawdź aktualizacje",
"error.page.action.updateTo": "Zaktualizuj do {{version}}", "error.page.action.updateTo": "Zaktualizuj do {{version}}",
+2
View File
@@ -448,6 +448,8 @@ export const dict = {
"error.page.description": "Произошла ошибка при загрузке приложения.", "error.page.description": "Произошла ошибка при загрузке приложения.",
"error.page.details.label": "Детали ошибки", "error.page.details.label": "Детали ошибки",
"error.page.action.restart": "Перезапустить", "error.page.action.restart": "Перезапустить",
"error.page.action.report": "Сообщить об ошибке",
"error.page.action.reported": "Об ошибке сообщено",
"error.page.action.checking": "Проверка...", "error.page.action.checking": "Проверка...",
"error.page.action.checkUpdates": "Проверить обновления", "error.page.action.checkUpdates": "Проверить обновления",
"error.page.action.updateTo": "Обновить до {{version}}", "error.page.action.updateTo": "Обновить до {{version}}",
+2
View File
@@ -447,6 +447,8 @@ export const dict = {
"error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน", "error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน",
"error.page.details.label": "รายละเอียดข้อผิดพลาด", "error.page.details.label": "รายละเอียดข้อผิดพลาด",
"error.page.action.restart": "รีสตาร์ท", "error.page.action.restart": "รีสตาร์ท",
"error.page.action.report": "รายงานข้อผิดพลาด",
"error.page.action.reported": "รายงานข้อผิดพลาดแล้ว",
"error.page.action.checking": "กำลังตรวจสอบ...", "error.page.action.checking": "กำลังตรวจสอบ...",
"error.page.action.checkUpdates": "ตรวจสอบการอัปเดต", "error.page.action.checkUpdates": "ตรวจสอบการอัปเดต",
"error.page.action.updateTo": "อัปเดตเป็น {{version}}", "error.page.action.updateTo": "อัปเดตเป็น {{version}}",
+2
View File
@@ -452,6 +452,8 @@ export const dict = {
"error.page.description": "Uygulama yüklenirken bir hata oluştu.", "error.page.description": "Uygulama yüklenirken bir hata oluştu.",
"error.page.details.label": "Hata Detayları", "error.page.details.label": "Hata Detayları",
"error.page.action.restart": "Yeniden Başlat", "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.checking": "Kontrol ediliyor...",
"error.page.action.checkUpdates": "Güncellemeleri kontrol et", "error.page.action.checkUpdates": "Güncellemeleri kontrol et",
"error.page.action.updateTo": "{{version}} sürümüne güncelle", "error.page.action.updateTo": "{{version}} sürümüne güncelle",
+2
View File
@@ -452,6 +452,8 @@ export const dict = {
"error.page.description": "加载应用程序时发生错误。", "error.page.description": "加载应用程序时发生错误。",
"error.page.details.label": "错误详情", "error.page.details.label": "错误详情",
"error.page.action.restart": "重启", "error.page.action.restart": "重启",
"error.page.action.report": "上报错误",
"error.page.action.reported": "错误已上报",
"error.page.action.checking": "检查中...", "error.page.action.checking": "检查中...",
"error.page.action.checkUpdates": "检查更新", "error.page.action.checkUpdates": "检查更新",
"error.page.action.updateTo": "更新到 {{version}}", "error.page.action.updateTo": "更新到 {{version}}",
+2
View File
@@ -445,6 +445,8 @@ export const dict = {
"error.page.description": "載入應用程式時發生錯誤。", "error.page.description": "載入應用程式時發生錯誤。",
"error.page.details.label": "錯誤詳情", "error.page.details.label": "錯誤詳情",
"error.page.action.restart": "重新啟動", "error.page.action.restart": "重新啟動",
"error.page.action.report": "回報錯誤",
"error.page.action.reported": "已回報錯誤",
"error.page.action.checking": "檢查中...", "error.page.action.checking": "檢查中...",
"error.page.action.checkUpdates": "檢查更新", "error.page.action.checkUpdates": "檢查更新",
"error.page.action.updateTo": "更新到 {{version}}", "error.page.action.updateTo": "更新到 {{version}}",
+20 -2
View File
@@ -1,7 +1,8 @@
import { TextField } from "@opencode-ai/ui/text-field" import { TextField } from "@opencode-ai/ui/text-field"
import * as Sentry from "@sentry/solid"
import { Logo } from "@opencode-ai/ui/logo" import { Logo } from "@opencode-ai/ui/logo"
import { Button } from "@opencode-ai/ui/button" 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 { createStore } from "solid-js/store"
import { usePlatform } from "@/context/platform" import { usePlatform } from "@/context/platform"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
@@ -270,10 +271,27 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
label={language.t("error.page.details.label")} label={language.t("error.page.details.label")}
hideLabel 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}> <Button size="large" onClick={platform.restart}>
{language.t("error.page.action.restart")} {language.t("error.page.action.restart")}
</Button> </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={platform.checkUpdate}>
<Show <Show
when={store.version} when={store.version}
+20 -2
View File
@@ -1,8 +1,26 @@
import { sentryVitePlugin } from "@sentry/vite-plugin"
import { defineConfig } from "vite" import { defineConfig } from "vite"
import desktopPlugin 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({ export default defineConfig({
plugins: [desktopPlugin] as any, plugins: [desktopPlugin, sentry] as any,
server: { server: {
host: "0.0.0.0", host: "0.0.0.0",
allowedHosts: true, allowedHosts: true,
@@ -10,6 +28,6 @@ export default defineConfig({
}, },
build: { build: {
target: "esnext", target: "esnext",
// sourcemap: true, sourcemap: true,
}, },
}) })
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/console-app", "name": "@opencode-ai/console-app",
"version": "1.14.30", "version": "1.14.33",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@@ -141,7 +141,10 @@ export async function handler(
) )
validateModelSettings(billingSource, authInfo) validateModelSettings(billingSource, authInfo)
updateProviderKey(authInfo, providerInfo) updateProviderKey(authInfo, providerInfo)
logger.metric({ provider: providerInfo.id }) logger.metric({
provider: providerInfo.id,
"provider.model": providerInfo.model,
})
const startTimestamp = Date.now() const startTimestamp = Date.now()
const reqUrl = providerInfo.modifyUrl(providerInfo.api, isStream) const reqUrl = providerInfo.modifyUrl(providerInfo.api, isStream)
@@ -149,12 +152,23 @@ export async function handler(
providerInfo.modifyBody({ providerInfo.modifyBody({
...createBodyConverter(opts.format, providerInfo.format)(body), ...createBodyConverter(opts.format, providerInfo.format)(body),
model: providerInfo.model, model: providerInfo.model,
...providerInfo.payloadModifier, ...(() => {
...Object.fromEntries( const replacer = (obj: Record<string, any>): Record<string, any> =>
Object.entries(providerInfo.payloadMappings ?? {}) Object.fromEntries(
.map(([k, v]) => [k, input.request.headers.get(v)]) Object.entries(obj).flatMap(([k, v]) => {
.filter(([_k, v]) => !!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) logger.debug("REQUEST URL: " + reqUrl)
@@ -514,7 +528,6 @@ export async function handler(
reqModel, reqModel,
providerModel: modelProvider.model, providerModel: modelProvider.model,
adjustCacheUsage: providerProps.adjustCacheUsage, adjustCacheUsage: providerProps.adjustCacheUsage,
safetyIdentifier: modelProvider.safetyIdentifier ? ip : undefined,
workspaceID: authInfo?.workspaceID, workspaceID: authInfo?.workspaceID,
} }
if (format === "anthropic") return anthropicHelper(opts) 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", format: "oa-compat",
modifyUrl: (providerApi: string) => providerApi + "/chat/completions", modifyUrl: (providerApi: string) => providerApi + "/chat/completions",
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => { modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
@@ -34,7 +34,6 @@ export const oaCompatHelper: ProviderHelper = ({ adjustCacheUsage, safetyIdentif
return { return {
...body, ...body,
...(body.stream ? { stream_options: { include_usage: true } } : {}), ...(body.stream ? { stream_options: { include_usage: true } } : {}),
...(safetyIdentifier ? { safety_identifier: safetyIdentifier } : {}),
} }
}, },
createBinaryStreamDecoder: () => undefined, createBinaryStreamDecoder: () => undefined,
@@ -18,10 +18,7 @@ export const openaiHelper: ProviderHelper = ({ workspaceID }) => ({
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => { modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
headers.set("authorization", `Bearer ${apiKey}`) headers.set("authorization", `Bearer ${apiKey}`)
}, },
modifyBody: (body: Record<string, any>) => ({ modifyBody: (body: Record<string, any>) => body,
...body,
...(workspaceID ? { safety_identifier: workspaceID } : {}),
}),
createBinaryStreamDecoder: () => undefined, createBinaryStreamDecoder: () => undefined,
streamSeparator: "\n\n", streamSeparator: "\n\n",
createUsageParser: () => { createUsageParser: () => {
@@ -37,7 +37,6 @@ export type ProviderHelper = (input: {
reqModel: string reqModel: string
providerModel: string providerModel: string
adjustCacheUsage?: boolean adjustCacheUsage?: boolean
safetyIdentifier?: string
workspaceID?: string workspaceID?: string
}) => { }) => {
format: ZenData.Format format: ZenData.Format
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core", "name": "@opencode-ai/console-core",
"version": "1.14.30", "version": "1.14.33",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
-1
View File
@@ -40,7 +40,6 @@ export namespace ZenData {
disabled: z.boolean().optional(), disabled: z.boolean().optional(),
storeModel: z.string().optional(), storeModel: z.string().optional(),
payloadModifier: z.record(z.string(), z.any()).optional(), payloadModifier: z.record(z.string(), z.any()).optional(),
safetyIdentifier: z.boolean().optional(),
}), }),
), ),
}) })
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/console-function", "name": "@opencode-ai/console-function",
"version": "1.14.30", "version": "1.14.33",
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"private": true, "private": true,
"type": "module", "type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/console-mail", "name": "@opencode-ai/console-mail",
"version": "1.14.30", "version": "1.14.33",
"dependencies": { "dependencies": {
"@jsx-email/all": "2.2.3", "@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3", "@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"version": "1.14.30", "version": "1.14.33",
"name": "@opencode-ai/core", "name": "@opencode-ai/core",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+14 -1
View File
@@ -1,4 +1,5 @@
import { Config } from "effect" import { Config } from "effect"
import { InstallationChannel } from "../installation/version"
function truthy(key: string) { function truthy(key: string) {
const value = process.env[key]?.toLowerCase() const value = process.env[key]?.toLowerCase()
@@ -10,6 +11,10 @@ function falsy(key: string) {
return value === "false" || value === "0" 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) { function number(key: string) {
const value = process.env[key] const value = process.env[key]
if (!value) return undefined if (!value) return undefined
@@ -81,8 +86,16 @@ export const Flag = {
OPENCODE_STRICT_CONFIG_DEPS: truthy("OPENCODE_STRICT_CONFIG_DEPS"), OPENCODE_STRICT_CONFIG_DEPS: truthy("OPENCODE_STRICT_CONFIG_DEPS"),
OPENCODE_WORKSPACE_ID: process.env["OPENCODE_WORKSPACE_ID"], 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_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 // Evaluated at access time (not module load) because tests, the CLI, and
// external tooling set these env vars at runtime. // external tooling set these env vars at runtime.
+5
View File
@@ -11,6 +11,7 @@ const data = path.join(xdgData!, app)
const cache = path.join(xdgCache!, app) const cache = path.join(xdgCache!, app)
const config = path.join(xdgConfig!, app) const config = path.join(xdgConfig!, app)
const state = path.join(xdgState!, app) const state = path.join(xdgState!, app)
const tmp = path.join(os.tmpdir(), app)
const paths = { const paths = {
get home() { get home() {
@@ -22,6 +23,7 @@ const paths = {
cache, cache,
config, config,
state, state,
tmp,
} }
export const Path = paths export const Path = paths
@@ -32,6 +34,7 @@ await Promise.all([
fs.mkdir(Path.data, { recursive: true }), fs.mkdir(Path.data, { recursive: true }),
fs.mkdir(Path.config, { recursive: true }), fs.mkdir(Path.config, { recursive: true }),
fs.mkdir(Path.state, { recursive: true }), fs.mkdir(Path.state, { recursive: true }),
fs.mkdir(Path.tmp, { recursive: true }),
fs.mkdir(Path.log, { recursive: true }), fs.mkdir(Path.log, { recursive: true }),
fs.mkdir(Path.bin, { recursive: true }), fs.mkdir(Path.bin, { recursive: true }),
]) ])
@@ -44,6 +47,7 @@ export interface Interface {
readonly cache: string readonly cache: string
readonly config: string readonly config: string
readonly state: string readonly state: string
readonly tmp: string
readonly bin: string readonly bin: string
readonly log: string readonly log: string
} }
@@ -55,6 +59,7 @@ export function make(input: Partial<Interface> = {}): Interface {
cache: Path.cache, cache: Path.cache,
config: Flag.OPENCODE_CONFIG_DIR ?? Path.config, config: Flag.OPENCODE_CONFIG_DIR ?? Path.config,
state: Path.state, state: Path.state,
tmp: Path.tmp,
bin: Path.bin, bin: Path.bin,
log: Path.log, log: Path.log,
...input, ...input,
+6 -2
View File
@@ -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)) return resolveEntryPoint(name, path.join(dir, "node_modules", name))
} }
const tree = yield* reify({ dir, add: [pkg] }) const tree = yield* reify({ dir, add: [pkg] })
const first = tree.edgesOut.values().next().value?.to 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) return resolveEntryPoint(first.name, first.path)
}, Effect.scoped) }, Effect.scoped)
+2
View File
@@ -1,3 +1,5 @@
export * as Log from "./log"
import path from "path" import path from "path"
import fs from "fs/promises" import fs from "fs/promises"
import { createWriteStream } from "fs" import { createWriteStream } from "fs"
+16
View 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)
})
})
+35
View File
@@ -1,7 +1,12 @@
import fs from "fs/promises" import fs from "fs/promises"
import path from "path" import path from "path"
import { describe, expect, test } from "bun:test" 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 { Npm } from "@opencode-ai/core/npm"
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
import { tmpdir } from "./fixture/tmpdir" import { tmpdir } from "./fixture/tmpdir"
const win = process.platform === "win32" 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", () => { describe("Npm.sanitize", () => {
test("keeps normal scoped package specs unchanged", () => { test("keeps normal scoped package specs unchanged", () => {
expect(Npm.sanitize("@opencode/acme")).toBe("@opencode/acme") 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", () => { describe("Npm.install", () => {
test("respects omit from project .npmrc", async () => { test("respects omit from project .npmrc", async () => {
await using tmp = await tmpdir() await using tmp = await tmpdir()
+1 -8
View File
@@ -2,13 +2,6 @@
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json", "extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"noUncheckedIndexedAccess": false, "noUncheckedIndexedAccess": false
"plugins": [
{
"name": "@effect/language-service",
"transform": "@effect/language-service/transform",
"namespaceImportPackages": ["effect", "@effect/*"]
}
]
} }
} }
@@ -1,3 +1,4 @@
import { sentryVitePlugin } from "@sentry/vite-plugin"
import { defineConfig } from "electron-vite" import { defineConfig } from "electron-vite"
import appPlugin from "@opencode-ai/app/vite" import appPlugin from "@opencode-ai/app/vite"
import * as fs from "node:fs/promises" 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 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({ export default defineConfig({
main: { main: {
define: { define: {
@@ -61,13 +79,14 @@ export default defineConfig({
}, },
}, },
renderer: { renderer: {
plugins: [appPlugin], plugins: [appPlugin, sentry],
publicDir: "../../../app/public", publicDir: "../../../app/public",
root: "src/renderer", root: "src/renderer",
define: { define: {
"import.meta.env.VITE_OPENCODE_CHANNEL": JSON.stringify(channel), "import.meta.env.VITE_OPENCODE_CHANNEL": JSON.stringify(channel),
}, },
build: { build: {
sourcemap: true,
rollupOptions: { rollupOptions: {
input: { input: {
main: "src/renderer/index.html", main: "src/renderer/index.html",
+3 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@opencode-ai/desktop-electron", "name": "@opencode-ai/desktop-electron",
"private": true, "private": true,
"version": "1.14.30", "version": "1.14.33",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"homepage": "https://opencode.ai", "homepage": "https://opencode.ai",
@@ -38,6 +38,8 @@
"@lydell/node-pty": "catalog:", "@lydell/node-pty": "catalog:",
"@opencode-ai/app": "workspace:*", "@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*", "@opencode-ai/ui": "workspace:*",
"@sentry/solid": "catalog:",
"@sentry/vite-plugin": "catalog:",
"@solid-primitives/i18n": "2.2.1", "@solid-primitives/i18n": "2.2.1",
"@solid-primitives/storage": "catalog:", "@solid-primitives/storage": "catalog:",
"@solidjs/meta": "catalog:", "@solidjs/meta": "catalog:",
@@ -14,6 +14,7 @@ import {
ServerConnection, ServerConnection,
useCommand, useCommand,
} from "@opencode-ai/app" } from "@opencode-ai/app"
import * as Sentry from "@sentry/solid"
import type { AsyncStorage } from "@solid-primitives/storage" import type { AsyncStorage } from "@solid-primitives/storage"
import { MemoryRouter } from "@solidjs/router" import { MemoryRouter } from "@solidjs/router"
import { createEffect, createResource, onCleanup, onMount, Show } from "solid-js" 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")) 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() void initI18n()
const deepLinkEvent = "opencode:deep-link" const deepLinkEvent = "opencode:deep-link"
@@ -26,13 +26,20 @@ const applyZoom = (next: number) => {
window.addEventListener("keydown", (event) => { window.addEventListener("keydown", (event) => {
if (!(OS_NAME === "macos" ? event.metaKey : event.ctrlKey)) return if (!(OS_NAME === "macos" ? event.metaKey : event.ctrlKey)) return
let newZoom = webviewZoom() if (event.key === "-") {
event.preventDefault()
if (event.key === "-") newZoom -= 0.2 applyZoom(clamp(webviewZoom() - 0.2))
if (event.key === "=" || event.key === "+") newZoom += 0.2 return
if (event.key === "0") newZoom = 1 }
if (event.key === "=" || event.key === "+") {
applyZoom(clamp(newZoom)) event.preventDefault()
applyZoom(clamp(webviewZoom() + 0.2))
return
}
if (event.key === "0") {
event.preventDefault()
applyZoom(1)
}
}) })
export { webviewZoom } export { webviewZoom }
+3 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@opencode-ai/desktop", "name": "@opencode-ai/desktop",
"private": true, "private": true,
"version": "1.14.30", "version": "1.14.33",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@@ -15,6 +15,7 @@
"dependencies": { "dependencies": {
"@opencode-ai/app": "workspace:*", "@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*", "@opencode-ai/ui": "workspace:*",
"@sentry/solid": "catalog:",
"@solid-primitives/i18n": "2.2.1", "@solid-primitives/i18n": "2.2.1",
"@solid-primitives/storage": "catalog:", "@solid-primitives/storage": "catalog:",
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
@@ -35,6 +36,7 @@
}, },
"devDependencies": { "devDependencies": {
"@actions/artifact": "4.0.0", "@actions/artifact": "4.0.0",
"@sentry/vite-plugin": "catalog:",
"@tauri-apps/cli": "^2", "@tauri-apps/cli": "^2",
"@types/bun": "catalog:", "@types/bun": "catalog:",
"@typescript/native-preview": "catalog:", "@typescript/native-preview": "catalog:",
+9
View 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
}
+1
View File
@@ -14,6 +14,7 @@ import {
ServerConnection, ServerConnection,
useCommand, useCommand,
} from "@opencode-ai/app" } from "@opencode-ai/app"
import * as Sentry from "@sentry/solid"
import type { AsyncStorage } from "@solid-primitives/storage" import type { AsyncStorage } from "@solid-primitives/storage"
import { getCurrentWindow } from "@tauri-apps/api/window" import { getCurrentWindow } from "@tauri-apps/api/window"
import { readImage } from "@tauri-apps/plugin-clipboard-manager" import { readImage } from "@tauri-apps/plugin-clipboard-manager"
+3 -3
View File
@@ -15,9 +15,9 @@ export default defineConfig({
// Improves production stack traces // Improves production stack traces
keepNames: true, keepNames: true,
}, },
// build: { build: {
// sourcemap: true, sourcemap: true,
// }, },
// 2. tauri expects a fixed port, fail if that port is not available // 2. tauri expects a fixed port, fail if that port is not available
server: { server: {
port: 1420, port: 1420,
@@ -0,0 +1,24 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/effect-drizzle-sqlite",
"version": "0.0.0",
"private": true,
"type": "module",
"license": "MIT",
"scripts": {
"test": "bun test",
"typecheck": "tsgo --noEmit"
},
"exports": {
".": "./src/index.ts"
},
"dependencies": {
"drizzle-orm": "catalog:",
"effect": "catalog:"
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:"
}
}
+246
View File
@@ -0,0 +1,246 @@
import { Database } from "bun:sqlite"
import { drizzle as drizzleBun, type SQLiteBunDatabase } from "drizzle-orm/bun-sqlite"
import type { AnyRelations, EmptyRelations } from "drizzle-orm/relations"
import { SQLiteCountBuilder } from "drizzle-orm/sqlite-core/query-builders/count"
import { SQLiteDeleteBase } from "drizzle-orm/sqlite-core/query-builders/delete"
import { SQLiteInsertBase } from "drizzle-orm/sqlite-core/query-builders/insert"
import { SQLiteRelationalQuery, SQLiteSyncRelationalQuery } from "drizzle-orm/sqlite-core/query-builders/_query"
import { SQLiteSelectBase } from "drizzle-orm/sqlite-core/query-builders/select"
import { SQLiteUpdateBase } from "drizzle-orm/sqlite-core/query-builders/update"
import type { PreparedQueryConfig, SQLiteSession, SQLiteTransaction, SQLiteTransactionConfig } from "drizzle-orm/sqlite-core/session"
import { SQLitePreparedQuery } from "drizzle-orm/sqlite-core/session"
import type { DrizzleConfig } from "drizzle-orm/utils"
import { Cause, Effect, Exit, Schema } from "effect"
import * as Effectable from "effect/Effectable"
export class EffectDrizzleQueryError extends Schema.TaggedErrorClass<EffectDrizzleQueryError>()(
"EffectDrizzleQueryError",
{
query: Schema.String,
params: Schema.Array(Schema.Unknown),
cause: Schema.Unknown,
},
) {
override get message() {
return `Failed query: ${this.query}\nparams: ${this.params}`
}
constructor(params: { readonly query: string; readonly params: ReadonlyArray<unknown>; readonly cause: unknown }) {
super(params)
Error.captureStackTrace?.(this, EffectDrizzleQueryError)
}
}
export type EffectSQLiteDatabase<
TSchema extends Record<string, unknown> = Record<string, never>,
TRelations extends AnyRelations = EmptyRelations,
> = SQLiteBunDatabase<TSchema, TRelations> & {
readonly $client: Database
readonly withTransaction: <A, E, R>(
effect: Effect.Effect<A, E, R>,
config?: SQLiteTransactionConfig,
) => Effect.Effect<A, E, R>
}
export type MakeConfig<
TSchema extends Record<string, unknown> = Record<string, never>,
TRelations extends AnyRelations = EmptyRelations,
> = DrizzleConfig<TSchema, TRelations> & {
readonly client?: Database
}
type EffectLikeQuery<A = unknown> = {
readonly asEffect?: () => Effect.Effect<A, EffectDrizzleQueryError>
readonly toSQL?: () => { readonly sql: string; readonly params?: readonly unknown[] }
}
type PreparedLike<A = unknown> = EffectLikeQuery<A> & {
readonly execute: () => unknown
readonly getQuery?: () => { readonly sql: string; readonly params?: readonly unknown[] }
}
type SelectLike<A = unknown> = EffectLikeQuery<A> & {
readonly all: () => A
}
type GetLike<A = unknown> = EffectLikeQuery & {
readonly get: () => A
}
type MutationLike<A = unknown> = EffectLikeQuery<A> & {
readonly all: () => A
readonly run: () => A
readonly config?: { readonly returning?: unknown }
}
type CountLike = EffectLikeQuery<number> & {
readonly session: { readonly values: (sql: unknown) => unknown[][] }
readonly sql: unknown
}
class TransactionFailure extends Error {
constructor(readonly effectCause: Cause.Cause<unknown>) {
super("Effect transaction failed")
}
}
const queryInfo = (query: EffectLikeQuery | PreparedLike) => {
const info = "getQuery" in query && typeof query.getQuery === "function" ? query.getQuery() : query.toSQL?.()
return {
query: info?.sql ?? "<unknown>",
params: [...(info?.params ?? [])],
}
}
const queryError = (query: EffectLikeQuery | PreparedLike, cause: unknown) =>
new EffectDrizzleQueryError({
...queryInfo(query),
cause,
})
const fromSync = <A>(query: EffectLikeQuery, run: () => A) =>
Effect.try({
try: run,
catch: (cause) => queryError(query, cause),
})
const fromMutation = (query: MutationLike) => fromSync(query, () => (query.config?.returning ? query.all() : query.run()))
const fromCount = (query: CountLike) => fromSync(query, () => Number(query.session.values(query.sql)[0]?.[0] ?? 0))
export const getOne = <A>(query: GetLike<A>) => fromSync(query, () => query.get())
const fromExecuteResult = (result: unknown) => {
if (result && typeof result === "object" && "sync" in result && typeof result.sync === "function") {
return result.sync()
}
return result
}
const queryEffectProto = {
...Effectable.Prototype<Effect.Effect<unknown, EffectDrizzleQueryError> & EffectLikeQuery>({
label: "DrizzleSqliteQuery",
evaluate(this: EffectLikeQuery) {
return this.asEffect?.() ?? Effect.die("Drizzle SQLite query is missing asEffect()")
},
}),
commit(this: EffectLikeQuery) {
return this.asEffect?.() ?? Effect.die("Drizzle SQLite query is missing asEffect()")
},
}
const patchClass = <A>(ctor: { readonly prototype: object }, asEffect: (self: A) => Effect.Effect<unknown, EffectDrizzleQueryError>) => {
if (Object.prototype.hasOwnProperty.call(ctor.prototype, "asEffect")) return
Object.assign(ctor.prototype, queryEffectProto, {
asEffect(this: A) {
return asEffect(this)
},
})
}
// `patchClass` is idempotent via `hasOwnProperty` check, so calling this
// repeatedly is cheap. Patches are applied to Drizzle prototypes globally and
// survive any Database close/reopen cycle.
const patchQueryBuilders = () => {
patchClass(SQLitePreparedQuery, (query: PreparedLike) => fromSync(query, () => fromExecuteResult(query.execute())))
patchClass(SQLiteSelectBase, (query: SelectLike) => fromSync(query, () => query.all()))
patchClass(SQLiteInsertBase, fromMutation)
patchClass(SQLiteUpdateBase, fromMutation)
patchClass(SQLiteDeleteBase, fromMutation)
patchClass(SQLiteRelationalQuery, (query: EffectLikeQuery & { readonly executeRaw: () => unknown }) =>
fromSync(query, () => query.executeRaw()),
)
patchClass(SQLiteSyncRelationalQuery, (query: EffectLikeQuery & { readonly executeRaw: () => unknown }) =>
fromSync(query, () => query.executeRaw()),
)
patchClass(SQLiteCountBuilder, fromCount)
}
const attachTransaction = <
TSchema extends Record<string, unknown> = Record<string, never>,
TRelations extends AnyRelations = EmptyRelations,
>(db: SQLiteBunDatabase<TSchema, TRelations> & { readonly $client: Database }): EffectSQLiteDatabase<TSchema, TRelations> => {
const txStack: Array<SQLiteTransaction<"sync", void, TSchema, TRelations>> = []
const bound = new WeakMap<object, Map<PropertyKey, unknown>>()
const current = () => txStack.at(-1) ?? db
const runTransaction = (target: SQLiteBunDatabase<TSchema, TRelations> | SQLiteTransaction<"sync", void, TSchema, TRelations>) =>
target.transaction.bind(target) as (
transaction: (tx: SQLiteTransaction<"sync", void, TSchema, TRelations>) => unknown,
config?: SQLiteTransactionConfig,
) => unknown
const withTransaction = <A, E, R>(
effect: Effect.Effect<A, E, R>,
config?: SQLiteTransactionConfig,
): Effect.Effect<A, E, R> =>
Effect.context<R>().pipe(
Effect.flatMap((context) =>
Effect.sync(
() =>
runTransaction(current())((tx) => {
txStack.push(tx)
try {
const exit = Effect.runSyncExit(Effect.provideContext(effect, context))
if (Exit.isSuccess(exit)) return exit.value
throw new TransactionFailure(exit.cause)
} finally {
txStack.pop()
}
}, config) as A,
).pipe(
Effect.catchDefect((defect) =>
defect instanceof TransactionFailure ? Effect.failCause(defect.effectCause as Cause.Cause<E>) : Effect.die(defect),
),
),
),
)
return new Proxy(db, {
get(_target, property) {
if (property === "withTransaction") return withTransaction
if (property === "$client") return db.$client
const target = current()
const value = Reflect.get(target, property)
if (typeof value !== "function") return value
const methods = bound.get(target) ?? new Map<PropertyKey, unknown>()
bound.set(target, methods)
if (!methods.has(property)) methods.set(property, value.bind(target))
return methods.get(property)
},
}) as EffectSQLiteDatabase<TSchema, TRelations>
}
export const make = <
TSchema extends Record<string, unknown> = Record<string, never>,
TRelations extends AnyRelations = EmptyRelations,
>(config: MakeConfig<TSchema, TRelations> = {}): EffectSQLiteDatabase<TSchema, TRelations> => {
patchQueryBuilders()
return attachTransaction(
drizzleBun({
...config,
client: config.client ?? new Database(":memory:"),
}),
)
}
export const drizzle = make
declare module "drizzle-orm/query-promise" {
interface QueryPromise<T> extends Effect.Effect<T, EffectDrizzleQueryError> {
asEffect(): Effect.Effect<T, EffectDrizzleQueryError>
}
}
declare module "drizzle-orm/sqlite-core/session" {
interface SQLitePreparedQuery<T extends PreparedQueryConfig> extends Effect.Effect<T["execute"], EffectDrizzleQueryError> {
asEffect(): Effect.Effect<T["execute"], EffectDrizzleQueryError>
}
}
declare module "drizzle-orm/sqlite-core/query-builders/count" {
interface SQLiteCountBuilder<TSession extends SQLiteSession<any, any, any, any>>
extends Effect.Effect<number, EffectDrizzleQueryError> {
asEffect(): Effect.Effect<number, EffectDrizzleQueryError>
}
}
@@ -0,0 +1,246 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
import { Database } from "bun:sqlite"
import { eq } from "drizzle-orm"
import { relations } from "drizzle-orm/_relations"
import { drizzle as drizzleBun } from "drizzle-orm/bun-sqlite"
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"
import { Cause, Effect, Exit } from "effect"
import { EffectDrizzleQueryError, getOne, make, type EffectSQLiteDatabase } from "../src"
const users = sqliteTable("users", {
id: integer().primaryKey(),
name: text().notNull(),
})
const posts = sqliteTable("posts", {
id: integer().primaryKey(),
user_id: integer()
.notNull()
.references(() => users.id),
title: text().notNull(),
})
const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}))
const postsRelations = relations(posts, ({ one }) => ({
user: one(users, {
fields: [posts.user_id],
references: [users.id],
}),
}))
const schema = { users, posts, usersRelations, postsRelations }
let db: EffectSQLiteDatabase<typeof schema>
const testEffect = <A, E>(name: string, effect: () => Effect.Effect<A, E>) => test(name, () => Effect.runPromise(effect()))
beforeEach(() => {
db = make({ schema })
db.$client.run("PRAGMA foreign_keys = ON")
db.$client.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
db.$client.run(
"CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id), title TEXT NOT NULL)",
)
})
afterEach(() => {
db.$client.close()
})
describe("effect drizzle sqlite", () => {
test("keeps normal Drizzle Bun SQLite clients usable after patching", async () => {
const sqlite = new Database(":memory:")
try {
const normal = drizzleBun({ client: sqlite })
sqlite.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
normal.insert(users).values({ id: 1, name: "Ada" }).run()
expect(normal.select().from(users).all()).toEqual([{ id: 1, name: "Ada" }])
expect(await normal.select().from(users)).toEqual([{ id: 1, name: "Ada" }])
} finally {
sqlite.close()
}
})
testEffect("makes select/insert/update/delete query builders yieldable Effects", () =>
Effect.gen(function* () {
yield* db.insert(users).values({ id: 1, name: "Ada" })
yield* db.insert(users).values({ id: 2, name: "Grace" })
const selected = yield* db.select().from(users).orderBy(users.id)
expect(selected).toEqual([
{ id: 1, name: "Ada" },
{ id: 2, name: "Grace" },
])
const updated = yield* db.update(users).set({ name: "Lovelace" }).where(eq(users.id, 1)).returning()
expect(updated).toEqual([{ id: 1, name: "Lovelace" }])
const deleted = yield* db.delete(users).where(eq(users.id, 2)).returning({ id: users.id })
expect(deleted).toEqual([{ id: 2 }])
expect(yield* db.select().from(users)).toEqual([{ id: 1, name: "Lovelace" }])
}),
)
testEffect("supports direct Effect combinators on queries", () =>
Effect.gen(function* () {
yield* db.insert(users).values({ id: 1, name: "Ada" })
expect(
yield* (db.select().from(users) as Effect.Effect<Array<{ readonly name: string }>, EffectDrizzleQueryError>).pipe(
Effect.map((rows) => rows.map((row) => row.name)),
),
).toEqual(["Ada"])
}),
)
testEffect("supports relational query builders", () =>
Effect.gen(function* () {
yield* db.insert(users).values({ id: 1, name: "Ada" })
yield* db.insert(posts).values({ id: 1, user_id: 1, title: "Notes" })
expect(
yield* db._query.users.findMany({
with: {
posts: true,
},
}),
).toEqual([
{
id: 1,
name: "Ada",
posts: [{ id: 1, user_id: 1, title: "Notes" }],
},
])
}),
)
testEffect("runs synchronous Effect programs inside transactions", () =>
Effect.gen(function* () {
yield* Effect.gen(function* () {
yield* db.insert(users).values({ id: 1, name: "Ada" })
return yield* db.select().from(users)
}).pipe(db.withTransaction)
expect(yield* db.select().from(users)).toEqual([{ id: 1, name: "Ada" }])
const exit = yield* Effect.exit(
Effect.gen(function* () {
yield* db.insert(users).values({ id: 2, name: "Grace" })
return yield* Effect.fail("rollback")
}).pipe(db.withTransaction),
)
expect(Exit.isFailure(exit)).toBe(true)
expect(yield* db.select().from(users).orderBy(users.id)).toEqual([{ id: 1, name: "Ada" }])
}),
)
testEffect("supports pipeable transactions using the same database service", () =>
Effect.gen(function* () {
const exit = yield* Effect.gen(function* () {
yield* db.insert(users).values({ id: 1, name: "Ada" })
return yield* Effect.fail("rollback")
}).pipe(db.withTransaction, Effect.exit)
expect(Exit.isFailure(exit)).toBe(true)
expect(yield* db.select().from(users)).toEqual([])
yield* Effect.gen(function* () {
yield* db.insert(users).values({ id: 2, name: "Grace" })
expect(yield* db.$count(users)).toBe(1)
}).pipe(db.withTransaction)
expect(yield* db.select().from(users)).toEqual([{ id: 2, name: "Grace" }])
}),
)
testEffect("supports count builders and prepared queries", () =>
Effect.gen(function* () {
yield* db.insert(users).values([
{ id: 1, name: "Ada" },
{ id: 2, name: "Grace" },
])
expect(yield* db.$count(users)).toBe(2)
const prepared = db.select().from(users).orderBy(users.id).prepare()
expect(yield* prepared).toEqual([
{ id: 1, name: "Ada" },
{ id: 2, name: "Grace" },
])
}),
)
testEffect("supports single-row select effects", () =>
Effect.gen(function* () {
yield* db.insert(users).values({ id: 1, name: "Ada" })
expect(yield* getOne(db.select().from(users).where(eq(users.id, 1)))).toEqual({ id: 1, name: "Ada" })
expect(yield* getOne(db.select().from(users).where(eq(users.id, 2)))).toBeUndefined()
}),
)
testEffect("nested pipeable transactions commit or roll back with the outer transaction", () =>
Effect.gen(function* () {
yield* Effect.gen(function* () {
yield* db.insert(users).values({ id: 1, name: "Ada" })
yield* Effect.gen(function* () {
yield* db.insert(users).values({ id: 2, name: "Grace" })
}).pipe(db.withTransaction)
}).pipe(db.withTransaction)
expect(yield* db.select().from(users).orderBy(users.id)).toEqual([
{ id: 1, name: "Ada" },
{ id: 2, name: "Grace" },
])
const exit = yield* Effect.gen(function* () {
yield* db.insert(users).values({ id: 3, name: "Katherine" })
yield* Effect.gen(function* () {
yield* db.insert(users).values({ id: 4, name: "Dorothy" })
return yield* Effect.fail("inner rollback")
}).pipe(db.withTransaction)
}).pipe(db.withTransaction, Effect.exit)
expect(Exit.isFailure(exit)).toBe(true)
expect(yield* db.select().from(users).orderBy(users.id)).toEqual([
{ id: 1, name: "Ada" },
{ id: 2, name: "Grace" },
])
}),
)
testEffect("defects inside transactions roll back and stay defects", () =>
Effect.gen(function* () {
const exit = yield* Effect.gen(function* () {
yield* db.insert(users).values({ id: 1, name: "Ada" })
return yield* Effect.die("boom")
}).pipe(db.withTransaction, Effect.exit)
expect(Exit.isFailure(exit)).toBe(true)
if (Exit.isFailure(exit)) {
expect(exit.cause.reasons.some(Cause.isDieReason)).toBe(true)
}
expect(yield* db.select().from(users)).toEqual([])
}),
)
testEffect("wraps query failures with query text and parameters", () =>
Effect.gen(function* () {
const exit = yield* Effect.exit(db.insert(posts).values({ id: 1, user_id: 404, title: "Missing" }))
expect(Exit.isFailure(exit)).toBe(true)
if (Exit.isFailure(exit)) {
const error = exit.cause.reasons.filter(Cause.isFailReason)[0]?.error
expect(error).toBeInstanceOf(EffectDrizzleQueryError)
expect((error as EffectDrizzleQueryError).query).toContain("insert into")
expect((error as EffectDrizzleQueryError).params).toEqual([1, 404, "Missing"])
}
}),
)
})
@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"types": ["bun"],
"noUncheckedIndexedAccess": false,
"plugins": [
{
"name": "@effect/language-service",
"transform": "@effect/language-service/transform",
"namespaceImportPackages": ["effect", "@effect/*"]
}
]
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/enterprise", "name": "@opencode-ai/enterprise",
"version": "1.14.30", "version": "1.14.33",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+6 -6
View File
@@ -1,7 +1,7 @@
id = "opencode" id = "opencode"
name = "OpenCode" name = "OpenCode"
description = "The open source coding agent." description = "The open source coding agent."
version = "1.14.30" version = "1.14.33"
schema_version = 1 schema_version = 1
authors = ["Anomaly"] authors = ["Anomaly"]
repository = "https://github.com/anomalyco/opencode" repository = "https://github.com/anomalyco/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg" icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64] [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" cmd = "./opencode"
args = ["acp"] args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64] [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" cmd = "./opencode"
args = ["acp"] args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64] [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" cmd = "./opencode"
args = ["acp"] args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64] [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" cmd = "./opencode"
args = ["acp"] args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64] [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" cmd = "./opencode.exe"
args = ["acp"] args = ["acp"]
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/function", "name": "@opencode-ai/function",
"version": "1.14.30", "version": "1.14.33",
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"private": true, "private": true,
"type": "module", "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", "version": "7",
"dialect": "sqlite", "dialect": "sqlite",
"id": "aaa2ebeb-caa4-478d-8365-4fc595d16856", "id": "aaa2ebeb-caa4-478d-8365-4fc595d16856",
"prevIds": ["66cbe0d7-def0-451b-b88a-7608513a9b44"], "prevIds": ["61f807f9-6398-4067-be05-804acc2561bc"],
"ddl": [ "ddl": [
{ {
"name": "account_state", "name": "account_state",
@@ -37,7 +37,7 @@
"entityType": "tables" "entityType": "tables"
}, },
{ {
"name": "session_entry", "name": "session_message",
"entityType": "tables" "entityType": "tables"
}, },
{ {
@@ -598,7 +598,7 @@
"generated": null, "generated": null,
"name": "id", "name": "id",
"entityType": "columns", "entityType": "columns",
"table": "session_entry" "table": "session_message"
}, },
{ {
"type": "text", "type": "text",
@@ -608,7 +608,7 @@
"generated": null, "generated": null,
"name": "session_id", "name": "session_id",
"entityType": "columns", "entityType": "columns",
"table": "session_entry" "table": "session_message"
}, },
{ {
"type": "text", "type": "text",
@@ -618,7 +618,7 @@
"generated": null, "generated": null,
"name": "type", "name": "type",
"entityType": "columns", "entityType": "columns",
"table": "session_entry" "table": "session_message"
}, },
{ {
"type": "integer", "type": "integer",
@@ -628,7 +628,7 @@
"generated": null, "generated": null,
"name": "time_created", "name": "time_created",
"entityType": "columns", "entityType": "columns",
"table": "session_entry" "table": "session_message"
}, },
{ {
"type": "integer", "type": "integer",
@@ -638,7 +638,7 @@
"generated": null, "generated": null,
"name": "time_updated", "name": "time_updated",
"entityType": "columns", "entityType": "columns",
"table": "session_entry" "table": "session_message"
}, },
{ {
"type": "text", "type": "text",
@@ -648,7 +648,7 @@
"generated": null, "generated": null,
"name": "data", "name": "data",
"entityType": "columns", "entityType": "columns",
"table": "session_entry" "table": "session_message"
}, },
{ {
"type": "text", "type": "text",
@@ -1112,9 +1112,9 @@
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
"name": "fk_session_entry_session_id_session_id_fk", "name": "fk_session_message_session_id_session_id_fk",
"entityType": "fks", "entityType": "fks",
"table": "session_entry" "table": "session_message"
}, },
{ {
"columns": ["project_id"], "columns": ["project_id"],
@@ -1226,8 +1226,8 @@
{ {
"columns": ["id"], "columns": ["id"],
"nameExplicit": false, "nameExplicit": false,
"name": "session_entry_pk", "name": "session_message_pk",
"table": "session_entry", "table": "session_message",
"entityType": "pks" "entityType": "pks"
}, },
{ {
@@ -1322,9 +1322,9 @@
"isUnique": false, "isUnique": false,
"where": null, "where": null,
"origin": "manual", "origin": "manual",
"name": "session_entry_session_idx", "name": "session_message_session_idx",
"entityType": "indexes", "entityType": "indexes",
"table": "session_entry" "table": "session_message"
}, },
{ {
"columns": [ "columns": [
@@ -1340,9 +1340,9 @@
"isUnique": false, "isUnique": false,
"where": null, "where": null,
"origin": "manual", "origin": "manual",
"name": "session_entry_session_type_idx", "name": "session_message_session_type_idx",
"entityType": "indexes", "entityType": "indexes",
"table": "session_entry" "table": "session_message"
}, },
{ {
"columns": [ "columns": [
@@ -1354,9 +1354,9 @@
"isUnique": false, "isUnique": false,
"where": null, "where": null,
"origin": "manual", "origin": "manual",
"name": "session_entry_time_created_idx", "name": "session_message_time_created_idx",
"entityType": "indexes", "entityType": "indexes",
"table": "session_entry" "table": "session_message"
}, },
{ {
"columns": [ "columns": [
@@ -0,0 +1,2 @@
ALTER TABLE `session` ADD `agent` text;--> statement-breakpoint
ALTER TABLE `session` ADD `model` text;
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -1,12 +1,11 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"version": "1.14.30", "version": "1.14.33",
"name": "opencode", "name": "opencode",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"scripts": { "scripts": {
"prepare": "effect-language-service patch || true",
"typecheck": "tsgo --noEmit", "typecheck": "tsgo --noEmit",
"test": "bun test --timeout 30000", "test": "bun test --timeout 30000",
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml", "test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
@@ -42,7 +41,6 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.28.4", "@babel/core": "7.28.4",
"@effect/language-service": "0.84.2",
"@octokit/webhooks-types": "7.6.1", "@octokit/webhooks-types": "7.6.1",
"@opencode-ai/script": "workspace:*", "@opencode-ai/script": "workspace:*",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
@@ -112,6 +110,7 @@
"@octokit/graphql": "9.0.2", "@octokit/graphql": "9.0.2",
"@octokit/rest": "catalog:", "@octokit/rest": "catalog:",
"@openauthjs/openauth": "catalog:", "@openauthjs/openauth": "catalog:",
"@opencode-ai/effect-drizzle-sqlite": "workspace:*",
"@opencode-ai/plugin": "workspace:*", "@opencode-ai/plugin": "workspace:*",
"@opencode-ai/script": "workspace:*", "@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
+1
View File
@@ -61,6 +61,7 @@ const createEmbeddedWebUIBundle = async () => {
await $`bun run --cwd ${appDir} build` await $`bun run --cwd ${appDir} build`
const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: dist }))) const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: dist })))
.map((file) => file.replaceAll("\\", "/")) .map((file) => file.replaceAll("\\", "/"))
.filter((file) => !file.endsWith(".map"))
.sort() .sort()
const imports = files.map((file, i) => { const imports = files.map((file, i) => {
const spec = path.relative(dir, path.join(dist, file)).replaceAll("\\", "/") const spec = path.relative(dir, path.join(dist, file)).replaceAll("\\", "/")
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -198,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 | | `project` | `bridged` | list, current, git init, update |
| `file` | `bridged` partial | find text/file/symbol, list/content/status | | `file` | `bridged` partial | find text/file/symbol, list/content/status |
| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | | `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 | | 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 | | 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 | | `session` | `bridged` | read, lifecycle, prompt, message/part mutations, revert, permission reply |
@@ -290,7 +290,7 @@ This checklist tracks bridge parity only. Checked routes are available through t
### Workspace Routes ### 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] `POST /experimental/workspace` - create workspace.
- [x] `GET /experimental/workspace` - list workspaces. - [x] `GET /experimental/workspace` - list workspaces.
- [x] `GET /experimental/workspace/status` - workspace status. - [x] `GET /experimental/workspace/status` - workspace status.
+1 -1
View File
@@ -353,7 +353,7 @@ piecewise.
- [ ] `src/cli/cmd/tui/event.ts` - [ ] `src/cli/cmd/tui/event.ts`
- [ ] `src/cli/ui.ts` - [ ] `src/cli/ui.ts`
- [ ] `src/command/index.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/types.ts`
- [x] `src/control-plane/workspace.ts` - [x] `src/control-plane/workspace.ts`
- [ ] `src/file/index.ts` - [ ] `src/file/index.ts`
+21 -17
View File
@@ -51,6 +51,7 @@ import { LoadAPIKeyError } from "ai"
import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2" import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
import { applyPatch } from "diff" import { applyPatch } from "diff"
import { InstallationVersion } from "@opencode-ai/core/installation/version" import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { ShellID } from "@/tool/shell/id"
type ModeOption = { id: string; name: string; description?: string } type ModeOption = { id: string; name: string; description?: string }
type ModelOption = { modelId: string; name: 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 { return {
create: (connection: AgentSideConnection, fullConfig: ACPConfig) => { create: (connection: AgentSideConnection, fullConfig: ACPConfig) => {
return new Agent(connection, fullConfig) return new Agent(connection, fullConfig)
@@ -144,7 +145,7 @@ export class Agent implements ACPAgent {
private sessionManager: ACPSessionManager private sessionManager: ACPSessionManager
private eventAbort = new AbortController() private eventAbort = new AbortController()
private eventStarted = false private eventStarted = false
private bashSnapshots = new Map<string, string>() private shellSnapshots = new Map<string, string>()
private toolStarts = new Set<string>() private toolStarts = new Set<string>()
private permissionQueues = new Map<string, Promise<void>>() private permissionQueues = new Map<string, Promise<void>>()
private permissionOptions: PermissionOption[] = [ private permissionOptions: PermissionOption[] = [
@@ -283,16 +284,16 @@ export class Agent implements ACPAgent {
switch (part.state.status) { switch (part.state.status) {
case "pending": case "pending":
this.bashSnapshots.delete(part.callID) this.shellSnapshots.delete(part.callID)
return return
case "running": case "running":
const output = this.bashOutput(part) const output = this.shellOutput(part)
const content: ToolCallContent[] = [] const content: ToolCallContent[] = []
if (output) { if (output) {
const hash = Hash.fast(output) const hash = Hash.fast(output)
if (part.tool === "bash") { if (part.tool === ShellID.ToolID) {
if (this.bashSnapshots.get(part.callID) === hash) { if (this.shellSnapshots.get(part.callID) === hash) {
await this.connection await this.connection
.sessionUpdate({ .sessionUpdate({
sessionId, sessionId,
@@ -311,7 +312,7 @@ export class Agent implements ACPAgent {
}) })
return return
} }
this.bashSnapshots.set(part.callID, hash) this.shellSnapshots.set(part.callID, hash)
} }
content.push({ content.push({
type: "content", type: "content",
@@ -342,7 +343,7 @@ export class Agent implements ACPAgent {
case "completed": { case "completed": {
this.toolStarts.delete(part.callID) this.toolStarts.delete(part.callID)
this.bashSnapshots.delete(part.callID) this.shellSnapshots.delete(part.callID)
const kind = toToolKind(part.tool) const kind = toToolKind(part.tool)
const content: ToolCallContent[] = [ const content: ToolCallContent[] = [
{ {
@@ -423,7 +424,7 @@ export class Agent implements ACPAgent {
} }
case "error": case "error":
this.toolStarts.delete(part.callID) this.toolStarts.delete(part.callID)
this.bashSnapshots.delete(part.callID) this.shellSnapshots.delete(part.callID)
await this.connection await this.connection
.sessionUpdate({ .sessionUpdate({
sessionId, sessionId,
@@ -837,10 +838,10 @@ export class Agent implements ACPAgent {
await this.toolStart(sessionId, part) await this.toolStart(sessionId, part)
switch (part.state.status) { switch (part.state.status) {
case "pending": case "pending":
this.bashSnapshots.delete(part.callID) this.shellSnapshots.delete(part.callID)
break break
case "running": case "running":
const output = this.bashOutput(part) const output = this.shellOutput(part)
const runningContent: ToolCallContent[] = [] const runningContent: ToolCallContent[] = []
if (output) { if (output) {
runningContent.push({ runningContent.push({
@@ -871,7 +872,7 @@ export class Agent implements ACPAgent {
break break
case "completed": case "completed":
this.toolStarts.delete(part.callID) this.toolStarts.delete(part.callID)
this.bashSnapshots.delete(part.callID) this.shellSnapshots.delete(part.callID)
const kind = toToolKind(part.tool) const kind = toToolKind(part.tool)
const content: ToolCallContent[] = [ const content: ToolCallContent[] = [
{ {
@@ -951,7 +952,7 @@ export class Agent implements ACPAgent {
break break
case "error": case "error":
this.toolStarts.delete(part.callID) this.toolStarts.delete(part.callID)
this.bashSnapshots.delete(part.callID) this.shellSnapshots.delete(part.callID)
await this.connection await this.connection
.sessionUpdate({ .sessionUpdate({
sessionId, sessionId,
@@ -1105,8 +1106,8 @@ export class Agent implements ACPAgent {
} }
} }
private bashOutput(part: ToolPart) { private shellOutput(part: ToolPart) {
if (part.tool !== "bash") return if (part.tool !== ShellID.ToolID) return
if (!("metadata" in part.state) || !part.state.metadata || typeof part.state.metadata !== "object") return if (!("metadata" in part.state) || !part.state.metadata || typeof part.state.metadata !== "object") return
const output = part.state.metadata["output"] const output = part.state.metadata["output"]
if (typeof output !== "string") return if (typeof output !== "string") return
@@ -1549,9 +1550,11 @@ export class Agent implements ACPAgent {
function toToolKind(toolName: string): ToolKind { function toToolKind(toolName: string): ToolKind {
const tool = toolName.toLocaleLowerCase() const tool = toolName.toLocaleLowerCase()
switch (tool) { switch (tool) {
case "bash": case ShellID.ToolID:
return "execute" return "execute"
case "webfetch": case "webfetch":
return "fetch" return "fetch"
@@ -1576,6 +1579,7 @@ function toToolKind(toolName: string): ToolKind {
function toLocations(toolName: string, input: Record<string, any>): { path: string }[] { function toLocations(toolName: string, input: Record<string, any>): { path: string }[] {
const tool = toolName.toLocaleLowerCase() const tool = toolName.toLocaleLowerCase()
switch (tool) { switch (tool) {
case "read": case "read":
case "edit": case "edit":
@@ -1584,7 +1588,7 @@ function toLocations(toolName: string, input: Record<string, any>): { path: stri
case "glob": case "glob":
case "grep": case "grep":
return input["path"] ? [{ path: input["path"] }] : [] return input["path"] ? [{ path: input["path"] }] : []
case "bash": case ShellID.ToolID:
return [] return []
default: default:
return [] return []
+5 -1
View File
@@ -81,7 +81,11 @@ export const layer = Layer.effect(
Effect.fn("Agent.state")(function* (ctx) { Effect.fn("Agent.state")(function* (ctx) {
const cfg = yield* config.get() const cfg = yield* config.get()
const skillDirs = yield* skill.dirs() 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({ const defaults = Permission.fromConfig({
"*": "allow", "*": "allow",
+2
View File
@@ -24,6 +24,7 @@ export function payloads() {
.map(([type, def]) => { .map(([type, def]) => {
return z return z
.object({ .object({
id: z.string(),
type: z.literal(type), type: z.literal(type),
properties: zodObject(def.properties), properties: zodObject(def.properties),
}) })
@@ -39,6 +40,7 @@ export function effectPayloads() {
.entries() .entries()
.map(([type, def]) => .map(([type, def]) =>
Schema.Struct({ Schema.Struct({
id: Schema.String,
type: Schema.Literal(type), type: Schema.Literal(type),
properties: def.properties, properties: def.properties,
}).annotate({ identifier: `Event.${type}` }), }).annotate({ identifier: `Event.${type}` }),
+12 -2
View File
@@ -1,4 +1,5 @@
import { EventEmitter } from "events" import { EventEmitter } from "events"
import { Identifier } from "@/id/id"
export type GlobalEvent = { export type GlobalEvent = {
directory?: string directory?: string
@@ -7,6 +8,15 @@ export type GlobalEvent = {
payload: any payload: any
} }
export const GlobalBus = new EventEmitter<{ class GlobalBusEmitter extends EventEmitter<{
event: [GlobalEvent] 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()
+20 -5
View File
@@ -5,6 +5,7 @@ import { BusEvent } from "./bus-event"
import { GlobalBus } from "./global" import { GlobalBus } from "./global"
import { InstanceState } from "@/effect/instance-state" import { InstanceState } from "@/effect/instance-state"
import { makeRuntime } from "@/effect/run-service" import { makeRuntime } from "@/effect/run-service"
import { Identifier } from "@/id/id"
const log = Log.create({ service: "bus" }) const log = Log.create({ service: "bus" })
@@ -18,6 +19,7 @@ export const InstanceDisposed = BusEvent.define(
) )
type Payload<D extends BusEvent.Definition = BusEvent.Definition> = { type Payload<D extends BusEvent.Definition = BusEvent.Definition> = {
id: string
type: D["type"] type: D["type"]
properties: BusProperties<D> properties: BusProperties<D>
} }
@@ -28,7 +30,11 @@ type State = {
} }
export interface Interface { 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 subscribe: <D extends BusEvent.Definition>(def: D) => Stream.Stream<Payload<D>>
readonly subscribeAll: () => Stream.Stream<Payload> readonly subscribeAll: () => Stream.Stream<Payload>
readonly subscribeCallback: <D extends BusEvent.Definition>( readonly subscribeCallback: <D extends BusEvent.Definition>(
@@ -53,6 +59,7 @@ export const layer = Layer.effect(
// Publish InstanceDisposed before shutting down so subscribers see it // Publish InstanceDisposed before shutting down so subscribers see it
yield* PubSub.publish(wildcard, { yield* PubSub.publish(wildcard, {
type: InstanceDisposed.type, type: InstanceDisposed.type,
id: createID(),
properties: { directory: ctx.directory }, properties: { directory: ctx.directory },
}) })
yield* PubSub.shutdown(wildcard) 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* () { return Effect.gen(function* () {
const s = yield* InstanceState.get(state) 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 }) log.info("publishing", { type: def.type })
const ps = s.typed.get(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, // 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. // 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>) { export function createID() {
return runPromise((svc) => svc.publish(def, properties)) 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) { export function subscribe<D extends BusEvent.Definition>(def: D, callback: (event: Payload<D>) => unknown) {
+4 -5
View File
@@ -1,17 +1,16 @@
import { AppRuntime } from "@/effect/app-runtime"
import { InstanceBootstrap } from "../project/bootstrap"
import { Instance } from "../project/instance" 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>) { export async function bootstrap<T>(directory: string, cb: () => Promise<T>) {
return Instance.provide({ return WithInstance.provide({
directory, directory,
init: () => AppRuntime.runPromise(InstanceBootstrap),
fn: async () => { fn: async () => {
try { try {
const result = await cb() const result = await cb()
return result return result
} finally { } finally {
await Instance.dispose() await InstanceRuntime.disposeInstance(Instance.current)
} }
}, },
}) })
+26 -21
View File
@@ -3,7 +3,7 @@ import { Duration, Effect, Match, Option } from "effect"
import { UI } from "../ui" import { UI } from "../ui"
import { Account } from "@/account/account" import { Account } from "@/account/account"
import { AccountID, OrgID, PollExpired, type PollResult, type AccountError } from "@/account/schema" 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 * as Prompt from "../effect/prompt"
import open from "open" import open from "open"
@@ -172,60 +172,65 @@ const openEffect = Effect.fn("open")(function* () {
yield* Prompt.outro("Opened " + url) yield* Prompt.outro("Opened " + url)
}) })
export const LoginCommand = cmd({ export const LoginCommand = effectCmd({
command: "login <url>", command: "login <url>",
describe: false, describe: false,
instance: false,
builder: (yargs) => builder: (yargs) =>
yargs.positional("url", { yargs.positional("url", {
describe: "server URL", describe: "server URL",
type: "string", type: "string",
demandOption: true, demandOption: true,
}), }),
async handler(args) { handler: Effect.fn("Cli.account.login")(function* (args) {
UI.empty() 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]", command: "logout [email]",
describe: false, describe: false,
instance: false,
builder: (yargs) => builder: (yargs) =>
yargs.positional("email", { yargs.positional("email", {
describe: "account email to log out from", describe: "account email to log out from",
type: "string", type: "string",
}), }),
async handler(args) { handler: Effect.fn("Cli.account.logout")(function* (args) {
UI.empty() UI.empty()
await AppRuntime.runPromise(logoutEffect(args.email)) yield* Effect.orDie(logoutEffect(args.email))
}, }),
}) })
export const SwitchCommand = cmd({ export const SwitchCommand = effectCmd({
command: "switch", command: "switch",
describe: false, describe: false,
async handler() { instance: false,
handler: Effect.fn("Cli.account.switch")(function* () {
UI.empty() UI.empty()
await AppRuntime.runPromise(switchEffect()) yield* Effect.orDie(switchEffect())
}, }),
}) })
export const OrgsCommand = cmd({ export const OrgsCommand = effectCmd({
command: "orgs", command: "orgs",
describe: false, describe: false,
async handler() { instance: false,
handler: Effect.fn("Cli.account.orgs")(function* () {
UI.empty() UI.empty()
await AppRuntime.runPromise(orgsEffect()) yield* Effect.orDie(orgsEffect())
}, }),
}) })
export const OpenCommand = cmd({ export const OpenCommand = effectCmd({
command: "open", command: "open",
describe: false, describe: false,
async handler() { instance: false,
handler: Effect.fn("Cli.account.open")(function* () {
UI.empty() UI.empty()
await AppRuntime.runPromise(openEffect()) yield* Effect.orDie(openEffect())
}, }),
}) })
export const ConsoleCommand = cmd({ export const ConsoleCommand = cmd({
+49 -48
View File
@@ -1,6 +1,6 @@
import * as Log from "@opencode-ai/core/util/log" import * as Log from "@opencode-ai/core/util/log"
import { bootstrap } from "../bootstrap" import { Effect } from "effect"
import { cmd } from "./cmd" import { effectCmd } from "../effect-cmd"
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk" import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
import { ACP } from "@/acp/agent" import { ACP } from "@/acp/agent"
import { Server } from "@/server/server" import { Server } from "@/server/server"
@@ -9,7 +9,7 @@ import { withNetworkOptions, resolveNetworkOptions } from "../network"
const log = Log.create({ service: "acp-command" }) const log = Log.create({ service: "acp-command" })
export const AcpCommand = cmd({ export const AcpCommand = effectCmd({
command: "acp", command: "acp",
describe: "start ACP (Agent Client Protocol) server", describe: "start ACP (Agent Client Protocol) server",
builder: (yargs) => { builder: (yargs) => {
@@ -19,52 +19,53 @@ export const AcpCommand = cmd({
default: process.cwd(), default: process.cwd(),
}) })
}, },
handler: async (args) => { handler: Effect.fn("Cli.acp")(function* (args) {
process.env.OPENCODE_CLIENT = "acp" process.env.OPENCODE_CLIENT = "acp"
await bootstrap(process.cwd(), async () => { const opts = yield* Effect.promise(() => resolveNetworkOptions(args))
const opts = await resolveNetworkOptions(args) const server = yield* Effect.promise(() => Server.listen(opts))
const server = await Server.listen(opts)
const sdk = createOpencodeClient({ const sdk = createOpencodeClient({
baseUrl: `http://${server.hostname}:${server.port}`, 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 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)
}),
)
}),
}) })
+27 -30
View File
@@ -9,9 +9,11 @@ import path from "path"
import fs from "fs/promises" import fs from "fs/promises"
import { Filesystem } from "@/util/filesystem" import { Filesystem } from "@/util/filesystem"
import matter from "gray-matter" import matter from "gray-matter"
import { Instance } from "../../project/instance" import { InstanceRef } from "@/effect/instance-ref"
import { EOL } from "os" import { EOL } from "os"
import type { Argv } from "yargs" import type { Argv } from "yargs"
import { Effect } from "effect"
import { effectCmd } from "../effect-cmd"
type AgentMode = "all" | "primary" | "subagent" type AgentMode = "all" | "primary" | "subagent"
@@ -32,7 +34,7 @@ const AVAILABLE_PERMISSIONS = [
"skill", "skill",
] ]
const AgentCreateCommand = cmd({ const AgentCreateCommand = effectCmd({
command: "create", command: "create",
describe: "create a new agent", describe: "create a new agent",
builder: (yargs: Argv) => builder: (yargs: Argv) =>
@@ -60,10 +62,11 @@ const AgentCreateCommand = cmd({
alias: ["m"], alias: ["m"],
describe: "model to use in the format of provider/model", describe: "model to use in the format of provider/model",
}), }),
async handler(args) { handler: Effect.fn("Cli.agent.create")(function* (args) {
await Instance.provide({ const maybeCtx = yield* InstanceRef
directory: process.cwd(), if (!maybeCtx) return yield* Effect.die("InstanceRef not provided")
async fn() { const ctx = maybeCtx
yield* Effect.promise(async () => {
const cliPath = args.path const cliPath = args.path
const cliDescription = args.description const cliDescription = args.description
const cliMode = args.mode as AgentMode | undefined const cliMode = args.mode as AgentMode | undefined
@@ -76,7 +79,7 @@ const AgentCreateCommand = cmd({
prompts.intro("Create agent") prompts.intro("Create agent")
} }
const project = Instance.project const project = ctx.project
// Determine scope/path // Determine scope/path
let targetPath: string let targetPath: string
@@ -91,7 +94,7 @@ const AgentCreateCommand = cmd({
{ {
label: "Current project", label: "Current project",
value: "project" as const, value: "project" as const,
hint: Instance.worktree, hint: ctx.worktree,
}, },
{ {
label: "Global", label: "Global",
@@ -104,7 +107,7 @@ const AgentCreateCommand = cmd({
scope = scopeResult scope = scopeResult
} }
targetPath = path.join( targetPath = path.join(
scope === "global" ? Global.Path.config : path.join(Instance.worktree, ".opencode"), scope === "global" ? Global.Path.config : path.join(ctx.worktree, ".opencode"),
"agent", "agent",
) )
} }
@@ -227,33 +230,27 @@ const AgentCreateCommand = cmd({
prompts.log.success(`Agent created: ${filePath}`) prompts.log.success(`Agent created: ${filePath}`)
prompts.outro("Done") prompts.outro("Done")
} }
},
}) })
}, }),
}) })
const AgentListCommand = cmd({ const AgentListCommand = effectCmd({
command: "list", command: "list",
describe: "list all available agents", describe: "list all available agents",
async handler() { handler: Effect.fn("Cli.agent.list")(function* () {
await Instance.provide({ const agents = yield* Agent.Service.use((svc) => svc.list())
directory: process.cwd(), const sortedAgents = agents.sort((a, b) => {
async fn() { if (a.native !== b.native) {
const agents = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.list())) return a.native ? -1 : 1
const sortedAgents = agents.sort((a, b) => { }
if (a.native !== b.native) { return a.name.localeCompare(b.name)
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)
}
},
}) })
},
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({ export const AgentCommand = cmd({
+1 -1
View File
@@ -1,6 +1,6 @@
import type { CommandModule } from "yargs" 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>>) { export function cmd<T, U>(input: CommandModule<T, WithDoubleDash<U>>) {
return input return input
+88 -101
View File
@@ -7,14 +7,13 @@ import { Session } from "@/session/session"
import type { MessageV2 } from "../../../session/message-v2" import type { MessageV2 } from "../../../session/message-v2"
import { MessageID, PartID } from "../../../session/schema" import { MessageID, PartID } from "../../../session/schema"
import { ToolRegistry } from "@/tool/registry" import { ToolRegistry } from "@/tool/registry"
import { Instance } from "../../../project/instance"
import { Permission } from "../../../permission" import { Permission } from "../../../permission"
import { iife } from "../../../util/iife" import { iife } from "../../../util/iife"
import { bootstrap } from "../../bootstrap" import { effectCmd, fail } from "../../effect-cmd"
import { cmd } from "../cmd" import { InstanceRef } from "@/effect/instance-ref"
import { AppRuntime } from "@/effect/app-runtime" import type { InstanceContext } from "@/project/instance"
export const AgentCommand = cmd({ export const AgentCommand = effectCmd({
command: "agent <name>", command: "agent <name>",
describe: "show agent configuration details", describe: "show agent configuration details",
builder: (yargs) => builder: (yargs) =>
@@ -32,60 +31,60 @@ export const AgentCommand = cmd({
type: "string", type: "string",
description: "Tool params as JSON or a JS object literal", description: "Tool params as JSON or a JS object literal",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.agent")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const agentName = args.name as string if (!ctx) return
const agent = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.get(agentName))) return yield* run(args, ctx)
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)
})
},
}) })
async function getAvailableTools(agent: Agent.Info) { const run = Effect.fn("Cli.debug.agent.body")(function* (
return AppRuntime.runPromise( args: { name: string; tool?: string; params?: string },
Effect.gen(function* () { ctx: InstanceContext,
const provider = yield* Provider.Service ) {
const registry = yield* ToolRegistry.Service const agentName = args.name
const model = agent.model ?? (yield* provider.defaultModel()) const agent = yield* Agent.Service.use((svc) => svc.get(agentName))
return yield* registry.tools({ if (!agent) {
...model, process.stderr.write(
agent, `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( const disabled = Permission.disabled(
availableTools.map((tool) => tool.id), availableTools.map((tool) => tool.id),
agent.permission, agent.permission,
@@ -123,50 +122,38 @@ function parseToolParams(input?: string) {
return parsed as Record<string, unknown> return parsed as Record<string, unknown>
} }
async function createToolContext(agent: Agent.Info) { const createToolContext = Effect.fn("Cli.debug.agent.createToolContext")(function* (
const { session, messageID } = await AppRuntime.runPromise( agent: Agent.Info,
Effect.gen(function* () { ctx: InstanceContext,
const session = yield* Session.Service ) {
const result = yield* session.create({ title: `Debug tool run (${agent.name})` }) const sessionSvc = yield* Session.Service
const messageID = MessageID.ascending() const session = yield* sessionSvc.create({ title: `Debug tool run (${agent.name})` })
const model = agent.model const messageID = MessageID.ascending()
? agent.model const model = agent.model
: yield* Effect.gen(function* () { ? agent.model
const provider = yield* Provider.Service : yield* Effect.gen(function* () {
return yield* provider.defaultModel() const provider = yield* Provider.Service
}) return yield* provider.defaultModel()
const now = Date.now() })
const message: MessageV2.Assistant = { const now = Date.now()
id: messageID, const message: MessageV2.Assistant = {
sessionID: result.id, id: messageID,
role: "assistant", sessionID: session.id,
time: { role: "assistant",
created: now, time: { created: now },
}, parentID: messageID,
parentID: messageID, modelID: model.modelID,
modelID: model.modelID, providerID: model.providerID,
providerID: model.providerID, mode: "debug",
mode: "debug", agent: agent.name,
agent: agent.name, path: {
path: { cwd: ctx.directory,
cwd: Instance.directory, root: ctx.worktree,
root: Instance.worktree, },
}, cost: 0,
cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
tokens: { }
input: 0, yield* sessionSvc.updateMessage(message)
output: 0,
reasoning: 0,
cache: {
read: 0,
write: 0,
},
},
}
yield* session.updateMessage(message)
return { session: result, messageID }
}),
)
const ruleset = Permission.merge(agent.permission, session.permission ?? []) const ruleset = Permission.merge(agent.permission, session.permission ?? [])
@@ -189,4 +176,4 @@ async function createToolContext(agent: Agent.Info) {
}) })
}, },
} }
} })
+7 -10
View File
@@ -1,17 +1,14 @@
import { EOL } from "os" import { EOL } from "os"
import { Effect } from "effect"
import { Config } from "@/config/config" import { Config } from "@/config/config"
import { AppRuntime } from "@/effect/app-runtime" import { effectCmd } from "../../effect-cmd"
import { bootstrap } from "../../bootstrap"
import { cmd } from "../cmd"
export const ConfigCommand = cmd({ export const ConfigCommand = effectCmd({
command: "config", command: "config",
describe: "show resolved configuration", describe: "show resolved configuration",
builder: (yargs) => yargs, builder: (yargs) => yargs,
async handler() { handler: Effect.fn("Cli.debug.config")(function* () {
await bootstrap(process.cwd(), async () => { const config = yield* Config.Service.use((cfg) => cfg.get())
const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.get())) process.stdout.write(JSON.stringify(config, null, 2) + EOL)
process.stdout.write(JSON.stringify(config, null, 2) + EOL) }),
})
},
}) })
+27 -37
View File
@@ -1,11 +1,11 @@
import { EOL } from "os" import { EOL } from "os"
import { AppRuntime } from "@/effect/app-runtime" import { Effect } from "effect"
import { File } from "../../../file" import { File } from "../../../file"
import { Ripgrep } from "@/file/ripgrep" import { Ripgrep } from "@/file/ripgrep"
import { bootstrap } from "../../bootstrap" import { effectCmd } from "../../effect-cmd"
import { cmd } from "../cmd" import { cmd } from "../cmd"
const FileSearchCommand = cmd({ const FileSearchCommand = effectCmd({
command: "search <query>", command: "search <query>",
describe: "search files by query", describe: "search files by query",
builder: (yargs) => builder: (yargs) =>
@@ -14,15 +14,13 @@ const FileSearchCommand = cmd({
demandOption: true, demandOption: true,
description: "Search query", description: "Search query",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.file.search")(function* (args) {
await bootstrap(process.cwd(), async () => { const results = yield* File.Service.use((svc) => svc.search({ query: args.query }))
const results = await AppRuntime.runPromise(File.Service.use((svc) => svc.search({ query: args.query }))) process.stdout.write(results.join(EOL) + EOL)
process.stdout.write(results.join(EOL) + EOL) }),
})
},
}) })
const FileReadCommand = cmd({ const FileReadCommand = effectCmd({
command: "read <path>", command: "read <path>",
describe: "read file contents as JSON", describe: "read file contents as JSON",
builder: (yargs) => builder: (yargs) =>
@@ -31,27 +29,23 @@ const FileReadCommand = cmd({
demandOption: true, demandOption: true,
description: "File path to read", description: "File path to read",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.file.read")(function* (args) {
await bootstrap(process.cwd(), async () => { const content = yield* File.Service.use((svc) => svc.read(args.path))
const content = await AppRuntime.runPromise(File.Service.use((svc) => svc.read(args.path))) process.stdout.write(JSON.stringify(content, null, 2) + EOL)
process.stdout.write(JSON.stringify(content, null, 2) + EOL) }),
})
},
}) })
const FileStatusCommand = cmd({ const FileStatusCommand = effectCmd({
command: "status", command: "status",
describe: "show file status information", describe: "show file status information",
builder: (yargs) => yargs, builder: (yargs) => yargs,
async handler() { handler: Effect.fn("Cli.debug.file.status")(function* () {
await bootstrap(process.cwd(), async () => { const status = yield* File.Service.use((svc) => svc.status())
const status = await AppRuntime.runPromise(File.Service.use((svc) => svc.status())) process.stdout.write(JSON.stringify(status, null, 2) + EOL)
process.stdout.write(JSON.stringify(status, null, 2) + EOL) }),
})
},
}) })
const FileListCommand = cmd({ const FileListCommand = effectCmd({
command: "list <path>", command: "list <path>",
describe: "list files in a directory", describe: "list files in a directory",
builder: (yargs) => builder: (yargs) =>
@@ -60,15 +54,13 @@ const FileListCommand = cmd({
demandOption: true, demandOption: true,
description: "File path to list", description: "File path to list",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.file.list")(function* (args) {
await bootstrap(process.cwd(), async () => { const files = yield* File.Service.use((svc) => svc.list(args.path))
const files = await AppRuntime.runPromise(File.Service.use((svc) => svc.list(args.path))) process.stdout.write(JSON.stringify(files, null, 2) + EOL)
process.stdout.write(JSON.stringify(files, null, 2) + EOL) }),
})
},
}) })
const FileTreeCommand = cmd({ const FileTreeCommand = effectCmd({
command: "tree [dir]", command: "tree [dir]",
describe: "show directory tree", describe: "show directory tree",
builder: (yargs) => builder: (yargs) =>
@@ -77,12 +69,10 @@ const FileTreeCommand = cmd({
description: "Directory to tree", description: "Directory to tree",
default: process.cwd(), default: process.cwd(),
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.file.tree")(function* (args) {
await bootstrap(process.cwd(), async () => { const tree = yield* Effect.orDie(Ripgrep.Service.use((svc) => svc.tree({ cwd: args.dir, limit: 200 })))
const tree = await AppRuntime.runPromise(Ripgrep.Service.use((svc) => svc.tree({ cwd: args.dir, limit: 200 }))) console.log(JSON.stringify(tree, null, 2))
console.log(JSON.stringify(tree, null, 2)) }),
})
},
}) })
export const FileCommand = cmd({ export const FileCommand = cmd({
+11 -10
View File
@@ -1,5 +1,6 @@
import { Global } from "@opencode-ai/core/global" 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 { cmd } from "../cmd"
import { ConfigCommand } from "./config" import { ConfigCommand } from "./config"
import { FileCommand } from "./file" import { FileCommand } from "./file"
@@ -26,19 +27,19 @@ export const DebugCommand = cmd({
.command(StartupCommand) .command(StartupCommand)
.command(AgentCommand) .command(AgentCommand)
.command(PathsCommand) .command(PathsCommand)
.command({ .command(WaitCommand)
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))
})
},
})
.demandCommand(), .demandCommand(),
async handler() {}, 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({ const PathsCommand = cmd({
command: "paths", command: "paths",
describe: "show global paths (data, config, cache, state)", describe: "show global paths (data, config, cache, state)",
+23 -32
View File
@@ -1,7 +1,6 @@
import { LSP } from "@/lsp/lsp" import { LSP } from "@/lsp/lsp"
import { AppRuntime } from "../../../effect/app-runtime"
import { Effect } from "effect" import { Effect } from "effect"
import { bootstrap } from "../../bootstrap" import { effectCmd } from "../../effect-cmd"
import { cmd } from "../cmd" import { cmd } from "../cmd"
import * as Log from "@opencode-ai/core/util/log" import * as Log from "@opencode-ai/core/util/log"
import { EOL } from "os" import { EOL } from "os"
@@ -14,47 +13,39 @@ export const LSPCommand = cmd({
async handler() {}, async handler() {},
}) })
const DiagnosticsCommand = cmd({ const DiagnosticsCommand = effectCmd({
command: "diagnostics <file>", command: "diagnostics <file>",
describe: "get diagnostics for a file", describe: "get diagnostics for a file",
builder: (yargs) => yargs.positional("file", { type: "string", demandOption: true }), builder: (yargs) => yargs.positional("file", { type: "string", demandOption: true }),
async handler(args) { handler: Effect.fn("Cli.debug.lsp.diagnostics")(function* (args) {
await bootstrap(process.cwd(), async () => { const out = yield* LSP.Service.use((lsp) =>
const out = await AppRuntime.runPromise( Effect.gen(function* () {
LSP.Service.use((lsp) => yield* lsp.touchFile(args.file, "full")
Effect.gen(function* () { return yield* lsp.diagnostics()
yield* lsp.touchFile(args.file, "full") }),
return yield* lsp.diagnostics() )
}), process.stdout.write(JSON.stringify(out, null, 2) + EOL)
), }),
)
process.stdout.write(JSON.stringify(out, null, 2) + EOL)
})
},
}) })
export const SymbolsCommand = cmd({ export const SymbolsCommand = effectCmd({
command: "symbols <query>", command: "symbols <query>",
describe: "search workspace symbols", describe: "search workspace symbols",
builder: (yargs) => yargs.positional("query", { type: "string", demandOption: true }), builder: (yargs) => yargs.positional("query", { type: "string", demandOption: true }),
async handler(args) { handler: Effect.fn("Cli.debug.lsp.symbols")(function* (args) {
await bootstrap(process.cwd(), async () => { using _ = Log.Default.time("symbols")
using _ = Log.Default.time("symbols") const results = yield* LSP.Service.use((lsp) => lsp.workspaceSymbol(args.query))
const results = await AppRuntime.runPromise(LSP.Service.use((lsp) => lsp.workspaceSymbol(args.query))) process.stdout.write(JSON.stringify(results, null, 2) + EOL)
process.stdout.write(JSON.stringify(results, null, 2) + EOL) }),
})
},
}) })
export const DocumentSymbolsCommand = cmd({ export const DocumentSymbolsCommand = effectCmd({
command: "document-symbols <uri>", command: "document-symbols <uri>",
describe: "get symbols from a document", describe: "get symbols from a document",
builder: (yargs) => yargs.positional("uri", { type: "string", demandOption: true }), builder: (yargs) => yargs.positional("uri", { type: "string", demandOption: true }),
async handler(args) { handler: Effect.fn("Cli.debug.lsp.documentSymbols")(function* (args) {
await bootstrap(process.cwd(), async () => { using _ = Log.Default.time("document-symbols")
using _ = Log.Default.time("document-symbols") const results = yield* LSP.Service.use((lsp) => lsp.documentSymbol(args.uri))
const results = await AppRuntime.runPromise(LSP.Service.use((lsp) => lsp.documentSymbol(args.uri))) process.stdout.write(JSON.stringify(results, null, 2) + EOL)
process.stdout.write(JSON.stringify(results, null, 2) + EOL) }),
})
},
}) })
+42 -48
View File
@@ -1,10 +1,9 @@
import { EOL } from "os" import { EOL } from "os"
import { Effect, Stream } from "effect" import { Effect, Stream } from "effect"
import { AppRuntime } from "../../../effect/app-runtime"
import { Ripgrep } from "../../../file/ripgrep" import { Ripgrep } from "../../../file/ripgrep"
import { Instance } from "../../../project/instance" import { effectCmd } from "../../effect-cmd"
import { bootstrap } from "../../bootstrap"
import { cmd } from "../cmd" import { cmd } from "../cmd"
import { InstanceRef } from "@/effect/instance-ref"
export const RipgrepCommand = cmd({ export const RipgrepCommand = cmd({
command: "rg", command: "rg",
@@ -13,24 +12,22 @@ export const RipgrepCommand = cmd({
async handler() {}, async handler() {},
}) })
const TreeCommand = cmd({ const TreeCommand = effectCmd({
command: "tree", command: "tree",
describe: "show file tree using ripgrep", describe: "show file tree using ripgrep",
builder: (yargs) => builder: (yargs) =>
yargs.option("limit", { yargs.option("limit", {
type: "number", type: "number",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.rg.tree")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const tree = await AppRuntime.runPromise( if (!ctx) return
Ripgrep.Service.use((svc) => svc.tree({ cwd: Instance.directory, limit: args.limit })), const tree = yield* Effect.orDie(Ripgrep.Service.use((svc) => svc.tree({ cwd: ctx.directory, limit: args.limit })))
) process.stdout.write(tree + EOL)
process.stdout.write(tree + EOL) }),
})
},
}) })
const FilesCommand = cmd({ const FilesCommand = effectCmd({
command: "files", command: "files",
describe: "list files using ripgrep", describe: "list files using ripgrep",
builder: (yargs) => builder: (yargs) =>
@@ -47,29 +44,26 @@ const FilesCommand = cmd({
type: "number", type: "number",
description: "Limit number of results", description: "Limit number of results",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.rg.files")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const files = await AppRuntime.runPromise( if (!ctx) return
Effect.gen(function* () { const rg = yield* Ripgrep.Service
const rg = yield* Ripgrep.Service const files = yield* rg
return yield* rg .files({
.files({ cwd: ctx.directory,
cwd: Instance.directory, glob: args.glob ? [args.glob] : undefined,
glob: args.glob ? [args.glob] : undefined, })
}) .pipe(
.pipe( Stream.take(args.limit ?? Infinity),
Stream.take(args.limit ?? Infinity), Stream.runCollect,
Stream.runCollect, Effect.map((c) => [...c]),
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>", command: "search <pattern>",
describe: "search file contents using ripgrep", describe: "search file contents using ripgrep",
builder: (yargs) => builder: (yargs) =>
@@ -87,19 +81,19 @@ const SearchCommand = cmd({
type: "number", type: "number",
description: "Limit number of results", description: "Limit number of results",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.rg.search")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const results = await AppRuntime.runPromise( if (!ctx) return
Ripgrep.Service.use((svc) => const results = yield* Effect.orDie(
svc.search({ Ripgrep.Service.use((svc) =>
cwd: Instance.directory, svc.search({
pattern: args.pattern, cwd: ctx.directory,
glob: args.glob as string[] | undefined, pattern: args.pattern,
limit: args.limit, glob: args.glob as string[] | undefined,
}), limit: args.limit,
), }),
) ),
process.stdout.write(JSON.stringify(results.items, null, 2) + EOL) )
}) process.stdout.write(JSON.stringify(results.items, null, 2) + EOL)
}, }),
}) })
+7 -15
View File
@@ -1,23 +1,15 @@
import { EOL } from "os" import { EOL } from "os"
import { Effect } from "effect" import { Effect } from "effect"
import { AppRuntime } from "@/effect/app-runtime"
import { Skill } from "../../../skill" import { Skill } from "../../../skill"
import { bootstrap } from "../../bootstrap" import { effectCmd } from "../../effect-cmd"
import { cmd } from "../cmd"
export const SkillCommand = cmd({ export const SkillCommand = effectCmd({
command: "skill", command: "skill",
describe: "list all available skills", describe: "list all available skills",
builder: (yargs) => yargs, builder: (yargs) => yargs,
async handler() { handler: Effect.fn("Cli.debug.skill")(function* () {
await bootstrap(process.cwd(), async () => { const skill = yield* Skill.Service
const skills = await AppRuntime.runPromise( const skills = yield* skill.all()
Effect.gen(function* () { process.stdout.write(JSON.stringify(skills, null, 2) + EOL)
const skill = yield* Skill.Service }),
return yield* skill.all()
}),
)
process.stdout.write(JSON.stringify(skills, null, 2) + EOL)
})
},
}) })
+17 -20
View File
@@ -1,6 +1,6 @@
import { AppRuntime } from "@/effect/app-runtime" import { Effect } from "effect"
import { Snapshot } from "../../../snapshot" import { Snapshot } from "../../../snapshot"
import { bootstrap } from "../../bootstrap" import { effectCmd } from "../../effect-cmd"
import { cmd } from "../cmd" import { cmd } from "../cmd"
export const SnapshotCommand = cmd({ export const SnapshotCommand = cmd({
@@ -10,17 +10,16 @@ export const SnapshotCommand = cmd({
async handler() {}, async handler() {},
}) })
const TrackCommand = cmd({ const TrackCommand = effectCmd({
command: "track", command: "track",
describe: "track current snapshot state", describe: "track current snapshot state",
async handler() { handler: Effect.fn("Cli.debug.snapshot.track")(function* () {
await bootstrap(process.cwd(), async () => { const out = yield* Snapshot.Service.use((svc) => svc.track())
console.log(await AppRuntime.runPromise(Snapshot.Service.use((svc) => svc.track()))) console.log(out)
}) }),
},
}) })
const PatchCommand = cmd({ const PatchCommand = effectCmd({
command: "patch <hash>", command: "patch <hash>",
describe: "show patch for a snapshot hash", describe: "show patch for a snapshot hash",
builder: (yargs) => builder: (yargs) =>
@@ -29,14 +28,13 @@ const PatchCommand = cmd({
description: "hash", description: "hash",
demandOption: true, demandOption: true,
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.snapshot.patch")(function* (args) {
await bootstrap(process.cwd(), async () => { const out = yield* Snapshot.Service.use((svc) => svc.patch(args.hash))
console.log(await AppRuntime.runPromise(Snapshot.Service.use((svc) => svc.patch(args.hash)))) console.log(out)
}) }),
},
}) })
const DiffCommand = cmd({ const DiffCommand = effectCmd({
command: "diff <hash>", command: "diff <hash>",
describe: "show diff for a snapshot hash", describe: "show diff for a snapshot hash",
builder: (yargs) => builder: (yargs) =>
@@ -45,9 +43,8 @@ const DiffCommand = cmd({
description: "hash", description: "hash",
demandOption: true, demandOption: true,
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.snapshot.diff")(function* (args) {
await bootstrap(process.cwd(), async () => { const out = yield* Snapshot.Service.use((svc) => svc.diff(args.hash))
console.log(await AppRuntime.runPromise(Snapshot.Service.use((svc) => svc.diff(args.hash)))) console.log(out)
}) }),
},
}) })
+63 -78
View File
@@ -1,13 +1,11 @@
import type { Argv } from "yargs"
import { Session } from "@/session/session" import { Session } from "@/session/session"
import { MessageV2 } from "../../session/message-v2" import { MessageV2 } from "../../session/message-v2"
import { SessionID } from "../../session/schema" import { SessionID } from "../../session/schema"
import { cmd } from "./cmd" import { effectCmd, fail } from "../effect-cmd"
import { bootstrap } from "../bootstrap"
import { UI } from "../ui" import { UI } from "../ui"
import * as prompts from "@clack/prompts" import * as prompts from "@clack/prompts"
import { EOL } from "os" import { EOL } from "os"
import { AppRuntime } from "@/effect/app-runtime" import { Effect } from "effect"
function redact(kind: string, id: string, value: string) { function redact(kind: string, id: string, value: string) {
return value.trim() ? `[redacted:${kind}:${id}]` : value 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]", command: "export [sessionID]",
describe: "export session data as JSON", describe: "export session data as JSON",
builder: (yargs: Argv) => { builder: (yargs) =>
return yargs yargs
.positional("sessionID", { .positional("sessionID", {
describe: "session id to export", describe: "session id to export",
type: "string", type: "string",
@@ -232,75 +230,62 @@ export const ExportCommand = cmd({
.option("sanitize", { .option("sanitize", {
describe: "redact sensitive transcript and file data", describe: "redact sensitive transcript and file data",
type: "boolean", type: "boolean",
}) }),
}, handler: Effect.fn("Cli.export")(function* (args) {
handler: async (args) => { return yield* run(args)
await bootstrap(process.cwd(), async () => { }),
let sessionID = args.sessionID ? SessionID.make(args.sessionID) : undefined })
process.stderr.write(`Exporting session: ${sessionID ?? "latest"}\n`)
const run = Effect.fn("Cli.export.body")(function* (args: { sessionID?: string; sanitize?: boolean }) {
if (!sessionID) { const svc = yield* Session.Service
UI.empty() let sessionID = args.sessionID ? SessionID.make(args.sessionID) : undefined
prompts.intro("Export session", { process.stderr.write(`Exporting session: ${sessionID ?? "latest"}\n`)
output: process.stderr,
}) if (!sessionID) {
UI.empty()
const sessions = [] prompts.intro("Export session", { output: process.stderr })
for await (const session of Session.list()) {
sessions.push(session) const sessions = yield* svc.list()
}
if (sessions.length === 0) {
if (sessions.length === 0) { prompts.log.error("No sessions found", { output: process.stderr })
prompts.log.error("No sessions found", { prompts.outro("Done", { output: process.stderr })
output: process.stderr, return
}) }
prompts.outro("Done", {
output: process.stderr, sessions.sort((a, b) => b.time.updated - a.time.updated)
})
return const selectedSession = yield* Effect.promise(() =>
} prompts.autocomplete({
message: "Select session to export",
sessions.sort((a, b) => b.time.updated - a.time.updated) maxItems: 10,
options: sessions.map((session) => ({
const selectedSession = await prompts.autocomplete({ label: session.title,
message: "Select session to export", value: session.id,
maxItems: 10, hint: `${new Date(session.time.updated).toLocaleString()}${session.id.slice(-8)}`,
options: sessions.map((session) => ({ })),
label: session.title, output: process.stderr,
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())
}
if (prompts.isCancel(selectedSession)) {
throw new UI.CancelledError() sessionID = selectedSession
}
prompts.outro("Exporting session...", { output: process.stderr })
sessionID = selectedSession }
prompts.outro("Exporting session...", { // Match legacy try/catch — catches both typed failures and defects
output: process.stderr, // (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 })
try {
const sessionInfo = await AppRuntime.runPromise(Session.Service.use((svc) => svc.get(sessionID!))) const exportData = { info: sessionInfo, messages }
const messages = await AppRuntime.runPromise(
Session.Service.use((svc) => svc.messages({ sessionID: sessionInfo.id })), process.stdout.write(JSON.stringify(args.sanitize ? sanitize(exportData) : exportData, null, 2))
) process.stdout.write(EOL)
}).pipe(Effect.catchCause(() => fail(`Session not found: ${sessionID!}`)))
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)
}
})
},
}) })
+186 -184
View File
@@ -18,9 +18,9 @@ import type {
} from "@octokit/webhooks-types" } from "@octokit/webhooks-types"
import { UI } from "../ui" import { UI } from "../ui"
import { cmd } from "./cmd" import { cmd } from "./cmd"
import { effectCmd } from "../effect-cmd"
import { ModelsDev } from "@/provider/models" import { ModelsDev } from "@/provider/models"
import { Instance } from "@/project/instance" import { InstanceRef } from "@/effect/instance-ref"
import { bootstrap } from "../bootstrap"
import { SessionShare } from "@/share/session" import { SessionShare } from "@/share/session"
import { Session } from "@/session/session" import { Session } from "@/session/session"
import type { SessionID } from "../../session/schema" import type { SessionID } from "../../session/schema"
@@ -199,191 +199,192 @@ export const GithubCommand = cmd({
async handler() {}, async handler() {},
}) })
export const GithubInstallCommand = cmd({ export const GithubInstallCommand = effectCmd({
command: "install", command: "install",
describe: "install the GitHub agent", describe: "install the GitHub agent",
async handler() { handler: Effect.fn("Cli.github.install")(function* () {
await Instance.provide({ const maybeCtx = yield* InstanceRef
directory: process.cwd(), if (!maybeCtx) return yield* Effect.die("InstanceRef not provided")
async fn() { const ctx = maybeCtx
{ yield* Effect.promise(async () => {
UI.empty() {
prompts.intro("Install GitHub agent") UI.empty()
const app = await getAppInfo() prompts.intro("Install GitHub agent")
await installGitHubApp() const app = await getAppInfo()
await installGitHubApp()
const providers = await ModelsDev.get().then((p) => { const providers = await AppRuntime.runPromise(ModelsDev.Service.use((s) => s.get())).then((p) => {
// TODO: add guide for copilot, for now just hide it // TODO: add guide for copilot, for now just hide it
delete p["github-copilot"] delete p["github-copilot"]
return p 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() if (prompts.isCancel(provider)) throw new UI.CancelledError()
const model = await promptModel()
//const key = await promptKey()
await addWorkflowFiles() return provider
printNextSteps() }
function printNextSteps() { async function promptModel() {
let step2 const providerData = providers[provider]!
if (provider === "amazon-bedrock") {
step2 = const model = await prompts.select({
"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" message: "Select model",
} else { maxItems: 8,
step2 = [ options: pipe(
` 2. Add the following secrets in org or repo (${app.owner}/${app.repo}) settings`, providerData.models,
"", values(),
...providers[provider].env.map((e) => ` - ${e}`), sortBy((x) => x.name ?? x.id),
].join("\n") 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( // Wait for installation
[ s.message("Waiting for GitHub app to be installed")
"Next steps:", const MAX_RETRIES = 120
"", let retries = 0
` 1. Commit the \`${WORKFLOW_FILE}\` file and push`, do {
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
const installation = await getInstallation() const installation = await getInstallation()
if (installation) return s.stop("GitHub app already installed") if (installation) break
// Open browser if (retries > MAX_RETRIES) {
const url = "https://github.com/apps/opencode-agent" s.stop(
const command = `Failed to detect GitHub app installation. Make sure to install the app for the \`${app.owner}/${app.repo}\` repository.`,
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}`,
) )
.then((res) => res.json()) throw new UI.CancelledError()
.then((data) => data.installation)
} }
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() { async function addWorkflowFiles() {
const envStr = const envStr =
provider === "amazon-bedrock" provider === "amazon-bedrock"
? "" ? ""
: `\n env:${providers[provider].env.map((e) => `\n ${e}: \${{ secrets.${e} }}`).join("")}` : `\n env:${providers[provider].env.map((e) => `\n ${e}: \${{ secrets.${e} }}`).join("")}`
await Filesystem.write( await Filesystem.write(
path.join(app.root, WORKFLOW_FILE), path.join(app.root, WORKFLOW_FILE),
`name: opencode `name: opencode
on: on:
issue_comment: issue_comment:
@@ -414,17 +415,16 @@ jobs:
uses: anomalyco/opencode/github@latest${envStr} uses: anomalyco/opencode/github@latest${envStr}
with: with:
model: ${provider}/${model}`, 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", command: "run",
describe: "run the GitHub agent", describe: "run the GitHub agent",
builder: (yargs) => builder: (yargs) =>
@@ -437,8 +437,10 @@ export const GithubRunCommand = cmd({
type: "string", type: "string",
describe: "GitHub personal access token (github_pat_********)", describe: "GitHub personal access token (github_pat_********)",
}), }),
async handler(args) { handler: Effect.fn("Cli.github.run")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
if (!ctx) return yield* Effect.die("InstanceRef not provided")
yield* Effect.promise(async () => {
const isMock = args.token || args.event const isMock = args.token || args.event
const context = isMock ? (JSON.parse(args.event!) as Context) : github.context const context = isMock ? (JSON.parse(args.event!) as Context) : github.context
@@ -501,21 +503,21 @@ export const GithubRunCommand = cmd({
: "issue" : "issue"
: undefined : undefined
const gitText = async (args: string[]) => { 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) { if (result.exitCode !== 0) {
throw new Process.RunFailedError(["git", ...args], result.exitCode, result.stdout, result.stderr) throw new Process.RunFailedError(["git", ...args], result.exitCode, result.stdout, result.stderr)
} }
return result.text().trim() return result.text().trim()
} }
const gitRun = async (args: string[]) => { 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) { if (result.exitCode !== 0) {
throw new Process.RunFailedError(["git", ...args], result.exitCode, result.stdout, result.stderr) throw new Process.RunFailedError(["git", ...args], result.exitCode, result.stdout, result.stderr)
} }
return result return result
} }
const gitStatus = (args: string[]) => 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 commitChanges = async (summary: string, actor?: string) => {
const args = ["commit", "-m", summary] const args = ["commit", "-m", summary]
if (actor) args.push("-m", `Co-authored-by: ${actor} <${actor}@users.noreply.github.com>`) if (actor) args.push("-m", `Co-authored-by: ${actor} <${actor}@users.noreply.github.com>`)
@@ -879,7 +881,7 @@ export const GithubRunCommand = cmd({
function subscribeSessionEvents() { function subscribeSessionEvents() {
const TOOL: Record<string, [string, string]> = { const TOOL: Record<string, [string, string]> = {
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD], 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], edit: ["Edit", UI.Style.TEXT_SUCCESS_BOLD],
glob: ["Glob", UI.Style.TEXT_INFO_BOLD], glob: ["Glob", UI.Style.TEXT_INFO_BOLD],
grep: ["Grep", UI.Style.TEXT_INFO_BOLD], grep: ["Grep", UI.Style.TEXT_INFO_BOLD],
@@ -1645,5 +1647,5 @@ query($owner: String!, $repo: String!, $number: Int!) {
}) })
} }
}) })
}, }),
}) })
+125 -120
View File
@@ -1,17 +1,14 @@
import type { Argv } from "yargs"
import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2" import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2"
import { Session } from "@/session/session" import { Session } from "@/session/session"
import { MessageV2 } from "../../session/message-v2" import { MessageV2 } from "../../session/message-v2"
import { cmd } from "./cmd" import { CliError, effectCmd } from "../effect-cmd"
import { bootstrap } from "../bootstrap"
import { Database } from "@/storage/db" import { Database } from "@/storage/db"
import { SessionTable, MessageTable, PartTable } from "../../session/session.sql" 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 { ShareNext } from "@/share/share-next"
import { EOL } from "os" import { EOL } from "os"
import { Filesystem } from "@/util/filesystem" import { Filesystem } from "@/util/filesystem"
import { AppRuntime } from "@/effect/app-runtime" import { Effect, Schema } from "effect"
import { Schema } from "effect"
const decodeMessageInfo = Schema.decodeUnknownSync(MessageV2.Info) const decodeMessageInfo = Schema.decodeUnknownSync(MessageV2.Info)
const decodePart = Schema.decodeUnknownSync(MessageV2.Part) 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>", command: "import <file>",
describe: "import session data from JSON file or URL", describe: "import session data from JSON file or URL",
builder: (yargs: Argv) => { builder: (yargs) =>
return yargs.positional("file", { yargs.positional("file", {
describe: "path to JSON file or share URL", describe: "path to JSON file or share URL",
type: "string", type: "string",
demandOption: true, 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" }),
}) })
}, const transformed = transformShareData(shareData)
handler: async (args) => {
await bootstrap(process.cwd(), async () => {
let exportData:
| {
info: SDKSession
messages: Array<{
info: Message
parts: Part[]
}>
}
| undefined
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) { exportData = transformed
const slug = parseShareUrl(args.file) } else {
if (!slug) { exportData = yield* Effect.promise(() =>
const baseUrl = await AppRuntime.runPromise(ShareNext.Service.use((svc) => svc.url())) Filesystem.readJson<NonNullable<typeof exportData>>(file).catch(() => undefined),
process.stdout.write(`Invalid URL format. Expected: ${baseUrl}/share/<slug>`) )
process.stdout.write(EOL) if (!exportData) {
return process.stdout.write(`File not found: ${file}`)
} process.stdout.write(EOL)
return
}
}
const parsed = new URL(args.file) if (!exportData) {
const baseUrl = parsed.origin process.stdout.write(`Failed to read session data`)
const req = await AppRuntime.runPromise(ShareNext.Service.use((svc) => svc.request())) process.stdout.write(EOL)
const headers = shouldAttachShareAuthHeaders(args.file, req.baseUrl) ? req.headers : {} return
}
const dataPath = req.api.data(slug) const info = Schema.decodeUnknownSync(Session.Info)({
let response = await fetch(`${baseUrl}${dataPath}`, { ...exportData.info,
headers, 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`) { for (const part of msg.parts) {
response = await fetch(`${baseUrl}/api/share/${slug}/data`, { const partInfo = decodePart(part) as MessageV2.Part
headers, const { id: partId, sessionID: _s, messageID, ...partData } = partInfo
})
}
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)
Database.use((db) => Database.use((db) =>
db db
.insert(SessionTable) .insert(PartTable)
.values(row) .values({
.onConflictDoUpdate({ target: SessionTable.id, set: { project_id: row.project_id } }) id: partId,
message_id: messageID,
session_id: row.id,
data: partData,
})
.onConflictDoNothing()
.run(), .run(),
) )
}
}
for (const msg of exportData.messages) { process.stdout.write(`Imported session: ${exportData.info.id}`)
const msgInfo = decodeMessageInfo(msg.info) as MessageV2.Info process.stdout.write(EOL)
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)
})
},
}) })
+242 -257
View File
@@ -1,4 +1,6 @@
import { cmd } from "./cmd" import { cmd } from "./cmd"
import { effectCmd } from "../effect-cmd"
import { Cause } from "effect"
import { Client } from "@modelcontextprotocol/sdk/client/index.js" import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js" import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js" import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
@@ -9,7 +11,7 @@ import { McpAuth } from "../../mcp/auth"
import { McpOAuthProvider } from "../../mcp/oauth-provider" import { McpOAuthProvider } from "../../mcp/oauth-provider"
import { Config } from "@/config/config" import { Config } from "@/config/config"
import { ConfigMCP } from "../../config/mcp" import { ConfigMCP } from "../../config/mcp"
import { Instance } from "../../project/instance" import { InstanceRef } from "@/effect/instance-ref"
import { Installation } from "../../installation" import { Installation } from "../../installation"
import { InstallationVersion } from "@opencode-ai/core/installation/version" import { InstallationVersion } from "@opencode-ai/core/installation/version"
import path from "path" import path from "path"
@@ -64,35 +66,31 @@ function oauthServers(config: Config.Info) {
) )
} }
async function listState() { function listState() {
return AppRuntime.runPromise( return Effect.gen(function* () {
Effect.gen(function* () { const cfg = yield* Config.Service
const cfg = yield* Config.Service const mcp = yield* MCP.Service
const mcp = yield* MCP.Service const config = yield* cfg.get()
const config = yield* cfg.get() const statuses = yield* mcp.status()
const statuses = yield* mcp.status() const stored = yield* Effect.all(
const stored = yield* Effect.all( Object.fromEntries(configuredServers(config).map(([name]) => [name, mcp.hasStoredTokens(name)])),
Object.fromEntries(configuredServers(config).map(([name]) => [name, mcp.hasStoredTokens(name)])), { concurrency: "unbounded" },
{ concurrency: "unbounded" }, )
) return { config, statuses, stored }
return { config, statuses, stored } })
}),
)
} }
async function authState() { function authState() {
return AppRuntime.runPromise( return Effect.gen(function* () {
Effect.gen(function* () { const cfg = yield* Config.Service
const cfg = yield* Config.Service const mcp = yield* MCP.Service
const mcp = yield* MCP.Service const config = yield* cfg.get()
const config = yield* cfg.get() const auth = yield* Effect.all(
const auth = yield* Effect.all( Object.fromEntries(oauthServers(config).map(([name]) => [name, mcp.getAuthStatus(name)])),
Object.fromEntries(oauthServers(config).map(([name]) => [name, mcp.getAuthStatus(name)])), { concurrency: "unbounded" },
{ concurrency: "unbounded" }, )
) return { config, auth }
return { config, auth } })
}),
)
} }
export const McpCommand = cmd({ export const McpCommand = cmd({
@@ -109,73 +107,68 @@ export const McpCommand = cmd({
async handler() {}, async handler() {},
}) })
export const McpListCommand = cmd({ export const McpListCommand = effectCmd({
command: "list", command: "list",
aliases: ["ls"], aliases: ["ls"],
describe: "list MCP servers and their status", describe: "list MCP servers and their status",
async handler() { handler: Effect.fn("Cli.mcp.list")(function* () {
await Instance.provide({ UI.empty()
directory: process.cwd(), prompts.intro("MCP Servers")
async fn() {
UI.empty()
prompts.intro("MCP Servers")
const { config, statuses, stored } = await listState() const { config, statuses, stored } = yield* listState()
const servers = configuredServers(config) const servers = configuredServers(config)
if (servers.length === 0) { if (servers.length === 0) {
prompts.log.warn("No MCP servers configured") prompts.log.warn("No MCP servers configured")
prompts.outro("Add servers with: opencode mcp add") prompts.outro("Add servers with: opencode mcp add")
return 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 typeHint = serverConfig.type === "remote" ? serverConfig.url : serverConfig.command.join(" ")
const status = statuses[name] prompts.log.info(
const hasOAuth = isMcpRemote(serverConfig) && !!serverConfig.oauth `${statusIcon} ${name} ${UI.Style.TEXT_DIM}${statusText}${hint}\n ${UI.Style.TEXT_DIM}${typeHint}`,
const hasStoredTokens = stored[name] )
}
let statusIcon: string prompts.outro(`${servers.length} server(s)`)
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)`)
},
})
},
}) })
export const McpAuthCommand = cmd({ export const McpAuthCommand = effectCmd({
command: "auth [name]", command: "auth [name]",
describe: "authenticate with an OAuth-enabled MCP server", describe: "authenticate with an OAuth-enabled MCP server",
builder: (yargs) => builder: (yargs) =>
@@ -185,98 +178,98 @@ export const McpAuthCommand = cmd({
type: "string", type: "string",
}) })
.command(McpAuthListCommand), .command(McpAuthListCommand),
async handler(args) { handler: Effect.fn("Cli.mcp.auth")(function* (args) {
await Instance.provide({ UI.empty()
directory: process.cwd(), prompts.intro("MCP OAuth Authentication")
async fn() {
UI.empty()
prompts.intro("MCP OAuth Authentication")
const { config, auth } = await authState() const { config, auth } = yield* authState()
const mcpServers = config.mcp ?? {} const mcpServers = config.mcp ?? {}
const servers = oauthServers(config) const servers = oauthServers(config)
if (servers.length === 0) { if (servers.length === 0) {
prompts.log.warn("No OAuth-capable MCP servers configured") 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("Remote MCP servers support OAuth by default. Add a remote server in opencode.json:")
prompts.log.info(` prompts.log.info(`
"mcp": { "mcp": {
"my-server": { "my-server": {
"type": "remote", "type": "remote",
"url": "https://example.com/mcp" "url": "https://example.com/mcp"
} }
}`) }`)
prompts.outro("Done") prompts.outro("Done")
return 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 const selected = yield* Effect.promise(() =>
if (!serverName) { prompts.select({
// Build options with auth status message: "Select MCP server to authenticate",
const options = servers.map(([name, cfg]) => { options,
const authStatus = auth[name] }),
const icon = getAuthStatusIcon(authStatus) )
const statusText = getAuthStatusText(authStatus) if (prompts.isCancel(selected)) throw new UI.CancelledError()
const url = cfg.url serverName = selected
return { }
label: `${icon} ${name} (${statusText})`,
value: name,
hint: url,
}
})
const selected = await prompts.select({ const serverConfig = mcpServers[serverName]
message: "Select MCP server to authenticate", if (!serverConfig) {
options, prompts.log.error(`MCP server not found: ${serverName}`)
}) prompts.outro("Done")
if (prompts.isCancel(selected)) throw new UI.CancelledError() return
serverName = selected }
}
const serverConfig = mcpServers[serverName] if (!isMcpRemote(serverConfig) || serverConfig.oauth === false) {
if (!serverConfig) { prompts.log.error(`MCP server ${serverName} is not an OAuth-capable remote server`)
prompts.log.error(`MCP server not found: ${serverName}`) prompts.outro("Done")
prompts.outro("Done") return
return }
}
if (!isMcpRemote(serverConfig) || serverConfig.oauth === false) { // Check if already authenticated
prompts.log.error(`MCP server ${serverName} is not an OAuth-capable remote server`) const authStatus = auth[serverName] ?? (yield* MCP.Service.use((mcp) => mcp.getAuthStatus(serverName)))
prompts.outro("Done") if (authStatus === "authenticated") {
return 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 spinner = prompts.spinner()
const authStatus = spinner.start("Starting OAuth flow...")
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() // Subscribe to browser open failure events to show URL for manual opening
spinner.start("Starting OAuth flow...") const unsubscribe = Bus.subscribe(MCP.BrowserOpenFailed, (evt) => {
if (evt.properties.mcpName === serverName) {
// Subscribe to browser open failure events to show URL for manual opening spinner.stop("Could not open browser automatically")
const unsubscribe = Bus.subscribe(MCP.BrowserOpenFailed, (evt) => { prompts.log.warn("Please open this URL in your browser to authenticate:")
if (evt.properties.mcpName === serverName) { prompts.log.info(evt.properties.url)
spinner.stop("Could not open browser automatically") spinner.start("Waiting for authorization...")
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)))
yield* MCP.Service.use((mcp) => mcp.authenticate(serverName)).pipe(
Effect.tap((status) =>
Effect.sync(() => {
if (status.status === "connected") { if (status.status === "connected") {
spinner.stop("Authentication successful!") spinner.stop("Authentication successful!")
} else if (status.status === "needs_client_registration") { } else if (status.status === "needs_client_registration") {
@@ -300,55 +293,53 @@ export const McpAuthCommand = cmd({
} else { } else {
spinner.stop("Unexpected status: " + status.status, 1) spinner.stop("Unexpected status: " + status.status, 1)
} }
} catch (error) { }),
),
Effect.catchCause((cause) =>
Effect.sync(() => {
spinner.stop("Authentication failed", 1) spinner.stop("Authentication failed", 1)
const error = Cause.squash(cause)
prompts.log.error(error instanceof Error ? error.message : String(error)) 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", command: "list",
aliases: ["ls"], aliases: ["ls"],
describe: "list OAuth-capable MCP servers and their auth status", describe: "list OAuth-capable MCP servers and their auth status",
async handler() { handler: Effect.fn("Cli.mcp.auth.list")(function* () {
await Instance.provide({ UI.empty()
directory: process.cwd(), prompts.intro("MCP OAuth Status")
async fn() {
UI.empty()
prompts.intro("MCP OAuth Status")
const { config, auth } = await authState() const { config, auth } = yield* authState()
const servers = oauthServers(config) const servers = oauthServers(config)
if (servers.length === 0) { if (servers.length === 0) {
prompts.log.warn("No OAuth-capable MCP servers configured") prompts.log.warn("No OAuth-capable MCP servers configured")
prompts.outro("Done") prompts.outro("Done")
return return
} }
for (const [name, serverConfig] of servers) { for (const [name, serverConfig] of servers) {
const authStatus = auth[name] const authStatus = auth[name]
const icon = getAuthStatusIcon(authStatus) const icon = getAuthStatusIcon(authStatus)
const statusText = getAuthStatusText(authStatus) const statusText = getAuthStatusText(authStatus)
const url = serverConfig.url 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]", command: "logout [name]",
describe: "remove OAuth credentials for an MCP server", describe: "remove OAuth credentials for an MCP server",
builder: (yargs) => builder: (yargs) =>
@@ -356,57 +347,54 @@ export const McpLogoutCommand = cmd({
describe: "name of the MCP server", describe: "name of the MCP server",
type: "string", type: "string",
}), }),
async handler(args) { handler: Effect.fn("Cli.mcp.logout")(function* (args) {
await Instance.provide({ UI.empty()
directory: process.cwd(), prompts.intro("MCP OAuth Logout")
async fn() {
UI.empty()
prompts.intro("MCP OAuth Logout")
const credentials = await AppRuntime.runPromise(McpAuth.Service.use((auth) => auth.all())) const credentials = yield* McpAuth.Service.use((auth) => auth.all())
const serverNames = Object.keys(credentials) const serverNames = Object.keys(credentials)
if (serverNames.length === 0) { if (serverNames.length === 0) {
prompts.log.warn("No MCP OAuth credentials stored") prompts.log.warn("No MCP OAuth credentials stored")
prompts.outro("Done") prompts.outro("Done")
return return
} }
let serverName = args.name let serverName = args.name
if (!serverName) { if (!serverName) {
const selected = await prompts.select({ const selected = yield* Effect.promise(() =>
message: "Select MCP server to logout", prompts.select({
options: serverNames.map((name) => { message: "Select MCP server to logout",
const entry = credentials[name] options: serverNames.map((name) => {
const hasTokens = !!entry.tokens const entry = credentials[name]
const hasClient = !!entry.clientInfo const hasTokens = !!entry.tokens
let hint = "" const hasClient = !!entry.clientInfo
if (hasTokens && hasClient) hint = "tokens + client" let hint = ""
else if (hasTokens) hint = "tokens" if (hasTokens && hasClient) hint = "tokens + client"
else if (hasClient) hint = "client registration" else if (hasTokens) hint = "tokens"
return { else if (hasClient) hint = "client registration"
label: name, return {
value: name, label: name,
hint, value: name,
} hint,
}), }
}) }),
if (prompts.isCancel(selected)) throw new UI.CancelledError() }),
serverName = selected )
} if (prompts.isCancel(selected)) throw new UI.CancelledError()
serverName = selected
}
if (!credentials[serverName]) { if (!credentials[serverName]) {
prompts.log.error(`No credentials found for: ${serverName}`) prompts.log.error(`No credentials found for: ${serverName}`)
prompts.outro("Done") prompts.outro("Done")
return return
} }
await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.removeAuth(serverName))) yield* MCP.Service.use((mcp) => mcp.removeAuth(serverName))
prompts.log.success(`Removed OAuth credentials for ${serverName}`) prompts.log.success(`Removed OAuth credentials for ${serverName}`)
prompts.outro("Done") prompts.outro("Done")
}, }),
})
},
}) })
async function resolveConfigPath(baseDir: string, global = false) { async function resolveConfigPath(baseDir: string, global = false) {
@@ -444,21 +432,22 @@ async function addMcpToConfig(name: string, mcpConfig: ConfigMCP.Info, configPat
return configPath return configPath
} }
export const McpAddCommand = cmd({ export const McpAddCommand = effectCmd({
command: "add", command: "add",
describe: "add an MCP server", describe: "add an MCP server",
async handler() { handler: Effect.fn("Cli.mcp.add")(function* () {
await Instance.provide({ const maybeCtx = yield* InstanceRef
directory: process.cwd(), if (!maybeCtx) return yield* Effect.die("InstanceRef not provided")
async fn() { const ctx = maybeCtx
yield* Effect.promise(async () => {
UI.empty() UI.empty()
prompts.intro("Add MCP server") prompts.intro("Add MCP server")
const project = Instance.project const project = ctx.project
// Resolve config paths eagerly for hints // Resolve config paths eagerly for hints
const [projectConfigPath, globalConfigPath] = await Promise.all([ const [projectConfigPath, globalConfigPath] = await Promise.all([
resolveConfigPath(Instance.worktree), resolveConfigPath(ctx.worktree),
resolveConfigPath(Global.Path.config, true), resolveConfigPath(Global.Path.config, true),
]) ])
@@ -603,12 +592,11 @@ export const McpAddCommand = cmd({
} }
prompts.outro("MCP server added successfully") prompts.outro("MCP server added successfully")
},
}) })
}, }),
}) })
export const McpDebugCommand = cmd({ export const McpDebugCommand = effectCmd({
command: "debug <name>", command: "debug <name>",
describe: "debug OAuth connection for an MCP server", describe: "debug OAuth connection for an MCP server",
builder: (yargs) => builder: (yargs) =>
@@ -617,10 +605,8 @@ export const McpDebugCommand = cmd({
type: "string", type: "string",
demandOption: true, demandOption: true,
}), }),
async handler(args) { handler: Effect.fn("Cli.mcp.debug")(function* (args) {
await Instance.provide({ yield* Effect.promise(async () => {
directory: process.cwd(),
async fn() {
UI.empty() UI.empty()
prompts.intro("MCP OAuth Debug") prompts.intro("MCP OAuth Debug")
@@ -792,7 +778,6 @@ export const McpDebugCommand = cmd({
} }
prompts.outro("Debug complete") prompts.outro("Debug complete")
},
}) })
}, }),
}) })

Some files were not shown because too many files have changed in this diff Show More