Compare commits

..
1153 Commits
Author SHA1 Message Date
opencode c4c489a5bc release: v1.1.29
Update Nix Hashes / update-node-modules-hashes (push) Has been cancelled
2026-01-21 17:17:32 +00:00
Adam cd34f5e07c feat(app): new sound effects, downmixed to mono 2026-01-21 11:14:11 -06:00
Adam 621550ac77 fix(app): keybind search height 2026-01-21 11:14:11 -06:00
Adam b746c006cf feat(app): new sounds 2026-01-21 11:14:11 -06:00
Adam 850d50eb64 fix(app): missing i18n keys 2026-01-21 11:14:11 -06:00
Aiden Cline ab3d412a81 tweak: adjust skill tool description to make it more clear which skills are available 2026-01-21 11:08:28 -06:00
0e1a8a1839 fix: Claude w/bedrock custom inference profile - caching support (#9838)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-21 10:42:13 -06:00
GitHub Action 178767af70 chore: generate 2026-01-21 16:36:03 +00:00
Rahul A MistryandGitHub b8a0e420f8 feat(app): search on settings shortcuts (#9850) 2026-01-21 10:35:15 -06:00
nuno maduroandGitHub bfbcbc8863 feat(formatters): add laravel pint as a .php formatter (#7312) 2026-01-21 10:31:39 -06:00
SpoonandGitHub fd77d31b49 tweak(session title): change prompt to have the response with user language (#9847) 2026-01-21 10:05:09 -06:00
Rahul A MistryandGitHub 9f02ffe02d fix(app): new workspace button with all languages (#9848) 2026-01-21 10:03:29 -06:00
GitHub Action b10f423743 chore: generate 2026-01-21 15:58:36 +00:00
Nolan DarilekandGitHub 0059fdc1f5 fix(app): add aria-labels to titlebar and sidebar buttons (#9843) 2026-01-21 09:57:50 -06:00
Adam f7f2d9700a test(app): fix e2e 2026-01-21 09:46:24 -06:00
Frank 97e0e79f1a wip: black 2026-01-21 10:42:58 -05:00
Adam 4fc7bcf09e fix: type error 2026-01-21 09:30:40 -06:00
Adam 63da3a338a fix(app): breaking out of auto-scroll 2026-01-21 09:28:56 -06:00
GitHub Action f736751a8c chore: generate 2026-01-21 15:26:15 +00:00
Ronan KearnsandGitHub 6ac8c85b34 feat(app): model tooltip metadata in chooser (per Figma request) (#9707) 2026-01-21 09:25:34 -06:00
GitHub Action 19f68382fd chore: generate 2026-01-21 15:23:21 +00:00
DNGriffinandGitHub 368cd2af4c fix(app): workspaces padding wonkiness (#9772) 2026-01-21 09:22:40 -06:00
Brendan AllanandGitHub d00b8df770 feat(desktop): properly integrate window controls on windows (#9835) 2026-01-21 08:35:05 -06:00
Adam 7ed448a7e8 test(app): fix e2e 2026-01-21 07:48:15 -06:00
Halil Tezcan KARABULUTandGitHub 87d91c29e2 fix(app): terminal improvements - focus, rename, error state, CSP (#9700) 2026-01-21 06:49:46 -06:00
Adam 259b2a3c2d fix(app): japanese language support 2026-01-21 06:16:32 -06:00
Brendan Allan ab705bbc31 fix(desktop): add workaround for nushell 2026-01-21 20:15:19 +08:00
Adam e237f06c96 test(app): fix e2e 2026-01-21 06:10:01 -06:00
Adam bb710e9ea1 fix(core): snapshot regression 2026-01-21 06:10:01 -06:00
GitHub Action 34e4d077cd ignore: update download stats 2026-01-21 2026-01-21 12:05:58 +00:00
Adam 4b8335160b test(app): fix e2e 2026-01-21 06:00:21 -06:00
Adam 8b0353cb2a feat(app): danish translations 2026-01-21 05:50:25 -06:00
Adam 4a386906dd feat(app): japanese translations 2026-01-21 05:50:25 -06:00
Adam efff52714d feat(app): french translations 2026-01-21 05:50:25 -06:00
Adam 09a9556c70 feat(app): spanish translations 2026-01-21 05:24:38 -06:00
Adam 118b4f65da feat(app): german translations 2026-01-21 05:24:38 -06:00
Adam e6438aa3f6 feat(app): korean translations 2026-01-21 05:24:38 -06:00
Adam 64c80f1b51 fix(app): don't show notification on session if active 2026-01-21 05:15:19 -06:00
Adam 7b8fad6202 test(app): fix e2e 2026-01-21 05:15:19 -06:00
Ronan KearnsandGitHub 996eeb1f68 feat(app): add manage models icon to selector (per Figma request) (#9722) 2026-01-21 04:44:17 -06:00
zerone0xandGitHub 2e5fe6d5c8 fix(ui): preserve filename casing in edit/write tool titles (#9752) 2026-01-21 04:41:45 -06:00
DNGriffinandGitHub 8e8fb6a54b feat(app): allow users to select directory text on new session (#9760) 2026-01-21 04:41:03 -06:00
GitHub Action 79aa931a05 chore: generate 2026-01-21 10:36:00 +00:00
shirukaiandGitHub cf6ad4c407 fix: handle special characters in paths and git snapshot reading logic(#9804) (#9807) 2026-01-21 04:35:23 -06:00
d6caaee816 fix(desktop): no proxy for connecting to sidecar (#9690)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-01-21 16:21:21 +08:00
Dax Raad 65938baf00 core: update session summary after revert to show file changes 2026-01-21 01:01:20 -05:00
Github Action a639961973 chore: update nix node_modules hashes 2026-01-21 05:40:07 +00:00
Michael HandGitHub 0f979bb87c chore(opencode): Use Bun.semver instead of node-semver (#9773) 2026-01-20 23:37:33 -06:00
GitHub Action 96e9c89cc6 chore: generate 2026-01-21 05:37:18 +00:00
a18ae2c8b7 feat: add OPENCODE_DISABLE_PROJECT_CONFIG env var (#8093)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-01-20 23:36:42 -06:00
c9ea966805 feat: add OPENCODE_DISABLE_FILETIME_CHECK flag (#6581)
Signed-off-by: luojiyin <luojiyin@hotmail.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-20 23:03:07 -06:00
GitHub Action 2049af4d6f chore: generate 2026-01-21 04:56:39 +00:00
Aiden Cline 74bd52e8a7 fix: ensure apply patch tool emits edited events 2026-01-20 22:55:50 -06:00
Aiden Cline 9dc95c4c69 tweak: ensure synthetic user message following subtasks is only added when user manually invoked subtask 2026-01-20 22:51:54 -06:00
Vinicius da MottaandGitHub b93f33eaa4 fix(tui): responsive layout for narrow screens (#9703) 2026-01-20 22:22:38 -06:00
Caleb NortonandGitHub 34d473c0f5 chore: revert "Update flake.lock" (#9725) 2026-01-20 22:20:24 -06:00
GitHub Action 217e4850db chore: generate 2026-01-21 04:07:26 +00:00
Frank be9a0bfee7 wip: support 2026-01-20 23:06:08 -05:00
GitHub Action dac73572e0 chore: generate 2026-01-21 02:39:31 +00:00
Ariane EmoryandGitHub cbe20d22d3 fix: don't update session timestamp for metadata-only changes (resolves #9494) (#9495) 2026-01-20 20:38:54 -06:00
yashandGitHub 3723e1b8d2 fix: correct dot prefix display in directory names for RTL text rendering issue #9579 (#9591) 2026-01-20 20:36:41 -06:00
Jacob BahnandGitHub 65d9e829e7 feat(desktop): standardize desktop layout icons (#9685) 2026-01-20 20:34:33 -06:00
GitHub Action 6793b4a6fd chore: generate 2026-01-21 02:28:43 +00:00
Ryan VogelandGitHub a71c40c717 fix(app): fix numbered list rendering in web markdown (#9723) 2026-01-20 20:28:01 -06:00
Alex YaroshukandGitHub 489f2d3709 fix(ui): remove portal spacer and fix terminal toggle padding (#9728) 2026-01-20 20:27:33 -06:00
Github Action 20c8624bb7 chore: update nix node_modules hashes 2026-01-21 00:00:53 +00:00
GitHub Action bb8bf32abe chore: generate 2026-01-20 23:58:59 +00:00
Adam 233d003b49 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam 6037e88ddf wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam b13c269162 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam ef36af0e55 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam f86c37f579 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam 9b7d9c8173 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam 0f2e8ea2b4 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam be493e8be0 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam 7e8e4d9938 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam 7a359ff67c wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam 835fea6bb1 wip(app): i18n prompt input 2026-01-20 17:58:06 -06:00
Adam 7138bd021c chore: spec 2026-01-20 17:58:06 -06:00
Adam a68e5a1c17 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam 92beae1410 wip(app): i18n 2026-01-20 17:58:06 -06:00
Adam 0470717c7f feat(app): initial i18n stubbing 2026-01-20 17:58:06 -06:00
Ryan VogelandGitHub 7f50b27996 docs: add Anthropic subscription warning and update feature list to highlight GitHub Copilot (#9721) 2026-01-20 18:22:47 -05:00
Aiden Cline 021e42c0bb core: fix issue when switching models (mainly between providers) where past reasoning/metadata would be sent to server and cause 400 errors since they came from another account/provider 2026-01-20 16:39:00 -06:00
Aiden Cline 0c4ffec857 chore: rename toModelMessage -> toModelMessages 2026-01-20 16:16:23 -06:00
GitHub Action 5c3e9cfa2c chore: generate 2026-01-20 22:13:03 +00:00
Adam 85ef23a098 fix(app): don't interfere with scroll when using message nav 2026-01-20 16:12:15 -06:00
David Hillandopencode 3b46f90124 fix: icon size in sidbar 2026-01-20 22:04:13 +00:00
David Hillandopencode 80dc74a0ec add keyboard shortcut (mod+,) to open settings dialog 2026-01-20 22:04:13 +00:00
Adamandopencode a0636fcd50 fix(app): auto-scroll ux 2026-01-20 22:04:13 +00:00
opencode d2fcdef571 release: v1.1.28
Update Nix Hashes / update-node-modules-hashes (push) Has been cancelled
2026-01-20 22:04:12 +00:00
Adam 8137e4dd9c chore: agents.md 2026-01-20 15:59:23 -06:00
David Hill 7be6671e6e refactor Select component to use settings variant for settings modal styling 2026-01-20 21:53:17 +00:00
David Hill 575cc59b37 fix: increase sidebar icon size by removing 16px constraint 2026-01-20 21:53:17 +00:00
David Hill 4350b8fd6b fix: show View all sessions button for active project and close hovercard on click 2026-01-20 21:53:17 +00:00
David Hill 2111473746 fix: remove close delay on hover cards to stop overlapping 2026-01-20 21:53:17 +00:00
David Hill 8c5c377680 fix: review empty state font size 2026-01-20 21:53:17 +00:00
KennyandGitHub d51089b52f docs(web): add KDCO plugins to ecosystem (#7694) 2026-01-20 15:47:07 -06:00
Trevor WalkerandGitHub 694695050a fix(opencode): preserve tool input from running state for MCP tool results (#9667) 2026-01-20 15:12:15 -06:00
DNGriffinandGitHub 1f3b2b5951 fix(app): Edit-project name race condition (#9551) 2026-01-20 15:10:00 -06:00
David Hill de87694867 fix: resolve Select children type conflict with ButtonProps 2026-01-20 21:09:20 +00:00
David Hill ecd28fd520 fix: prompt agent button style 2026-01-20 21:08:50 +00:00
David Hill bf9047ccd1 fix settings sidebar active tab hover to use consistent background 2026-01-20 21:08:50 +00:00
Adam 96a9744347 fix: type error 2026-01-20 15:08:03 -06:00
Caleb NortonandGitHub c4594c4c1f fix(opencode): relax bun version requirement (#9682) 2026-01-20 14:57:19 -06:00
Caleb NortonandGitHub eea70be21a chore: follow conventional commit in nix CI (#9672) 2026-01-20 14:56:37 -06:00
Idris GadiandGitHub e83c01ad36 fix(tui): prevent sidebar height from overflowing. (#9689) 2026-01-20 14:56:18 -06:00
David Hill 7eb724f4e9 fix: dialog shadow 2026-01-20 20:34:46 +00:00
David Hill 7653e2d4d8 update: add new border and shadow style 2026-01-20 20:34:45 +00:00
David Hill 261b1eca2e update keyboard shortcuts panel to match general settings styling 2026-01-20 20:34:45 +00:00
David Hill dbc15d4816 add color scheme preview on hover in appearance dropdown 2026-01-20 20:34:45 +00:00
David Hill 602b6be4d4 update settings panel padding and make content full width 2026-01-20 20:34:45 +00:00
David Hill 7f4277695d set 32px spacing between main title and group title 2026-01-20 20:34:45 +00:00
David Hill 39afc055bf add fade gradient to settings panel headers 2026-01-20 20:34:45 +00:00
David Hill 73b1bc42f4 increase specificity of select trigger hover/expanded states 2026-01-20 20:34:45 +00:00
David Hill 3eea1d424e set select trigger value font weight to regular 2026-01-20 20:34:45 +00:00
David Hill 1092cf4034 increase gap between label and icon in select trigger to 12px 2026-01-20 20:34:45 +00:00
David Hill 0c270b4743 reset select trigger to default state after selection 2026-01-20 20:34:45 +00:00
David Hill 715860f997 set default select trigger background to transparent 2026-01-20 20:34:45 +00:00
David Hill 26f66b5f5d update: color token 2026-01-20 20:34:45 +00:00
David Hill f250a229c9 update select trigger icon styling and spacing 2026-01-20 20:34:45 +00:00
David Hill c2c2bb1fa9 add 4px left padding to sidebar section title 2026-01-20 20:34:45 +00:00
David Hill 19ac6f1948 set hover background of active sidebar item to surface-raised-base 2026-01-20 20:34:45 +00:00
David Hill 0d9ce6ad7b set settings sidebar width to 200px 2026-01-20 20:34:45 +00:00
David Hill 2b95956132 add x-large dialog size and use it for settings modal 2026-01-20 20:34:45 +00:00
David Hill 36bbe809fa add 4px gutter between select trigger and dropdown 2026-01-20 20:34:45 +00:00
David Hill a8113ee0df update select trigger and dropdown styling 2026-01-20 20:34:45 +00:00
David Hill bcb8d970f1 add selector icon and use it for select dropdown trigger 2026-01-20 20:34:44 +00:00
David Hill c57491ba48 add triggerStyle prop to Select and use it for font selector 2026-01-20 20:34:44 +00:00
David Hill 9ffb7141e5 set select dropdown border-radius to 8px 2026-01-20 20:34:44 +00:00
David Hill f3b0f312bf adjust select dropdown positioning and padding structure 2026-01-20 20:34:44 +00:00
David Hill 09a6107649 set select dropdown min-width to 180px 2026-01-20 20:34:44 +00:00
David Hill af8d91117c update select item styling: 4px radius, default cursor, 8px 2px padding 2026-01-20 20:34:44 +00:00
David Hill 0ffc2c2b39 increase select dropdown padding to 4px 2026-01-20 20:34:44 +00:00
David Hill f9c951aa8b render font options in their respective fonts 2026-01-20 20:34:44 +00:00
David Hill 78bcbda2fa wrap settings row groups with styled container 2026-01-20 20:34:44 +00:00
David Hill 262aca1bca remove border and background from settings panel headers 2026-01-20 20:34:44 +00:00
David Hill 0cbbe5af77 remove subheader from General settings panel 2026-01-20 20:34:44 +00:00
David Hill ecae24f426 use medium font weight for settings tab labels 2026-01-20 20:34:44 +00:00
David Hill 745206ffbb increase gap between icon and label in settings tabs to 12px 2026-01-20 20:34:44 +00:00
David Hill 83557e9b66 add keyboard icon and use it for Shortcuts settings tab 2026-01-20 20:34:44 +00:00
David Hill 1a4abe85e8 add sliders icon and use it for General settings tab 2026-01-20 20:34:44 +00:00
David Hill 175313922b use active background color for selected settings tab 2026-01-20 20:34:44 +00:00
David Hill 74ad6dd4c9 update settings tabs layout and spacing 2026-01-20 20:34:44 +00:00
David Hill a94667e8e7 increase icon letter size to 32px in edit project dialog 2026-01-20 20:34:44 +00:00
David Hill ff8abd8c2b increase session messages popover open delay to 1000ms 2026-01-20 20:34:44 +00:00
Adam 95e9407e63 test(app): fix e2e 2026-01-20 14:02:09 -06:00
Adam 1466b43c5c test(app): windows fixes 2026-01-20 14:02:09 -06:00
Adam f73d7e67d3 test(app): windows fixes 2026-01-20 14:02:09 -06:00
Adam 1ac0980c80 test(app): windows e2e 2026-01-20 14:02:09 -06:00
Adam 1d6f650f53 fix(app): ayu theme colors 2026-01-20 13:59:04 -06:00
Adam 0b9b85ea6e wip(app): ayu colors 2026-01-20 13:59:04 -06:00
Adam 5521d66bb8 wip(app): ayu colors 2026-01-20 13:59:04 -06:00
Rahul A MistryandGitHub 281c9d1870 fix(app): change terminal.new keybind to ctrl+alt+t (#9670) 2026-01-20 13:03:20 -06:00
Rahul A MistryandGitHub 80481c2247 fix(app): cleanup pty.exited event listener on unmount (#9671) 2026-01-20 13:03:01 -06:00
drunkpianoandGitHub 156ce54362 fix(ui): prevent Enter key action during IME composition (#9564) 2026-01-20 13:01:56 -06:00
Aiden Cline 1bc919dc74 ignore: add bun/file io skill just for our repo 2026-01-20 12:37:23 -06:00
Github Action 17438a2e90 Update node_modules hashes 2026-01-20 17:36:35 +00:00
17c4202ea8 fix(opencode): Allow compatible Bun versions in packageManager field (#9597)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-01-20 11:34:00 -06:00
Adam 7170983ef2 fix(app): duplicate session loads 2026-01-20 11:25:58 -06:00
Dax Raad b05d88a730 docs: clarify that malicious config files are not an attack vector 2026-01-20 12:17:58 -05:00
a3a06ffc4f fix(ui): show filename in Edit/Write permission titles (#9662)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-20 11:14:47 -06:00
Tommy D. RossiandGitHub 68e41a1ee7 fix: pass arguments to commands without explicit placeholders (#9606) 2026-01-20 11:12:43 -06:00
Aiden Cline 5622c53e1f tweak: adjust codex prompt to discourage unnecessary question asking and encourage more autonomy 2026-01-20 11:08:04 -06:00
GitHub Action dfe6ce211d chore: generate 2026-01-20 16:57:52 +00:00
Rahul A MistryandGitHub 8639b0767a feat(app): add tooltips to sidebar new session/workspace buttons (#9652) 2026-01-20 10:57:11 -06:00
Adam 5f67e6fd12 fix(app): don't jump accordion on expand/collapse 2026-01-20 10:54:04 -06:00
Adam 86b2002deb fix: type error 2026-01-20 10:34:10 -06:00
Adam 7f862533d8 fix(app): better pending states for workspace operations 2026-01-20 10:31:57 -06:00
msvechlaandGitHub 8f62d4a5e3 fix(mcp): register OAuth callback before opening browser (#9646) 2026-01-20 10:18:49 -06:00
GitHub Action 733226de9d chore: generate 2026-01-20 16:11:56 +00:00
e8b0a65c63 feat: Support ACP audience by mapping to ignore and synthetic (#9593)
Co-authored-by: noam-v <noam@bespo.ai>
2026-01-20 10:11:02 -06:00
GitHub Action cd2125eecd chore: generate 2026-01-20 16:01:54 +00:00
Adam 8595dae1a4 fix(app): session loading loop 2026-01-20 10:01:04 -06:00
Rahul MishraandGitHub c365f0a7c1 feat: add restart and reload menu items on macOS (#9212) 2026-01-20 09:44:15 -06:00
Rahul A MistryandGitHub 01b12949e3 fix(app): terminal no longer hangs on exit or ctrl + D and closes the pane (#9506) 2026-01-20 09:42:20 -06:00
Adam ac7e674a87 fix(app): broken 2026-01-20 09:05:04 -06:00
GitHub Action 47fa496701 chore: generate 2026-01-20 13:34:20 +00:00
Adam d77cbf9c46 chore: cleanup 2026-01-20 07:33:44 -06:00
Adam 340285575b chore: cleanup 2026-01-20 07:33:44 -06:00
Adam dd5b5f5482 chore: cleanup 2026-01-20 07:33:44 -06:00
Adam 924fc9ed80 wip(app): settings 2026-01-20 07:33:44 -06:00
Adam df094a10ff wip(app): settings 2026-01-20 07:33:44 -06:00
Adam de3641e8eb wip(app): settings 2026-01-20 07:33:44 -06:00
Adam 8bcbfd6396 wip(app): settings 2026-01-20 07:33:44 -06:00
opencode e521fee002 release: v1.1.27
Update Nix Hashes / update-node-modules-hashes (push) Has been cancelled
2026-01-20 12:13:48 +00:00
Adam 04e60f2b3d fix(app): no flash of home page on start 2026-01-20 06:10:53 -06:00
GitHub Action 23d71e125c ignore: update download stats 2026-01-20 2026-01-20 12:05:55 +00:00
GitHub Action 27406cf8ef chore: generate 2026-01-20 11:40:31 +00:00
Adam 0596b02f19 chore: cleanup 2026-01-20 05:39:54 -06:00
Adam 5145b72c4a chore: cleanup 2026-01-20 05:37:15 -06:00
Adam 347cd8ac63 chore: cleanup 2026-01-20 05:35:24 -06:00
Adam b711ca57f2 fix(app): localStorage quota 2026-01-20 05:21:33 -06:00
Adam 353115a895 fix(app): user message expand on click 2026-01-20 05:21:33 -06:00
Adam 5f0372183a fix(app): persist quota 2026-01-20 05:21:32 -06:00
GitHub Action 616329ae97 chore: generate 2026-01-20 06:33:16 +00:00
Aiden Cline 9706aaf552 rm filetime assertions from patch tool 2026-01-20 00:32:29 -06:00
Brendan Allan 8b379329a6 fix(desktop): completely disable pinch to zoom 2026-01-20 14:07:39 +08:00
Craig JellickandGitHub 68d1755a9e fix: add space toggle hint to tool selection prompt (#9535) 2026-01-19 23:38:26 -06:00
Aiden Cline 419004992d chore: remove duplicate prompt file 2026-01-19 23:22:57 -06:00
Aiden Cline 0d49df46ef fix: ensure truncation handling applies to mcp servers too 2026-01-19 23:19:24 -06:00
James MengandGitHub 36f5ba52e9 fix(batch): update batch tool definition to outline correct value for max tool calls (#9517) 2026-01-19 22:15:02 -06:00
GitHub Action 088b537657 chore: generate 2026-01-20 01:42:14 +00:00
FilipandGitHub 4ddfa86e7f fix(app): message list overflow & scrolling (#9530) 2026-01-19 19:41:42 -06:00
David Hill b91b76e9eb add 8px padding to recent sessions popover 2026-01-20 01:41:36 +00:00
David Hill 6ed656a615 remove top padding from edit project dialog form 2026-01-20 01:36:34 +00:00
David Hill 7b336add88 update session messages popover gutter to 28px 2026-01-20 01:21:36 +00:00
David Hill 7f9ffe57f9 update thinking text styling in desktop app 2026-01-20 00:42:55 +00:00
David Hill ad31b555a8 position session messages popover at top 2026-01-20 00:27:03 +00:00
David Hill a05c334702 retain session hover state when popover open and update border radius 2026-01-20 00:24:51 +00:00
David Hill cf284e32aa update session hover popover styling 2026-01-20 00:21:11 +00:00
David Hill 054ccee78d update review session empty state styling 2026-01-20 00:15:13 +00:00
DNGriffinandGitHub bfa986d45e feat(app): Add ability to select project directory text to web (#9344) 2026-01-19 17:38:52 -06:00
Dax Raad aa4b06e165 tui: fix message history cleanup to prevent memory leaks 2026-01-19 18:22:19 -05:00
GitHub Action 2542693f7b chore: generate 2026-01-19 22:13:58 +00:00
Adam bec294b781 fix(app): remove copy button from summary 2026-01-19 16:13:16 -06:00
opencode 1ee8a9c0b2 release: v1.1.26
Update Nix Hashes / update-node-modules-hashes (push) Has been cancelled
2026-01-19 21:55:27 +00:00
Adam 4e04bee0c9 fix(app): favicon 2026-01-19 15:46:04 -06:00
SpoonandGitHub 673e79f457 tweak(batch): up restrictive max batch tool from 10 to 25 (#9275) 2026-01-19 15:44:58 -06:00
Adam 79ae749ed8 fix(app): don't change resize handle on hover 2026-01-19 15:28:37 -06:00
FilipandGitHub d605a78a05 fix(app): change keybind for cycling thinking effort (#9508) 2026-01-19 15:15:43 -06:00
GitHub Action 69b3b35ea5 chore: generate 2026-01-19 21:00:39 +00:00
Adam 3173ba1288 fix(app): fade under sticky elements 2026-01-19 14:59:50 -06:00
Adam a4d1824412 fix(app): no more favicons 2026-01-19 14:59:47 -06:00
Adam cac35bc52d fix(app): global terminal/review pane toggles 2026-01-19 14:59:46 -06:00
Adam ecc51ddb4e fix(app): hash nav 2026-01-19 14:59:46 -06:00
Aiden Cline 769c97af08 chore: rm double conditional 2026-01-19 14:49:51 -06:00
GitHub Action e29120317f chore: generate 2026-01-19 20:47:09 +00:00
Ronan KearnsandGitHub 88c5a7fe9e fix(tui): clarify resume session tip (#9490) 2026-01-19 14:46:32 -06:00
Joseph CampuzanoandGitHub 091e88c1e1 fix(opencode): sets input mode based on whether mouse vs keyboard is in use to prevent mouse events firing (#9449) 2026-01-19 14:46:17 -06:00
FilipandGitHub d19e76d96c fix: keyboard nav when mouse hovered over list (#9500) 2026-01-19 14:43:32 -06:00
FilipandGitHub c3393ecc6c fix(app): give feedback when trying to paste a unsupported filetype (#9452) 2026-01-19 14:16:25 -06:00
Ryan VogelandGitHub 889c60d63b fix(web): rename favicons to v2 for cache busting (#9492) 2026-01-19 15:04:59 -05:00
Ariane EmoryandGitHub c47699536f fix: Don't unnecessarily wrap lines and introduce an unneeded empty line (resolves #9489) (#9488) 2026-01-19 13:56:24 -06:00
Adam c2f9fd5fef fix(app): reload instance after workspace reset 2026-01-19 12:44:41 -06:00
Aiden Cline 3fd0043d19 chore: handle fields other than reasoning_content in interleaved block 2026-01-19 12:18:17 -06:00
Adam 092428633f fix(app): layout jumping 2026-01-19 11:44:20 -06:00
Adam fc50b2962c fix(app): make terminal sessions scoped to workspace 2026-01-19 11:28:24 -06:00
Aiden Cline dd0906be8c tweak: apply patch description 2026-01-19 11:22:00 -06:00
David Hill b72a00eaa3 fix text field border showing through focus ring 2026-01-19 17:10:27 +00:00
David Hill 2dbdd18483 add hover overlay with upload/trash icons to project icon in edit dialog 2026-01-19 17:10:27 +00:00
David Hill b0794172bf update: tighten edit project color spacing 2026-01-19 17:10:27 +00:00
David Hill 9fbf2e72b4 update: constrain edit project dialog width 2026-01-19 17:10:27 +00:00
David Hill 494e8d5be9 update: tweak edit project icon container 2026-01-19 17:10:27 +00:00
David Hill e12b94d91a update: adjust edit project icon helper text 2026-01-19 17:10:27 +00:00
David Hill 89be504abc update: align edit project dialog padding and avatar styles 2026-01-19 17:10:27 +00:00
David Hill c7f0cb3d2d fix: remove focus outline from dropdown menu 2026-01-19 17:10:26 +00:00
Adam eb779a7cc5 chore: cleanup 2026-01-19 10:55:57 -06:00
Adam c720a2163c chore: cleanup 2026-01-19 10:55:57 -06:00
Adam 7811e01c8e fix(app): new layout improvements 2026-01-19 10:55:57 -06:00
Adam befd0f1636 feat(app): new session layout 2026-01-19 10:55:57 -06:00
Adam 1f11a8a6ea feat(app): improved session layout 2026-01-19 10:55:57 -06:00
Goni ZahavyandGitHub d5ae8e0bef fix(opencode): cargo fmt is formatting whole workspace instead of edited file (#9436) 2026-01-19 10:48:59 -06:00
GitHub Action 453417ed47 chore: generate 2026-01-19 16:46:09 +00:00
Joseph CampuzanoandGitHub 72cb7ccc00 fix(app): list component jumping when mouse happens to be under the list and keyboard navigating. (#9435) 2026-01-19 10:43:27 -06:00
Adam 4ee540309f fix(app): hide settings button 2026-01-19 10:26:21 -06:00
Aiden Cline 5b86724632 fix: cargo fmt actually does not support formatting single files 2026-01-19 10:15:32 -06:00
b1684f3d12 fix(config): rename uv formatter from 'uv format' to 'uv' for config consistency (#9409)
Co-authored-by: Paul C. Lou <paul@exig.ai>
2026-01-19 09:59:51 -06:00
Vladimir GlafirovandGitHub 29e206b6c6 docs: Improve Gitlab self-hosted instances documentation (#9391) 2026-01-19 09:51:27 -06:00
Evgenii KosenkoandGitHub 31864cadb4 docs: update codecompanion.nvim acp doc (#9411) 2026-01-19 09:50:41 -06:00
Frank 843d76191e zen: fix black reset date 2026-01-19 10:12:50 -05:00
Github ActionandAdam 3186e7ec7c Update node_modules hashes 2026-01-19 09:03:52 -06:00
Adam 1ba7c606e6 chore: cleanup 2026-01-19 09:03:52 -06:00
Adam f00f18b926 chore: cleanup 2026-01-19 09:03:52 -06:00
Adam e9ede70793 chore: cleanup 2026-01-19 09:03:52 -06:00
Adam 2b086f0584 test(app): more e2e tests 2026-01-19 09:03:52 -06:00
Adam b90315bc7e chore: cleanup 2026-01-19 09:03:52 -06:00
Adam 182c43a78f chore: cleanup 2026-01-19 09:03:52 -06:00
Adam f1daf3b430 fix(app): tests in ci 2026-01-19 09:03:52 -06:00
Adam dd19c3d8f2 test(app): e2e utilities 2026-01-19 09:03:52 -06:00
Github ActionandAdam f5eb90514a Update node_modules hash (aarch64-darwin) 2026-01-19 09:03:52 -06:00
Github ActionandAdam 6bc823bd40 Update node_modules hash (x86_64-linux) 2026-01-19 09:03:52 -06:00
Github ActionandAdam 7621c5cafb Update flake.lock 2026-01-19 09:03:52 -06:00
Adam 91a708b12e test(app): more e2e tests 2026-01-19 09:03:52 -06:00
Adam 19d15ca4df test(app): more e2e tests 2026-01-19 09:03:52 -06:00
Adam 03d7467ea2 test(app): initial e2e test setup 2026-01-19 09:03:52 -06:00
GitHub Action 23e9c02a7f chore: generate 2026-01-19 13:37:19 +00:00
Adam 51804a47e9 chore: cleanup 2026-01-19 07:35:52 -06:00
Adam 55739b7aa1 chore: cleanup 2026-01-19 07:35:52 -06:00
Adam 295f290efd chore: cleanup 2026-01-19 07:35:52 -06:00
Adam 1a262c4ca8 chore: cleanup 2026-01-19 07:35:52 -06:00
Adam dca2540ca7 chore: cleanup 2026-01-19 07:35:52 -06:00
Adam fcfe6d3d26 chore: cleanup 2026-01-19 07:35:52 -06:00
Adam 093a3e7876 feat(app): reset worktree 2026-01-19 07:35:52 -06:00
Adam f26de6c52f feat(app): delete workspace 2026-01-19 07:35:52 -06:00
GitHub Action 06d03dec3b ignore: update download stats 2026-01-19 2026-01-19 12:05:55 +00:00
Mani SundararajanandGitHub 08005d755b refactor(desktop): tweak share button to prevent layout shift (#9322) 2026-01-19 04:34:40 -06:00
SloneandGitHub 13276aee82 fix(desktop): apply getComputedStyle polyfill on all platforms (#9369) 2026-01-19 04:32:41 -06:00
Aiden Cline 4299450d7d tweak apply_patch tool description 2026-01-19 01:31:30 -06:00
Aiden Cline 3515b4ff7d omit todo tools for openai models 2026-01-19 01:06:26 -06:00
Aiden Cline 4a7809f600 add proper variant support to copilot 2026-01-19 00:18:42 -06:00
GitHub Action 9d1803d000 chore: generate 2026-01-19 06:14:40 +00:00
Caleb NortonandGitHub 91787ceb3e fix: nix ci - swapped dash/underscore (#9352) 2026-01-19 00:14:14 -06:00
Aiden Cline 86df915df0 chore: cleanup provider code to assign copilot sdk earlier in flow 2026-01-19 00:13:58 -06:00
GitHub Action 6f847a794b chore: generate 2026-01-19 06:12:36 +00:00
260ab60c0b fix: track reasoning by output_index for copilot compatibility (#9124)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-19 00:11:54 -06:00
Aiden ClineandGitHub e2f1f4d81e add scheduler, cleanup module (#9346) 2026-01-18 23:33:23 -06:00
fc6c9cbbd2 fix(github-copilot): auto-route GPT-5+ models to Responses API (#5877)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-18 23:30:28 -06:00
Thiago MalekandGitHub 6b481b5fb0 fix(opencode): use streamObject when using openai oauth in agent generation (#9231) 2026-01-18 23:22:31 -06:00
Caleb NortonandGitHub 2fc4ab9687 ci: simplify nix hash updates (#9309) 2026-01-18 21:46:00 -06:00
Luke ParkerandGitHub d939a3ad54 feat(tui): use mouse for permission buttons (#9305) 2026-01-18 21:42:10 -06:00
Frank bee2f65409 zen: fix checkout link for black users 2026-01-18 19:19:00 -05:00
Luke ParkerandGitHub e81bb86795 fix: Windows evaluating text on copy (#9293) 2026-01-18 17:27:30 -06:00
Alan PogrebinschiandGitHub b4d4a1ea7d docs: clarify agent tool access and explore vs general distinction (#9300) 2026-01-18 16:46:04 -06:00
Aiden Cline 0d8e706fac test: fix transfomr test 2026-01-18 14:44:39 -06:00
Aiden Cline d841e70d26 fix: bad variants for grok models 2026-01-18 14:21:14 -06:00
Github Action 19cf9344e1 Update node_modules hashes 2026-01-18 19:24:21 +00:00
Aiden Cline c29d44fcef docs: note untracked files in review 2026-01-18 13:22:58 -06:00
38c641a2fc fix(tool): treat .fbs files as text instead of images (#9276)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-18 13:17:49 -06:00
Vladimir GlafirovandGitHub 501ef2d989 fix: update gitlab-ai-provider to 1.3.2 (#9279) 2026-01-18 13:11:34 -06:00
SpoonandGitHub bfd2f91d5b feat(hook): command execute before hook (#9267) 2026-01-18 13:11:22 -06:00
Caleb NortonandGitHub dac099a489 feat(nix): overhaul nix flake and packages (#9032) 2026-01-18 11:14:13 -06:00
GitHub Action 5009f10406 chore: generate 2026-01-18 16:46:02 +00:00
095a64291d fix(acp): preserve file attachment metadata during session replay (#6342)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-18 10:45:25 -06:00
Chawye HsuandGitHub f7fef99ddd refactor(installation): update scoop installation method (#9243)
Signed-off-by: Chawye Hsu <su+git@chawyehsu.com>
2026-01-18 09:58:34 -06:00
Aiden Cline 2dcca4755d fix: import issue in patch module 2026-01-18 09:47:18 -06:00
OpeOginniandGitHub ad2e03284b refactor(desktop): improve layout and styling of session search button (#9251) 2026-01-18 08:10:38 -06:00
Kit LangtonandGitHub 6c0991d162 fix(app): remove redundant toast for thinking effort changes (#9181) 2026-01-18 08:00:49 -06:00
GitHub Action 5c9cc9c748 ignore: update download stats 2026-01-18 2026-01-18 12:05:11 +00:00
Mani SundararajanandGitHub 06bc4dcb06 feat(desktop): implement session unshare button (#8660) 2026-01-18 05:12:07 -06:00
Mani SundararajanandGitHub 0ccf9bd9ac feat(cli): uninstall opencode installed via windows package managers (#8571) 2026-01-18 02:40:01 -06:00
ee4ea65311 fix: restore persisted model/agent when loading ACP session (#7809)
Co-authored-by: noam-v <noam@bespo.ai>
2026-01-18 01:29:57 -06:00
bef1f66281 fix(acp): use single global event subscription and route by sessionID (#5628)
Co-authored-by: noamzbr <noamzbr@users.noreply.github.com>
Co-authored-by: noam-v <noam@bespo.ai>
2026-01-18 01:29:42 -06:00
GitHub Action d13c0ea915 chore: generate 2026-01-18 06:42:13 +00:00
Bowen DwelleandGitHub 3591372c45 feat(tool): increase question header and label limits (#9201) 2026-01-18 00:41:36 -06:00
GitHub Action 90f848fbc6 chore: generate 2026-01-18 06:35:48 +00:00
Aiden ClineandGitHub b7ad6bd839 feat: apply_patch tool for openai models (#9127) 2026-01-18 00:35:09 -06:00
Patrick SchielandGitHub 10433cb45b fix(windows): fix jdtls download on Windows (#9195) 2026-01-18 00:30:45 -06:00
GitHub Action 073f9d99b5 chore: generate 2026-01-18 03:55:03 +00:00
bfb8c531c2 feat: bind vim-style line-by-line scrolling (#8980)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-01-17 21:54:26 -06:00
Aiden Cline 052f887a9a core: prevent env variables in config from being replaced with actual values
When opencode.json was missing a $schema, the config loader would add it
and write the file back - but with env variables like {env:API_KEY} replaced
with their actual secret values. This made it impossible to safely commit
opencode.json to version control.

Now the original config text is preserved when adding $schema, keeping
variable placeholders intact.
2026-01-17 20:59:50 -06:00
Kit LangtonandGitHub 759e68616e refactor(tui): unify command registry and derive slash commands (#9115) 2026-01-17 20:39:19 -06:00
opencode-agent[bot]GitHubopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-node
93e43d8e5e Hide variants hint when list empty (#9179)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2026-01-17 20:32:57 -06:00
David Hill 53c77e29df fix: remove max-width of session name tooltip 2026-01-18 00:59:41 +00:00
David Hill 260739a227 Revert "fix: increase max-width of session name tooltip"
This reverts commit c3ab76c8ad.
2026-01-18 00:57:21 +00:00
David Hill c3ab76c8ad fix: increase max-width of session name tooltip 2026-01-18 00:51:35 +00:00
David Hill 389d97ece9 fix: adjust project path tooltip placement
Move the desktop project path tooltip above the header and tune spacing/offset; add content style hooks to Tooltip for max-width and horizontal shift.
2026-01-18 00:48:49 +00:00
David Hill e36b3433fc fix: remove max width on sidebar new buttons 2026-01-18 00:48:06 +00:00
David Hill ded9bd26bb fix: adjust session list tooltip trigger and delay 2026-01-18 00:07:21 +00:00
David Hill c890853992 fix: keep project avatar hover styles while popover open 2026-01-17 23:40:06 +00:00
David Hill 2a4e8bc01c fix: adjust recent sessions popover padding 2026-01-17 23:21:34 +00:00
David Hill c19d031144 fix: reduce prompt dock bottom spacing 2026-01-17 22:54:30 +00:00
David Hill 0cc9a22a42 fix: show project name in avatar hover 2026-01-17 22:51:49 +00:00
David Hill b4075cd856 fix: remove loading text after splash 2026-01-17 21:54:51 +00:00
David Hill 53227bfc2a fix: command pallete file list item spacing 2026-01-17 21:46:23 +00:00
David Hill d3baaf7408 fix: shrink project notification dot and mask 2026-01-17 21:46:23 +00:00
David Hill 0384e6b0e1 fix: update desktop initializing splash logo 2026-01-17 21:46:23 +00:00
David Hill c3d33562c7 fix: align project avatar notification dot 2026-01-17 21:46:23 +00:00
Aiden Cline f3513bacff tui: fix model state persistence when model store is not ready 2026-01-17 14:41:42 -06:00
opencode-agent[bot]GitHubopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-node
3aff88c23d docs: add use_github_token to example (#9120)
publish-github-action / publish (push) Has been cancelled
Update Nix Hashes / update-flake (push) Has been cancelled
Update Nix Hashes / compute-node-modules-hash (blacksmith-4vcpu-ubuntu-2404, x86_64-linux) (push) Has been cancelled
Update Nix Hashes / compute-node-modules-hash (blacksmith-4vcpu-ubuntu-2404-arm, aarch64-linux) (push) Has been cancelled
Update Nix Hashes / compute-node-modules-hash (macos-15-intel, x86_64-darwin) (push) Has been cancelled
Update Nix Hashes / compute-node-modules-hash (macos-latest, aarch64-darwin) (push) Has been cancelled
Update Nix Hashes / commit-node-modules-hashes (push) Has been cancelled
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2026-01-17 13:36:54 -06:00
Aiden Cline 58f7da6e9f docs: document the plural forms 2026-01-17 13:09:30 -06:00
Rahul MishraandGitHub 5a199b04cb fix: don't try to open command palette if a dialog is already open (#9116) 2026-01-17 13:08:11 -06:00
Bernat PericàsandGitHub eb968a6651 docs(config): explain that autoupdate doesn't work when installed with a package manager (#9092) 2026-01-17 13:07:03 -06:00
Colby GilbertandGitHub a813fcb41c docs: add firmware provider to providers docs (#8993) 2026-01-17 13:04:43 -06:00
GitHub Action a58d1be822 ignore: update download stats 2026-01-17 2026-01-17 12:04:18 +00:00
SloneandGitHub 07dc8d8ce4 fix: escape CSS selector keys to handle special characters (#9030) 2026-01-17 05:48:38 -06:00
GitHub Action d377246491 chore: generate 2026-01-17 11:47:55 +00:00
Javier AceñaandGitHub 7030f49a74 fix: mdns discover hostname (#9039) 2026-01-17 05:47:19 -06:00
Eric GuoandGitHub c4e4f2a058 fix(desktop): Added a Windows-only guard that makes window.getComputedStyle fall back to document.documentElement (#9054) 2026-01-17 05:45:31 -06:00
Adam 2729705594 fix(app): archive session sometimes flaky 2026-01-17 05:23:17 -06:00
Aiden Cline ea13b6e8aa test: add azure test case 2026-01-17 00:35:49 -06:00
GitHub Action 85ab9798c6 chore: generate 2026-01-17 04:18:35 +00:00
Aiden Cline 33290c54cd Revert "feat(mcp): add OAuth redirect URI configuration for MCP servers (#7379)"
This reverts commit 40b275d7e6.
2026-01-16 22:17:33 -06:00
GitHub Action 5d613a038d chore: generate 2026-01-17 04:16:35 +00:00
benandGitHub db78a59f03 docs: Add OpenWork to ecosystem (#8741) 2026-01-16 22:15:59 -06:00
Aiden ClineandGitHub 7c3eeeb0fa fix: gpt id stuff fr fr this time :/ (#9006) 2026-01-16 22:09:36 -06:00
Github Action e8357a87b0 Update node_modules hashes 2026-01-17 02:51:01 +00:00
Jérôme BenoitandGitHub 06c543e938 fix(nix): resolve hash race condition in parallel matrix jobs (#8995) 2026-01-16 20:26:08 -06:00
David Hill 759ce8fb8e fix: prevent text clipping on search button descenders 2026-01-17 01:06:53 +00:00
David Hill 38847e13bb fix: truncate long search queries in empty state 2026-01-17 00:55:13 +00:00
David Hill e0c6459faa fix: remove smooth scroll behavior from list component 2026-01-17 00:55:13 +00:00
David Hill 80b278ddab fix: remove the secondary text from commands 2026-01-17 00:55:13 +00:00
David Hill ef7ef6538e fix: limit search modal max-height to 480px 2026-01-17 00:55:13 +00:00
David Hill d23c21023a fix: refine search modal styling and list component 2026-01-17 00:55:13 +00:00
David Hill dfa2a9f225 fix: reduce command item left padding in search modal 2026-01-17 00:55:13 +00:00
David Hill 6f78a71fa7 feat: add hideIcon and class options to List search, customize search modal input 2026-01-17 00:55:13 +00:00
David Hill f8f1f46a4f fix: adjust command item left padding in search modal 2026-01-17 00:55:13 +00:00
David Hill ab705dacfa fix: add left padding to command items in search modal 2026-01-17 00:55:13 +00:00
David Hill d1b93616f7 fix: increase keybind border-radius in search modal 2026-01-17 00:55:13 +00:00
David Hill 69215d456c fix: display arrow keys as symbols in keybind formatting 2026-01-17 00:55:13 +00:00
David Hill 54e52896a4 refactor: use Keybind component in search modal list 2026-01-17 00:55:13 +00:00
David Hill b18fb16e9c refactor: use Keybind component in titlebar search button 2026-01-17 00:55:13 +00:00
David Hill 1250486ddf feat: add Keybind component for displaying keyboard shortcuts 2026-01-17 00:55:13 +00:00
David Hill d645e8bbe1 fix: (desktop) command palette width 2026-01-17 00:55:13 +00:00
David Hill cad415872e fix: recent sessions gutter 2026-01-17 00:55:13 +00:00
Frank e8746ddb1d zen: fix opus unicode characters
closes #8967
2026-01-16 18:52:08 -05:00
GitHub Action 80020ade2e chore: generate 2026-01-16 23:23:57 +00:00
Amir HasanbasicandGitHub 08ef97b162 fix(opencode): add oauth polling safety margin in copilot device authentication (#8986) 2026-01-16 17:23:18 -06:00
Github Action 1aedb265dd Update node_modules hash (aarch64-darwin) 2026-01-16 23:16:53 +00:00
Github Action 5c13b209aa Update node_modules hash (x86_64-darwin) 2026-01-16 23:11:48 +00:00
Github Action 43a9c50389 Update node_modules hash (x86_64-linux) 2026-01-16 23:02:26 +00:00
Github Action 55224d64a2 Update flake.lock 2026-01-16 23:01:30 +00:00
Daniel PolitoandGitHub c325aa1142 fix(desktop): Stream bash output + strip-asni (#8961) 2026-01-16 17:00:56 -06:00
Caleb NortonandGitHub 6e020ef9ef chore: cleanup nix (#8964) 2026-01-16 16:59:34 -06:00
Caleb NortonandGitHub aca1eb6b5b fix(nix): add desktop application entry (#8972) 2026-01-16 16:59:07 -06:00
b3nwandGitHub 3d095e7fe7 fix: centralize OSC 52 clipboard support for SSH sessions (#8974) 2026-01-16 16:57:17 -06:00
GitHub Action 632f20558a chore: generate 2026-01-16 22:49:19 +00:00
Frank f96c4badd8 wip: black 2026-01-16 17:48:26 -05:00
Frank cbe1c81470 wip: black 2026-01-16 17:46:36 -05:00
Akshar PatelandGitHub c25155586c fix: open help dialog with tui/open-help route (#8596) 2026-01-16 16:42:27 -06:00
Seth CarltonandGitHub 08b94a6890 fix: keep primary model after subagent runs (#8951) 2026-01-16 16:19:17 -06:00
GitHub Action 8cddc9ea55 chore: generate 2026-01-16 22:14:23 +00:00
Aiden Cline 578239e0d0 chore: cleanup transform code a tad 2026-01-16 16:13:38 -06:00
Ariane Emoryandopencode 626fa1462b fix: make home/end keys work in menu list modal windows (resolves #7190) (#8347) 2026-01-16 21:57:59 +00:00
opencode 968239bb76 release: v1.1.25
Update Nix Hashes / update-flake (push) Has been cancelled
Update Nix Hashes / update-node-modules-hash (blacksmith-4vcpu-ubuntu-2404, x86_64-linux) (push) Has been cancelled
Update Nix Hashes / update-node-modules-hash (blacksmith-4vcpu-ubuntu-2404-arm, aarch64-linux) (push) Has been cancelled
Update Nix Hashes / update-node-modules-hash (macos-15-intel, x86_64-darwin) (push) Has been cancelled
Update Nix Hashes / update-node-modules-hash (macos-latest, aarch64-darwin) (push) Has been cancelled
2026-01-16 21:57:58 +00:00
Aiden Cline 8c24879246 test: fix 2026-01-16 15:52:51 -06:00
Aiden Cline 9127055ae7 tweak: wording 2026-01-16 15:50:24 -06:00
Aiden Cline f5a6a4af7f Revert "fix: ensure that tool attachments arent sent as user messages (#8944)"
This reverts commit 8fd1b92e6e.
2026-01-16 15:50:24 -06:00
Adam 6e00348bd7 fix(app): remember last opened project 2026-01-16 15:49:35 -06:00
Adam 95f7403daf fix(app): truncate workspace title 2026-01-16 15:49:35 -06:00
Aiden ClineandGitHub 14d1e20287 Revert "fix(app): support anthropic models on azure cognitive services" (#8966) 2026-01-16 15:25:23 -06:00
Unies Ananda RajaandGitHub b8e2895dfc fix(app): support anthropic models on azure cognitive services (#8335) 2026-01-16 15:24:06 -06:00
GitHub Action 6e028ec2dc chore: generate 2026-01-16 21:21:54 +00:00
8e0ddd1ac9 chore: cleanup server routes (#8965)
Co-authored-by: Leka74 <leke.dobruna@gmail.com>
Co-authored-by: Leka74 <791494+Leka74@users.noreply.github.com>
2026-01-16 15:21:13 -06:00
Adam da78b758d4 fix(app): handle new session correctly 2026-01-16 14:49:04 -06:00
Adam 360765c591 fix(app): center dialog on page instead of session 2026-01-16 14:33:32 -06:00
GitHub Action db0078bf17 chore: generate 2026-01-16 20:32:44 +00:00
kenryu42andGitHub 98578d3a7b fix(bun): reinstall plugins when cache module missing (#8815) 2026-01-16 14:32:05 -06:00
opencode bc3616d9c6 release: v1.1.24
Update Nix Hashes / update-flake (push) Has been cancelled
Update Nix Hashes / update-node-modules-hash (blacksmith-4vcpu-ubuntu-2404, x86_64-linux) (push) Has been cancelled
Update Nix Hashes / update-node-modules-hash (blacksmith-4vcpu-ubuntu-2404-arm, aarch64-linux) (push) Has been cancelled
Update Nix Hashes / update-node-modules-hash (macos-15-intel, x86_64-darwin) (push) Has been cancelled
Update Nix Hashes / update-node-modules-hash (macos-latest, aarch64-darwin) (push) Has been cancelled
2026-01-16 20:15:19 +00:00
Adam 71306cbd1f Revert "feat(desktop): Terminal Splits (#8767)"
This reverts commit 88fd6a294b.
2026-01-16 14:03:13 -06:00
Adam 0866034946 feat(app): edit project and session titles 2026-01-16 13:55:59 -06:00
Adam 2ccaa10e79 fix(app): open workspace if navigating to session in workspace 2026-01-16 13:24:47 -06:00
Adam e92d5b592c fix(app): can't expand workspaces 2026-01-16 13:24:47 -06:00
Adam 00ec29dae6 fix(app): scroll jumping when expanding workspaces 2026-01-16 13:24:47 -06:00
Github Action 438916de5f Update node_modules hash (x86_64-darwin) 2026-01-16 18:58:41 +00:00
Github Action 8d4a67324e Update node_modules hash (aarch64-linux) 2026-01-16 18:53:19 +00:00
Sebastian Herrlinger 0d683eaa8e upgrade opentui to v0.1.74, fix tmux kitty keyboard regression 2026-01-16 19:52:05 +01:00
Aiden ClineandGitHub 8fd1b92e6e fix: ensure that tool attachments arent sent as user messages (#8944) 2026-01-16 12:47:43 -06:00
Github Action 22e3240296 Update node_modules hash (x86_64-darwin) 2026-01-16 17:43:43 +00:00
Aiden Cline e1d0b2ba6e fix: use dynamic import for session event in config.ts to avoid circular dep 2026-01-16 11:39:22 -06:00
Bernat PericàsandGitHub ccc27e23df fix(docs): Broken URL (#8918) 2026-01-16 11:37:05 -06:00
Github Action ad4bdd9f0f Update node_modules hash (x86_64-linux) 2026-01-16 17:36:52 +00:00
Aiden Cline 5479928a9d Reapply "chore(sdk): update @hey-api/openapi-ts to 0.90.4" (#8927)
This reverts commit 91b8ba2186.
2026-01-16 11:34:59 -06:00
Aiden Cline 40836e9683 fix: fix the itemId stripping logic, this time it should fix that id issue w/ gpt models fr 2026-01-16 11:34:45 -06:00
Github Action 9a48f8e9e3 Update node_modules hash (aarch64-darwin) 2026-01-16 17:18:39 +00:00
Aiden ClineandGitHub 91b8ba2186 Revert "chore(sdk): update @hey-api/openapi-ts to 0.90.4" (#8927) 2026-01-16 11:11:33 -06:00
Cole LeavittandGitHub d075c097ac chore(sdk): update @hey-api/openapi-ts to 0.90.4 (#8921) 2026-01-16 11:11:07 -06:00
Daniel PolitoandGitHub 88fd6a294b feat(desktop): Terminal Splits (#8767) 2026-01-16 10:51:02 -06:00
Frank ea8ef37d50 wip: zen 2026-01-16 10:40:25 -05:00
Aiden Cline d510bd52a4 Revert "test: fix test now that image fix went in"
This reverts commit fffa718f5e.
2026-01-16 09:34:00 -06:00
Aiden Cline e0a854f035 Revert "fix: rm user message when dealing w/ image attachments, use proper tool attachment instead"
This reverts commit de2de099b4.
2026-01-16 09:33:54 -06:00
Aiden Cline bd914a8c06 Revert "stop select dialog event propagation"
This reverts commit 46be47d0be.
2026-01-16 09:30:06 -06:00
David Hill a49102db01 fix: truncate the workspace name on hover 2026-01-16 13:46:15 +00:00
David Hill 21012fab4b fix: load more label alignment 2026-01-16 13:38:52 +00:00
David Hill 9a71a73f50 fix: updating panel min size and button max-width 2026-01-16 13:37:14 +00:00
David Hill 2190e8c656 Revert "fix: expand workspaces by default when enabled"
This reverts commit 1fd496a5e2.
2026-01-16 13:18:56 +00:00
David Hill 1fd496a5e2 fix: expand workspaces by default when enabled 2026-01-16 13:17:22 +00:00
David Hill 74d584af34 fix: session icon and label alignment 2026-01-16 13:14:05 +00:00
David Hill 46f415ecb0 fix: desktop hamburger shift 2026-01-16 13:05:50 +00:00
David Hill d0399045da fix: make hamburger centred with project avatars 2026-01-16 13:00:44 +00:00
David Hill 4be0ba19ca fix: web mobile menu
there is a small shift/misalignment in the side panel on web, not investigating too much as this whole area is in development and may change to make the projects avatar list always in view.
2026-01-16 12:54:54 +00:00
Aaron IkerandGitHub d5a5e6e062 feat(console): /black shader improvements, performance, details (#8871) 2026-01-16 13:35:58 +01:00
David Hill e8dad85233 fix: responsive menu desktop 2026-01-16 12:29:28 +00:00
GitHub Action f197b8a0cd ignore: update download stats 2026-01-16 2026-01-16 12:05:18 +00:00
GitHub Action efaf854e09 chore: generate 2026-01-16 11:59:12 +00:00
David Hill 704276753b bug: moved createMemo down 2026-01-16 11:58:31 +00:00
David Hill 2c5437791b fix: updated project/sessions list width
this was originally 280px and included the 64px project avatar rail on the left, so the sessions list portion was actually 280 - 64 = 216px wide (as seen in line 1362: Math.max(layout.sidebar.width() - 64, 0)px).

i kept the logic the same in case it broke anything, i just updated the new width to account for new projects avatar panel
2026-01-16 11:55:15 +00:00
David Hill 94ab87ffad fix: view all sessions state styles 2026-01-16 11:55:15 +00:00
David Hill 416f419a81 fix: add default icon to sessions 2026-01-16 11:55:15 +00:00
David Hill 3ba03a97dc fix: search bar size and padding, and shortcut style 2026-01-16 11:55:15 +00:00
David Hill b1a22e08f5 fix: avatar radius and current project 2026-01-16 11:55:15 +00:00
Adam c551a4b6e3 fix(app): persist workspace order and collapsed state 2026-01-16 05:20:43 -06:00
Aiden Cline 524ea95a00 update gpt models prompt 2026-01-16 01:34:01 -06:00
Github Action 0e9664d300 Update node_modules hash (x86_64-darwin) 2026-01-16 06:17:52 +00:00
Aiden Cline fffa718f5e test: fix test now that image fix went in 2026-01-16 00:11:30 -06:00
Github Action cce4f64e0b Update node_modules hash (x86_64-linux) 2026-01-16 06:11:25 +00:00
Github Action ce6e9a822d Update node_modules hash (aarch64-linux) 2026-01-16 06:09:23 +00:00
Frank f66e6d7033 wip: zen 2026-01-16 01:07:03 -05:00
Aiden Cline de2de099b4 fix: rm user message when dealing w/ image attachments, use proper tool attachment instead 2026-01-16 00:05:10 -06:00
GitHub Action 0233dd1b39 chore: generate 2026-01-16 05:37:24 +00:00
Christopher TsoandGitHub 40b275d7e6 feat(mcp): add OAuth redirect URI configuration for MCP servers (#7379) 2026-01-15 23:36:48 -06:00
HyeonjongandGitHub e4a34beb8b chore: update GitHub stars and commits statistics (#8793) 2026-01-15 23:17:32 -06:00
Akshar PatelandGitHub ac54535486 feat: add version to session header and /status dialog (#8802) 2026-01-15 22:58:41 -06:00
Aiden Cline 1a43e5fe87 fix: adjust websearch tool to emphasize that it ISNT 2024, give more info as to current date 2026-01-15 20:50:22 -06:00
Sebastian Herrlinger 46be47d0be stop select dialog event propagation 2026-01-16 03:17:32 +01:00
Kit LangtonandGitHub 4af9defb89 fix(tui): correct theme count tip (#8779) 2026-01-15 19:39:35 -06:00
Kit LangtonandGitHub 12b621068a fix(tui): dim question option prefixes (#8776) 2026-01-15 19:38:55 -06:00
Kit LangtonandGitHub 07e7ebdb8e fix(tui): add tab navigation in questions (#8777) 2026-01-15 19:38:11 -06:00
Kit LangtonandGitHub 5092b5f07b docs: clarify question tool guidance (#8778) 2026-01-15 19:37:56 -06:00
Aiden Cline d8ef9f808d test: fix transform test 2026-01-15 19:27:04 -06:00
Aiden Cline d7192d6af9 tweak: set opencode as user agent for most interefence requests 2026-01-15 19:25:58 -06:00
GitHub Action 68e6c540bb chore: generate 2026-01-16 01:22:16 +00:00
Dan LapidandGitHub b572c68100 fix(mcp): show auth URL when browser cannot open in remote sessions (#7884) 2026-01-15 20:21:39 -05:00
Aiden Cline 25cb03dbe5 chore: cleanup 2026-01-15 19:19:28 -06:00
Aiden Cline d47510785a strip itemIds in more cases 2026-01-15 18:00:58 -06:00
Adam 657f3d5089 feat(app): unified search for commands and files 2026-01-15 17:59:26 -06:00
Adam 49939c4d8d feat(app): skeleton loader for sessions 2026-01-15 17:59:25 -06:00
Adam 529eb6e147 fix(app): persist workspace order and collapsed state 2026-01-15 17:59:25 -06:00
a7cae8f674 fix: nix desktop workflow (#8747)
Co-authored-by: Github Action <action@github.com>
2026-01-15 17:34:36 -06:00
Frank 12ae80856e wip: zen 2026-01-15 18:21:19 -05:00
Frank 7e619a9302 zen: black admin 2026-01-15 18:21:19 -05:00
GitHub Action 2abafbcd2f chore: generate 2026-01-15 23:19:16 +00:00
8b08d340ac fix: stop changing main model/agent from subtasks invocation (#7681)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-01-15 17:18:39 -06:00
Sercan SagmanandGitHub 81983d4a2e fix(agent): default agent selection in acp and headless mode (#8678)
Signed-off-by: assagman <ahmetsercansagman@gmail.com>
2026-01-15 17:09:19 -06:00
Aaron IkerandGitHub 7443b99295 feat(console): Fix /black page View Transition Safari issue (#8755) 2026-01-15 23:49:40 +01:00
David Hill 306fc05c00 fix: project avatar border radius 2026-01-15 22:11:07 +00:00
Aiden Cline 9d8d0e97ec Revert "fix:subagent reasoningEffort not being applied (#8646)"
This reverts commit f4086ac459.
2026-01-15 16:06:35 -06:00
f4086ac459 fix:subagent reasoningEffort not being applied (#8646)
Co-authored-by: Bot <bot@example.com>
2026-01-15 16:04:01 -06:00
GitHub Action b9b5d42bd8 chore: generate 2026-01-15 22:03:17 +00:00
outside.observerandGitHub 83ed1adcbd feat: add Carbonfox theme (#8723) 2026-01-15 16:02:29 -06:00
9b57db30d1 feat: add litellmProxy provider option for explicit LiteLLM compatibility (#8658)
Co-authored-by: Mark Henderson <Mark.Henderson99@hotmail.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-15 22:01:15 +00:00
opencode df8e6e6014 release: v1.1.23
Update Nix Hashes / update-flake (push) Has been cancelled
Update Nix Hashes / update-node-modules-hash (macos-15-intel, x86_64-darwin) (push) Has been cancelled
Update Nix Hashes / update-node-modules-hash (macos-latest, aarch64-darwin) (push) Has been cancelled
Update Nix Hashes / update-node-modules-hash (ubuntu-22.04-arm, aarch64-linux) (push) Has been cancelled
Update Nix Hashes / update-node-modules-hash (ubuntu-latest, x86_64-linux) (push) Has been cancelled
2026-01-15 22:01:14 +00:00
Adam 472a6cc83e fix(app): sidebar toggle on desktop 2026-01-15 15:45:12 -06:00
Adam 47d43aaf2d feat(app): persist workspace branch 2026-01-15 15:45:12 -06:00
Adam da3dea0429 fix(app): persist workspace order and collapsed state 2026-01-15 15:45:11 -06:00
9862303eed fix: update hix hashes for all systems (#8732)
Co-authored-by: Github Action <action@github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-15 15:45:03 -06:00
Qunhong ZengandGitHub b14622352e fix(session): ensure agent exists before processing title in session summary (#8662) 2026-01-15 15:24:13 -06:00
ea643f1e3f feat(console): Style improvements for /black, View Transition fixes (#8739)
Co-authored-by: Github Action <action@github.com>
2026-01-15 22:21:35 +01:00
Nhan NguyenandGitHub f5fd54598f docs: add /thinking command documentation (#8722) 2026-01-15 15:14:23 -06:00
David Hill 0f7b17b1b4 fix: thinking animation opacity and design 2026-01-15 20:42:14 +00:00
David Hill 4d3e983edb fix: session icon and name alignment 2026-01-15 20:42:14 +00:00
Github Action d3fc29bdec Update aarch64-darwin hash 2026-01-15 20:39:01 +00:00
fe58c649cb feat(console): Update /black plan selection, light rays effect. mobile styles (#8731)
Co-authored-by: Github Action <action@github.com>
2026-01-15 21:31:50 +01:00
Adam af2a09940c fix(core): more defensive project list 2026-01-15 13:58:39 -06:00
Adam 7e016fdda6 chore: cleanup 2026-01-15 13:34:53 -06:00
Adam beb97d21ff fix(app): show session busy even for active session 2026-01-15 13:33:49 -06:00
Adam b0345284f9 fix(core): filter dead worktrees 2026-01-15 13:33:49 -06:00
Adam d71153eae6 fix(core): loading models.dev in dev 2026-01-15 13:33:48 -06:00
dbpolitoandAdam e60ded01df chore(desktop): Stop Killing opencode-cli on dev 2026-01-15 13:17:57 -06:00
dbpolitoandAdam 4b2a14c154 chore(desktop): Question Tools Updates 2026-01-15 13:17:31 -06:00
David Hill b4717d8092 bun/package.json updates
this may not be required
2026-01-15 19:15:21 +00:00
David Hill dc8f8cc567 fix: current session background color 2026-01-15 19:15:21 +00:00
David Hill 99110d12c4 fix: remove the active state from load more button after press 2026-01-15 19:15:21 +00:00
David Hill 74b1349cf6 fix: new session tooltip position and add shortcut 2026-01-15 19:15:21 +00:00
David Hill 3b3505cfe8 fix: remove more options tooltip 2026-01-15 19:15:21 +00:00
David Hill 55bd6e487e fix: workspace name color 2026-01-15 19:15:21 +00:00
David Hill 1ee916a3c3 fix: hide view all sessions on active project 2026-01-15 19:15:21 +00:00
David Hill a5d47f076e fix: avatar button states 2026-01-15 19:15:21 +00:00
David Hill acd1eb574d fix: load more button font size 2026-01-15 19:15:21 +00:00
David Hill a71dcc189e fix: recent sessions title color 2026-01-15 19:15:21 +00:00
David Hill 3789a31423 fix: project dropdown labels and order 2026-01-15 19:15:21 +00:00
David Hill bb6e350d68 fix: move left panel toggle over
- not sure how this impacts on the titlebar when the traffic lights are there
2026-01-15 19:15:21 +00:00
David Hill f9a441d4f4 fix: avatar background 2026-01-15 19:15:21 +00:00
David Hill 1c05ebaea2 fix: show project options on hover of row 2026-01-15 19:15:21 +00:00
David Hill 520c47e81d fix: increase delay on session list tooltips 2026-01-15 19:15:21 +00:00
David Hill e5b08da0f1 fix: tooltip gutter spacing on session items and archive buttons 2026-01-15 19:15:21 +00:00
David Hill fe2cc0cff1 fix: archive icon replaces diff count on hover 2026-01-15 19:15:21 +00:00
David Hill fbc8f6eba9 fix: recent sessions hover gutter 2026-01-15 19:15:21 +00:00
David Hill 8cba7d7f53 fix: tooltips cleanup 2026-01-15 19:15:21 +00:00
David Hill 6450ba1b79 fix: search bar in header 2026-01-15 19:15:21 +00:00
Aiden ClineandGitHub dc1c25cff5 fix: ensure frontmatter can process same content as other agents (#8719) 2026-01-15 13:06:14 -06:00
Github Action 3f3550a16e Update aarch64-darwin hash 2026-01-15 18:29:11 +00:00
Github Action 161e3db795 Update Nix flake.lock and x86_64-linux hash 2026-01-15 18:17:44 +00:00
Aiden Cline 5a8a0f6a56 fix: downgrade bun to fix avx issue 2026-01-15 12:16:17 -06:00
37f30993fa fix: show toast error message on ConfigMarkdown parse error (#8049)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-01-15 11:53:06 -06:00
opencode-agent[bot]GitHubopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-node
ebc194ca9a Prettify retry duration display in TUI (#8608)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2026-01-15 11:50:18 -06:00
4edb4fa4fa fix: handle broken symlinks gracefully in grep tool (#8612)
Co-authored-by: Alex Johnson <nvidiattxpsli@gmail.com>
2026-01-15 11:40:37 -06:00
Aryan "LAG" GuptaandGitHub 63176bb049 docs: fix typos in documentation (#8703) 2026-01-15 11:06:16 -06:00
GitHub Action 216a2d87cf chore: generate 2026-01-15 16:32:09 +00:00
DaxandGitHub dd1f981d23 fix: honor per-server MCP timeouts (#8706) 2026-01-15 11:31:31 -05:00
Sebastian Herrlinger bfc9b24b48 use native text truncation for sidebar diff paths 2026-01-15 17:23:48 +01:00
Github Action 2691e1e666 Update aarch64-darwin hash 2026-01-15 15:47:53 +00:00
Github Action 3f16e0d89f Update Nix flake.lock and x86_64-linux hash 2026-01-15 15:41:26 +00:00
Sebastian Herrlinger 994c55f709 upgrade opentui to v0.1.73, fixing CJK word wrapping and thai text rendering (non-tmux) 2026-01-15 16:39:48 +01:00
Adam 2f32f2ceb5 chore: cleanup 2026-01-15 07:29:13 -06:00
Adam 076dfb3752 chore: cleanup 2026-01-15 07:29:13 -06:00
Github ActionandAdam 60aa0cb96e Update Nix flake.lock and x86_64-linux hash 2026-01-15 07:29:13 -06:00
Adam e5973e2860 chore: cleanup 2026-01-15 07:29:13 -06:00
Adam dbd1987f0a chore: cleanup 2026-01-15 07:29:13 -06:00
Adam f270ea65c5 fix(app): new layout issues 2026-01-15 07:29:13 -06:00
Adam 1698448016 fix(app): new layout sessions stale 2026-01-15 07:29:13 -06:00
Adam 564d3edfac fix(app): new layout issues 2026-01-15 07:29:13 -06:00
Adam 679270d9e0 feat(app): new layout 2026-01-15 07:29:13 -06:00
adamelmoreandAdam 9f66a45970 feat(app): new layout 2026-01-15 07:29:13 -06:00
Turcu LaurentiuandGitHub 779610d668 fix(desktop): open external links in system browser instead of webview (#7360) 2026-01-15 02:12:27 -06:00
Ryan VogelandGitHub 1fb611ef0a fix: enable sticky header on changelog and download pages (#8556) 2026-01-15 02:09:23 -06:00
GitHub Action 972f5ecc7d chore: generate 2026-01-15 07:35:52 +00:00
Brandon SmithandGitHub 8d720f9463 fix(opencode): add input limit for compaction (#8465) 2026-01-15 01:35:16 -06:00
Aiden ClineandGitHub 92931437c4 fix: codex id issue (#8605) 2026-01-15 01:31:50 -06:00
Ariane EmoryandGitHub 08ca1237cc fix(tui): Center the initially selected session in the session_list (resolves #8558) (#8560) 2026-01-15 01:04:20 -06:00
GitHub Action 6473e15793 chore: generate 2026-01-15 06:45:39 +00:00
Aiden ClineandGitHub 16cac69a72 Revert "feat: allow provider-level store option (#8000)" (#8613) 2026-01-15 00:45:03 -06:00
GitHub Action b2da41cfad chore: generate 2026-01-15 06:36:30 +00:00
Call WhiteandGitHub fcf2da9571 feat: allow provider-level store option (#8000) 2026-01-15 00:35:53 -06:00
GitHub Action 253b7ea784 chore: generate 2026-01-15 06:04:47 +00:00
Kit LangtonandGitHub 3a9fd1bb36 fix: restore brand integrity of TUI wordmark (#8584) 2026-01-15 00:04:11 -06:00
GitHub Action f84ac697dc chore: generate 2026-01-15 05:40:29 +00:00
CasandGitHub 76a79284d2 feat(tui): make dialog keybinds configurable (#6143) (#6144) 2026-01-14 23:39:52 -06:00
opencode 99a1e73fa1 release: v1.1.21
Update Nix Hashes / update-linux (push) Has been cancelled
Update Nix Hashes / update-macos (push) Has been cancelled
2026-01-15 02:34:07 +00:00
GitHub Action ba4c86448b chore: generate 2026-01-15 02:21:43 +00:00
Aiden Cline b36837ae93 tweak: add error message so people know to reauthenticate with copilot 2026-01-14 20:21:03 -06:00
Frank e03932e586 zen: black usage 2026-01-14 21:20:26 -05:00
Idris GadiandGitHub 6b019a125a docs: fix permission system documentation in agents section (#7652) 2026-01-14 20:17:04 -06:00
Aiden Cline 6a2fed7042 chore: bump cache version 2026-01-14 17:44:16 -06:00
Aiden Cline 74baae597a chore: bump plugin version 2026-01-14 17:43:12 -06:00
Aiden ClineandGitHub d78d31430d feat: official copilot plugin (#8393) 2026-01-14 17:42:51 -06:00
Aiden Cline 096e14d787 tweak: adjust lsp wording a bit more to encourage fixing 2026-01-14 15:44:44 -06:00
Frank bbb3120b59 zen: gpt-5.2-codex 2026-01-14 16:03:04 -05:00
Frank 9e4438f5bf wip: black 2026-01-14 16:03:04 -05:00
opencode-agent[bot]GitHubopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-node
87438fb38e ci: dedup stuff in changelog (#8522)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2026-01-14 14:34:57 -06:00
Dax Raad 6b6d6e9e07 update security 2026-01-14 14:52:39 -05:00
Dax Raad 207a59aad4 docs: add comprehensive security threat model and architecture documentation 2026-01-14 14:49:27 -05:00
Aiden Cline b3ae1931fc fix: plan path permissions 2026-01-14 13:28:56 -06:00
Samiul IslamandGitHub 4d08123ca0 feat(install): respect ZDOTDIR for zsh config detection (#8511)
Signed-off-by: sami <samiulsami7786@gmail.com>
2026-01-14 12:30:54 -06:00
Aiden Cline 7d3c7a9f65 add check incase provider doesnt exist in models list 2026-01-14 12:16:12 -06:00
50dfa9caf3 chore: upgrade bun from 1.3.5 -> 1.3.6, also update types/bun from 1.3.4 -> 1.3.6 and fix type errs (#8499)
Co-authored-by: Github Action <action@github.com>
2026-01-14 11:53:12 -06:00
Aiden Cline 1f86aa8bb9 fix: adjust gitlab logic in provider.ts 2026-01-14 11:42:45 -06:00
GitHub Action d83756eaaf chore: generate 2026-01-14 17:41:16 +00:00
Aiden Cline c0b43d3cb4 ignore: add slash command to checks ai sdk deps 2026-01-14 11:40:36 -06:00
Ryan VogelandGitHub 3206ed47e0 feat(console): add OG image and SEO meta tags for /black page (#8506) 2026-01-14 11:20:50 -06:00
Ryan VogelandGitHub 346c5e0da6 fix(console): make logo on /black link back to homepage (#8498) 2026-01-14 11:49:44 -05:00
Dax Raad 5b431c36f8 ignore: remove nowrap constraint to allow text wrapping in console UI 2026-01-14 11:39:52 -05:00
Dax Raad 44d24d42b8 ignore: fix auth redirect to preserve selected plan during subscription flow 2026-01-14 11:25:50 -05:00
Jacopo BinosiandGitHub 3a9e6b558c feat(opencode): add AWS Web Identity Token File support for Bedrock (#8461) 2026-01-14 10:20:47 -06:00
Dax Raad 9d92ae7530 copy changes 2026-01-14 11:17:11 -05:00
Ryan VogelandGitHub e6e7eaf6e0 docs: add Web usage page (#8482) 2026-01-14 10:03:48 -06:00
Aaron IkerandGitHub 8ce5c2b900 feat(console/app): Style changes, view transitions, small improvements (#8481) 2026-01-14 10:02:18 -06:00
Ryan VogelandGitHub 78be8fecdc feat(console): add /changelog page (#8476) 2026-01-14 10:01:30 -06:00
Github Action b5e9f96660 Update aarch64-darwin hash 2026-01-14 15:39:39 +00:00
Mani SundararajanandGitHub ad17e8d1f0 feat: add choco and scoop to opencode upgrade methods (#8439) 2026-01-14 09:39:01 -06:00
Ryan VogelandGitHub b75d4d1c5e docs: update screenshot images (#8479) 2026-01-14 09:36:10 -06:00
Github Action cc67bc005d Update Nix flake.lock and x86_64-linux hash 2026-01-14 15:35:07 +00:00
Vladimir GlafirovandGitHub 0ce849c3d5 chore: update gitlab-ai-provider to 3.1.1 and remove unused parameter (#8424) 2026-01-14 09:34:02 -06:00
6e13e2f74e fix(session): remove typo'd duplicate path import (#8408) (#8412)
Co-authored-by: opencode <opencode@sst.dev>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
2026-01-14 09:04:03 -06:00
GitHub Action 9fd61aef6e chore: generate 2026-01-14 15:01:42 +00:00
Ryan VogelandGitHub bb3926bf45 fix(homepage): Update FAQ to include desktop and web links (#8453) 2026-01-14 09:01:02 -06:00
Kit LangtonandGitHub b2b123a392 feat(tui): improve question prompt UX (#8339) 2026-01-14 10:00:29 -05:00
opencode 09ff3b9bb9 release: v1.1.20
Update Nix Hashes / update-linux (push) Has been cancelled
Update Nix Hashes / update-macos (push) Has been cancelled
2026-01-14 13:41:08 +00:00
GitHub Action 2256362ba2 chore: generate 2026-01-14 13:36:44 +00:00
Shane BishopandGitHub 077ca4454f fix(desktop): "load more" button behavior in desktop sidebar (#8430) 2026-01-14 07:36:08 -06:00
Andrew JazbecandGitHub 05cbb11709 fix(ui): layout-bottom icons (#8330) 2026-01-14 07:25:09 -06:00
Dax Raad fcc561ebb7 fix plan mode when not in git worktree 2026-01-14 08:21:26 -05:00
FilipandGitHub ee6ca104e5 fix(app): file listing (#8309) 2026-01-14 07:09:36 -06:00
GitHub Action 4347a77d89 ignore: update download stats 2026-01-14 2026-01-14 12:05:15 +00:00
GitHub Action 76b10d85ee chore: generate 2026-01-14 07:37:11 +00:00
Goni ZahavyandGitHub 45a770cdb1 fix(opencode): fix docker image after sst rename in tips (#8376) 2026-01-14 01:36:36 -06:00
Akshar PatelandGitHub a57c8669b6 feat: show connected providers in /connect dialog (#8351) 2026-01-14 01:35:59 -06:00
f9fcdead55 fix(session): skip duplicate system prompt for Codex OAuth sessions (#8357)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 23:44:39 -06:00
Github Action ff669d4414 Update aarch64-darwin hash 2026-01-14 00:59:52 +00:00
Github Action 9b2d595cfc Update Nix flake.lock and x86_64-linux hash 2026-01-14 00:54:57 +00:00
GitHub Action 3839d70a94 chore: generate 2026-01-14 00:54:06 +00:00
Frank 6fe265e7d8 Merge branch 'zen-black' into dev 2026-01-13 19:51:48 -05:00
GitHub ActionandFrank 2aed4d263b chore: generate 2026-01-13 19:51:02 -05:00
Felix SanchezandFrank e2ac588c84 fix: deduplicate file refs in sent prompts (#8303) 2026-01-13 19:51:02 -05:00
Daniel SauerandFrank 8917dfdf5e fix(tui): track all timeouts in Footer to prevent memory leak (#8255) 2026-01-13 19:51:02 -05:00
Daniel M BrasilandFrank 86900d71f5 fix: add missing metadata() and ask() defintions to ToolContext type (#8269) 2026-01-13 19:51:02 -05:00
⌞L⌝andFrank adcc661798 docs: add 302ai provider (#8142) 2026-01-13 19:51:01 -05:00
Eduard VoiculescuandFrank f4a28b2659 docs: Update plan mode restrictions (#8290) 2026-01-13 19:51:01 -05:00
GitHub ActionandFrank a160a35d0c chore: generate 2026-01-13 19:51:01 -05:00
LeonidasandFrank 90eaf9b3fc fix(TUI): make tui work when OPENCODE_SERVER_PASSWORD is set (#8179) 2026-01-13 19:51:01 -05:00
opencodeandFrank 16d516dbdb release: v1.1.19 2026-01-13 19:51:01 -05:00
Dax RaadandFrank 0026bc5815 do not allow agent to ask custom-less questions 2026-01-13 19:51:01 -05:00
Aiden ClineandFrank bcdaf7e779 tweak: prompt for explore agent better 2026-01-13 19:51:01 -05:00
GitHub ActionandFrank 874e22a045 chore: generate 2026-01-13 19:51:01 -05:00
Vladimir GlafirovandFrank 905226c01e fix: Add Plugin Mocks to Provider Tests (#8276) 2026-01-13 19:51:01 -05:00
AlanandFrank 73adf7e86f fix: update User-Agent string to latest Chrome version in webfetch (#8284) 2026-01-13 19:51:01 -05:00
Dax RaadandFrank 4c37e17ac2 remove plan 2026-01-13 19:51:01 -05:00
Dax RaadandFrank cd6e07355b test: fix plan agent test path from .opencode/plan/* to .opencode/plans/* 2026-01-13 19:51:01 -05:00
GitHub ActionandFrank 29703aee9a chore: generate 2026-01-13 19:51:01 -05:00
DaxandFrank 3997d3f2d7 feat: add plan mode with enter/exit tools (#8281) 2026-01-13 19:51:01 -05:00
Joe HarrisonandFrank 1fccb3bda4 fix(prompt-input): handle Shift+Enter before IME check to prevent stuck state (#8275) 2026-01-13 19:51:01 -05:00
Aiden ClineandFrank 16b2bfa8ef add family to gpt 5.2 codex in codex plugin 2026-01-13 19:51:01 -05:00
Aiden ClineandFrank 4eb6b57503 tweak: external dir permission rendering in tui 2026-01-13 19:51:01 -05:00
Aiden ClineandFrank 7599396162 tweak: ensure external dir and bash tool invocations render workdir details 2026-01-13 19:51:01 -05:00
Github ActionandFrank d99d1315ee Update aarch64-darwin hash 2026-01-13 19:51:01 -05:00
Github ActionandFrank d831432f93 Update Nix flake.lock and x86_64-linux hash 2026-01-13 19:51:01 -05:00
Dillon MulroyandFrank 0ddf8e6c6e fix(cli): mcp auth duplicate radio button icon (#8273) 2026-01-13 19:50:49 -05:00
a520c4ff98 feat: Add GitLab Duo Agentic Chat Provider Support (#7333)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-01-13 19:50:49 -05:00
Zeke SikelianosandFrank a184714f67 docs: document ~/.claude/CLAUDE.md compatibility behavior (#8268) 2026-01-13 19:50:49 -05:00
Daniel SauerandFrank 9b76337236 fix(state): delete key from recordsByKey on instance disposal (#8252) 2026-01-13 19:50:49 -05:00
Daniel SauerandFrank 4dc72669e5 fix(mcp): close existing client before reassignment to prevent leaks (#8253) 2026-01-13 19:50:49 -05:00
Daniel PolitoandFrank dfa59dd21d feat(desktop): Ask Question Tool Support (#8232) 2026-01-13 19:50:49 -05:00
GitHub ActionandFrank f642a6c5b9 chore: generate 2026-01-13 19:50:49 -05:00
cmdr-charaandFrank e37104cb10 feat: add Undertale and Deltarune built-in themes (#8240) 2026-01-13 19:50:49 -05:00
Daniel PolitoandFrank dc654c93d2 fix(desktop): Revert provider icon on select model dialog (#8245) 2026-01-13 19:50:49 -05:00
opencodeandFrank c67b0a9ba4 release: v1.1.18 2026-01-13 19:50:49 -05:00
LeonidasandFrank 5b699a0d9b fix(github): add persist-credentials: false to workflow templates (#8202) 2026-01-13 19:50:49 -05:00
Brendan AllanandFrank bc557e828d console: reduce desktop download cache ttl to 5 minutes 2026-01-13 19:50:49 -05:00
GitHub ActionandFrank fcaa041ef9 chore: generate 2026-01-13 19:50:49 -05:00
Daniel PolitoandFrank 3c9d80d75f feat(desktop): Adding Provider Icons (#8215) 2026-01-13 19:50:49 -05:00
usvimalandFrank a761f66a16 fix(desktop): correct health check endpoint URL to /global/health (#8231) 2026-01-13 19:50:49 -05:00
GitHub ActionandFrank 15e80fca69 chore: generate 2026-01-13 19:50:49 -05:00
Dax RaadandFrank 43680534df add fullscreen view to permission prompt 2026-01-13 19:50:48 -05:00
opencodeandFrank aa522aad62 release: v1.1.17 2026-01-13 19:50:48 -05:00
Frank 82319bbd83 wip: black 2026-01-13 19:46:14 -05:00
Frank 45fa4eda15 wip: black 2026-01-13 19:15:14 -05:00
GitHub Action f242541ef3 chore: generate 2026-01-14 00:04:24 +00:00
Felix SanchezandGitHub 562f067131 fix: deduplicate file refs in sent prompts (#8303) 2026-01-13 18:03:45 -06:00
Daniel SauerandGitHub 1ff46c75fa fix(tui): track all timeouts in Footer to prevent memory leak (#8255) 2026-01-13 18:03:34 -06:00
Daniel M BrasilandGitHub 73d5cacc06 fix: add missing metadata() and ask() defintions to ToolContext type (#8269) 2026-01-13 17:31:18 -06:00
⌞L⌝andGitHub b8828f2609 docs: add 302ai provider (#8142) 2026-01-13 17:00:23 -06:00
Eduard VoiculescuandGitHub 2f7b2cf603 docs: Update plan mode restrictions (#8290) 2026-01-13 16:52:02 -06:00
Frank eaf18d9915 wip: black 2026-01-13 17:51:21 -05:00
GitHub Action 7aa7dd3690 chore: generate 2026-01-13 22:50:56 +00:00
LeonidasandGitHub bee4b6801e fix(TUI): make tui work when OPENCODE_SERVER_PASSWORD is set (#8179) 2026-01-13 16:50:19 -06:00
opencode 3565d8e44d release: v1.1.19
Update Nix Hashes / update-linux (push) Has been cancelled
Update Nix Hashes / update-macos (push) Has been cancelled
2026-01-13 22:27:16 +00:00
Dax Raad 0187b6bb72 do not allow agent to ask custom-less questions 2026-01-13 17:14:12 -05:00
Aiden Cline 0eb898abcf tweak: prompt for explore agent better 2026-01-13 15:35:52 -06:00
GitHub Action 5a309c2dbf chore: generate 2026-01-13 21:24:19 +00:00
Vladimir GlafirovandGitHub 452f11ff77 fix: Add Plugin Mocks to Provider Tests (#8276) 2026-01-13 15:23:41 -06:00
AlanandGitHub 774c24e76e fix: update User-Agent string to latest Chrome version in webfetch (#8284) 2026-01-13 15:23:08 -06:00
Dax Raad ec4a44087b remove plan 2026-01-13 16:20:05 -05:00
Dax Raad 501347cda5 test: fix plan agent test path from .opencode/plan/* to .opencode/plans/* 2026-01-13 16:19:14 -05:00
GitHub Action 3f3816c0f2 chore: generate 2026-01-13 20:56:28 +00:00
DaxandGitHub 0a3c72d678 feat: add plan mode with enter/exit tools (#8281) 2026-01-13 15:55:48 -05:00
Joe HarrisonandGitHub 66b7a4991e fix(prompt-input): handle Shift+Enter before IME check to prevent stuck state (#8275) 2026-01-13 14:06:38 -06:00
Aiden Cline 1550ae47c0 add family to gpt 5.2 codex in codex plugin 2026-01-13 13:57:34 -06:00
Aiden Cline 33ba064c40 tweak: external dir permission rendering in tui 2026-01-13 13:52:16 -06:00
Aiden Cline 96ae5925c3 tweak: ensure external dir and bash tool invocations render workdir details 2026-01-13 13:52:15 -06:00
Github Action 3a750b0809 Update aarch64-darwin hash 2026-01-13 19:29:19 +00:00
Github Action 1258f7aeea Update Nix flake.lock and x86_64-linux hash 2026-01-13 19:22:49 +00:00
Dillon MulroyandGitHub 797a56873d fix(cli): mcp auth duplicate radio button icon (#8273) 2026-01-13 13:22:26 -06:00
05867f9318 feat: Add GitLab Duo Agentic Chat Provider Support (#7333)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-01-13 13:21:39 -06:00
Zeke SikelianosandGitHub 5947fe72e4 docs: document ~/.claude/CLAUDE.md compatibility behavior (#8268) 2026-01-13 12:58:09 -06:00
Github Action f3d4dd5099 Update aarch64-darwin hash 2026-01-13 18:43:58 +00:00
Daniel SauerandGitHub b68a4a8838 fix(state): delete key from recordsByKey on instance disposal (#8252) 2026-01-13 12:43:16 -06:00
Github Action b7a1d8f2f5 Update Nix flake.lock and x86_64-linux hash 2026-01-13 18:39:01 +00:00
Daniel SauerandGitHub 80e1173ef7 fix(mcp): close existing client before reassignment to prevent leaks (#8253) 2026-01-13 12:38:34 -06:00
Frank 8ae10f1c94 sync 2026-01-13 13:37:48 -05:00
Frank f24251f89e sync 2026-01-13 13:36:37 -05:00
Daniel PolitoandGitHub 3600bd27f4 feat(desktop): Ask Question Tool Support (#8232) 2026-01-13 12:28:08 -06:00
GitHub Action 92089bb295 chore: generate 2026-01-13 18:27:28 +00:00
cmdr-charaandGitHub a70932f742 feat: add Undertale and Deltarune built-in themes (#8240) 2026-01-13 12:26:45 -06:00
Daniel PolitoandGitHub 217cf24c3c fix(desktop): Revert provider icon on select model dialog (#8245) 2026-01-13 12:26:21 -06:00
opencode c87939ad12 release: v1.1.18
Update Nix Hashes / update-linux (push) Has been cancelled
Update Nix Hashes / update-macos (push) Has been cancelled
2026-01-13 16:17:17 +00:00
LeonidasandGitHub a092f567b7 fix(github): add persist-credentials: false to workflow templates (#8202)
publish-github-action / publish (push) Has been cancelled
Update Nix Hashes / update-linux (push) Has been cancelled
Update Nix Hashes / update-macos (push) Has been cancelled
2026-01-13 09:53:30 -06:00
Brendan Allan 883a6577d5 console: reduce desktop download cache ttl to 5 minutes 2026-01-13 23:51:16 +08:00
GitHub Action 20b52cad2a chore: generate 2026-01-13 15:42:58 +00:00
Daniel PolitoandGitHub 528291532b feat(desktop): Adding Provider Icons (#8215) 2026-01-13 09:41:35 -06:00
usvimalandGitHub 2b77a84c4f fix(desktop): correct health check endpoint URL to /global/health (#8231) 2026-01-13 09:39:43 -06:00
GitHub Action 7d0b52dc29 chore: generate 2026-01-13 14:58:51 +00:00
Dax Raad c86c2acf4c add fullscreen view to permission prompt 2026-01-13 09:58:08 -05:00
opencode 29bf731d47 release: v1.1.17
Update Nix Hashes / update-linux (push) Has been cancelled
Update Nix Hashes / update-macos (push) Has been cancelled
2026-01-13 14:41:54 +00:00
Brendan Allan 3c5a256f0f desktop: macos killall opencode-cli on launch 2026-01-13 22:38:24 +08:00
GitHub Action 067338bc25 chore: generate 2026-01-13 14:17:35 +00:00
736cd10847 fix(ui): track memo-based items in useFilteredList without affecting async function based lists (#8216)
Co-authored-by: neriousy <neriousy@users.noreply.github.com>
2026-01-13 08:16:57 -06:00
GitHub Action a03daa4252 chore: generate 2026-01-13 13:13:45 +00:00
Brendan Allan b01eec38d1 fix(desktop): set serverPassword 2026-01-13 21:13:06 +08:00
Brendan AllanandGitHub 35cb06e0e4 fix(app): provide pty socket auth if available from desktop (#8210) 2026-01-13 07:08:19 -06:00
Adam f3b7d2f786 fix(app): file search 2026-01-13 06:55:21 -06:00
GitHub Action 1facf7d8e4 ignore: update download stats 2026-01-13 2026-01-13 12:05:22 +00:00
Brendan AllanandGitHub ddd9c71cca feat(desktop): Tie desktop & CLI to the same Windows JobObject (#8153) 2026-01-13 15:32:54 +08:00
GitHub Action 520a814fc2 chore: generate 2026-01-13 07:08:25 +00:00
2072c8681a fix: remove the symlinkBinary function call that replaces the wrapper script (#8133)
Co-authored-by: Chuck Chen <459052+chuckchen@users.noreply.github.com>
2026-01-13 01:07:35 -06:00
21990621e2 fix(tui): prevent question tool keybindings when dialog is open (#8147)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 01:04:49 -06:00
GitHub Action 68a0947292 chore: generate 2026-01-13 07:01:50 +00:00
Aiden Cline f1f44644e2 fix: brew autoupgrade 2026-01-13 01:01:06 -06:00
opencode afb8a0d28a release: v1.1.16
Update Nix Hashes / update-linux (push) Has been cancelled
Update Nix Hashes / update-macos (push) Has been cancelled
2026-01-13 06:01:17 +00:00
Brendan AllanandGitHub f05f175842 feat(desktop): spawn local server with password (#8139) 2026-01-13 13:58:00 +08:00
ShoeBoomandGitHub c0b214232d fix(config): handle write errors when updating schema in opencode config (#8125) 2026-01-12 23:11:26 -06:00
Eric GuoandGitHub f4f8f2d151 feat(cli): Support debug tool calling directly in CLI. (#6564) 2026-01-12 23:10:56 -06:00
Aiden Cline b4ad5c138e tweak: for zai ensure clear_thinking is false 2026-01-12 23:04:50 -06:00
LeonidasandGitHub 789e111a0f fix(TUI): dont submit prompt when switching sessions (#8016) 2026-01-12 22:43:44 -05:00
Dax Raad eaa76dad0c get rid of extra file 2026-01-12 22:33:57 -05:00
Aiden Cline 66f9bdab32 core: tweak edit and write tool outputs to prevent agent from thinking edit didn't apply 2026-01-12 20:40:10 -06:00
efaa9166fb fix: prevent [object Object] error display in console output (#8116)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 20:40:03 -06:00
Patrick SchielandGitHub eb2044989e fix: add missing args to windows tauri cli spawn (#8084) 2026-01-13 10:38:01 +08:00
M. Adel AlhashemiandGitHub 5d37e58d34 fix(task): respect agent task permission for nested sub-agents (#8111) 2026-01-12 20:37:42 -06:00
Rohan MukherjeeandGitHub 20088a87b0 fix: max completion tokens error for cloudflare (#7970) 2026-01-12 20:08:27 -06:00
AdamandGitHub 05019dae76 fix(bedrock): use reasoningConfig for Anthropic models on Bedrock (#8092) 2026-01-12 18:42:19 -06:00
Darsh PatelandGitHub bf37a88f7f fix: await auth.set to prevent race condition when setting API key (#8075) 2026-01-12 18:20:50 -06:00
Jérôme BenoitandGitHub d839f70834 fix(nix): initialize hashes.json with per-system format (#8095) 2026-01-12 18:18:48 -06:00
Paolo RicciutiandGitHub 498a4ab408 fix: actually modify opencode config with mcp add (#7339) 2026-01-12 18:02:59 -06:00
GitHub Action 835e48cd28 chore: generate 2026-01-12 23:30:47 +00:00
OpeOginniandGitHub 1a5a63843e feat(terminal): implement better dynamic terminal numbering (#8081) 2026-01-12 17:30:09 -06:00
Jason KölkerandGitHub d954e1e3b6 fix(session): store api url in metadata (#8090) 2026-01-12 17:22:42 -06:00
opencode-agent[bot]GitHubopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-node
8f22a6b69d Add nova-2 to region prefix models (#8085)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2026-01-12 16:55:12 -06:00
Aiden Cline fd37d5b54e tweak: truncation dir perm 2026-01-12 16:46:47 -06:00
Frank e146083b73 wip: black 2026-01-12 17:10:50 -05:00
Adam d7a1c268d9 fix(app): sanitize markdown -> html 2026-01-12 15:50:24 -06:00
Aiden Cline 5c4345da4f test: fix read test 2026-01-12 15:49:31 -06:00
Aiden Cline 0be37cc2c6 tweak: make the .env reads ask user for permission instead of blocking 2026-01-12 15:32:43 -06:00
GitHub Action 64f0205f97 chore: generate 2026-01-12 21:27:36 +00:00
OpeOginniandGitHub b3a1360ad9 feat(dialog-select-server): add icon button for server removal (#8053) 2026-01-12 15:26:58 -06:00
GitHub Action 08d4d6d4af chore: generate 2026-01-12 21:22:21 +00:00
9c69c1de9f fix(formatter): use biome check instead of format to include import sorting (#8057)
Co-authored-by: kynnyhsap <kynnyhsap@users.noreply.github.com>
2026-01-12 15:21:38 -06:00
Aiden ClineandGitHub 735f3d17bc fix: ensure plurals are properly handled (#8070) 2026-01-12 15:21:01 -06:00
opencode db7243c364 release: v1.1.15
Update Nix Hashes / update-linux (push) Has been cancelled
Update Nix Hashes / update-macos (push) Has been cancelled
2026-01-12 21:03:52 +00:00
Dax Raad f0912ee838 core: rename OPENCODE_PASSWORD to OPENCODE_SERVER_PASSWORD for clearer authentication configuration 2026-01-12 15:59:17 -05:00
Ryan VogelandGitHub 983f8ffeca fix(tui): prevent layout jump when toggling tips visibility (#8068) 2026-01-12 14:57:49 -06:00
KennyandGitHub c474380684 docs: fix permission rule ordering in examples (#7010) 2026-01-12 14:45:17 -06:00
Dax Raad 7ca767de55 core: fix HTTP exception handling order to prevent NamedError from masking HTTP exceptions
docs: add OPENCODE_PASSWORD environment variable documentation for basic auth
2026-01-12 15:43:48 -05:00
Dax Raad 1954c1255e core: add password authentication and improve server security
- Add OPENCODE_PASSWORD flag for basic auth protection
- Show security warnings when password is not set
- Remove deprecated spawn command
- Improve error handling with HTTPException responses
2026-01-12 15:23:19 -05:00
Daniel PolitoandGitHub b4f33485a7 fix(desktop): Improve User Message Badges for Big Titles and Small Screens (#8037) 2026-01-12 14:22:03 -06:00
Dax Raad f2504d8eb2 security.md 2026-01-12 15:05:37 -05:00
Aiden Cline e47f383137 core: improve error handling for built-in plugin installation failures 2026-01-12 13:47:25 -06:00
Aiden Cline cd56845dce tui: add hint for variants toggle 2026-01-12 13:35:07 -06:00
Aiden Cline 1c24dd02a6 ci: adjust triage prompt 2026-01-12 13:28:23 -06:00
Frank b7b09fdfc2 admin unshare 2026-01-12 14:20:33 -05:00
Aiden Cline 62702fbd11 fix: permissions wildcarding so that for ex: 'ls *' includes ls * AND 'ls' to prevent having to double state commands or use 'ls*' 2026-01-12 13:02:29 -06:00
22c68a6992 fix(tui): reopen autocomplete after backspace deletes space (#6031)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-12 12:52:36 -06:00
Jérôme BenoitandGitHub ca1b597b01 fix(nix): filter optional dependencies by target platform (#8033) 2026-01-12 12:49:06 -06:00
Aiden Cline d527ceeb2b test: fix 2026-01-12 12:33:39 -06:00
Shubh PorwalandGitHub 2e9c22d911 docs: fix typo (#8041) 2026-01-12 12:30:30 -06:00
Github Action 762c58b756 Update Nix flake.lock and hashes 2026-01-12 18:26:56 +00:00
Aiden Cline 61d0b3e4dc chore: bump ai sdk packages 2026-01-12 12:25:19 -06:00
Dax Raad 71a7ad1a4e core: fix model selection in title generation to use user's model instead of assistant's 2026-01-12 12:45:24 -05:00
Dax Raad 20399bbdfe tui: rename kvStore to store for consistency 2026-01-12 09:53:08 -05:00
Dax Raad 547a975707 tui: redesign tips display on home screen 2026-01-12 09:42:28 -05:00
c009cab15b Redesign tips display on home screen (#6126)
Co-authored-by: Dax <mail@thdxr.com>
2026-01-12 09:31:36 -05:00
Dax Raad e6bc3b253b tui: remove update complete toast notification 2026-01-12 09:30:31 -05:00
Brendan Allan f1a13f25a4 ci: don't continue-on-error in tauri action 2026-01-12 21:27:52 +08:00
GitHub Action 4695e685cf ignore: update download stats 2026-01-12 2026-01-12 12:05:37 +00:00
Brendan Allan c6092e4ad9 disable appimage 2026-01-12 19:28:55 +08:00
GitHub Action e6045ca925 chore: generate 2026-01-12 09:58:50 +00:00
Brendan AllanandGitHub ebbb4dd88a fix(desktop): improve server detection & connection logic (#7962) 2026-01-12 17:58:13 +08:00
GitHub Action 087473be6e chore: generate 2026-01-12 05:22:12 +00:00
Brendan Allan e0eb460fc8 app: resolve defaultServerUrl inside AppInterface
The desktop app sometimes modified __OPENCODE__.serverUrl after the
window is created. Previously this was ignored since defaultServerUrl
was created at module scope, now it isn't created until desktop's
ensure_server_started query complete, after which point serverUrl is
guaranteed to be updated.
2026-01-12 13:21:21 +08:00
opencode 7c6b3f981e release: v1.1.14
Update Nix Hashes / update (push) Has been cancelled
2026-01-12 05:08:25 +00:00
Aiden Cline a5b6c57a76 tweak: make the subagent header have clickable nav 2026-01-11 22:43:09 -06:00
Eyal CherevatskyandGitHub 65724b693b docs: fix scroll_speed default value (#7867) 2026-01-11 21:40:23 -06:00
Andrey TaranovandGitHub 8b9a85b7e7 fix(mcp): support resource content type in MCP tool output (#7879) 2026-01-11 21:39:42 -06:00
Zeke SikelianosandGitHub 7cbec9a1a7 docs: fix typos in settings doc (#7892) 2026-01-11 21:20:19 -06:00
Prince Carlo JuguilonandGitHub c4ba5961c8 chore: update GitHub stars count to 60K (#7899) 2026-01-11 21:19:59 -06:00
indeep99andGitHub 82b432349e feat(tui): add mouse hover and click support to questions tool (#7905) 2026-01-11 22:01:48 -05:00
Dax Raad 68ed664a3f tui: fix prompt ref initialization to prevent undefined reference errors 2026-01-11 19:40:16 -05:00
Dax Raad 3a30773874 tui: refactor event streaming to use SDK instead of manual RPC subscription 2026-01-11 18:53:39 -05:00
OctaneandGitHub 0c0057a7de Fix: TUI single-line paste cursor position (#7277) 2026-01-12 00:50:27 +01:00
Dillon MulroyandGitHub fa79736b87 fix: check worktree for external_directory permission in subdirs (#7811) 2026-01-11 14:17:36 -06:00
Jacob GillespieandGitHub 2e0c2c9db7 chore(lander): fix spacing in desktop app banner (#7822) 2026-01-11 14:16:29 -06:00
Daniel PolitoandGitHub 3e9366487a feat(desktop): User Message Badges on Hover (#7835) 2026-01-11 14:14:26 -06:00
Daniel PolitoandGitHub 025ed04da0 feat(desktop): Image Preview support for Image Attachments (#7841) 2026-01-11 14:12:52 -06:00
Zeke SikelianosandGitHub b81eca4ebc docs: fix typos on the providers page (#7829) 2026-01-11 13:04:04 -06:00
Aiden Cline 20c18689c0 bump copilot plugin version 2026-01-11 12:58:14 -06:00
Kit LangtonandGitHub a803cf8aee feat(tui): add mouse hover and click support to autocomplete (#7820) 2026-01-11 12:45:20 -05:00
King'ori MainaandGitHub c526e2d908 fix(tui): copy oauth url when no device code (#7812) 2026-01-11 10:27:17 -06:00
GitHub Action bdbbcd8a0c chore: generate 2026-01-11 16:12:14 +00:00
Kit LangtonandGitHub 43c2da24d0 fix(tui): slash command autocomplete highlighted row jumping (#7815) 2026-01-11 11:11:40 -05:00
GitHub Action 3205db9c16 ignore: update download stats 2026-01-11 2026-01-11 12:04:38 +00:00
opencode efbab087df release: v1.1.13
Update Nix Hashes / update (push) Has been cancelled
2026-01-11 07:10:41 +00:00
Mani SundararajanandGitHub 9280db3297 fix(tui): move props.ref to onMount (#7702) 2026-01-11 00:20:04 -06:00
Aiden Cline 0cc3c3bc78 tweak: ensure codex built in plugin is always available 2026-01-10 22:59:39 -06:00
Aiden Cline ee8b38ab26 fix: ensure /connect works for openai business plans too (was missing field) 2026-01-10 22:48:28 -06:00
44fa3d5392 feat(acp): track file modified (#7723)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-01-10 21:46:53 -06:00
Kit LangtonandGitHub a457828a67 fix(opencode): command palette mouse hover highlights wrong item (#7721) 2026-01-10 19:52:17 -05:00
SpoonandGitHub 4752c83155 feat: pass sessionID to chat.system.transform (#7718) 2026-01-10 18:21:51 -06:00
Dax Raad f94ee5ce90 core: extract external directory validation to shared utility to reduce code duplication across tools 2026-01-10 18:49:42 -05:00
Daniel PolitoandGitHub 76386f5cfc feat(desktop): Fork Session (#7673) 2026-01-10 16:04:01 -06:00
Daniel PolitoandGitHub a9275def43 feat(desktop): Make mouse hover / click work on prompt input autocomplete (#7661) 2026-01-10 16:02:47 -06:00
Daniel PolitoandGitHub 50ed4c6b5d feat(deskop): Add Copy to Messages (#7658) 2026-01-10 16:01:23 -06:00
Jérôme BenoitandGitHub f882cca98a fix(desktop): add missing StoreExt import and fix unused mut warning (#7707) 2026-01-10 15:26:24 -06:00
Lekë DobrunaandGitHub 2d2a044961 fix(desktop): build failing - missing import (#7697) 2026-01-10 15:24:15 -06:00
Frank b41fbda68f wip: black 2026-01-10 13:57:49 -05:00
Alejandro ChinchillaandGitHub 794c5981a5 fix: exclude 'none' variant for gpt-5.2-codex (#7682) 2026-01-10 12:22:44 -06:00
KennyandGitHub d5738f542c fix(grep): follow symlinks by default in ripgrep searches (#7501) 2026-01-10 10:57:18 -06:00
Andrew ThalandGitHub 02b7eb59f8 feat: support configuring default server URL for desktop (#7363) 2026-01-10 08:56:48 -06:00
GitHub Action a8f23fb548 chore: generate 2026-01-10 14:55:55 +00:00
OpeOginniandGitHub 1a642a79a6 fix(desktop): remove split operation in serverDisplayName function (#7397) 2026-01-10 08:55:20 -06:00
zerone0xandGitHub b6b0097755 fix(ui): allow text editing shortcuts in search dialogs on macOS (#7419) 2026-01-10 08:53:52 -06:00
Daniel PolitoandGitHub afb1cad26d fix(desktop): Allow Selecting Card Errors (#7506) 2026-01-10 08:52:11 -06:00
GitHub Action 58186004db chore: generate 2026-01-10 14:44:47 +00:00
madflowandGitHub a593ed4c9b fix: disable telemetry in the php lsp server (#7649) 2026-01-10 08:44:12 -06:00
opencode 449270aacc release: v1.1.12
Update Nix Hashes / update (push) Has been cancelled
2026-01-10 13:56:12 +00:00
GitHub Action e20535655b chore: generate 2026-01-10 13:50:58 +00:00
Frédéric DE MATOSandGitHub 1662e149b3 fix: add ChatGPT-Account-Id header for organization subscriptions (#7603) 2026-01-10 07:50:24 -06:00
Vlad TemianandGitHub dfe3e79304 docs(ecosystem): add micode and octto plugins (#7327) 2026-01-10 07:44:21 -06:00
GitHub Action e92a2ec9db ignore: update download stats 2026-01-10 2026-01-10 12:04:24 +00:00
Github Action 7cba1ff793 Update Nix flake.lock and hashes 2026-01-10 03:10:45 +00:00
Adam 8c3cc0d447 chore: prep 2026-01-09 21:09:26 -06:00
Adam e30562d5f4 chore: prep 2026-01-09 20:18:42 -06:00
GitHub Action 7c06ef2477 chore: generate 2026-01-10 02:07:24 +00:00
Adam 58eccf7f53 chore: prep 2026-01-09 19:53:04 -06:00
Adam 6da60bd5d9 fix(app): more defensive dom access 2026-01-09 19:53:03 -06:00
opencode 8a43c24934 release: v1.1.11
Update Nix Hashes / update (push) Has been cancelled
2026-01-10 01:25:30 +00:00
Aiden Cline b03172d723 fix: ensure gpt-5.2-codex has variants 2026-01-09 19:15:57 -06:00
SpoonandGitHub e342795bd0 docs: add url based instructions to web docs (#7216) 2026-01-09 19:01:13 -06:00
Aiden Cline a44d4acb3f tweak: adjust codex styling and fix hint 2026-01-09 18:59:57 -06:00
Aiden Cline 8b8a358de1 update docs and auth methods for openai 2026-01-09 18:45:03 -06:00
Aiden Cline 075fa2c0e8 fix: instance dispose issue 2026-01-09 18:26:32 -06:00
Aiden Cline 8b287caaad tweak codex instructions prompt for codex plan 2026-01-09 18:07:24 -06:00
Frank b3e6b7a985 wip: black 2026-01-09 19:00:51 -05:00
GitHub Action 5c74bff8e1 chore: generate 2026-01-09 23:48:13 +00:00
Aiden ClineandGitHub 172bbdaced feat: codex auth support (#7537) 2026-01-09 17:47:37 -06:00
opencode 563b4c33f2 release: v1.1.10
Update Nix Hashes / update (push) Has been cancelled
2026-01-09 21:38:04 +00:00
DaxandGitHub 982b71e861 disable server unless explicitly opted in (#7529) 2026-01-09 16:33:46 -05:00
Ariane EmoryandGitHub 75df5040ea fix: report config errors instead of silent termination (#7522) 2026-01-09 14:21:01 -06:00
Github Action f2b2940298 Update Nix flake.lock and hashes 2026-01-09 19:20:11 +00:00
Sebastian Herrlinger 22f51c6b47 upgrade opentui to v0.1.72 2026-01-09 20:18:34 +01:00
Aiden Cline de286b08f6 ignore: bump plugin version 2026-01-09 12:33:51 -06:00
adamelmore 0f2124db32 fix(app): no inline js 2026-01-09 11:57:31 -06:00
Ravi KumarandGitHub e30a159264 fix(cli): enable API key prompt for Bedrock in auth login (#7332) 2026-01-09 11:56:19 -06:00
ryanwylerandGitHub a5edf3a311 fix: resolve broken forked sessions with compactions due to missing parent-child message references (#6445) 2026-01-09 11:42:55 -06:00
Aiden Cline 7c2907cbb4 ci: fix failing pipeline 2026-01-09 11:38:01 -06:00
GitHub Action bce9dc040f chore: generate 2026-01-09 17:03:51 +00:00
adamelmore 44297ffe78 fix(app): break words in user message 2026-01-09 11:03:11 -06:00
adamelmore cbb3141130 fix(app): no custom url param 2026-01-09 11:03:11 -06:00
Frank 18cf4df6c6 wip: zen 2026-01-09 11:52:31 -05:00
Aiden Cline f3e8a275b8 docs: update brew formula references 2026-01-09 10:39:59 -06:00
Github Action 7df36cf0fa Update Nix flake.lock and hashes 2026-01-09 16:28:31 +00:00
Sebastian Herrlinger e82b112759 upgrade opentui to v0.1.71, fixing capability detection and therefor kitty keyboard 2026-01-09 17:26:33 +01:00
AJ KaandGitHub 030b14ac4e fix: prevent model picker overflow with Zen in new sessions (#7495) 2026-01-09 09:53:18 -06:00
Jérôme BenoitandGitHub b1e381cff7 fix(desktop): complete symlink support implementation and enable CI for PRs (#7113) 2026-01-09 22:43:49 +08:00
adamelmore 0433d4d064 fix(app): store terminal and review pane visibility per session 2026-01-09 08:20:00 -06:00
GitHub Action d34fdac854 ignore: update download stats 2026-01-09 2026-01-09 12:04:56 +00:00
adamelmore ec828619ca fix(app): more defensive todo access 2026-01-09 06:02:45 -06:00
adamelmore 559013e124 docs: perf plans 2026-01-09 05:07:16 -06:00
Jeon SuyeolandGitHub 8e3ab4afa7 feat(config): deduplicate plugins by name with priority-based resolution (#5957) 2026-01-09 02:11:24 -06:00
Aiden Cline 13305966e5 ci: tweak pr standards workflow 2026-01-09 00:54:42 -06:00
Aiden ClineandFrank a618fbe8cf bump copilot plugin version 2026-01-09 01:32:00 -05:00
Aiden ClineandFrank 07dc1f8ecc fix: model dialog issue when searching for models in fav/recents list, also ensure that deprecated models dont appear in list (#7429) 2026-01-09 01:32:00 -05:00
GitHub ActionandFrank 445c8631a2 chore: generate 2026-01-09 01:32:00 -05:00
Frank c4eacd0ccf wip: black 2026-01-09 01:32:00 -05:00
Frank dd5ec26c8c wip: black 2026-01-09 01:32:00 -05:00
Daniel PolitoandFrank 8b062ed621 feat(desktop): Change Terminal's Cursor to Bar and Stop Blinking when not focused (#7402) 2026-01-09 01:32:00 -05:00
Frank ab97a95032 wip: black 2026-01-09 01:32:00 -05:00
GitHub ActionandFrank a98d108d2e chore: generate 2026-01-09 01:32:00 -05:00
Frank 2e875b2d65 wip: black 2026-01-09 01:32:00 -05:00
Frank 790baec41f wip: zen 2026-01-09 01:32:00 -05:00
Frank 52fbd16e08 wip: zen
wip: zen
2026-01-09 01:32:00 -05:00
GitHub Action cf97633d7d chore: generate 2026-01-09 00:13:26 +00:00
DanilandGitHub 3fe2e89d55 docs: Add Scaleway to provider docs (#7389) 2026-01-08 18:12:22 -06:00
Luke ParkerandGitHub eb5c113cff ignore: add PR template (#7391) 2026-01-08 18:11:59 -06:00
opencode bd9c13bb27 release: v1.1.8
Update Nix Hashes / update (push) Has been cancelled
2026-01-08 23:58:57 +00:00
Adam 3019b1f825 Revert "fix(desktop): open external links in default browser (#7221)"
This reverts commit 361a962673.
2026-01-08 17:51:30 -06:00
119cc8c795 feat(desktop): add Vesper theme (#7331)
Co-authored-by: Anthony Huang <ahuang@ramp.com>
2026-01-08 17:48:57 -06:00
Adam be9b2bab15 feat(app): cache session-scoped stores, optional context gating 2026-01-08 17:48:15 -06:00
Adam c949e5b390 feat(app): incrementally render turns, markdown cache, lazily render diffs 2026-01-08 17:48:15 -06:00
Adam 1c717d62e4 feat(app): gate hydration side-effects, handoff while stores load 2026-01-08 17:48:15 -06:00
Adam 27675dfd70 feat(app): prefetch adjacent sessions 2026-01-08 17:48:15 -06:00
Adam 374275eeb6 feat(app): chunk message loading, lazy load diffs 2026-01-08 17:48:15 -06:00
AJ KaandGitHub faa848cfb1 Fix for #7229 Fix/project sidebar folder name (#7232) 2026-01-08 17:47:15 -06:00
Dax Raad 80772e5ac2 ci 2026-01-08 18:26:51 -05:00
Dax Raad be34747526 add sponsored 2026-01-08 18:21:21 -05:00
Github Action 49d9f99924 Update Nix flake.lock and hashes 2026-01-08 22:56:55 +00:00
Sebastian Herrlinger 1f9e195cd8 stop esc propagation from dialogs 2026-01-08 23:53:00 +01:00
Sebastian Herrlinger 539d6baa8c upgrade opentui to v0.1.70 2026-01-08 23:52:36 +01:00
f6fc693c1f fix(ui): use full file path for fuzzy matching in autocomplete (#6705)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-08 16:43:05 -06:00
GitHub Action 50d8396c9a chore: generate 2026-01-08 22:33:10 +00:00
DaxandGitHub 22dd70b75b feat(question): support multi-select questions (#7386) 2026-01-08 17:32:21 -05:00
GitHub Action b4f8de0c0a chore: generate 2026-01-08 22:03:23 +00:00
Marc EspinandGitHub 768e0553bd fix(docs): Document cargofmt (#7383) 2026-01-08 16:00:03 -06:00
opencode f3e79235fb release: v1.1.7
Update Nix Hashes / update (push) Has been cancelled
2026-01-08 20:37:24 +00:00
ryanwylerandGitHub eacf3ad361 fix(tui): restore showDetails check removed in Permission rework (#7285) 2026-01-08 14:30:41 -06:00
Mani SundararajanandGitHub 72062d22a0 fix: cleanly restore terminal state on fatal error exit (#7359) 2026-01-08 14:22:24 -06:00
Aiden Cline 9f90f0e8ed ci: update fork_repo var 2026-01-08 14:19:44 -06:00
Aiden Cline c74bc323b6 ci: tweak enforcement of titles 2026-01-08 14:12:38 -06:00
zerone0xandGitHub c7b825a42a fix(app): show custom models without valid release_date in web UI mod… (#7349) 2026-01-08 14:06:18 -06:00
Dax Raad b1a613b3b9 ci: add retry logic to desktop build to handle intermittent failures 2026-01-08 14:57:55 -05:00
958f1edfef fix: Add kind, title, rawInput to ACP tool_call_update events (#7368)
Co-authored-by: noam-v <noam@bespo.ai>
2026-01-08 13:56:16 -06:00
DaxandGitHub 2bb299d741 tui: improve responsive layout by hiding header/footer when sidebar is visible (#7365) 2026-01-08 13:57:23 -05:00
Aiden Cline 9930ac6929 ci: adjust workflow 2026-01-08 12:50:17 -06:00
Aiden Cline 1906a347f3 docs: update pr workflows 2026-01-08 12:44:04 -06:00
Dax Raad e5d0c63b29 docs: simplify tools configuration by consolidating under permission field 2026-01-08 12:39:40 -05:00
Aiden Cline 970796b832 docs: add PR title guidelines and workflow to enforce conventional commits 2026-01-08 11:23:52 -06:00
Aiden Cline 3c5043497c ignore: ensure new file truncation stuff still works even if external_directoy is set to deny 2026-01-08 11:07:33 -06:00
Max StevensandGitHub 4d09c5618e refactor: session settings simplification (#7342) 2026-01-08 11:06:06 -06:00
Sercan SagmanandGitHub adae0d1853 fix(cli): add help text to debug command and subcommands (#7328)
Signed-off-by: assagman <ahmetsercansagman@gmail.com>
2026-01-08 09:42:12 -06:00
Aaron IkerandGitHub 61aeb2a2a7 feat: Polish dialog, popover shadows & borders, add missing provider logos (#7326) 2026-01-08 09:02:04 -06:00
GitHub Action 4b0f7b82ba ignore: update download stats 2026-01-08 2026-01-08 12:05:02 +00:00
GitHub Action 9fb24074c8 chore: generate 2026-01-08 06:11:00 +00:00
freespace8andGitHub 542c9d5346 feat(flags): allow disabling .claude prompt and skills loading (#7205) 2026-01-08 00:10:25 -06:00
Aiden Cline d5f0e3fccc test: add larger image test for read tool 2026-01-07 23:01:02 -06:00
Dax Raad 7d2bb5cb2b tui: remove unused imports and variables from header component to clean up code 2026-01-07 23:54:54 -05:00
DaxandGitHub ca7a70b628 tui: add overlay sidebar for narrow screens to improve mobile experience (#7288) 2026-01-07 23:51:49 -05:00
Dax Raad b3a2f9fb4e tui: add expandable bash output for long commands to improve readability 2026-01-07 23:11:46 -05:00
Dax Raad 8be5a29870 tui: remove username visibility toggle and simplify user message metadata display 2026-01-07 23:02:19 -05:00
Dax Raad 68092f22e1 tui: improve other answer option layout with better indentation and clearer label 2026-01-07 22:34:31 -05:00
GitHub Action 83f3c729e9 chore: generate 2026-01-08 03:30:18 +00:00
DaxandGitHub e37fd9c105 core: add interactive question tool for gathering user preferences and clarifying instructions (#7268) 2026-01-07 22:29:42 -05:00
Aiden Cline 2e4fe973c9 fix: issue w/ normal transform options conflicting w/ small model options when gen-ing title 2026-01-07 17:32:38 -06:00
Aiden ClineandGitHub 1b82511fbd feat: write truncated tool outputs to files (#7239) 2026-01-07 17:25:00 -06:00
Ariane EmoryandGitHub f24314438b fix(tui): ensure forked message text is inserted in prompt (resolves #7257) (#7259) 2026-01-07 16:12:03 -06:00
Andrew ThalandGitHub 361a962673 fix(desktop): open external links in default browser (#7221) 2026-01-07 16:04:27 -06:00
Adam fa9c283fcf fix(app): user message text wrap 2026-01-07 15:36:08 -06:00
Frank 947b864d96 wip: zen 2026-01-07 15:49:52 -05:00
Ariane EmoryandGitHub 03eabb10e4 fix: use selectedForeground's computer colour (or theme's selectedForeground value) for the colour of text in permission selection (resolves #7246) (#7251) 2026-01-07 14:45:30 -06:00
34c9d106ee refactor: simplify task tool subagent filtering (#7165)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-07 13:28:13 -06:00
Aiden Cline fe57d7bb38 tweak: title gen prompt & temp to avoid repetative 'Analyzing ...' titles 2026-01-07 12:56:41 -06:00
Ravi KumarandGitHub 68cf6b04a0 fix(tui): constrain autocomplete height to available screen space (#7181) 2026-01-07 19:24:44 +01:00
Github Action 9ffaf81fb3 Update Nix flake.lock and hashes 2026-01-07 18:07:10 +00:00
GitHub Action 50530b1ea7 chore: generate 2026-01-07 18:06:30 +00:00
Rodolfo CarvalhoandGitHub a160eee499 fix(mcp): use correct authorization server URL for OAuth discovery (#7234) 2026-01-07 12:05:54 -06:00
Thanh NguyenandGitHub d9aef1d73d fix(theme): add selectedListItemText to orng themes for button text visibility (#7169) 2026-01-07 12:05:46 -06:00
Matt SilverlockandGitHub 4ba0b22b04 fix: config precedence now correctly allows local config to override remote (#7141) 2026-01-07 12:07:21 -05:00
Aiden Cline 662d2b205a docs: update brew formula stuff to recommend our tap 2026-01-07 11:06:54 -06:00
GitHub Action 75960ae00c chore: generate 2026-01-07 17:03:43 +00:00
Aiden Cline 528f198c39 tweak: update formula for anomalyco tap 2026-01-07 11:02:37 -06:00
opencode 184834da98 release: v1.1.6
Update Nix Hashes / update (push) Has been cancelled
2026-01-07 16:38:32 +00:00
Dax Raad 008a5c10cc oops 2026-01-07 11:35:11 -05:00
Daniel PolitoandGitHub 2d5b9a5cc6 Desktop: Fix Paste image on empty input (#7130) 2026-01-07 09:40:21 -06:00
Andrew ThalandGitHub fb3ca895d6 fix(ui): prevent iOS Safari auto-zoom on input focus (#7214) 2026-01-07 09:22:48 -06:00
Brendan Allan d3d379fe2e desktop: bundleMediaFramework 2026-01-07 22:55:29 +08:00
GitHub Action b41626049c chore: generate 2026-01-07 14:50:57 +00:00
Dax Raad e59be27810 theme 2026-01-07 09:50:04 -05:00
opencode 1e2992244f release: v1.1.5
Update Nix Hashes / update (push) Has been cancelled
2026-01-07 14:37:04 +00:00
Dax Raad fd22b26478 theme reload 2026-01-07 09:34:11 -05:00
Dax Raad ea2ee46f45 clear palette cache 2026-01-07 08:49:35 -05:00
Adam 4e1b6b3417 fix(app): select first item when filtering 2026-01-07 07:15:06 -06:00
2d52a461a0 fix: encode non-ASCII directory paths in HTTP headers (#7145)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-07 06:46:17 -06:00
9cce0cf4f4 feat: add Night Owl theme (desktop) (#7151)
Co-authored-by: shuv <shuv@shuv.dev>
2026-01-07 06:44:27 -06:00
Brendan Allan a41c8508da desktop: go back to regular tauri cli 2026-01-07 20:42:10 +08:00
GitHub Action 4f7458b47d ignore: update download stats 2026-01-07 2026-01-07 12:04:44 +00:00
Brendan Allan 270cd05195 fix entitlements 2026-01-07 20:03:38 +08:00
24c933ae60 Add audio input and camera permissions to entitlements (#7117)
Co-authored-by: Brendan Allan <brendonovich@outlook.com>
2026-01-07 17:56:46 +08:00
Brendan Allan 2b7a021ba3 desktop: appimage bundleMediaFramework 2026-01-07 17:39:35 +08:00
GitHub Action cbf87c50b9 chore: generate 2026-01-07 08:48:34 +00:00
Brendan Allan 3c375b971e desktop: use Show instead of Suspense 2026-01-07 16:47:45 +08:00
Aiden Cline 6590c1641f add truncation for all tools 2026-01-07 02:01:32 -06:00
GitHub Action 0ffe496869 chore: generate 2026-01-07 07:46:40 +00:00
Frank ce4e595881 wip: black 2026-01-07 02:45:57 -05:00
Frank e91cc7e514 wip: black 2026-01-07 02:45:57 -05:00
GitHub Action c961072d20 chore: generate 2026-01-07 07:43:43 +00:00
Aiden ClineandGitHub 429240f439 ignore: add truncation funcs (#7178) 2026-01-07 01:43:06 -06:00
Github Action a0dc90bfcc Update Nix flake.lock and hashes 2026-01-07 07:36:49 +00:00
Brendan Allan 6bac501be5 add ui package to desktop 2026-01-07 15:35:32 +08:00
Daniel PolitoandGitHub b5be883758 Make General SubAgent not hidden (#6752) 2026-01-07 01:31:47 -06:00
Brendan Allan 0021a09ba8 try using forked tauri-cli again 2026-01-07 15:29:14 +08:00
a8c2928a87 perf: show window immediately during desktop startup (#6734)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-01-07 15:19:01 +08:00
Brendan Allan 79f6910697 desktop: use correct tauri-cli branch 2026-01-07 14:36:43 +08:00
Aiden Cline 04cea9cf11 tweak: unserializable error handling 2026-01-07 00:29:41 -06:00
Brendan Allan 61c334f1fb desktop: use regular tauri cli again 2026-01-07 14:25:25 +08:00
SpoonandGitHub 85ed329318 add agent color to inline task tool ascii char and highlight agent name (#7142) 2026-01-07 00:10:00 -06:00
Brendan Allan 37decee795 use custom tauri-cli on linux 2026-01-07 13:34:18 +08:00
Frank 5d0007ade4 wip: black 2026-01-07 00:08:49 -05:00
GitHub Action 31dd9fd13a chore: generate 2026-01-07 04:35:15 +00:00
Frank 23fc675ad5 wip: black 2026-01-06 23:34:12 -05:00
Frank 22b058a33d wip: black 2026-01-06 23:34:12 -05:00
GitHub Action 939c0940aa chore: generate 2026-01-07 04:29:50 +00:00
fd7b7eacd3 Added: Ability to hide subagents from primary agents system prompt. (#4773)
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-01-06 22:29:17 -06:00
GitHub Action eaa0826e7f chore: generate 2026-01-07 03:42:44 +00:00
Adam f6055ad3d2 chore: fix types 2026-01-06 21:42:04 -06:00
Adam 761863ae35 chore(app): rework storage approach 2026-01-06 21:42:03 -06:00
NSPC911andGitHub dadc08ddc7 fix: escape backticks when passing to powershell (#7157) 2026-01-06 21:41:33 -06:00
Github Action d7afb01d13 Update Nix flake.lock and hashes 2026-01-07 03:20:32 +00:00
GitHub Action 5168988156 chore: generate 2026-01-07 03:19:47 +00:00
Adam 9a3bd0ade1 fix(app): hide line numbers in diffs on mobile 2026-01-06 21:18:50 -06:00
Adam 4e5b0b00b0 fix(app): session navigation with shortcuts 2026-01-06 21:18:41 -06:00
Adam 7672b722ca fix(app): permission auto-accept should hide when not needed 2026-01-06 21:18:29 -06:00
Adam 488c3502a7 fix(app): terminal selection color contrast 2026-01-06 21:18:17 -06:00
Aiden Cline a2f80f7c0d fix: add --use-system-ca flag in execArgv 2026-01-06 21:01:03 -06:00
Rafi KhardalianandGitHub dee0226741 fix(tui): restore attach session lookup behavior (#7150) 2026-01-06 20:59:12 -06:00
kamkmandGitHub 58aba1c797 chore: Refactor style guide to use consistent language (#7139) 2026-01-06 17:54:16 -06:00
Sebastian Herrlinger 9906d42e1c remove hallucinated keybinding 2026-01-07 00:19:56 +01:00
GitHub Action 43eefbc349 chore: generate 2026-01-06 23:06:08 +00:00
Aiden Cline 625c9dae5c fix: ensure commands listing subagent work 2026-01-06 17:05:20 -06:00
opencode b2341c2d9a release: v1.1.4
Update Nix Hashes / update (push) Has been cancelled
2026-01-06 22:06:38 +00:00
Daniel PolitoandGitHub dadddcaf57 Desktop: Fix Big Messages (#7133) 2026-01-06 16:00:37 -06:00
JayandGitHub f7b3371b02 docs: readme
Removed section about unrelated repository.
2026-01-06 16:31:39 -05:00
GitHub Action 409f8f678e chore: generate 2026-01-06 21:17:29 +00:00
Frank dc62f9393a zen: fix rate limit 2026-01-06 16:16:35 -05:00
SpoonandGitHub 32e0b612d9 adding timeout (#7128) 2026-01-06 15:00:34 -06:00
Mateusz TymekandGitHub 7d6ce6fc5e docs: add OpenCode-Obsidian plugin (#7129) 2026-01-06 15:00:22 -06:00
Justas RaudoniusandGitHub ba105246ea fix(app): open links in new tab or browser (#7127) 2026-01-06 14:58:36 -06:00
SpoonandGitHub a10cc63403 feat: url based instructions (#7125) 2026-01-06 14:48:22 -06:00
MaikandGitHub cde06e90d0 chore: update stars count (#7120) 2026-01-06 14:28:47 -06:00
galkatz373andGitHub d4e7a88bba feat(cli): frecency file autocomplete (#6603) 2026-01-06 14:05:57 -06:00
Thomas GormleyandGitHub 630476afc0 load OPENCODE_CONFIG_DIR AGENTS.md into the system prompt (#7115) 2026-01-06 13:43:55 -06:00
Adam 5181e4e90a fix(app): copy and paste in terminal was broken 2026-01-06 13:38:15 -06:00
Aiden Cline 5db78f20e9 core: fix title generation for subtask-only messages to extract actual user prompts instead of generic tool execution descriptions 2026-01-06 13:30:39 -06:00
Frank 5fc4472921 OpenCode Black 2026-01-06 14:22:54 -05:00
Damian BarabonkovandGitHub 3049ac576a docs: Expand keybinds documentation (#7108) 2026-01-06 13:10:35 -06:00
Aiden Cline 494e03490e docs: fix desktop stuff 2026-01-06 13:07:20 -06:00
Aiden Cline 675eba6588 Revert "fix(desktop): use current_binary() to support symlinked executables (#7102)"
This reverts commit bb09df0c77.
2026-01-06 12:59:05 -06:00
Aiden Cline 01eadf3ded test: fix test 2026-01-06 12:31:41 -06:00
Aiden Cline c7a2c737e8 fix: ensure 'name' isnt being sent in request body for custom agent 2026-01-06 12:21:30 -06:00
Jérôme BenoitandGitHub bb09df0c77 fix(desktop): use current_binary() to support symlinked executables (#7102) 2026-01-06 12:07:18 -06:00
Adam ecbcbfbe90 fix(app): more contrast in terminal text 2026-01-06 12:03:24 -06:00
M. Adel AlhashemiandGitHub 485aadcbfa fix: restore skill filtering by agent permissions (#7042) 2026-01-06 12:01:37 -06:00
Matthijs WoltingandGitHub d76d6db589 fix: add missing await for available skills in skill tool (#7072) 2026-01-06 11:56:49 -06:00
Akinfolami Akin-AlamuandGitHub aa612b27d4 feat(tui): add 'c' shortcut to copy device code in OAuth flow (#7020) 2026-01-06 11:54:49 -06:00
GitHub Action a35c278424 chore: generate 2026-01-06 17:29:44 +00:00
Guofang.TangandGitHub 8265621d48 fix: prevent jdtls path checks from throwing (#7052) 2026-01-06 11:29:06 -06:00
ikeda-tomoya-swxandGitHub 1016a52cf1 fix(provider): add jp. prefix auto-assignment for Tokyo region (ap-northeast-1) (#7053) 2026-01-06 11:18:34 -06:00
Guofang.TangandGitHub d0a1e6fa46 docs: add Simplified Chinese README (#7055) 2026-01-06 11:18:13 -06:00
ryanwylerandGitHub f0e559c0ed fix: sidebar title padding to prevent scrollbar edge case (#7089) 2026-01-06 11:15:39 -06:00
Andrew ThalandGitHub 528c6c1a75 docs(ecosystem): add opencode-devcontainers plugin (#7100) 2026-01-06 10:58:55 -06:00
Justas RaudoniusandGitHub 6092f8792e feat(app): add view button to open files from review sidebar (#7095) 2026-01-06 10:28:03 -06:00
Justas RaudoniusandGitHub 4142e1bcf6 fix(app): open review sidebar when selecting file from picker (#7096) 2026-01-06 10:27:22 -06:00
Justas RaudoniusandGitHub 49d837e0c1 feat(app): add middle-click to close tabs in review sidebar (#7094) 2026-01-06 10:15:17 -06:00
Adam b88bcd49fd fix(app): code splitting for web load perf gains 2026-01-06 08:19:17 -06:00
Adam 3f463bc916 fix(app): scroll store performance 2026-01-06 08:18:49 -06:00
GitHub Action 0b02f6d22f ignore: update download stats 2026-01-06 2026-01-06 12:04:50 +00:00
Eric GuoandGitHub 4ecb305820 Fix(app): @pierre/diffs will crash when a diff has undefined text (#7059) 2026-01-06 05:22:47 -06:00
Brendan Allan e5a868157e update Cargo.lock 2026-01-06 17:08:34 +08:00
Junseo5andGitHub f510d17bd3 fix(desktop): add single-instance plugin to prevent multiple windows (#6966) 2026-01-06 14:19:29 +08:00
BerylandGitHub 45fea6587e fix: use actual version in install script (#7044) 2026-01-05 23:26:44 -06:00
Aiden Cline a721810682 ci: tweak prompt 2026-01-05 23:24:58 -06:00
Shane BishopandGitHub d486c1c7c8 docs: fix order of permissions in agents docs (permissions subsection) (#7041) 2026-01-05 23:10:50 -06:00
9197a2a7a1 docs: polish markdown wording and capitalization (#7019)
Co-authored-by: Tang Guofang <tangguofang@mychery.com>
2026-01-05 22:45:40 -06:00
Frank 8da890649f wip: zen 2026-01-05 22:48:07 -05:00
GitHub Action 21053732e7 chore: generate 2026-01-05 23:10:23 +00:00
Frank cf069dd046 wip: zen 2026-01-05 18:09:39 -05:00
Frank 4dc3cb9115 wip: zen 2026-01-05 18:09:38 -05:00
Adam 5c66c8b8e1 fix(core): filter dead worktrees 2026-01-05 16:37:18 -06:00
Adam 2ca0ae7755 fix(app): more defensive, handle no git 2026-01-05 15:12:02 -06:00
Github Action 19123b6803 Update Nix flake.lock and hashes 2026-01-05 20:19:14 +00:00
Sebastian Herrlinger 4137c66581 upgrade opentui to v0.1.69, with some text rendering performance improvements 2026-01-05 21:17:45 +01:00
GitHub Action 48d14d4cac chore: generate 2026-01-05 19:33:32 +00:00
OpeOginniandGitHub 8996185f3b Feat/clickable subtask (#6846) 2026-01-05 13:32:16 -06:00
opencode 4f49967518 release: v1.1.3
Update Nix Hashes / update (push) Has been cancelled
2026-01-05 19:26:40 +00:00
Adam ec637aa21e fix(app): store image attachments 2026-01-05 13:21:33 -06:00
Adam 2ff9a757b6 fix(app): bottom padding 2026-01-05 13:21:32 -06:00
Adam cb8533ef5b fix(app): terminal long-running processes 2026-01-05 13:21:32 -06:00
Adam cdbb009ab0 fix(app): terminal flakiness 2026-01-05 13:21:31 -06:00
Adam 001b486356 fix(app): performance improvements through event batching 2026-01-05 13:21:30 -06:00
Adam d315026abc fix(app): prompt input shouldn't cover content 2026-01-05 13:21:30 -06:00
Adam d1191675c6 fix(app): worktree selection should navigate to worktree 2026-01-05 13:21:29 -06:00
GitHub Action 362a657b4f chore: generate 2026-01-05 19:10:42 +00:00
Dax Raad 0276885181 core: preserve permission config key order to maintain user-defined permission precedence 2026-01-05 14:09:37 -05:00
Guorui YuandGitHub 5a38a6f248 tui: autocomplete: expand directory on Tab, select on Enter (#6975)
Signed-off-by: yuguorui <yuguorui@pku.edu.cn>
2026-01-05 13:08:17 -06:00
GitHub Action aef01003e7 chore: generate 2026-01-05 19:07:34 +00:00
a38e1701ee fix: pass image parts to custom commands (#6525)
Co-authored-by: Melih Mucuk <melih@monkeysteam.com>
2026-01-05 13:06:57 -06:00
ikeda-tomoya-swxandGitHub bf9ee32d4a fix(provider): add support for jp. prefix in Amazon Bedrock cross-region inference profiles (#6916) 2026-01-05 13:03:09 -06:00
Josh ThomasandGitHub 0917991361 docs: update GHA examples to use actions/checkout@v6 (#6969)
publish-github-action / publish (push) Has been cancelled
Update Nix Hashes / update (push) Has been cancelled
2026-01-05 13:00:22 -06:00
c6a241e331 ci: prevent duplicate PR check from flagging current PR as duplicate (#6924)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-01-05 12:59:42 -06:00
Rohan MukherjeeandGitHub 4b7301e8ca fix: lucent-orng bg transparency for slash commands (#6938) 2026-01-05 12:56:49 -06:00
Daniel VélezandGitHub 1bf20f0a2b docs: add description for MCP command (#6944) 2026-01-05 12:56:00 -06:00
Grégoire MorpainandGitHub e3b4d4ad49 feat(bedrock): config options and authentication precedence (#6377) 2026-01-05 12:51:43 -06:00
MogamiTsuchikawaandGitHub 6b207b09d6 fix(app): avoid unintended submits during IME composition (#6952) 2026-01-05 12:38:38 -06:00
Albin GroenandGitHub 9771325026 feat(app): highlight collapsed active project in sidebar (#6958) 2026-01-05 12:37:46 -06:00
Albin GroenandGitHub bbd1c071c4 fix(app): fix flicker and navigation when collapsing/expanding projects (#6658) 2026-01-05 11:24:49 -06:00
8e9a0c4ad0 Desktop: Install CLI (#6526)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-01-06 01:07:46 +08:00
Frank ced093e646 sync 2026-01-05 11:59:17 -05:00
Frank 283bdce358 sync 2026-01-05 11:13:59 -05:00
Dax Raad 91d5ce8bf3 tui: add system theme resolution and event handling 2026-01-05 10:38:35 -05:00
Frank 7f870cc9d4 wip: zen 2026-01-05 10:16:47 -05:00
2cb3b0484b fix: cleaner interrupted experience (#6785)
Co-authored-by: Dax <mail@thdxr.com>
2026-01-05 09:53:19 -05:00
Frank 11b0df6b86 wip: zen 2026-01-05 06:16:04 -05:00
Frank e15af828fa zen: optimize query 2026-01-05 05:58:39 -05:00
Albin GroenandGitHub 265cbaea7c fix(app): fix image dragging in project edit dialog (#6700) 2026-01-05 04:54:11 -06:00
GitHub Action d39ebbc947 chore: generate 2026-01-05 07:41:02 +00:00
Aiden Cline 06acd70670 tweak: transform 2026-01-05 01:40:15 -06:00
Aiden Cline c285304acf fix: for anthropic compat ensure empty msgs and empty reasoning is filtered out 2026-01-05 01:40:15 -06:00
opencode 4d187af9d2 release: v1.1.2
Update Nix Hashes / update (push) Has been cancelled
2026-01-05 07:16:26 +00:00
Aiden Cline 7e14cc687a ci: fix OPENCODE_PERMISSION env vars 2026-01-05 00:40:34 -06:00
Dax Raad 2f5b2b23d5 core: fix permission rule matching to use permission field instead of pattern field 2026-01-05 01:21:49 -05:00
Aiden Cline 035baa4b38 ignore: add codeowners file for adam 2026-01-05 00:17:32 -06:00
Dax Raad 9f38af44db core: fix permission evaluation to use rule-based matching instead of wildcard patterns 2026-01-05 01:07:03 -05:00
7324b2260a fix(tui): allow exit when viewing child session (#6898)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-01-04 23:14:43 -06:00
GitHub Action 166f169dbf chore: generate 2026-01-05 03:47:12 +00:00
Frank 9c55cb729b zen: add index 2026-01-04 22:46:21 -05:00
Aiden Cline f2e65e40ea fix: handle skill scan failures for .claude gracefully 2026-01-04 21:39:45 -06:00
Aiden Cline 8b3ae08a55 acp: handle case where big-pickle is unavailable as a fallback 2026-01-04 21:10:30 -06:00
Aiden Cline 555d7fcdde ci: make sure opencode is installed 2026-01-04 20:35:41 -06:00
opencode-agent[bot]GitHubopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-node
2410a6bc9e Fix symmetric padding in TUI input field (#6894)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2026-01-04 20:34:14 -06:00
GitHub Action 59ed8ccbd8 chore: generate 2026-01-05 02:18:20 +00:00
OpeOginniandGitHub 91ed101378 feat(desktop): implement auto-scroll for active command in slash popover (#6797) 2026-01-04 20:17:47 -06:00
Daniel PolitoandGitHub fb60f9c396 Desktop: Fix Responsive Menu (#6789) 2026-01-04 18:47:14 -06:00
Shkumbin HasaniandGitHub e93699b741 perf: optimize model dialog visibility lookups (#6791) 2026-01-04 18:46:23 -06:00
Daniel PolitoandGitHub 9ac00f55bc Desktop: Adding Home Icon on Responsive Menu (#6794) 2026-01-04 18:44:28 -06:00
Daniel PolitoandGitHub 393cf78ca6 Desktop: Improve Big Session Navigation - Scrollable (#6837) 2026-01-04 18:40:58 -06:00
GitHub Action 478fec61ab chore: generate 2026-01-05 00:39:48 +00:00
shuvandGitHub 52ad134d55 feat(app): add SVG preview support in session viewer (#6868) 2026-01-04 18:39:15 -06:00
NN708andGitHub 3e09abbfda feat(desktop): add AppStream MetaInfo file (#6030) 2026-01-04 18:36:07 -06:00
Dax Raad 5450644c67 docs: restructure permissions documentation to clarify v1.1.1 changes and action-based model 2026-01-04 19:35:04 -05:00
Carter McBrideandGitHub 0c2ccf25dc Fix a few mobile screen size issues (#6808) 2026-01-04 18:32:48 -06:00
Ravi KumarandGitHub 65c7168492 fix(app): fix custom slash commands not showing on initial / (#6829) 2026-01-04 18:30:34 -06:00
Albin GroenandGitHub c74c66e6b4 fix(ui): fix select chevron alignment (#6690) 2026-01-04 18:29:19 -06:00
Aiden Cline c545fa2a28 ci: nix desktop 2026-01-04 13:52:32 -06:00
Aiden Cline 80235f325e ci: fix dup pr action 2026-01-04 13:30:58 -06:00
Rohan GodhaandGitHub 88c306efd2 fix: prevent session list rows from wrapping to 2 lines (#6812) 2026-01-04 13:29:44 -06:00
554572bc39 fix: prevent main model thinking variant from applying to small model (#6839)
Co-authored-by: Melih Mucuk <melih@monkeysteam.com>
2026-01-04 13:28:22 -06:00
Aiden Cline e5abe1e78b tweak: bump default to 30 seconds (lots of people complained about 5...) 2026-01-04 13:26:43 -06:00
Aiden Cline 1d54f90330 docs: add instructions for running web and desktop apps during development 2026-01-04 13:12:43 -06:00
Dax Raad 5f10243e91 tui: fix session configuration merge conflict resolution 2026-01-04 13:43:33 -05:00
Dax Raad 226a5c2000 tui: fix optional session access to prevent runtime errors 2026-01-04 13:43:33 -05:00
Github Action f8442ad016 Update Nix flake.lock and hashes 2026-01-04 18:39:44 +00:00
GitHub Action 1e28d10610 chore: generate 2026-01-04 18:39:08 +00:00
Dax Raad 7304ba616e tui: add session search functionality with debounced input and server-side filtering 2026-01-04 13:38:30 -05:00
Dax Raad cdd6ea514b core: improve Rust formatter detection and add cargo fmt support 2026-01-04 13:04:28 -05:00
GitHub Action 24d9c1d18d chore: generate 2026-01-04 17:09:30 +00:00
Adam 5ca2f6c5a9 fix(app): prompt input improvements 2026-01-04 11:08:47 -06:00
Adam 12ffb270fb fix(app): prompt input improvements 2026-01-04 10:37:56 -06:00
686 changed files with 102607 additions and 13477 deletions
+4
View File
@@ -0,0 +1,4 @@
# web + desktop packages
packages/app/ @adamdotdevin
packages/tauri/ @adamdotdevin
packages/desktop/ @adamdotdevin
+3
View File
@@ -0,0 +1,3 @@
### What does this PR do?
### How did you verify your code works?
+2 -2
View File
@@ -28,8 +28,8 @@ jobs:
OPENCODE_PERMISSION: |
{
"bash": {
"gh issue*": "allow",
"*": "deny"
"*": "deny",
"gh issue*": "allow"
},
"webfetch": "deny"
}
+6 -1
View File
@@ -30,6 +30,9 @@ jobs:
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Install dependencies
run: bun install
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
@@ -41,6 +44,8 @@ jobs:
{
echo "Check for duplicate PRs related to this new PR:"
echo ""
echo "CURRENT_PR_NUMBER: $PR_NUMBER"
echo ""
echo "Title: $(gh pr view "$PR_NUMBER" --json title --jq .title)"
echo ""
echo "Description:"
@@ -53,7 +58,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
COMMENT=$(opencode run --agent duplicate-pr --print -f pr_info.txt "Check the attached file for PR details and search for duplicates")
COMMENT=$(bun script/duplicate-pr.ts -f pr_info.txt "Check the attached file for PR details and search for duplicates")
gh pr comment "$PR_NUMBER" --body "_The following comment was made by an LLM, it may be inaccurate:_
+19 -2
View File
@@ -1,8 +1,23 @@
name: nix desktop
on:
pull_request:
push:
branches: [dev]
paths:
- "flake.nix"
- "flake.lock"
- "nix/**"
- "packages/app/**"
- "packages/desktop/**"
- ".github/workflows/nix-desktop.yml"
pull_request:
paths:
- "flake.nix"
- "flake.lock"
- "nix/**"
- "packages/app/**"
- "packages/desktop/**"
- ".github/workflows/nix-desktop.yml"
workflow_dispatch:
jobs:
@@ -12,6 +27,8 @@ jobs:
matrix:
os:
- blacksmith-4vcpu-ubuntu-2404
- blacksmith-4vcpu-ubuntu-2404-arm
- macos-15-intel
- macos-latest
runs-on: ${{ matrix.os }}
timeout-minutes: 60
@@ -20,7 +37,7 @@ jobs:
uses: actions/checkout@v6
- name: Setup Nix
uses: DeterminateSystems/nix-installer-action@v21
uses: nixbuild/nix-quick-install-action@v34
- name: Build desktop via flake
run: |
+139
View File
@@ -0,0 +1,139 @@
name: PR Standards
on:
pull_request_target:
types: [opened, edited, synchronize]
jobs:
check-standards:
if: |
github.event.pull_request.user.login != 'actions-user' &&
github.event.pull_request.user.login != 'opencode' &&
github.event.pull_request.user.login != 'rekram1-node' &&
github.event.pull_request.user.login != 'thdxr' &&
github.event.pull_request.user.login != 'kommander' &&
github.event.pull_request.user.login != 'jayair' &&
github.event.pull_request.user.login != 'fwang' &&
github.event.pull_request.user.login != 'adamdotdevin' &&
github.event.pull_request.user.login != 'iamdavidhill' &&
github.event.pull_request.user.login != 'opencode-agent[bot]'
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Check PR standards
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const title = pr.title;
async function addLabel(label) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: [label]
});
}
async function removeLabel(label) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: label
});
} catch (e) {
// Label wasn't present, ignore
}
}
async function comment(marker, body) {
const markerText = `<!-- pr-standards:${marker} -->`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
});
const existing = comments.find(c => c.body.includes(markerText));
if (existing) return;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: markerText + '\n' + body
});
}
// Step 1: Check title format
// Matches: feat:, feat(scope):, feat (scope):, etc.
const titlePattern = /^(feat|fix|docs|chore|refactor|test)\s*(\([a-zA-Z0-9-]+\))?\s*:/;
const hasValidTitle = titlePattern.test(title);
if (!hasValidTitle) {
await addLabel('needs:title');
await comment('title', `Hey! Your PR title \`${title}\` doesn't follow conventional commit format.
Please update it to start with one of:
- \`feat:\` or \`feat(scope):\` new feature
- \`fix:\` or \`fix(scope):\` bug fix
- \`docs:\` or \`docs(scope):\` documentation changes
- \`chore:\` or \`chore(scope):\` maintenance tasks
- \`refactor:\` or \`refactor(scope):\` code refactoring
- \`test:\` or \`test(scope):\` adding or updating tests
Where \`scope\` is the package name (e.g., \`app\`, \`desktop\`, \`opencode\`).
See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#pr-titles) for details.`);
return;
}
await removeLabel('needs:title');
// Step 2: Check for linked issue (skip for docs/refactor PRs)
const skipIssueCheck = /^(docs|refactor)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
if (skipIssueCheck) {
await removeLabel('needs:issue');
console.log('Skipping issue check for docs/refactor PR');
return;
}
const query = `
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
closingIssuesReferences(first: 1) {
totalCount
}
}
}
}
`;
const result = await github.graphql(query, {
owner: context.repo.owner,
repo: context.repo.repo,
number: pr.number
});
const linkedIssues = result.repository.pullRequest.closingIssuesReferences.totalCount;
if (linkedIssues === 0) {
await addLabel('needs:issue');
await comment('issue', `Thanks for your contribution!
This PR doesn't have a linked issue. All PRs must reference an existing issue.
Please:
1. Open an issue describing the bug/feature (if one doesn't exist)
2. Add \`Fixes #<number>\` or \`Closes #<number>\` to this PR description
See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#issue-first-policy) for details.`);
return;
}
await removeLabel('needs:issue');
console.log('PR meets all standards');
+17 -13
View File
@@ -92,7 +92,7 @@ jobs:
publish-tauri:
needs: publish
continue-on-error: true
continue-on-error: false
strategy:
fail-fast: false
matrix:
@@ -177,8 +177,22 @@ jobs:
cargo tauri --version
- name: Build and upload artifacts
timeout-minutes: 20
uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
uses: Wandalen/wretry.action@v3
timeout-minutes: 60
with:
attempt_limit: 3
attempt_delay: 10000
action: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
with: |
projectPath: packages/desktop
uploadWorkflowArtifacts: true
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose
updaterJsonPreferNsis: true
releaseId: ${{ needs.publish.outputs.release }}
tagName: ${{ needs.publish.outputs.tag }}
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
releaseDraft: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
@@ -190,16 +204,6 @@ jobs:
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
with:
projectPath: packages/desktop
uploadWorkflowArtifacts: true
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose
updaterJsonPreferNsis: true
releaseId: ${{ needs.publish.outputs.release }}
tagName: ${{ needs.publish.outputs.tag }}
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
releaseDraft: true
publish-release:
needs:
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENCODE_PERMISSION: '{ "bash": { "gh*": "allow", "gh pr review*": "deny", "*": "deny" } }'
OPENCODE_PERMISSION: '{ "bash": { "*": "deny", "gh*": "allow", "gh pr review*": "deny" } }'
PR_TITLE: ${{ steps.pr-details.outputs.title }}
run: |
PR_BODY=$(jq -r .body pr_data.json)
+1 -1
View File
@@ -9,7 +9,7 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
stats:
if: github.repository == 'sst/opencode'
if: github.repository == 'anomalyco/opencode'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
+114 -6
View File
@@ -8,7 +8,29 @@ on:
workflow_dispatch:
jobs:
test:
runs-on: blacksmith-4vcpu-ubuntu-2404
name: test (${{ matrix.settings.name }})
strategy:
fail-fast: false
matrix:
settings:
- name: linux
host: blacksmith-4vcpu-ubuntu-2404
playwright: bunx playwright install --with-deps
workdir: .
command: |
git config --global user.email "bot@opencode.ai"
git config --global user.name "opencode"
bun turbo typecheck
bun turbo test
- name: windows
host: windows-latest
playwright: bunx playwright install
workdir: packages/app
command: bun test:e2e:local
runs-on: ${{ matrix.settings.host }}
defaults:
run:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -18,11 +40,97 @@ jobs:
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: run
- name: Install Playwright browsers
working-directory: packages/app
run: ${{ matrix.settings.playwright }}
- name: Set OS-specific paths
run: |
git config --global user.email "bot@opencode.ai"
git config --global user.name "opencode"
bun turbo typecheck
bun turbo test
if [ "${{ runner.os }}" = "Windows" ]; then
printf '%s\n' "OPENCODE_E2E_ROOT=${{ runner.temp }}\\opencode-e2e" >> "$GITHUB_ENV"
printf '%s\n' "OPENCODE_TEST_HOME=${{ runner.temp }}\\opencode-e2e\\home" >> "$GITHUB_ENV"
printf '%s\n' "XDG_DATA_HOME=${{ runner.temp }}\\opencode-e2e\\share" >> "$GITHUB_ENV"
printf '%s\n' "XDG_CACHE_HOME=${{ runner.temp }}\\opencode-e2e\\cache" >> "$GITHUB_ENV"
printf '%s\n' "XDG_CONFIG_HOME=${{ runner.temp }}\\opencode-e2e\\config" >> "$GITHUB_ENV"
printf '%s\n' "XDG_STATE_HOME=${{ runner.temp }}\\opencode-e2e\\state" >> "$GITHUB_ENV"
printf '%s\n' "MODELS_DEV_API_JSON=${{ github.workspace }}\\packages\\opencode\\test\\tool\\fixtures\\models-api.json" >> "$GITHUB_ENV"
else
printf '%s\n' "OPENCODE_E2E_ROOT=${{ runner.temp }}/opencode-e2e" >> "$GITHUB_ENV"
printf '%s\n' "OPENCODE_TEST_HOME=${{ runner.temp }}/opencode-e2e/home" >> "$GITHUB_ENV"
printf '%s\n' "XDG_DATA_HOME=${{ runner.temp }}/opencode-e2e/share" >> "$GITHUB_ENV"
printf '%s\n' "XDG_CACHE_HOME=${{ runner.temp }}/opencode-e2e/cache" >> "$GITHUB_ENV"
printf '%s\n' "XDG_CONFIG_HOME=${{ runner.temp }}/opencode-e2e/config" >> "$GITHUB_ENV"
printf '%s\n' "XDG_STATE_HOME=${{ runner.temp }}/opencode-e2e/state" >> "$GITHUB_ENV"
printf '%s\n' "MODELS_DEV_API_JSON=${{ github.workspace }}/packages/opencode/test/tool/fixtures/models-api.json" >> "$GITHUB_ENV"
fi
- name: Seed opencode data
if: matrix.settings.name != 'windows'
working-directory: packages/opencode
run: bun script/seed-e2e.ts
env:
MODELS_DEV_API_JSON: ${{ env.MODELS_DEV_API_JSON }}
OPENCODE_DISABLE_MODELS_FETCH: "true"
OPENCODE_DISABLE_SHARE: "true"
OPENCODE_DISABLE_LSP_DOWNLOAD: "true"
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true"
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true"
OPENCODE_TEST_HOME: ${{ env.OPENCODE_TEST_HOME }}
XDG_DATA_HOME: ${{ env.XDG_DATA_HOME }}
XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }}
XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }}
XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }}
OPENCODE_E2E_PROJECT_DIR: ${{ github.workspace }}
OPENCODE_E2E_SESSION_TITLE: "E2E Session"
OPENCODE_E2E_MESSAGE: "Seeded for UI e2e"
OPENCODE_E2E_MODEL: "opencode/gpt-5-nano"
- name: Run opencode server
if: matrix.settings.name != 'windows'
working-directory: packages/opencode
run: bun dev -- --print-logs --log-level WARN serve --port 4096 --hostname 127.0.0.1 &
env:
MODELS_DEV_API_JSON: ${{ env.MODELS_DEV_API_JSON }}
OPENCODE_DISABLE_MODELS_FETCH: "true"
OPENCODE_DISABLE_SHARE: "true"
OPENCODE_DISABLE_LSP_DOWNLOAD: "true"
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true"
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true"
OPENCODE_TEST_HOME: ${{ env.OPENCODE_TEST_HOME }}
XDG_DATA_HOME: ${{ env.XDG_DATA_HOME }}
XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }}
XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }}
XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }}
OPENCODE_CLIENT: "app"
- name: Wait for opencode server
if: matrix.settings.name != 'windows'
run: |
for i in {1..120}; do
curl -fsS "http://127.0.0.1:4096/global/health" > /dev/null && exit 0
sleep 1
done
exit 1
- name: run
working-directory: ${{ matrix.settings.workdir }}
run: ${{ matrix.settings.command }}
env:
CI: true
MODELS_DEV_API_JSON: ${{ env.MODELS_DEV_API_JSON }}
OPENCODE_DISABLE_MODELS_FETCH: "true"
OPENCODE_DISABLE_SHARE: "true"
OPENCODE_DISABLE_LSP_DOWNLOAD: "true"
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true"
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true"
OPENCODE_TEST_HOME: ${{ env.OPENCODE_TEST_HOME }}
XDG_DATA_HOME: ${{ env.XDG_DATA_HOME }}
XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }}
XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }}
XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }}
PLAYWRIGHT_SERVER_HOST: "127.0.0.1"
PLAYWRIGHT_SERVER_PORT: "4096"
VITE_OPENCODE_SERVER_HOST: "127.0.0.1"
VITE_OPENCODE_SERVER_PORT: "4096"
OPENCODE_CLIENT: "app"
timeout-minutes: 30
+64 -28
View File
@@ -10,22 +10,26 @@ on:
- "bun.lock"
- "package.json"
- "packages/*/package.json"
- "flake.lock"
- ".github/workflows/update-nix-hashes.yml"
pull_request:
paths:
- "bun.lock"
- "package.json"
- "packages/*/package.json"
- "flake.lock"
- ".github/workflows/update-nix-hashes.yml"
jobs:
update:
update-node-modules-hashes:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: blacksmith-4vcpu-ubuntu-2404
env:
SYSTEM: x86_64-linux
TITLE: node_modules hashes
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
@@ -33,39 +37,76 @@ jobs:
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
- name: Setup Nix
uses: DeterminateSystems/nix-installer-action@v20
uses: nixbuild/nix-quick-install-action@v34
- name: Configure git
run: |
git config --global user.email "action@github.com"
git config --global user.name "Github Action"
- name: Update flake.lock
- name: Pull latest changes
env:
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
run: |
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
git pull --rebase --autostash origin "$BRANCH"
- name: Compute all node_modules hashes
run: |
set -euo pipefail
echo "📦 Updating flake.lock..."
nix flake update
echo "✅ flake.lock updated successfully"
- name: Update node_modules hash
run: |
set -euo pipefail
echo "🔄 Updating node_modules hash..."
nix/scripts/update-hashes.sh
echo "✅ node_modules hash updated successfully"
HASH_FILE="nix/hashes.json"
SYSTEMS="x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin"
- name: Commit hash changes
if [ ! -f "$HASH_FILE" ]; then
mkdir -p "$(dirname "$HASH_FILE")"
echo '{"nodeModules":{}}' > "$HASH_FILE"
fi
for SYSTEM in $SYSTEMS; do
echo "Computing hash for ${SYSTEM}..."
BUILD_LOG=$(mktemp)
trap 'rm -f "$BUILD_LOG"' EXIT
# The updater derivations use fakeHash, so they will fail and reveal the correct hash
UPDATER_ATTR=".#packages.x86_64-linux.${SYSTEM}_node_modules"
nix build "$UPDATER_ATTR" --no-link 2>&1 | tee "$BUILD_LOG" || true
CORRECT_HASH="$(grep -E 'got:\s+sha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | awk '{print $2}' | head -n1 || true)"
if [ -z "$CORRECT_HASH" ]; then
CORRECT_HASH="$(grep -A2 'hash mismatch' "$BUILD_LOG" | grep 'got:' | awk '{print $2}' | sed 's/sha256:/sha256-/' || true)"
fi
if [ -z "$CORRECT_HASH" ]; then
echo "Failed to determine correct node_modules hash for ${SYSTEM}."
cat "$BUILD_LOG"
exit 1
fi
echo " ${SYSTEM}: ${CORRECT_HASH}"
jq --arg sys "$SYSTEM" --arg h "$CORRECT_HASH" \
'.nodeModules[$sys] = $h' "$HASH_FILE" > "${HASH_FILE}.tmp"
mv "${HASH_FILE}.tmp" "$HASH_FILE"
done
echo "All hashes computed:"
cat "$HASH_FILE"
- name: Commit ${{ env.TITLE }} changes
env:
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
run: |
set -euo pipefail
echo "🔍 Checking for changes in tracked Nix files..."
HASH_FILE="nix/hashes.json"
echo "Checking for changes..."
summarize() {
local status="$1"
{
echo "### Nix Hash Update"
echo "### Nix $TITLE"
echo ""
echo "- ref: ${GITHUB_REF_NAME}"
echo "- status: ${status}"
@@ -76,27 +117,22 @@ jobs:
echo "" >> "$GITHUB_STEP_SUMMARY"
}
FILES=(flake.lock flake.nix nix/node-modules.nix nix/hashes.json)
FILES=("$HASH_FILE")
STATUS="$(git status --short -- "${FILES[@]}" || true)"
if [ -z "$STATUS" ]; then
echo "No changes detected. Hashes are already up to date."
echo "No changes detected."
summarize "no changes"
exit 0
fi
echo "📝 Changes detected:"
echo "Changes detected:"
echo "$STATUS"
echo "🔗 Staging files..."
git add "${FILES[@]}"
echo "💾 Committing changes..."
git commit -m "Update Nix flake.lock and hashes"
echo "✅ Changes committed"
git commit -m "chore: update nix node_modules hashes"
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
echo "🌳 Pulling latest from branch: $BRANCH"
git pull --rebase origin "$BRANCH"
echo "🚀 Pushing changes to branch: $BRANCH"
git pull --rebase --autostash origin "$BRANCH"
git push origin HEAD:"$BRANCH"
echo "Changes pushed successfully"
echo "Changes pushed successfully"
summarize "committed $(git rev-parse --short HEAD)"
+2
View File
@@ -20,7 +20,9 @@ opencode.json
a.out
target
.scripts
.direnv/
# Local dev files
opencode-dev
logs/
*.bun-build
+3 -1
View File
@@ -12,6 +12,8 @@ You are a duplicate PR detection agent. When a PR is opened, your job is to sear
Use the github-pr-search tool to search for PRs that might be addressing the same issue or feature.
IMPORTANT: The input will contain a line `CURRENT_PR_NUMBER: NNNN`. This is the current PR number, you should not mark that the current PR as a duplicate of itself.
Search using keywords from the PR title and description. Try multiple searches with different relevant terms.
If you find potential duplicates:
@@ -19,6 +21,6 @@ If you find potential duplicates:
- List them with their titles and URLs
- Briefly explain why they might be related
If no duplicates are found, say so clearly.
If no duplicates are found, say so clearly. BUT ONLY SAY "No duplicate PRs found" (don't say anything else if no dups)
Keep your response concise and actionable.
+2 -2
View File
@@ -45,9 +45,9 @@ Desktop app issues:
#### zen
**Only** add if the issue mentions "zen" or "opencode zen". Zen is our gateway for coding models. **Do not** add for other gateways or inference providers.
**Only** add if the issue mentions "zen" or "opencode zen" or "opencode black".
If the issue doesn't have "zen" in it then don't add zen label
If the issue doesn't have "zen" or "opencode black" in it then don't add zen label
#### docs
+24
View File
@@ -0,0 +1,24 @@
---
description: "Bump AI sdk dependencies minor / patch versions only"
---
Please read @package.json and @packages/opencode/package.json.
Your job is to look into AI SDK dependencies, figure out if they have versions that can be upgraded (minor or patch versions ONLY no major ignore major changes).
I want a report of every dependency and the version that can be upgraded to.
What would be even better is if you can give me links to the changelog for each dependency, or at least some reference info so I can see what bugs were fixed or new features were added.
Consider using subagents for each dep to save your context window.
Here is a short list of some deps (please be comprehensive tho):
- "ai"
- "@ai-sdk/openai"
- "@ai-sdk/anthropic"
- "@openrouter/ai-sdk-provider"
- etc, etc
DO NOT upgrade the dependencies yet, just make a list of all dependencies and their versions that can be upgraded to minor or patch versions only.
Write up your findings to ai-sdk-updates.md
-5
View File
@@ -10,11 +10,6 @@
"options": {},
},
},
"permission": {
"bash": {
"ls foo": "ask",
},
},
"mcp": {
"context7": {
"type": "remote",
+39
View File
@@ -0,0 +1,39 @@
---
name: bun-file-io
description: Use this when you are working on file operations like reading, writing, scanning, or deleting files. It summarizes the preferred file APIs and patterns used in this repo. It also notes when to use filesystem helpers for directories.
---
## Use this when
- Editing file I/O or scans in `packages/opencode`
- Handling directory operations or external tools
## Bun file APIs (from Bun docs)
- `Bun.file(path)` is lazy; call `text`, `json`, `stream`, `arrayBuffer`, `bytes`, `exists` to read.
- Metadata: `file.size`, `file.type`, `file.name`.
- `Bun.write(dest, input)` writes strings, buffers, Blobs, Responses, or files.
- `Bun.file(...).delete()` deletes a file.
- `file.writer()` returns a FileSink for incremental writes.
- `Bun.Glob` + `Array.fromAsync(glob.scan({ cwd, absolute, onlyFiles, dot }))` for scans.
- Use `Bun.which` to find a binary, then `Bun.spawn` to run it.
- `Bun.readableStreamToText/Bytes/JSON` for stream output.
## When to use node:fs
- Use `node:fs/promises` for directories (`mkdir`, `readdir`, recursive operations).
## Repo patterns
- Prefer Bun APIs over Node `fs` for file access.
- Check `Bun.file(...).exists()` before reading.
- For binary/large files use `arrayBuffer()` and MIME checks via `file.type`.
- Use `Bun.Glob` + `Array.fromAsync` for scans.
- Decode tool stderr with `Bun.readableStreamToText`.
- For large writes, use `Bun.write(Bun.file(path), text)`.
## Quick checklist
- Use Bun APIs first.
- Use `path.join`/`path.resolve` for paths.
- Prefer promise `.catch(...)` over `try/catch` when possible.
-6
View File
@@ -1,6 +0,0 @@
---
name: test-skill
description: use this when asked to test skill
---
woah this is a test skill
+5
View File
@@ -45,6 +45,11 @@ export default tool({
}
const prs = result.items as PR[]
if (prs.length === 0) {
return `No other PRs found matching "${args.query}"`
}
const formatted = prs.map((pr) => `${pr.title}\n${pr.html_url}`).join("\n\n")
return `Found ${result.total_count} PRs (showing ${prs.length}):\n\n${formatted}`
+3 -3
View File
@@ -1,4 +1,4 @@
- To test opencode in the `packages/opencode` directory you can run `bun dev`
- To regenerate the javascript SDK, run ./packages/sdk/js/script/build.ts
- To test opencode in `packages/opencode`, run `bun dev`.
- To regenerate the JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
- the default branch in this repo is `dev`
- The default branch in this repo is `dev`.
+97 -4
View File
@@ -67,8 +67,49 @@ Replace `<platform>` with your platform (e.g., `darwin-arm64`, `linux-x64`).
- Core pieces:
- `packages/opencode`: OpenCode core business logic & server.
- `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui)
- `packages/app`: The shared web UI components, written in SolidJS
- `packages/desktop`: The native desktop app, built with Tauri (wraps `packages/app`)
- `packages/plugin`: Source for `@opencode-ai/plugin`
### Running the Web App
To test UI changes during development, run the web app:
```bash
bun run --cwd packages/app dev
```
This starts a local dev server at http://localhost:5173 (or similar port shown in output). Most UI changes can be tested here.
### Running the Desktop App
The desktop app is a native Tauri application that wraps the web UI.
To run the native desktop app:
```bash
bun run --cwd packages/desktop tauri dev
```
This starts the web dev server on http://localhost:1420 and opens the native window.
If you only want the web dev server (no native shell):
```bash
bun run --cwd packages/desktop dev
```
To create a production `dist/` and build the native app bundle:
```bash
bun run --cwd packages/desktop tauri build
```
This runs `bun run --cwd packages/desktop build` automatically via Tauris `beforeBuildCommand`.
> [!NOTE]
> Running the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions.
> [!NOTE]
> If you make changes to the API or SDK (e.g. `packages/opencode/src/server/server.ts`), run `./script/generate.ts` to regenerate the SDK and related files.
@@ -108,11 +149,63 @@ With that said, you may want to try these methods, as they might work for you.
## Pull Request Expectations
- Try to keep pull requests small and focused.
- Link relevant issue(s) in the description
### Issue First Policy
**All PRs must reference an existing issue.** Before opening a PR, open an issue describing the bug or feature. This helps maintainers triage and prevents duplicate work. PRs without a linked issue may be closed without review.
- Use `Fixes #123` or `Closes #123` in your PR description to link the issue
- For small fixes, a brief issue is fine - just enough context for maintainers to understand the problem
### General Requirements
- Keep pull requests small and focused
- Explain the issue and why your change fixes it
- Avoid having verbose LLM generated PR descriptions
- Before adding new functions or functionality, ensure that such behavior doesn't already exist elsewhere in the codebase.
- Before adding new functionality, ensure it doesn't already exist elsewhere in the codebase
### UI Changes
If your PR includes UI changes, please include screenshots or videos showing the before and after. This helps maintainers review faster and gives you quicker feedback.
### Logic Changes
For non-UI changes (bug fixes, new features, refactors), explain **how you verified it works**:
- What did you test?
- How can a reviewer reproduce/confirm the fix?
### No AI-Generated Walls of Text
Long, AI-generated PR descriptions and issues are not acceptable and may be ignored. Respect the maintainers' time:
- Write short, focused descriptions
- Explain what changed and why in your own words
- If you can't explain it briefly, your PR might be too large
### PR Titles
PR titles should follow conventional commit standards:
- `feat:` new feature or functionality
- `fix:` bug fix
- `docs:` documentation or README changes
- `chore:` maintenance tasks, dependency updates, etc.
- `refactor:` code refactoring without changing behavior
- `test:` adding or updating tests
You can optionally include a scope to indicate which package is affected:
- `feat(app):` feature in the app package
- `fix(desktop):` bug fix in the desktop package
- `chore(opencode):` maintenance in the opencode package
Examples:
- `docs: update contributing guidelines`
- `fix: resolve crash on startup`
- `feat: add dark mode support`
- `feat(app): add dark mode support`
- `fix(desktop): resolve crash on startup`
- `chore: bump dependency versions`
### Style Preferences
+6 -8
View File
@@ -26,9 +26,10 @@ curl -fsSL https://opencode.ai/install | bash
# Package managers
npm i -g opencode-ai@latest # or bun/pnpm/yarn
scoop bucket add extras; scoop install extras/opencode # Windows
scoop install opencode # Windows
choco install opencode # Windows
brew install opencode # macOS and Linux
brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)
brew install opencode # macOS and Linux (official brew formula, updated less)
paru -S opencode-bin # Arch Linux
mise use -g opencode # Any OS
nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch
@@ -51,6 +52,8 @@ OpenCode is also available as a desktop application. Download directly from the
```bash
# macOS (Homebrew)
brew install --cask opencode-desktop
# Windows (Scoop)
scoop bucket add extras; scoop install extras/opencode-desktop
```
#### Installation Directory
@@ -70,8 +73,7 @@ XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
### Agents
OpenCode includes two built-in agents you can switch between,
you can switch between these using the `Tab` key.
OpenCode includes two built-in agents you can switch between with the `Tab` key.
- **build** - Default, full access agent for development work
- **plan** - Read-only agent for analysis and code exploration
@@ -108,10 +110,6 @@ It's very similar to Claude Code in terms of capability. Here are the key differ
- 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 can drive it remotely from a mobile app. Meaning that the TUI frontend is just one of the possible clients.
#### What's the other repo?
The other confusingly named repo has no relation to this one. You can [read the story behind it here](https://x.com/thdxr/status/1933561254481666466).
---
**Join our community** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
+118
View File
@@ -0,0 +1,118 @@
<p align="center">
<a href="https://opencode.ai">
<picture>
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
</picture>
</a>
</p>
<p align="center">开源的 AI Coding Agent。</p>
<p align="center">
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
<a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
---
### 安装
```bash
# 直接安装 (YOLO)
curl -fsSL https://opencode.ai/install | bash
# 软件包管理器
npm i -g opencode-ai@latest # 也可使用 bun/pnpm/yarn
scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS 和 Linux(推荐,始终保持最新)
brew install opencode # macOS 和 Linux(官方 brew formula,更新频率较低)
paru -S opencode-bin # Arch Linux
mise use -g opencode # 任意系统
nix run nixpkgs#opencode # 或用 github:anomalyco/opencode 获取最新 dev 分支
```
> [!TIP]
> 安装前请先移除 0.1.x 之前的旧版本。
### 桌面应用程序 (BETA)
OpenCode 也提供桌面版应用。可直接从 [发布页 (releases page)](https://github.com/anomalyco/opencode/releases) 或 [opencode.ai/download](https://opencode.ai/download) 下载。
| 平台 | 下载文件 |
| --------------------- | ------------------------------------- |
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
| Windows | `opencode-desktop-windows-x64.exe` |
| Linux | `.deb``.rpm` 或 AppImage |
```bash
# macOS (Homebrew Cask)
brew install --cask opencode-desktop
# Windows (Scoop)
scoop bucket add extras; scoop install extras/opencode-desktop
```
#### 安装目录
安装脚本按照以下优先级决定安装路径:
1. `$OPENCODE_INSTALL_DIR` - 自定义安装目录
2. `$XDG_BIN_DIR` - 符合 XDG 基础目录规范的路径
3. `$HOME/bin` - 如果存在或可创建的用户二进制目录
4. `$HOME/.opencode/bin` - 默认备用路径
```bash
# 示例
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
```
### Agents
OpenCode 内置两种 Agent,可用 `Tab` 键快速切换:
- **build** - 默认模式,具备完整权限,适合开发工作
- **plan** - 只读模式,适合代码分析与探索
- 默认拒绝修改文件
- 运行 bash 命令前会询问
- 便于探索未知代码库或规划改动
另外还包含一个 **general** 子 Agent,用于复杂搜索和多步任务,内部使用,也可在消息中输入 `@general` 调用。
了解更多 [Agents](https://opencode.ai/docs/agents) 相关信息。
### 文档
更多配置说明请查看我们的 [**官方文档**](https://opencode.ai/docs)。
### 参与贡献
如有兴趣贡献代码,请在提交 PR 前阅读 [贡献指南 (Contributing Docs)](./CONTRIBUTING.md)。
### 基于 OpenCode 进行开发
如果你在项目名中使用了 “opencode”(如 “opencode-dashboard” 或 “opencode-mobile”),请在 README 里注明该项目不是 OpenCode 团队官方开发,且不存在隶属关系。
### 常见问题 (FAQ)
#### 这和 Claude Code 有什么不同?
功能上很相似,关键差异:
- 100% 开源。
- 不绑定特定提供商。推荐使用 [OpenCode Zen](https://opencode.ai/zen) 的模型,但也可搭配 Claude、OpenAI、Google 甚至本地模型。模型迭代会缩小差异、降低成本,因此保持 provider-agnostic 很重要。
- 内置 LSP 支持。
- 聚焦终端界面 (TUI)。OpenCode 由 Neovim 爱好者和 [terminal.shop](https://terminal.shop) 的创建者打造,会持续探索终端的极限。
- 客户端/服务器架构。可在本机运行,同时用移动设备远程驱动。TUI 只是众多潜在客户端之一。
#### 另一个同名的仓库是什么?
另一个名字相近的仓库与本项目无关。[点击这里了解背后故事](https://x.com/thdxr/status/1933561254481666466)。
---
**加入我们的社区** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
+5 -2
View File
@@ -26,9 +26,10 @@ curl -fsSL https://opencode.ai/install | bash
# 套件管理員
npm i -g opencode-ai@latest # 也可使用 bun/pnpm/yarn
scoop bucket add extras; scoop install extras/opencode # Windows
scoop install opencode # Windows
choco install opencode # Windows
brew install opencode # macOS 與 Linux
brew install anomalyco/tap/opencode # macOS 與 Linux(推薦,始終保持最新)
brew install opencode # macOS 與 Linux(官方 brew formula,更新頻率較低)
paru -S opencode-bin # Arch Linux
mise use -g github:anomalyco/opencode # 任何作業系統
nix run nixpkgs#opencode # 或使用 github:anomalyco/opencode 以取得最新開發分支
@@ -51,6 +52,8 @@ OpenCode 也提供桌面版應用程式。您可以直接從 [發佈頁面 (rele
```bash
# macOS (Homebrew Cask)
brew install --cask opencode-desktop
# Windows (Scoop)
scoop bucket add extras; scoop install extras/opencode-desktop
```
#### 安裝目錄
+41
View File
@@ -0,0 +1,41 @@
# Security
## Threat Model
### Overview
OpenCode is an AI-powered coding assistant that runs locally on your machine. It provides an agent system with access to powerful tools including shell execution, file operations, and web access.
### No Sandbox
OpenCode does **not** sandbox the agent. The permission system exists as a UX feature to help users stay aware of what actions the agent is taking - it prompts for confirmation before executing commands, writing files, etc. However, it is not designed to provide security isolation.
If you need true isolation, run OpenCode inside a Docker container or VM.
### Server Mode
Server mode is opt-in only. When enabled, set `OPENCODE_SERVER_PASSWORD` to require HTTP Basic Auth. Without this, the server runs unauthenticated (with a warning). It is the end user's responsibility to secure the server - any functionality it provides is not a vulnerability.
### Out of Scope
| Category | Rationale |
| ------------------------------- | ----------------------------------------------------------------------- |
| **Server access when opted-in** | If you enable server mode, API access is expected behavior |
| **Sandbox escapes** | The permission system is not a sandbox (see above) |
| **LLM provider data handling** | Data sent to your configured LLM provider is governed by their policies |
| **MCP server behavior** | External MCP servers you configure are outside our trust boundary |
| **Malicious config files** | Users control their own config; modifying it is not an attack vector |
---
# Reporting Security Issues
We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/anomalyco/opencode/security/advisories/new) tab.
The team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
## Escalation
If you do not receive an acknowledgement of your report within 6 business days, you may send an email to security@anoma.ly
+207 -189
View File
@@ -1,191 +1,209 @@
# Download Stats
| Date | GitHub Downloads | npm Downloads | Total |
| ---------- | ------------------- | ------------------- | ------------------- |
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
| 2025-10-02 | 440,852 (+7,261) | 348,099 (+6,357) | 788,951 (+13,618) |
| 2025-10-03 | 446,829 (+5,977) | 359,937 (+11,838) | 806,766 (+17,815) |
| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |
| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
| 2025-10-10 | 484,374 (+5,171) | 406,015 (+5,489) | 890,389 (+10,660) |
| 2025-10-11 | 488,427 (+4,053) | 414,699 (+8,684) | 903,126 (+12,737) |
| 2025-10-12 | 492,125 (+3,698) | 418,745 (+4,046) | 910,870 (+7,744) |
| 2025-10-14 | 505,130 (+13,005) | 429,286 (+10,541) | 934,416 (+23,546) |
| 2025-10-15 | 512,717 (+7,587) | 439,290 (+10,004) | 952,007 (+17,591) |
| 2025-10-16 | 517,719 (+5,002) | 447,137 (+7,847) | 964,856 (+12,849) |
| 2025-10-17 | 526,239 (+8,520) | 457,467 (+10,330) | 983,706 (+18,850) |
| 2025-10-18 | 531,564 (+5,325) | 465,272 (+7,805) | 996,836 (+13,130) |
| 2025-10-19 | 536,209 (+4,645) | 469,078 (+3,806) | 1,005,287 (+8,451) |
| 2025-10-20 | 541,264 (+5,055) | 472,952 (+3,874) | 1,014,216 (+8,929) |
| 2025-10-21 | 548,721 (+7,457) | 479,703 (+6,751) | 1,028,424 (+14,208) |
| 2025-10-22 | 557,949 (+9,228) | 491,395 (+11,692) | 1,049,344 (+20,920) |
| 2025-10-23 | 564,716 (+6,767) | 498,736 (+7,341) | 1,063,452 (+14,108) |
| 2025-10-24 | 572,692 (+7,976) | 506,905 (+8,169) | 1,079,597 (+16,145) |
| 2025-10-25 | 578,927 (+6,235) | 516,129 (+9,224) | 1,095,056 (+15,459) |
| 2025-10-26 | 584,409 (+5,482) | 521,179 (+5,050) | 1,105,588 (+10,532) |
| 2025-10-27 | 589,999 (+5,590) | 526,001 (+4,822) | 1,116,000 (+10,412) |
| 2025-10-28 | 595,776 (+5,777) | 532,438 (+6,437) | 1,128,214 (+12,214) |
| 2025-10-29 | 606,259 (+10,483) | 542,064 (+9,626) | 1,148,323 (+20,109) |
| 2025-10-30 | 613,746 (+7,487) | 542,064 (+0) | 1,155,810 (+7,487) |
| 2025-10-30 | 617,846 (+4,100) | 555,026 (+12,962) | 1,172,872 (+17,062) |
| 2025-10-31 | 626,612 (+8,766) | 564,579 (+9,553) | 1,191,191 (+18,319) |
| 2025-11-01 | 636,100 (+9,488) | 581,806 (+17,227) | 1,217,906 (+26,715) |
| 2025-11-02 | 644,067 (+7,967) | 590,004 (+8,198) | 1,234,071 (+16,165) |
| 2025-11-03 | 653,130 (+9,063) | 597,139 (+7,135) | 1,250,269 (+16,198) |
| 2025-11-04 | 663,912 (+10,782) | 608,056 (+10,917) | 1,271,968 (+21,699) |
| 2025-11-05 | 675,074 (+11,162) | 619,690 (+11,634) | 1,294,764 (+22,796) |
| 2025-11-06 | 686,252 (+11,178) | 630,885 (+11,195) | 1,317,137 (+22,373) |
| 2025-11-07 | 696,646 (+10,394) | 642,146 (+11,261) | 1,338,792 (+21,655) |
| 2025-11-08 | 706,035 (+9,389) | 653,489 (+11,343) | 1,359,524 (+20,732) |
| 2025-11-09 | 713,462 (+7,427) | 660,459 (+6,970) | 1,373,921 (+14,397) |
| 2025-11-10 | 722,288 (+8,826) | 668,225 (+7,766) | 1,390,513 (+16,592) |
| 2025-11-11 | 729,769 (+7,481) | 677,501 (+9,276) | 1,407,270 (+16,757) |
| 2025-11-12 | 740,180 (+10,411) | 686,454 (+8,953) | 1,426,634 (+19,364) |
| 2025-11-13 | 749,905 (+9,725) | 696,157 (+9,703) | 1,446,062 (+19,428) |
| 2025-11-14 | 759,928 (+10,023) | 705,237 (+9,080) | 1,465,165 (+19,103) |
| 2025-11-15 | 765,955 (+6,027) | 712,870 (+7,633) | 1,478,825 (+13,660) |
| 2025-11-16 | 771,069 (+5,114) | 716,596 (+3,726) | 1,487,665 (+8,840) |
| 2025-11-17 | 780,161 (+9,092) | 723,339 (+6,743) | 1,503,500 (+15,835) |
| 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) |
| 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) |
| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) |
| 2025-11-21 | 826,309 (+11,689) | 769,307 (+11,400) | 1,595,616 (+23,089) |
| 2025-11-22 | 837,269 (+10,960) | 780,996 (+11,689) | 1,618,265 (+22,649) |
| 2025-11-23 | 846,609 (+9,340) | 795,069 (+14,073) | 1,641,678 (+23,413) |
| 2025-11-24 | 856,733 (+10,124) | 804,033 (+8,964) | 1,660,766 (+19,088) |
| 2025-11-25 | 869,423 (+12,690) | 817,339 (+13,306) | 1,686,762 (+25,996) |
| 2025-11-26 | 881,414 (+11,991) | 832,518 (+15,179) | 1,713,932 (+27,170) |
| 2025-11-27 | 893,960 (+12,546) | 846,180 (+13,662) | 1,740,140 (+26,208) |
| 2025-11-28 | 901,741 (+7,781) | 856,482 (+10,302) | 1,758,223 (+18,083) |
| 2025-11-29 | 908,689 (+6,948) | 863,361 (+6,879) | 1,772,050 (+13,827) |
| 2025-11-30 | 916,116 (+7,427) | 870,194 (+6,833) | 1,786,310 (+14,260) |
| 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
| 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) |
| 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) |
| 2025-12-05 | 977,996 (+12,385) | 930,616 (+14,145) | 1,908,612 (+26,530) |
| 2025-12-06 | 987,884 (+9,888) | 943,773 (+13,157) | 1,931,657 (+23,045) |
| 2025-12-07 | 994,046 (+6,162) | 951,425 (+7,652) | 1,945,471 (+13,814) |
| 2025-12-08 | 1,000,898 (+6,852) | 957,149 (+5,724) | 1,958,047 (+12,576) |
| 2025-12-09 | 1,011,488 (+10,590) | 973,922 (+16,773) | 1,985,410 (+27,363) |
| 2025-12-10 | 1,025,891 (+14,403) | 991,708 (+17,786) | 2,017,599 (+32,189) |
| 2025-12-11 | 1,045,110 (+19,219) | 1,010,559 (+18,851) | 2,055,669 (+38,070) |
| 2025-12-12 | 1,061,340 (+16,230) | 1,030,838 (+20,279) | 2,092,178 (+36,509) |
| 2025-12-13 | 1,073,561 (+12,221) | 1,044,608 (+13,770) | 2,118,169 (+25,991) |
| 2025-12-14 | 1,082,042 (+8,481) | 1,052,425 (+7,817) | 2,134,467 (+16,298) |
| 2025-12-15 | 1,093,632 (+11,590) | 1,059,078 (+6,653) | 2,152,710 (+18,243) |
| 2025-12-16 | 1,120,477 (+26,845) | 1,078,022 (+18,944) | 2,198,499 (+45,789) |
| 2025-12-17 | 1,151,067 (+30,590) | 1,097,661 (+19,639) | 2,248,728 (+50,229) |
| 2025-12-18 | 1,178,658 (+27,591) | 1,113,418 (+15,757) | 2,292,076 (+43,348) |
| 2025-12-19 | 1,203,485 (+24,827) | 1,129,698 (+16,280) | 2,333,183 (+41,107) |
| 2025-12-20 | 1,223,000 (+19,515) | 1,146,258 (+16,560) | 2,369,258 (+36,075) |
| 2025-12-21 | 1,242,675 (+19,675) | 1,158,909 (+12,651) | 2,401,584 (+32,326) |
| 2025-12-22 | 1,262,522 (+19,847) | 1,169,121 (+10,212) | 2,431,643 (+30,059) |
| 2025-12-23 | 1,286,548 (+24,026) | 1,186,439 (+17,318) | 2,472,987 (+41,344) |
| 2025-12-24 | 1,309,323 (+22,775) | 1,203,767 (+17,328) | 2,513,090 (+40,103) |
| 2025-12-25 | 1,333,032 (+23,709) | 1,217,283 (+13,516) | 2,550,315 (+37,225) |
| 2025-12-26 | 1,352,411 (+19,379) | 1,227,615 (+10,332) | 2,580,026 (+29,711) |
| 2025-12-27 | 1,371,771 (+19,360) | 1,238,236 (+10,621) | 2,610,007 (+29,981) |
| 2025-12-28 | 1,390,388 (+18,617) | 1,245,690 (+7,454) | 2,636,078 (+26,071) |
| 2025-12-29 | 1,415,560 (+25,172) | 1,257,101 (+11,411) | 2,672,661 (+36,583) |
| 2025-12-30 | 1,445,450 (+29,890) | 1,272,689 (+15,588) | 2,718,139 (+45,478) |
| 2025-12-31 | 1,479,598 (+34,148) | 1,293,235 (+20,546) | 2,772,833 (+54,694) |
| 2026-01-01 | 1,508,883 (+29,285) | 1,309,874 (+16,639) | 2,818,757 (+45,924) |
| 2026-01-02 | 1,563,474 (+54,591) | 1,320,959 (+11,085) | 2,884,433 (+65,676) |
| Date | GitHub Downloads | npm Downloads | Total |
| ---------- | -------------------- | -------------------- | -------------------- |
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
| 2025-10-02 | 440,852 (+7,261) | 348,099 (+6,357) | 788,951 (+13,618) |
| 2025-10-03 | 446,829 (+5,977) | 359,937 (+11,838) | 806,766 (+17,815) |
| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |
| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
| 2025-10-10 | 484,374 (+5,171) | 406,015 (+5,489) | 890,389 (+10,660) |
| 2025-10-11 | 488,427 (+4,053) | 414,699 (+8,684) | 903,126 (+12,737) |
| 2025-10-12 | 492,125 (+3,698) | 418,745 (+4,046) | 910,870 (+7,744) |
| 2025-10-14 | 505,130 (+13,005) | 429,286 (+10,541) | 934,416 (+23,546) |
| 2025-10-15 | 512,717 (+7,587) | 439,290 (+10,004) | 952,007 (+17,591) |
| 2025-10-16 | 517,719 (+5,002) | 447,137 (+7,847) | 964,856 (+12,849) |
| 2025-10-17 | 526,239 (+8,520) | 457,467 (+10,330) | 983,706 (+18,850) |
| 2025-10-18 | 531,564 (+5,325) | 465,272 (+7,805) | 996,836 (+13,130) |
| 2025-10-19 | 536,209 (+4,645) | 469,078 (+3,806) | 1,005,287 (+8,451) |
| 2025-10-20 | 541,264 (+5,055) | 472,952 (+3,874) | 1,014,216 (+8,929) |
| 2025-10-21 | 548,721 (+7,457) | 479,703 (+6,751) | 1,028,424 (+14,208) |
| 2025-10-22 | 557,949 (+9,228) | 491,395 (+11,692) | 1,049,344 (+20,920) |
| 2025-10-23 | 564,716 (+6,767) | 498,736 (+7,341) | 1,063,452 (+14,108) |
| 2025-10-24 | 572,692 (+7,976) | 506,905 (+8,169) | 1,079,597 (+16,145) |
| 2025-10-25 | 578,927 (+6,235) | 516,129 (+9,224) | 1,095,056 (+15,459) |
| 2025-10-26 | 584,409 (+5,482) | 521,179 (+5,050) | 1,105,588 (+10,532) |
| 2025-10-27 | 589,999 (+5,590) | 526,001 (+4,822) | 1,116,000 (+10,412) |
| 2025-10-28 | 595,776 (+5,777) | 532,438 (+6,437) | 1,128,214 (+12,214) |
| 2025-10-29 | 606,259 (+10,483) | 542,064 (+9,626) | 1,148,323 (+20,109) |
| 2025-10-30 | 613,746 (+7,487) | 542,064 (+0) | 1,155,810 (+7,487) |
| 2025-10-30 | 617,846 (+4,100) | 555,026 (+12,962) | 1,172,872 (+17,062) |
| 2025-10-31 | 626,612 (+8,766) | 564,579 (+9,553) | 1,191,191 (+18,319) |
| 2025-11-01 | 636,100 (+9,488) | 581,806 (+17,227) | 1,217,906 (+26,715) |
| 2025-11-02 | 644,067 (+7,967) | 590,004 (+8,198) | 1,234,071 (+16,165) |
| 2025-11-03 | 653,130 (+9,063) | 597,139 (+7,135) | 1,250,269 (+16,198) |
| 2025-11-04 | 663,912 (+10,782) | 608,056 (+10,917) | 1,271,968 (+21,699) |
| 2025-11-05 | 675,074 (+11,162) | 619,690 (+11,634) | 1,294,764 (+22,796) |
| 2025-11-06 | 686,252 (+11,178) | 630,885 (+11,195) | 1,317,137 (+22,373) |
| 2025-11-07 | 696,646 (+10,394) | 642,146 (+11,261) | 1,338,792 (+21,655) |
| 2025-11-08 | 706,035 (+9,389) | 653,489 (+11,343) | 1,359,524 (+20,732) |
| 2025-11-09 | 713,462 (+7,427) | 660,459 (+6,970) | 1,373,921 (+14,397) |
| 2025-11-10 | 722,288 (+8,826) | 668,225 (+7,766) | 1,390,513 (+16,592) |
| 2025-11-11 | 729,769 (+7,481) | 677,501 (+9,276) | 1,407,270 (+16,757) |
| 2025-11-12 | 740,180 (+10,411) | 686,454 (+8,953) | 1,426,634 (+19,364) |
| 2025-11-13 | 749,905 (+9,725) | 696,157 (+9,703) | 1,446,062 (+19,428) |
| 2025-11-14 | 759,928 (+10,023) | 705,237 (+9,080) | 1,465,165 (+19,103) |
| 2025-11-15 | 765,955 (+6,027) | 712,870 (+7,633) | 1,478,825 (+13,660) |
| 2025-11-16 | 771,069 (+5,114) | 716,596 (+3,726) | 1,487,665 (+8,840) |
| 2025-11-17 | 780,161 (+9,092) | 723,339 (+6,743) | 1,503,500 (+15,835) |
| 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) |
| 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) |
| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) |
| 2025-11-21 | 826,309 (+11,689) | 769,307 (+11,400) | 1,595,616 (+23,089) |
| 2025-11-22 | 837,269 (+10,960) | 780,996 (+11,689) | 1,618,265 (+22,649) |
| 2025-11-23 | 846,609 (+9,340) | 795,069 (+14,073) | 1,641,678 (+23,413) |
| 2025-11-24 | 856,733 (+10,124) | 804,033 (+8,964) | 1,660,766 (+19,088) |
| 2025-11-25 | 869,423 (+12,690) | 817,339 (+13,306) | 1,686,762 (+25,996) |
| 2025-11-26 | 881,414 (+11,991) | 832,518 (+15,179) | 1,713,932 (+27,170) |
| 2025-11-27 | 893,960 (+12,546) | 846,180 (+13,662) | 1,740,140 (+26,208) |
| 2025-11-28 | 901,741 (+7,781) | 856,482 (+10,302) | 1,758,223 (+18,083) |
| 2025-11-29 | 908,689 (+6,948) | 863,361 (+6,879) | 1,772,050 (+13,827) |
| 2025-11-30 | 916,116 (+7,427) | 870,194 (+6,833) | 1,786,310 (+14,260) |
| 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
| 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) |
| 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) |
| 2025-12-05 | 977,996 (+12,385) | 930,616 (+14,145) | 1,908,612 (+26,530) |
| 2025-12-06 | 987,884 (+9,888) | 943,773 (+13,157) | 1,931,657 (+23,045) |
| 2025-12-07 | 994,046 (+6,162) | 951,425 (+7,652) | 1,945,471 (+13,814) |
| 2025-12-08 | 1,000,898 (+6,852) | 957,149 (+5,724) | 1,958,047 (+12,576) |
| 2025-12-09 | 1,011,488 (+10,590) | 973,922 (+16,773) | 1,985,410 (+27,363) |
| 2025-12-10 | 1,025,891 (+14,403) | 991,708 (+17,786) | 2,017,599 (+32,189) |
| 2025-12-11 | 1,045,110 (+19,219) | 1,010,559 (+18,851) | 2,055,669 (+38,070) |
| 2025-12-12 | 1,061,340 (+16,230) | 1,030,838 (+20,279) | 2,092,178 (+36,509) |
| 2025-12-13 | 1,073,561 (+12,221) | 1,044,608 (+13,770) | 2,118,169 (+25,991) |
| 2025-12-14 | 1,082,042 (+8,481) | 1,052,425 (+7,817) | 2,134,467 (+16,298) |
| 2025-12-15 | 1,093,632 (+11,590) | 1,059,078 (+6,653) | 2,152,710 (+18,243) |
| 2025-12-16 | 1,120,477 (+26,845) | 1,078,022 (+18,944) | 2,198,499 (+45,789) |
| 2025-12-17 | 1,151,067 (+30,590) | 1,097,661 (+19,639) | 2,248,728 (+50,229) |
| 2025-12-18 | 1,178,658 (+27,591) | 1,113,418 (+15,757) | 2,292,076 (+43,348) |
| 2025-12-19 | 1,203,485 (+24,827) | 1,129,698 (+16,280) | 2,333,183 (+41,107) |
| 2025-12-20 | 1,223,000 (+19,515) | 1,146,258 (+16,560) | 2,369,258 (+36,075) |
| 2025-12-21 | 1,242,675 (+19,675) | 1,158,909 (+12,651) | 2,401,584 (+32,326) |
| 2025-12-22 | 1,262,522 (+19,847) | 1,169,121 (+10,212) | 2,431,643 (+30,059) |
| 2025-12-23 | 1,286,548 (+24,026) | 1,186,439 (+17,318) | 2,472,987 (+41,344) |
| 2025-12-24 | 1,309,323 (+22,775) | 1,203,767 (+17,328) | 2,513,090 (+40,103) |
| 2025-12-25 | 1,333,032 (+23,709) | 1,217,283 (+13,516) | 2,550,315 (+37,225) |
| 2025-12-26 | 1,352,411 (+19,379) | 1,227,615 (+10,332) | 2,580,026 (+29,711) |
| 2025-12-27 | 1,371,771 (+19,360) | 1,238,236 (+10,621) | 2,610,007 (+29,981) |
| 2025-12-28 | 1,390,388 (+18,617) | 1,245,690 (+7,454) | 2,636,078 (+26,071) |
| 2025-12-29 | 1,415,560 (+25,172) | 1,257,101 (+11,411) | 2,672,661 (+36,583) |
| 2025-12-30 | 1,445,450 (+29,890) | 1,272,689 (+15,588) | 2,718,139 (+45,478) |
| 2025-12-31 | 1,479,598 (+34,148) | 1,293,235 (+20,546) | 2,772,833 (+54,694) |
| 2026-01-01 | 1,508,883 (+29,285) | 1,309,874 (+16,639) | 2,818,757 (+45,924) |
| 2026-01-02 | 1,563,474 (+54,591) | 1,320,959 (+11,085) | 2,884,433 (+65,676) |
| 2026-01-03 | 1,618,065 (+54,591) | 1,331,914 (+10,955) | 2,949,979 (+65,546) |
| 2026-01-04 | 1,672,656 (+39,702) | 1,339,883 (+7,969) | 3,012,539 (+62,560) |
| 2026-01-05 | 1,738,171 (+65,515) | 1,353,043 (+13,160) | 3,091,214 (+78,675) |
| 2026-01-06 | 1,960,988 (+222,817) | 1,377,377 (+24,334) | 3,338,365 (+247,151) |
| 2026-01-07 | 2,123,239 (+162,251) | 1,398,648 (+21,271) | 3,521,887 (+183,522) |
| 2026-01-08 | 2,272,630 (+149,391) | 1,432,480 (+33,832) | 3,705,110 (+183,223) |
| 2026-01-09 | 2,443,565 (+170,935) | 1,469,451 (+36,971) | 3,913,016 (+207,906) |
| 2026-01-10 | 2,632,023 (+188,458) | 1,503,670 (+34,219) | 4,135,693 (+222,677) |
| 2026-01-11 | 2,836,394 (+204,371) | 1,530,479 (+26,809) | 4,366,873 (+231,180) |
| 2026-01-12 | 3,053,594 (+217,200) | 1,553,671 (+23,192) | 4,607,265 (+240,392) |
| 2026-01-13 | 3,297,078 (+243,484) | 1,595,062 (+41,391) | 4,892,140 (+284,875) |
| 2026-01-14 | 3,568,928 (+271,850) | 1,645,362 (+50,300) | 5,214,290 (+322,150) |
| 2026-01-16 | 4,121,550 (+552,622) | 1,754,418 (+109,056) | 5,875,968 (+661,678) |
| 2026-01-17 | 4,389,558 (+268,008) | 1,805,315 (+50,897) | 6,194,873 (+318,905) |
| 2026-01-18 | 4,627,623 (+238,065) | 1,839,171 (+33,856) | 6,466,794 (+271,921) |
| 2026-01-19 | 4,861,108 (+233,485) | 1,863,112 (+23,941) | 6,724,220 (+257,426) |
| 2026-01-20 | 5,128,999 (+267,891) | 1,903,665 (+40,553) | 7,032,664 (+308,444) |
| 2026-01-21 | 5,444,842 (+315,843) | 1,962,531 (+58,866) | 7,407,373 (+374,709) |
+69 -10
View File
@@ -1,12 +1,71 @@
## Style Guide
- Try to keep things in one function unless composable or reusable
- DO NOT do unnecessary destructuring of variables
- DO NOT use `else` statements unless necessary
- DO NOT use `try`/`catch` if it can be avoided
- AVOID `try`/`catch` where possible
- AVOID `else` statements
- AVOID using `any` type
- AVOID `let` statements
- PREFER single word variable names where possible
- Use as many bun apis as possible like Bun.file()
- Keep things in one function unless composable or reusable
- Avoid unnecessary destructuring. Instead of `const { a, b } = obj`, use `obj.a` and `obj.b` to preserve context
- Avoid `try`/`catch` where possible
- Avoid using the `any` type
- Prefer single word variable names where possible
- Use Bun APIs when possible, like `Bun.file()`
# Avoid let statements
We don't like `let` statements, especially combined with if/else statements.
Prefer `const`.
Good:
```ts
const foo = condition ? 1 : 2
```
Bad:
```ts
let foo
if (condition) foo = 1
else foo = 2
```
# Avoid else statements
Prefer early returns or using an `iife` to avoid else statements.
Good:
```ts
function foo() {
if (condition) return 1
return 2
}
```
Bad:
```ts
function foo() {
if (condition) return 1
else return 2
}
```
# Prefer single word naming
Try your best to find a single word name for your variables, functions, etc.
Only use multiple words if you cannot.
Good:
```ts
const foo = 1
const bar = 2
const baz = 3
```
Bad:
```ts
const fooBar = 1
const barBaz = 2
const bazFoo = 3
```
+221 -109
View File
@@ -16,13 +16,14 @@
"@tsconfig/bun": "catalog:",
"husky": "9.1.7",
"prettier": "3.6.2",
"semver": "^7.6.0",
"sst": "3.17.23",
"turbo": "2.5.6",
},
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -32,6 +33,7 @@
"@solid-primitives/active-element": "2.1.3",
"@solid-primitives/audio": "1.4.2",
"@solid-primitives/event-bus": "1.1.2",
"@solid-primitives/i18n": "2.2.1",
"@solid-primitives/media": "2.3.3",
"@solid-primitives/resize-observer": "2.1.3",
"@solid-primitives/scroll": "2.1.3",
@@ -56,6 +58,7 @@
},
"devDependencies": {
"@happy-dom/global-registrator": "20.0.11",
"@playwright/test": "1.57.0",
"@tailwindcss/vite": "catalog:",
"@tsconfig/bun": "1.0.9",
"@types/bun": "catalog:",
@@ -70,7 +73,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -81,24 +84,30 @@
"@opencode-ai/console-mail": "workspace:*",
"@opencode-ai/console-resource": "workspace:*",
"@opencode-ai/ui": "workspace:*",
"@smithy/eventstream-codec": "4.2.7",
"@smithy/util-utf8": "4.2.0",
"@solidjs/meta": "catalog:",
"@solidjs/router": "catalog:",
"@solidjs/start": "catalog:",
"@stripe/stripe-js": "8.6.1",
"chart.js": "4.5.1",
"nitro": "3.0.1-alpha.1",
"solid-js": "catalog:",
"solid-list": "0.3.0",
"solid-stripe": "0.8.1",
"vite": "catalog:",
"zod": "catalog:",
},
"devDependencies": {
"@typescript/native-preview": "catalog:",
"@webgpu/types": "0.1.54",
"typescript": "catalog:",
"wrangler": "4.50.0",
},
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -125,7 +134,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -149,7 +158,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -173,9 +182,10 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
"@solid-primitives/storage": "catalog:",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "~2",
@@ -201,7 +211,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -230,7 +240,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -246,7 +256,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.1.1",
"version": "1.1.29",
"bin": {
"opencode": "./bin/opencode",
},
@@ -254,29 +264,30 @@
"@actions/core": "1.11.1",
"@actions/github": "6.0.1",
"@agentclientprotocol/sdk": "0.5.1",
"@ai-sdk/amazon-bedrock": "3.0.57",
"@ai-sdk/anthropic": "2.0.56",
"@ai-sdk/azure": "2.0.82",
"@ai-sdk/cerebras": "1.0.33",
"@ai-sdk/cohere": "2.0.21",
"@ai-sdk/deepinfra": "1.0.30",
"@ai-sdk/gateway": "2.0.23",
"@ai-sdk/google": "2.0.49",
"@ai-sdk/google-vertex": "3.0.81",
"@ai-sdk/groq": "2.0.33",
"@ai-sdk/mistral": "2.0.26",
"@ai-sdk/openai": "2.0.71",
"@ai-sdk/openai-compatible": "1.0.29",
"@ai-sdk/perplexity": "2.0.22",
"@ai-sdk/provider": "2.0.0",
"@ai-sdk/provider-utils": "3.0.19",
"@ai-sdk/togetherai": "1.0.30",
"@ai-sdk/amazon-bedrock": "3.0.73",
"@ai-sdk/anthropic": "2.0.57",
"@ai-sdk/azure": "2.0.91",
"@ai-sdk/cerebras": "1.0.34",
"@ai-sdk/cohere": "2.0.22",
"@ai-sdk/deepinfra": "1.0.31",
"@ai-sdk/gateway": "2.0.25",
"@ai-sdk/google": "2.0.52",
"@ai-sdk/google-vertex": "3.0.97",
"@ai-sdk/groq": "2.0.34",
"@ai-sdk/mistral": "2.0.27",
"@ai-sdk/openai": "2.0.89",
"@ai-sdk/openai-compatible": "1.0.30",
"@ai-sdk/perplexity": "2.0.23",
"@ai-sdk/provider": "2.0.1",
"@ai-sdk/provider-utils": "3.0.20",
"@ai-sdk/togetherai": "1.0.31",
"@ai-sdk/vercel": "1.0.31",
"@ai-sdk/xai": "2.0.42",
"@ai-sdk/xai": "2.0.51",
"@clack/prompts": "1.0.0-alpha.1",
"@gitlab/gitlab-ai-provider": "3.1.2",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
"@modelcontextprotocol/sdk": "1.15.1",
"@modelcontextprotocol/sdk": "1.25.2",
"@octokit/graphql": "9.0.2",
"@octokit/rest": "catalog:",
"@openauthjs/openauth": "catalog:",
@@ -285,11 +296,12 @@
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.5.2",
"@opentui/core": "0.1.68",
"@opentui/solid": "0.1.68",
"@opentui/core": "0.1.74",
"@opentui/solid": "0.1.74",
"@parcel/watcher": "2.5.1",
"@pierre/diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
"@solid-primitives/scheduled": "1.5.2",
"@standard-schema/spec": "1.0.0",
"@zip.js/zip.js": "2.7.62",
"ai": "catalog:",
@@ -348,7 +360,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -368,9 +380,9 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.1.1",
"version": "1.1.29",
"devDependencies": {
"@hey-api/openapi-ts": "0.88.1",
"@hey-api/openapi-ts": "0.90.4",
"@tsconfig/node22": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
@@ -379,7 +391,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -392,7 +404,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -400,9 +412,11 @@
"@pierre/diffs": "catalog:",
"@shikijs/transformers": "3.9.2",
"@solid-primitives/bounds": "0.1.3",
"@solid-primitives/media": "2.3.3",
"@solid-primitives/resize-observer": "2.1.3",
"@solidjs/meta": "catalog:",
"@typescript/native-preview": "catalog:",
"dompurify": "3.3.1",
"fuzzysort": "catalog:",
"katex": "0.16.27",
"luxon": "catalog:",
@@ -413,6 +427,7 @@
"shiki": "catalog:",
"solid-js": "catalog:",
"solid-list": "catalog:",
"strip-ansi": "7.1.2",
"virtua": "catalog:",
},
"devDependencies": {
@@ -430,7 +445,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"zod": "catalog:",
},
@@ -441,7 +456,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.1.1",
"version": "1.1.29",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -490,6 +505,7 @@
"@octokit/rest": "22.0.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/diffs": "1.0.2",
"@playwright/test": "1.51.0",
"@solid-primitives/storage": "4.3.3",
"@solidjs/meta": "0.29.4",
"@solidjs/router": "0.15.4",
@@ -497,12 +513,14 @@
"@tailwindcss/vite": "4.1.11",
"@tsconfig/bun": "1.0.9",
"@tsconfig/node22": "22.0.2",
"@types/bun": "1.3.4",
"@types/bun": "1.3.5",
"@types/luxon": "3.7.1",
"@types/node": "22.13.9",
"@types/semver": "7.7.1",
"@typescript/native-preview": "7.0.0-dev.20251207.1",
"ai": "5.0.97",
"ai": "5.0.119",
"diff": "8.0.2",
"dompurify": "3.3.1",
"fuzzysort": "3.1.0",
"hono": "4.10.7",
"hono-openapi": "1.1.2",
@@ -538,48 +556,52 @@
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.5.1", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg=="],
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.57", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.45", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-mOUSLe+RgZzx0rtL1p9QXmSd/08z1EkBR+vQ1ydpd1t5P0Nx2kB8afiukEgM8nuDvmO9eYQlp7VTy1n5ffPs2g=="],
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.73", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.57", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EAAGJ/dfbAZaqIhK3w52hq6cftSLZwXdC6uHKh8Cls1T0N4MxS6ykDf54UyFO3bZWkQxR+Mdw1B3qireGOxtJQ=="],
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
"@ai-sdk/azure": ["@ai-sdk/azure@2.0.82", "", { "dependencies": { "@ai-sdk/openai": "2.0.80", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Bpab51ETBB4adZC1xGMYsryL/CB8j1sA+t5aDqhRv3t3WRLTxhaBDcFKtQTIuxiEQTFosz9Q2xQqdfBvQm5jHw=="],
"@ai-sdk/azure": ["@ai-sdk/azure@2.0.91", "", { "dependencies": { "@ai-sdk/openai": "2.0.89", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9tznVSs6LGQNKKxb8pKd7CkBV9yk+a/ENpFicHCj2CmBUKefxzwJ9JbUqrlK3VF6dGZw3LXq0dWxt7/Yekaj1w=="],
"@ai-sdk/cerebras": ["@ai-sdk/cerebras@1.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2gSSS/7kunIwMdC4td5oWsUAzoLw84ccGpz6wQbxVnrb1iWnrEnKa5tRBduaP6IXpzLWsu8wME3+dQhZy+gT7w=="],
"@ai-sdk/cerebras": ["@ai-sdk/cerebras@1.0.34", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XOK0dJsAGoPYi/lfR4KFBi8xhvJ46oCpAxUD6FmJAuJ4eh0qlj5zDt+myvzM8gvN7S6K7zHD+mdWlOPKGQT8Vg=="],
"@ai-sdk/cohere": ["@ai-sdk/cohere@2.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZjaZFvJlc5XOPi3QwTLEFZbHIgTJc6YGvxz+8zIMGVZi/hdynR8/f/C1A9x6mhzmBtAqi/dZ2h11oouAQH5z4g=="],
"@ai-sdk/cohere": ["@ai-sdk/cohere@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yJ9kP5cEDJwo8qpITq5TQFD8YNfNtW+HbyvWwrKMbFzmiMvIZuk95HIaFXE7PCTuZsqMA05yYu+qX/vQ3rNKjA=="],
"@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.30", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XK8oRZFApzo6xnS5C+FhWUUkB2itA5Nfon3pU9dJVM0goViq8GwdleZTBRqhu4DE4KJURo5DGWpJr2hfV54cEg=="],
"@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.31", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-87qFcYNvDF/89hB//MQjYTb3tlsAfmgeZrZ34RESeBTZpSgs0EzYOMqPMwFTHUNp4wteoifikDJbaS/9Da8cfw=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-qmX7afPRszUqG5hryHF3UN8ITPIRSGmDW6VYCmByzjoUkgm3MekzSx2hMV1wr0P+llDeuXb378SjqUfpvWJulg=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.25", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Rq+FX55ne7lMiqai7NcvvDZj4HLsr+hg77WayqmySqc6zhw3tIOLxd4Ty6OpwNj0C0bVMi3iCl2zvJIEirh9XA=="],
"@ai-sdk/google": ["@ai-sdk/google@2.0.49", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-efwKk4mOV0SpumUaQskeYABk37FJPmEYwoDJQEjyLRmGSjtHRe9P5Cwof5ffLvaFav2IaJpBGEz98pyTs7oNWA=="],
"@ai-sdk/google": ["@ai-sdk/google@2.0.52", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2XUnGi3f7TV4ujoAhA+Fg3idUoG/+Y2xjCRg70a1/m0DH1KSQqYaCboJ1C19y6ZHGdf5KNT20eJdswP6TvrY2g=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.81", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.50", "@ai-sdk/google": "2.0.44", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18", "google-auth-library": "^9.15.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yrl5Ug0Mqwo9ya45oxczgy2RWgpEA/XQQCSFYP+3NZMQ4yA3Iim1vkOjVCsGaZZ8rjVk395abi1ZMZV0/6rqVA=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.97", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.57", "@ai-sdk/google": "2.0.52", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-s4tI7Z15i6FlbtCvS4SBRal8wRfkOXJzKxlS6cU4mJW/QfUfoVy4b22836NVNJwDvkG/HkDSfzwm/X8mn46MhA=="],
"@ai-sdk/groq": ["@ai-sdk/groq@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FWGl7xNr88NBveao3y9EcVWYUt9ABPrwLFY7pIutSNgaTf32vgvyhREobaMrLU4Scr5G/2tlNqOPZ5wkYMaZig=="],
"@ai-sdk/groq": ["@ai-sdk/groq@2.0.34", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wfCYkVgmVjxNA32T57KbLabVnv9aFUflJ4urJ7eWgTwbnmGQHElCTu+rJ3ydxkXSqxOkXPwMOttDm7XNrvPjmg=="],
"@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.26", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jxDB++4WI1wEx5ONNBI+VbkmYJOYIuS8UQY13/83UGRaiW7oB/WHiH4ETe6KzbKpQPB3XruwTJQjUMsMfKyTXA=="],
"@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.27", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-gaptHgaXjMw3+eA0Q4FABcsj5nQNP6EpFaGUR+Pj5WJy7Kn6mApl975/x57224MfeJIShNpt8wFKK3tvh5ewKg=="],
"@ai-sdk/openai": ["@ai-sdk/openai@2.0.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="],
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-luHVcU+yKzwv3ekKgbP3v+elUVxb2Rt+8c6w9qi7g2NYG2/pEL21oIrnaEnc6UtTZLLZX9EFBcpq2N1FQKDIMw=="],
"@ai-sdk/perplexity": ["@ai-sdk/perplexity@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zwzcnk08R2J3mZcQPn4Ifl4wYGrvANR7jsBB0hCTUSbb+Rx3ybpikSWiGuXQXxdiRc1I5MWXgj70m+bZaLPvHw=="],
"@ai-sdk/perplexity": ["@ai-sdk/perplexity@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-aiaRvnc6mhQZKhTTSXPCjPH8Iqr5D/PfCN1hgVP/3RGTBbJtsd9HemIBSABeSdAKbsMH/PwJxgnqH75HEamcBA=="],
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="],
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="],
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
"@ai-sdk/togetherai": ["@ai-sdk/togetherai@1.0.30", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9bxQbIXnWSN4bNismrza3NvIo+ui/Y3pj3UN6e9vCszCWFCN45RgISi4oDe10RqmzaJ/X8cfO/Tem+K8MT3wGQ=="],
"@ai-sdk/togetherai": ["@ai-sdk/togetherai@1.0.31", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RlYubjStoZQxna4Ng91Vvo8YskvL7lW9zj68IwZfCnaDBSAp1u6Nhc5BR4ZtKnY6PA3XEtu4bATIQl7yiiQ+Lw=="],
"@ai-sdk/vercel": ["@ai-sdk/vercel@1.0.31", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ggvwAMt/KsbqcdR6ILQrjwrRONLV/8aG6rOLbjcOGvV0Ai+WdZRRKQj5nOeQ06PvwVQtKdkp7S4IinpXIhCiHg=="],
"@ai-sdk/xai": ["@ai-sdk/xai@2.0.42", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wlwO4yRoZ/d+ca29vN8SDzxus7POdnL7GBTyRdSrt6icUF0hooLesauC8qRUC4aLxtqvMEc1YHtJOU7ZnLWbTQ=="],
"@ai-sdk/xai": ["@ai-sdk/xai@2.0.51", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-AI3le03qiegkZvn9hpnpDwez49lOvQLj4QUBT8H41SMbrdTYOxn3ktTwrsSu90cNDdzKGMvoH0u2GHju1EdnCg=="],
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.71.2", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ=="],
"@anycable/core": ["@anycable/core@0.9.2", "", { "dependencies": { "nanoevents": "^7.0.1" } }, "sha512-x5ZXDcW/N4cxWl93CnbHs/u7qq4793jS2kNPWm+duPrXlrva+ml2ZGT7X9tuOBKzyIHf60zWCdIK7TUgMPAwXA=="],
"@astrojs/cloudflare": ["@astrojs/cloudflare@12.6.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "@astrojs/underscore-redirects": "1.0.0", "@cloudflare/workers-types": "^4.20250507.0", "tinyglobby": "^0.2.13", "vite": "^6.3.5", "wrangler": "^4.14.1" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-xhJptF5tU2k5eo70nIMyL1Udma0CqmUEnGSlGyFflLqSY82CRQI6nWZ/xZt0ZvmXuErUjIx0YYQNfZsz5CNjLQ=="],
"@astrojs/compiler": ["@astrojs/compiler@2.13.0", "", {}, "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw=="],
@@ -900,13 +922,19 @@
"@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
"@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.1.2", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-p0NZhZJSavWDX9r/Px/mOK2YIC803GZa8iRzcg3f1C6S0qfea/HBTe4/NWvT2+2kWIwhCePGuI4FN2UFiUWXUg=="],
"@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="],
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
"@hey-api/codegen-core": ["@hey-api/codegen-core@0.3.3", "", { "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-vArVDtrvdzFewu1hnjUm4jX1NBITlSCeO81EdWq676MxQbyxsGcDPAgohaSA+Wvr4HjPSvsg2/1s2zYxUtXebg=="],
"@hey-api/codegen-core": ["@hey-api/codegen-core@0.5.2", "", { "dependencies": { "ansi-colors": "4.1.3", "color-support": "1.1.3" }, "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-88cqrrB2cLXN8nMOHidQTcVOnZsJ5kebEbBefjMCifaUCwTA30ouSSWvTZqrOX4O104zjJyu7M8Gcv/NNYQuaA=="],
"@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.2.2", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.1", "lodash": "^4.17.21" } }, "sha512-oS+5yAdwnK20lSeFO1d53Ku+yaGCsY8PcrmSq2GtSs3bsBfRnHAbpPKSVzQcaxAOrzj5NB+f34WhZglVrNayBA=="],
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.88.1", "", { "dependencies": { "@hey-api/codegen-core": "^0.3.3", "@hey-api/json-schema-ref-parser": "1.2.2", "ansi-colors": "4.1.3", "c12": "3.3.2", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.2" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-x/nDTupOnV9VuSeNIiJpgIpc915GHduhyseJeMTnI0JMsXaObmpa0rgPr3ASVEYMLgpvqozIEG1RTOOnal6zLQ=="],
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.90.4", "", { "dependencies": { "@hey-api/codegen-core": "^0.5.2", "@hey-api/json-schema-ref-parser": "1.2.2", "ansi-colors": "4.1.3", "c12": "3.3.3", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.3" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-9l++kjcb0ui4JqPlueZ6OZ9zKn6eK/8//Z2jHcIXb5MRwDRgubOOSpTU5llEv3uvWfT10VzcMp99dySWq0AASw=="],
"@hono/node-server": ["@hono/node-server@1.19.7", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw=="],
"@hono/standard-validator": ["@hono/standard-validator@0.1.5", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="],
@@ -1092,7 +1120,7 @@
"@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.15.1", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-W/XlN9c528yYn+9MQkVjxiTPgPxoxt+oczfjHBDsJx0+59+O7B75Zhsp0B16Xbwbz8ANISDajh6+V7nIcPMc5w=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.2", "", { "dependencies": { "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww=="],
"@motionone/animation": ["@motionone/animation@10.18.0", "", { "dependencies": { "@motionone/easing": "^10.18.0", "@motionone/types": "^10.17.1", "@motionone/utils": "^10.18.0", "tslib": "^2.3.1" } }, "sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw=="],
@@ -1196,21 +1224,21 @@
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@opentui/core": ["@opentui/core@0.1.68", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.68", "@opentui/core-darwin-x64": "0.1.68", "@opentui/core-linux-arm64": "0.1.68", "@opentui/core-linux-x64": "0.1.68", "@opentui/core-win32-arm64": "0.1.68", "@opentui/core-win32-x64": "0.1.68", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-SZz5qNO+2lJ8jDEoTSieyXH23t49myu6NetLex+xzqOf67XsU6QKlDcw5oMmc3zrKvETXhgbBvlSnbyJNQoBMg=="],
"@opentui/core": ["@opentui/core@0.1.74", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.74", "@opentui/core-darwin-x64": "0.1.74", "@opentui/core-linux-arm64": "0.1.74", "@opentui/core-linux-x64": "0.1.74", "@opentui/core-win32-arm64": "0.1.74", "@opentui/core-win32-x64": "0.1.74", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-g4W16ymv12JdgZ+9B4t7mpIICvzWy2+eHERfmDf80ALduOQCUedKQdULcBFhVCYUXIkDRtIy6CID5thMAah3FA=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.68", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ipPX2gavBLVtw3d8L4ZPJDLlEwIjIRNdlNlxu07rqSEGSfxD5s29yc+33wLAlYXbmnJDajOqm0Dx6HnlY1Y9Fg=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.74", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rfmlDLtm/u17CnuhJgCxPeYMvOST+A2MOdVOk46IurtHO849bdYqK6iudKNlFRs1FOrymgSKF9GlWBHAOKeRjg=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.68", "", { "os": "darwin", "cpu": "x64" }, "sha512-9dW0S9HINnuVjvC9QLj+S+329H7qEBQQtyJ9WHpykemokiJ5k4rnuDkfws5FxgTHIf/ddoYYTyPoGCS7WN5gsQ=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.74", "", { "os": "darwin", "cpu": "x64" }, "sha512-WAD8orsDV0ZdW/5GwjOOB4FY96772xbkz+rcV7WRzEFUVaqoBaC04IuqYzS9d5s+cjkbT5Cpj47hrVYkkVQKng=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.68", "", { "os": "linux", "cpu": "arm64" }, "sha512-/el6TbSQriBUfPhIa6SBfCCc7tjU98Bnhf2+w0zKwQFBjf3F3kmnI42++YxedMGFmL7bRt3EUawGOkQRZZzFAg=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.74", "", { "os": "linux", "cpu": "arm64" }, "sha512-lgmHzrzLy4e+rgBS+lhtsMLLgIMLbtLNMm6EzVPyYVDlLDGjM7+ulXMem7AtpaRrWrUUl4REiG9BoQUsCFDwYA=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.68", "", { "os": "linux", "cpu": "x64" }, "sha512-9NzVI3GZzmICoIu3YhWBdkEt0KvY27m++tu/MqW+xb6fnvN74jZkRWzlgjTdM70obL4eUGQdvU08sDHgZjsIJw=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.74", "", { "os": "linux", "cpu": "x64" }, "sha512-8Mn2WbdBQ29xCThuPZezjDhd1N3+fXwKkGvCBOdTI0le6h2A/vCNbfUVjwfr/EGZSRXxCG+Yapol34BAULGpOA=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.68", "", { "os": "win32", "cpu": "arm64" }, "sha512-wrAeotyotOplUjQVBSxOGA8GCr9FWXSd6xCEo1PEGo/NjuAOtvHmKoENzyFEP0GzFsjvoUOyy2dZb987oFAn9A=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.74", "", { "os": "win32", "cpu": "arm64" }, "sha512-dvYUXz03avnI6ZluyLp00HPmR0UT/IE/6QS97XBsgJlUTtpnbKkBtB5jD1NHwWkElaRj1Qv2QP36ngFoJqbl9g=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.68", "", { "os": "win32", "cpu": "x64" }, "sha512-w0yBjvzs/oMIwVdWICL4XlUrfsPoVXd4+RDqiuu+Xi/zD0UgANSTRY2asXca+gPe5zPHLsxvz1bAG0Z7uGtmyw=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.74", "", { "os": "win32", "cpu": "x64" }, "sha512-3wfWXaAKOIlDQz6ZZIESf2M+YGZ7uFHijjTEM8w/STRlLw8Y6+QyGYi1myHSM4d6RSO+/s2EMDxvjDf899W9vQ=="],
"@opentui/solid": ["@opentui/solid@0.1.68", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.68", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-S1oHvCQaY+gCQu2kiiksPIScP8i0FiDOlAlLjtfwcRlgeSjzT0wRwFkvoh4uVUPuAlyigox7vMCE3j04SYSGKg=="],
"@opentui/solid": ["@opentui/solid@0.1.74", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.74", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-Vz82cI8T9YeJjGsVg4ULp6ral4N+xyt1j9A6Tbu3aaQgEKiB74LW03EXREehfjPr1irOFxtKfWPbx5NKH0Upag=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
@@ -1332,6 +1360,8 @@
"@planetscale/database": ["@planetscale/database@1.19.0", "", {}, "sha512-Tv4jcFUFAFjOWrGSio49H6R2ijALv0ZzVBfJKIdm+kl9X046Fh4LLawrF9OMsglVbK6ukqMJsUCeucGAFTBcMA=="],
"@playwright/test": ["@playwright/test@1.57.0", "", { "dependencies": { "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" } }, "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA=="],
"@poppinss/colors": ["@poppinss/colors@4.1.5", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw=="],
"@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="],
@@ -1502,7 +1532,7 @@
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="],
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="],
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.7", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ=="],
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.5", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw=="],
@@ -1592,6 +1622,8 @@
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
"@solid-primitives/active-element": ["@solid-primitives/active-element@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9t5K4aR2naVDj950XU8OjnLgOg94a8k5wr6JNOPK+N5ESLsJDq42c1ZP8UKpewi1R+wplMMxiM6OPKRzbxJY7A=="],
"@solid-primitives/audio": ["@solid-primitives/audio@1.4.2", "", { "dependencies": { "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-UMD3ORQfI5Ky8yuKPxidDiEazsjv/dsoiKK5yZxLnsgaeNR1Aym3/77h/qT1jBYeXUgj4DX6t7NMpFUSVr14OQ=="],
@@ -1602,6 +1634,8 @@
"@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg=="],
"@solid-primitives/i18n": ["@solid-primitives/i18n@2.2.1", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-TnTnE2Ku11MGYZ1JzhJ8pYscwg1fr9MteoYxPwsfxWfh9Jp5K7RRJncJn9BhOHvNLwROjqOHZ46PT7sPHqbcXw=="],
"@solid-primitives/keyed": ["@solid-primitives/keyed@1.5.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-BgoEdqPw48URnI+L5sZIHdF4ua4Las1eWEBBPaoSFs42kkhnHue+rwCBPL2Z9ebOyQ75sUhUfOETdJfmv0D6Kg=="],
"@solid-primitives/map": ["@solid-primitives/map@0.4.13", "", { "dependencies": { "@solid-primitives/trigger": "^1.1.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew=="],
@@ -1616,6 +1650,8 @@
"@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ=="],
"@solid-primitives/scheduled": ["@solid-primitives/scheduled@1.5.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA=="],
"@solid-primitives/scroll": ["@solid-primitives/scroll@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-Ejq/Z7zKo/6eIEFr1bFLzXFxiGBCMLuqCM8QB8urr3YdPzjSETFLzYRWUyRiDWaBQN0F7k0SY6S7ig5nWOP7vg=="],
"@solid-primitives/static-store": ["@solid-primitives/static-store@0.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw=="],
@@ -1642,6 +1678,8 @@
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
"@stripe/stripe-js": ["@stripe/stripe-js@8.6.1", "", {}, "sha512-UJ05U2062XDgydbUcETH1AoRQLNhigQ2KmDn1BG8sC3xfzu6JKg95Qt6YozdzFpxl1Npii/02m2LEWFt1RYjVA=="],
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
@@ -1748,7 +1786,7 @@
"@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="],
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
@@ -1820,6 +1858,8 @@
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@types/tsscmp": ["@types/tsscmp@1.0.2", "", {}, "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g=="],
"@types/tunnel": ["@types/tunnel@0.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA=="],
@@ -1876,7 +1916,7 @@
"@vitest/utils": ["@vitest/utils@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="],
"@webgpu/types": ["@webgpu/types@0.1.66", "", {}, "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA=="],
"@webgpu/types": ["@webgpu/types@0.1.54", "", {}, "sha512-81oaalC8LFrXjhsczomEQ0u3jG+TqE6V9QHLA8GNZq/Rnot0KDugu3LhSYSlie8tSdooAN1Hov05asrUUp9qgg=="],
"@zip.js/zip.js": ["@zip.js/zip.js@2.7.62", "", {}, "sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA=="],
@@ -1896,9 +1936,11 @@
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
"ai": ["ai@5.0.97", "", { "dependencies": { "@ai-sdk/gateway": "2.0.12", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8zBx0b/owis4eJI2tAlV8a1Rv0BANmLxontcAelkLNwEHhgfgXeKpDkhNB6OgV+BJSwboIUDkgd9312DdJnCOQ=="],
"ai": ["ai@5.0.119", "", { "dependencies": { "@ai-sdk/gateway": "2.0.25", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HUOwhc17fl2SZTJGZyA/99aNu706qKfXaUBCy9vgZiXBwrxg2eTzn2BCz7kmYDsfx6Fg2ACBy2icm41bsDXCTw=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
@@ -2046,7 +2088,7 @@
"bun-pty": ["bun-pty@0.4.4", "", {}, "sha512-WK4G6uWsZgu1v4hKIlw6G1q2AOf8Rbga2Yr7RnxArVjjyb+mtVa/CFc9GOJf+OYSJSH8k7LonAtQOVeNAddRyg=="],
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
"bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="],
@@ -2062,7 +2104,7 @@
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
"c12": ["c12@3.3.2", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A=="],
"c12": ["c12@3.3.3", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q=="],
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
@@ -2196,6 +2238,8 @@
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
@@ -2268,6 +2312,8 @@
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
"dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="],
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
"dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
@@ -2300,6 +2346,10 @@
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"engine.io-client": ["engine.io-client@6.6.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw=="],
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
@@ -2402,7 +2452,7 @@
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="],
@@ -2410,6 +2460,8 @@
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
"file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
@@ -2440,6 +2492,8 @@
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
@@ -2462,9 +2516,9 @@
"fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="],
"gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="],
"gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="],
"gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="],
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
"gel": ["gel@2.2.0", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ=="],
@@ -2510,17 +2564,21 @@
"globby": ["globby@11.0.4", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.1.1", "ignore": "^5.1.4", "merge2": "^1.3.0", "slash": "^3.0.0" } }, "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg=="],
"google-auth-library": ["google-auth-library@9.15.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", "jws": "^4.0.0" } }, "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng=="],
"google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="],
"google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
"google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"graphql": ["graphql@16.12.0", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="],
"graphql-request": ["graphql-request@6.1.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", "cross-fetch": "^3.1.5" }, "peerDependencies": { "graphql": "14 - 16" } }, "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw=="],
"gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
"gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="],
"gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="],
"h3": ["h3@2.0.1-rc.4", "", { "dependencies": { "rou3": "^0.7.8", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-vZq8pEUp6THsXKXrUXX44eOqfChic2wVQ1GlSzQCBr7DeFBkfIZAo2WyNND4GSv54TAa0E4LYIK73WSPdgKUgw=="],
@@ -2746,6 +2804,8 @@
"isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
"iterate-iterator": ["iterate-iterator@1.0.2", "", {}, "sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw=="],
"iterate-value": ["iterate-value@1.0.2", "", { "dependencies": { "es-get-iterator": "^1.0.2", "iterate-iterator": "^1.0.1" } }, "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ=="],
@@ -2778,7 +2838,11 @@
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
@@ -3052,6 +3116,8 @@
"named-placeholders": ["named-placeholders@1.1.3", "", { "dependencies": { "lru-cache": "^7.14.1" } }, "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w=="],
"nanoevents": ["nanoevents@7.0.1", "", {}, "sha512-o6lpKiCxLeijK4hgsqfR6CNToPyRU3keKyyI6uwuHRvpRTbZ0wXw51WRgyldVugZqoJfkGFrjrIenYH3bfEO3Q=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
@@ -3234,6 +3300,10 @@
"planck": ["planck@1.4.2", "", { "peerDependencies": { "stage-js": "^1.0.0-alpha.12" } }, "sha512-mNbhnV3g8X2rwGxzcesjmN8BDA6qfXgQxXVMkWau9MCRlQY0RLNEkyHlVp6yFy/X6qrzAXyNONCnZ1cGDLrNew=="],
"playwright": ["playwright@1.57.0", "", { "dependencies": { "playwright-core": "1.57.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw=="],
"playwright-core": ["playwright-core@1.57.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ=="],
"pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
@@ -3380,6 +3450,8 @@
"remeda": ["remeda@2.26.0", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-lmNNwtaC6Co4m0WTTNoZ/JlpjEqAjPZO0+czC9YVRQUpkbS4x8Hmh+Mn9HPfJfiXqUQ5IXXgSXSOB2pBKAytdA=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="],
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
@@ -3404,6 +3476,8 @@
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
"rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="],
"rou3": ["rou3@0.7.10", "", {}, "sha512-aoFj6f7MJZ5muJ+Of79nrhs9N3oLGqi2VEMe94Zbkjb6Wupha46EuoYgpWSOZlXww3bbd8ojgXTAA2mzimX5Ww=="],
@@ -3434,7 +3508,7 @@
"selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
@@ -3490,6 +3564,10 @@
"smol-toml": ["smol-toml@1.5.2", "", {}, "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ=="],
"socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="],
"socket.io-parser": ["socket.io-parser@4.2.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ=="],
"solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="],
"solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="],
@@ -3500,6 +3578,8 @@
"solid-refresh": ["solid-refresh@0.6.3", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="],
"solid-stripe": ["solid-stripe@0.8.1", "", { "peerDependencies": { "@stripe/stripe-js": ">=1.44.1 <8.0.0", "solid-js": "^1.6.0" } }, "sha512-l2SkWoe51rsvk9u1ILBRWyCHODZebChSGMR6zHYJTivTRC0XWrRnNNKs5x1PYXsaIU71KYI6ov5CZB5cOtGLWw=="],
"solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="],
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
@@ -3654,6 +3734,8 @@
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
@@ -3754,8 +3836,6 @@
"update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"url": ["url@0.10.3", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="],
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
@@ -3848,6 +3928,8 @@
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
"xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
@@ -3898,35 +3980,35 @@
"@agentclientprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.45", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Ipv62vavDCmrV/oE/lXehL9FzwQuZOnnlhPEftWizx464Wb6lvnBTJx8uhmEYruFSzOWTI95Z33ncZ4tA8E6RQ=="],
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.57", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="],
"@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
"@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.80", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tNHuraF11db+8xJEDBoU9E3vMcpnHFKRhnLQ3DQX2LnEzfPB9DksZ8rE+yVuDN1WRW9cm2OWAhgHFgVKs7ICuw=="],
"@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
"@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
"@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
"@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
"@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="],
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.57", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
"@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="],
"@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
"@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
"@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
"@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
"@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
"@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
"@ai-sdk/vercel/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
"@ai-sdk/vercel/@ai-sdk/provider": ["@ai-sdk/provider@2.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="],
"@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
"@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
"@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
"@astrojs/cloudflare/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
@@ -4000,6 +4082,8 @@
"@expressive-code/plugin-shiki/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
"@gitlab/gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@hey-api/json-schema-ref-parser/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
"@hey-api/openapi-ts/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
@@ -4054,9 +4138,11 @@
"@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"@modelcontextprotocol/sdk/jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
"@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
"@modelcontextprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
"@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="],
@@ -4168,6 +4254,10 @@
"@slack/web-api/p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
"@smithy/eventstream-codec/@smithy/types": ["@smithy/types@4.11.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA=="],
"@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="],
"@solidjs/start/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
"@solidjs/start/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
@@ -4192,10 +4282,6 @@
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"ai/@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.12", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W+cB1sOWvPcz9qiIsNtD+HxUrBUva2vWv2K1EFukuImX+HA0uZx3EyyOjhYQ9gtf/teqEG80M6OvJ7xx/VLV2A=="],
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
@@ -4208,6 +4294,8 @@
"astro/diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="],
"astro/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"astro/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
"astro/unstorage": ["unstorage@1.17.3", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q=="],
@@ -4228,6 +4316,10 @@
"body-parser/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
"bun-webgpu/@webgpu/types": ["@webgpu/types@0.1.66", "", {}, "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA=="],
"c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"clean-css/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
@@ -4244,6 +4336,10 @@
"editorconfig/minimatch": ["minimatch@9.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="],
"editorconfig/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"engine.io-client/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
"esbuild-plugin-copy/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -4260,13 +4356,15 @@
"express/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
"fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"gaxios/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
"gel/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
@@ -4282,6 +4380,8 @@
"jsonwebtoken/jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
"jsonwebtoken/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
"lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
@@ -4308,11 +4408,11 @@
"nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ=="],
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.57", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
"opencode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.71", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tg+gj+R0z/On9P4V7hy7/7o04cQPjKGayMCL3gzWD/aNGjAKkhEnaocuNDidSnghizt8g2zJn16cAuAolnW+qQ=="],
"opencode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
@@ -4340,6 +4440,8 @@
"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=="],
"postcss-load-config/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
@@ -4350,6 +4452,8 @@
"readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
"rimraf/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
"safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
@@ -4364,6 +4468,8 @@
"sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"sharp/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"shiki/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
"shiki/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
@@ -4400,8 +4506,6 @@
"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=="],
"uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
"vitest/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
@@ -4844,6 +4948,8 @@
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
@@ -4914,8 +5020,6 @@
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
@@ -4932,6 +5036,12 @@
"readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"rimraf/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=="],
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
@@ -5114,6 +5224,8 @@
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
"rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
Generated
+3 -3
View File
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1767364772,
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
"lastModified": 1768302833,
"narHash": "sha256-h5bRFy9bco+8QcK7rGoOiqMxMbmn21moTACofNLRMP4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
"rev": "61db79b0c6b838d9894923920b612048e1201926",
"type": "github"
},
"original": {
+41 -91
View File
@@ -6,10 +6,7 @@
};
outputs =
{
nixpkgs,
...
}:
{ self, nixpkgs, ... }:
let
systems = [
"aarch64-linux"
@@ -17,103 +14,56 @@
"aarch64-darwin"
"x86_64-darwin"
];
inherit (nixpkgs) lib;
forEachSystem = lib.genAttrs systems;
pkgsFor = system: nixpkgs.legacyPackages.${system};
packageJson = builtins.fromJSON (builtins.readFile ./packages/opencode/package.json);
bunTarget = {
"aarch64-linux" = "bun-linux-arm64";
"x86_64-linux" = "bun-linux-x64";
"aarch64-darwin" = "bun-darwin-arm64";
"x86_64-darwin" = "bun-darwin-x64";
};
defaultNodeModules = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
hashesFile = "${./nix}/hashes.json";
hashesData =
if builtins.pathExists hashesFile then builtins.fromJSON (builtins.readFile hashesFile) else { };
nodeModulesHash = hashesData.nodeModules or defaultNodeModules;
modelsDev = forEachSystem (
system:
let
pkgs = pkgsFor system;
in
pkgs."models-dev"
);
forEachSystem = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
rev = self.shortRev or self.dirtyShortRev or "dirty";
in
{
devShells = forEachSystem (
system:
let
pkgs = pkgsFor system;
in
{
default = pkgs.mkShell {
packages = with pkgs; [
bun
nodejs_20
pkg-config
openssl
git
];
};
}
);
devShells = forEachSystem (pkgs: {
default = pkgs.mkShell {
packages = with pkgs; [
bun
nodejs_20
pkg-config
openssl
git
];
};
});
packages = forEachSystem (
system:
pkgs:
let
pkgs = pkgsFor system;
mkNodeModules = pkgs.callPackage ./nix/node-modules.nix {
hash = nodeModulesHash;
node_modules = pkgs.callPackage ./nix/node_modules.nix {
inherit rev;
};
mkOpencode = pkgs.callPackage ./nix/opencode.nix { };
mkDesktop = pkgs.callPackage ./nix/desktop.nix { };
opencodePkg = mkOpencode {
inherit (packageJson) version;
src = ./.;
scripts = ./nix/scripts;
target = bunTarget.${system};
modelsDev = "${modelsDev.${system}}/dist/_api.json";
inherit mkNodeModules;
opencode = pkgs.callPackage ./nix/opencode.nix {
inherit node_modules;
};
desktopPkg = mkDesktop {
inherit (packageJson) version;
src = ./.;
scripts = ./nix/scripts;
mkNodeModules = mkNodeModules;
opencode = opencodePkg;
desktop = pkgs.callPackage ./nix/desktop.nix {
inherit opencode;
};
# nixpkgs cpu naming to bun cpu naming
cpuMap = { x86_64 = "x64"; aarch64 = "arm64"; };
# matrix of node_modules builds - these will always fail due to fakeHash usage
# but allow computation of the correct hash from any build machine for any cpu/os
# see the update-nix-hashes workflow for usage
moduleUpdaters = pkgs.lib.listToAttrs (
pkgs.lib.concatMap (cpu:
map (os: {
name = "${cpu}-${os}_node_modules";
value = node_modules.override {
bunCpu = cpuMap.${cpu};
bunOs = os;
hash = pkgs.lib.fakeHash;
};
}) [ "linux" "darwin" ]
) [ "x86_64" "aarch64" ]
);
in
{
default = opencodePkg;
desktop = desktopPkg;
}
);
apps = forEachSystem (
system:
let
pkgs = pkgsFor system;
in
{
opencode-dev = {
type = "app";
meta = {
description = "Nix devshell shell for OpenCode";
runtimeInputs = [ pkgs.bun ];
};
program = "${
pkgs.writeShellApplication {
name = "opencode-dev";
text = ''
exec bun run dev "$@"
'';
}
}/bin/opencode-dev";
};
}
default = opencode;
inherit opencode desktop;
} // moduleUpdaters
);
};
}
+8 -5
View File
@@ -81,17 +81,20 @@ This will walk you through installing the GitHub app, creating the workflow, and
permissions:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 1
persist-credentials: false
- name: Run opencode
- name: Run opencode
uses: anomalyco/opencode/github@latest
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
model: anthropic/claude-sonnet-4-20250514
use_github_token: true
```
3. Store the API keys in secrets. In your organization or project **settings**, expand **Secrets and variables** on the left and select **Actions**. Add the required API keys.
+14 -1
View File
@@ -4,6 +4,10 @@ const GITHUB_APP_ID = new sst.Secret("GITHUB_APP_ID")
const GITHUB_APP_PRIVATE_KEY = new sst.Secret("GITHUB_APP_PRIVATE_KEY")
export const EMAILOCTOPUS_API_KEY = new sst.Secret("EMAILOCTOPUS_API_KEY")
const ADMIN_SECRET = new sst.Secret("ADMIN_SECRET")
const DISCORD_SUPPORT_BOT_TOKEN = new sst.Secret("DISCORD_SUPPORT_BOT_TOKEN")
const DISCORD_SUPPORT_CHANNEL_ID = new sst.Secret("DISCORD_SUPPORT_CHANNEL_ID")
const FEISHU_APP_ID = new sst.Secret("FEISHU_APP_ID")
const FEISHU_APP_SECRET = new sst.Secret("FEISHU_APP_SECRET")
const bucket = new sst.cloudflare.Bucket("Bucket")
export const api = new sst.cloudflare.Worker("Api", {
@@ -13,7 +17,16 @@ export const api = new sst.cloudflare.Worker("Api", {
WEB_DOMAIN: domain,
},
url: true,
link: [bucket, GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, ADMIN_SECRET],
link: [
bucket,
GITHUB_APP_ID,
GITHUB_APP_PRIVATE_KEY,
ADMIN_SECRET,
DISCORD_SUPPORT_BOT_TOKEN,
DISCORD_SUPPORT_CHANNEL_ID,
FEISHU_APP_ID,
FEISHU_APP_SECRET,
],
transform: {
worker: (args) => {
args.logpush = true
+20
View File
@@ -76,6 +76,7 @@ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint",
"checkout.session.completed",
"checkout.session.expired",
"charge.refunded",
"invoice.payment_succeeded",
"customer.created",
"customer.deleted",
"customer.updated",
@@ -97,6 +98,19 @@ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint",
],
})
const zenProduct = new stripe.Product("ZenBlack", {
name: "OpenCode Black",
})
const zenPrice = new stripe.Price("ZenBlackPrice", {
product: zenProduct.id,
unitAmount: 20000,
currency: "usd",
recurring: {
interval: "month",
intervalCount: 1,
},
})
const ZEN_MODELS = [
new sst.Secret("ZEN_MODELS1"),
new sst.Secret("ZEN_MODELS2"),
@@ -105,8 +119,11 @@ const ZEN_MODELS = [
new sst.Secret("ZEN_MODELS5"),
new sst.Secret("ZEN_MODELS6"),
new sst.Secret("ZEN_MODELS7"),
new sst.Secret("ZEN_MODELS8"),
]
const ZEN_BLACK = new sst.Secret("ZEN_BLACK")
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
const STRIPE_PUBLISHABLE_KEY = new sst.Secret("STRIPE_PUBLISHABLE_KEY")
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
properties: { value: auth.url.apply((url) => url!) },
})
@@ -147,6 +164,8 @@ new sst.cloudflare.x.SolidStart("Console", {
EMAILOCTOPUS_API_KEY,
AWS_SES_ACCESS_KEY_ID,
AWS_SES_SECRET_ACCESS_KEY,
ZEN_BLACK,
new sst.Secret("ZEN_SESSION_SECRET"),
...ZEN_MODELS,
...($dev
? [
@@ -160,6 +179,7 @@ new sst.cloudflare.x.SolidStart("Console", {
//VITE_DOCS_URL: web.url.apply((url) => url!),
//VITE_API_URL: gateway.url.apply((url) => url!),
VITE_AUTH_URL: auth.url.apply((url) => url!),
VITE_STRIPE_PUBLISHABLE_KEY: STRIPE_PUBLISHABLE_KEY.value,
},
transform: {
server: {
+146 -119
View File
@@ -16,16 +16,19 @@ Usage: install.sh [options]
Options:
-h, --help Display this help message
-v, --version <version> Install a specific version (e.g., 1.0.180)
-b, --binary <path> Install from a local binary instead of downloading
--no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
Examples:
curl -fsSL https://opencode.ai/install | bash
curl -fsSL https://opencode.ai/install | bash -s -- --version 1.0.180
./install --binary /path/to/opencode
EOF
}
requested_version=${VERSION:-}
no_modify_path=false
binary_path=""
while [[ $# -gt 0 ]]; do
case "$1" in
@@ -42,6 +45,15 @@ while [[ $# -gt 0 ]]; do
exit 1
fi
;;
-b|--binary)
if [[ -n "${2:-}" ]]; then
binary_path="$2"
shift 2
else
echo -e "${RED}Error: --binary requires a path argument${NC}"
exit 1
fi
;;
--no-modify-path)
no_modify_path=true
shift
@@ -53,119 +65,128 @@ while [[ $# -gt 0 ]]; do
esac
done
raw_os=$(uname -s)
os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
case "$raw_os" in
Darwin*) os="darwin" ;;
Linux*) os="linux" ;;
MINGW*|MSYS*|CYGWIN*) os="windows" ;;
esac
arch=$(uname -m)
if [[ "$arch" == "aarch64" ]]; then
arch="arm64"
fi
if [[ "$arch" == "x86_64" ]]; then
arch="x64"
fi
if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)
if [ "$rosetta_flag" = "1" ]; then
arch="arm64"
fi
fi
combo="$os-$arch"
case "$combo" in
linux-x64|linux-arm64|darwin-x64|darwin-arm64|windows-x64)
;;
*)
echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}"
exit 1
;;
esac
archive_ext=".zip"
if [ "$os" = "linux" ]; then
archive_ext=".tar.gz"
fi
is_musl=false
if [ "$os" = "linux" ]; then
if [ -f /etc/alpine-release ]; then
is_musl=true
fi
if command -v ldd >/dev/null 2>&1; then
if ldd --version 2>&1 | grep -qi musl; then
is_musl=true
fi
fi
fi
needs_baseline=false
if [ "$arch" = "x64" ]; then
if [ "$os" = "linux" ]; then
if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then
needs_baseline=true
fi
fi
if [ "$os" = "darwin" ]; then
avx2=$(sysctl -n hw.optional.avx2_0 2>/dev/null || echo 0)
if [ "$avx2" != "1" ]; then
needs_baseline=true
fi
fi
fi
target="$os-$arch"
if [ "$needs_baseline" = "true" ]; then
target="$target-baseline"
fi
if [ "$is_musl" = "true" ]; then
target="$target-musl"
fi
filename="$APP-$target$archive_ext"
if [ "$os" = "linux" ]; then
if ! command -v tar >/dev/null 2>&1; then
echo -e "${RED}Error: 'tar' is required but not installed.${NC}"
exit 1
fi
else
if ! command -v unzip >/dev/null 2>&1; then
echo -e "${RED}Error: 'unzip' is required but not installed.${NC}"
exit 1
fi
fi
INSTALL_DIR=$HOME/.opencode/bin
mkdir -p "$INSTALL_DIR"
if [ -z "$requested_version" ]; then
url="https://github.com/anomalyco/opencode/releases/latest/download/$filename"
specific_version=$(curl -s https://api.github.com/repos/anomalyco/opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
if [[ $? -ne 0 || -z "$specific_version" ]]; then
echo -e "${RED}Failed to fetch version information${NC}"
# If --binary is provided, skip all download/detection logic
if [ -n "$binary_path" ]; then
if [ ! -f "$binary_path" ]; then
echo -e "${RED}Error: Binary not found at ${binary_path}${NC}"
exit 1
fi
specific_version="local"
else
# Strip leading 'v' if present
requested_version="${requested_version#v}"
url="https://github.com/anomalyco/opencode/releases/download/v${requested_version}/$filename"
specific_version=$requested_version
# Verify the release exists before downloading
http_status=$(curl -sI -o /dev/null -w "%{http_code}" "https://github.com/anomalyco/opencode/releases/tag/v${requested_version}")
if [ "$http_status" = "404" ]; then
echo -e "${RED}Error: Release v${requested_version} not found${NC}"
echo -e "${MUTED}Available releases: https://github.com/anomalyco/opencode/releases${NC}"
raw_os=$(uname -s)
os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
case "$raw_os" in
Darwin*) os="darwin" ;;
Linux*) os="linux" ;;
MINGW*|MSYS*|CYGWIN*) os="windows" ;;
esac
arch=$(uname -m)
if [[ "$arch" == "aarch64" ]]; then
arch="arm64"
fi
if [[ "$arch" == "x86_64" ]]; then
arch="x64"
fi
if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)
if [ "$rosetta_flag" = "1" ]; then
arch="arm64"
fi
fi
combo="$os-$arch"
case "$combo" in
linux-x64|linux-arm64|darwin-x64|darwin-arm64|windows-x64)
;;
*)
echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}"
exit 1
;;
esac
archive_ext=".zip"
if [ "$os" = "linux" ]; then
archive_ext=".tar.gz"
fi
is_musl=false
if [ "$os" = "linux" ]; then
if [ -f /etc/alpine-release ]; then
is_musl=true
fi
if command -v ldd >/dev/null 2>&1; then
if ldd --version 2>&1 | grep -qi musl; then
is_musl=true
fi
fi
fi
needs_baseline=false
if [ "$arch" = "x64" ]; then
if [ "$os" = "linux" ]; then
if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then
needs_baseline=true
fi
fi
if [ "$os" = "darwin" ]; then
avx2=$(sysctl -n hw.optional.avx2_0 2>/dev/null || echo 0)
if [ "$avx2" != "1" ]; then
needs_baseline=true
fi
fi
fi
target="$os-$arch"
if [ "$needs_baseline" = "true" ]; then
target="$target-baseline"
fi
if [ "$is_musl" = "true" ]; then
target="$target-musl"
fi
filename="$APP-$target$archive_ext"
if [ "$os" = "linux" ]; then
if ! command -v tar >/dev/null 2>&1; then
echo -e "${RED}Error: 'tar' is required but not installed.${NC}"
exit 1
fi
else
if ! command -v unzip >/dev/null 2>&1; then
echo -e "${RED}Error: 'unzip' is required but not installed.${NC}"
exit 1
fi
fi
if [ -z "$requested_version" ]; then
url="https://github.com/anomalyco/opencode/releases/latest/download/$filename"
specific_version=$(curl -s https://api.github.com/repos/anomalyco/opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
if [[ $? -ne 0 || -z "$specific_version" ]]; then
echo -e "${RED}Failed to fetch version information${NC}"
exit 1
fi
else
# Strip leading 'v' if present
requested_version="${requested_version#v}"
url="https://github.com/anomalyco/opencode/releases/download/v${requested_version}/$filename"
specific_version=$requested_version
# Verify the release exists before downloading
http_status=$(curl -sI -o /dev/null -w "%{http_code}" "https://github.com/anomalyco/opencode/releases/tag/v${requested_version}")
if [ "$http_status" = "404" ]; then
echo -e "${RED}Error: Release v${requested_version} not found${NC}"
echo -e "${MUTED}Available releases: https://github.com/anomalyco/opencode/releases${NC}"
exit 1
fi
fi
fi
@@ -187,11 +208,8 @@ check_version() {
if command -v opencode >/dev/null 2>&1; then
opencode_path=$(which opencode)
## TODO: check if version is installed
# installed_version=$(opencode version)
installed_version="0.0.1"
installed_version=$(echo $installed_version | awk '{print $2}')
## Check the installed version
installed_version=$(opencode --version 2>/dev/null || echo "")
if [[ "$installed_version" != "$specific_version" ]]; then
print_message info "${MUTED}Installed version: ${NC}$installed_version."
@@ -267,11 +285,11 @@ download_with_progress() {
{
local length=0
local bytes=0
while IFS=" " read -r -a line; do
[ "${#line[@]}" -lt 2 ] && continue
local tag="${line[0]} ${line[1]}"
if [ "$tag" = "0000: content-length:" ]; then
length="${line[2]}"
length=$(echo "$length" | tr -d '\r')
@@ -296,7 +314,7 @@ download_and_install() {
print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version"
local tmp_dir="${TMPDIR:-/tmp}/opencode_install_$$"
mkdir -p "$tmp_dir"
if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
# Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails
curl -# -L -o "$tmp_dir/$filename" "$url"
@@ -307,14 +325,24 @@ download_and_install() {
else
unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
fi
mv "$tmp_dir/opencode" "$INSTALL_DIR"
chmod 755 "${INSTALL_DIR}/opencode"
rm -rf "$tmp_dir"
}
check_version
download_and_install
install_from_binary() {
print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}from: ${NC}$binary_path"
cp "$binary_path" "${INSTALL_DIR}/opencode"
chmod 755 "${INSTALL_DIR}/opencode"
}
if [ -n "$binary_path" ]; then
install_from_binary
else
check_version
download_and_install
fi
add_to_path() {
@@ -341,7 +369,7 @@ case $current_shell in
config_files="$HOME/.config/fish/config.fish"
;;
zsh)
config_files="$HOME/.zshrc $HOME/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
config_files="${ZDOTDIR:-$HOME}/.zshrc ${ZDOTDIR:-$HOME}/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
;;
bash)
config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
@@ -416,4 +444,3 @@ echo -e ""
echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/docs"
echo -e ""
echo -e ""
-40
View File
@@ -1,40 +0,0 @@
#!/usr/bin/env bun
import solidPlugin from "./node_modules/@opentui/solid/scripts/solid-plugin"
import path from "path"
import fs from "fs"
const dir = process.cwd()
const parser = fs.realpathSync(path.join(dir, "node_modules/@opentui/core/parser.worker.js"))
const worker = "./src/cli/cmd/tui/worker.ts"
const version = process.env.OPENCODE_VERSION ?? "local"
const channel = process.env.OPENCODE_CHANNEL ?? "local"
fs.rmSync(path.join(dir, "dist"), { recursive: true, force: true })
const result = await Bun.build({
entrypoints: ["./src/index.ts", worker, parser],
outdir: "./dist",
target: "bun",
sourcemap: "none",
tsconfig: "./tsconfig.json",
plugins: [solidPlugin],
external: ["@opentui/core"],
define: {
OPENCODE_VERSION: `'${version}'`,
OPENCODE_CHANNEL: `'${channel}'`,
// Leave undefined so runtime picks bundled/dist worker or fallback in code.
OPENCODE_WORKER_PATH: "undefined",
OTUI_TREE_SITTER_WORKER_PATH: 'new URL("./cli/cmd/tui/parser.worker.js", import.meta.url).href',
},
})
if (!result.success) {
console.error("bundle failed")
for (const log of result.logs) console.error(log)
process.exit(1)
}
const parserOut = path.join(dir, "dist/src/cli/cmd/tui/parser.worker.js")
fs.mkdirSync(path.dirname(parserOut), { recursive: true })
await Bun.write(parserOut, Bun.file(parser))
+62 -107
View File
@@ -2,144 +2,99 @@
lib,
stdenv,
rustPlatform,
bun,
pkg-config,
dbus ? null,
openssl,
glib ? null,
gtk3 ? null,
libsoup_3 ? null,
webkitgtk_4_1 ? null,
librsvg ? null,
libappindicator-gtk3 ? null,
cargo-tauri,
bun,
nodejs,
cargo,
rustc,
makeBinaryWrapper,
nodejs,
jq,
wrapGAppsHook4,
makeWrapper,
dbus,
glib,
gtk4,
libsoup_3,
librsvg,
libappindicator,
glib-networking,
openssl,
webkitgtk_4_1,
gst_all_1,
opencode,
}:
args:
let
scripts = args.scripts;
mkModules =
attrs:
args.mkNodeModules (
attrs
// {
canonicalizeScript = scripts + "/canonicalize-node-modules.ts";
normalizeBinsScript = scripts + "/normalize-bun-binaries.ts";
}
);
in
rustPlatform.buildRustPackage rec {
rustPlatform.buildRustPackage (finalAttrs: {
pname = "opencode-desktop";
version = args.version;
inherit (opencode)
version
src
node_modules
patches
;
src = args.src;
# We need to set the root for cargo, but we also need access to the whole repo.
postUnpack = ''
# Update sourceRoot to point to the tauri app
sourceRoot+=/packages/desktop/src-tauri
'';
cargoLock = {
lockFile = ../packages/desktop/src-tauri/Cargo.lock;
allowBuiltinFetchGit = true;
};
node_modules = mkModules {
version = version;
src = src;
};
cargoRoot = "packages/desktop/src-tauri";
cargoLock.lockFile = ../packages/desktop/src-tauri/Cargo.lock;
buildAndTestSubdir = finalAttrs.cargoRoot;
nativeBuildInputs = [
pkg-config
cargo-tauri.hook
bun
makeBinaryWrapper
nodejs # for patchShebangs node_modules
cargo
rustc
nodejs
jq
];
buildInputs = [
openssl
makeWrapper
]
++ lib.optionals stdenv.isLinux [
++ lib.optionals stdenv.hostPlatform.isLinux [ wrapGAppsHook4 ];
buildInputs = lib.optionals stdenv.isLinux [
dbus
glib
gtk3
gtk4
libsoup_3
webkitgtk_4_1
librsvg
libappindicator-gtk3
libappindicator
glib-networking
openssl
webkitgtk_4_1
gst_all_1.gstreamer
gst_all_1.gst-plugins-base
gst_all_1.gst-plugins-good
];
strictDeps = true;
preBuild = ''
# Restore node_modules
pushd ../../..
# Copy node_modules from the fixed-output derivation
# We use cp -r --no-preserve=mode to ensure we can write to them if needed,
# though we usually just read.
cp -r ${node_modules}/node_modules .
cp -r ${node_modules}/packages .
# Ensure node_modules is writable so patchShebangs can update script headers
chmod -R u+w node_modules
# Ensure workspace packages are writable for tsgo incremental outputs (.tsbuildinfo)
chmod -R u+w packages
# Patch shebangs so scripts can run
cp -a ${finalAttrs.node_modules}/{node_modules,packages} .
chmod -R u+w node_modules packages
patchShebangs node_modules
patchShebangs packages/desktop/node_modules
# Copy sidecar
mkdir -p packages/desktop/src-tauri/sidecars
targetTriple=${stdenv.hostPlatform.rust.rustcTarget}
cp ${args.opencode}/bin/opencode packages/desktop/src-tauri/sidecars/opencode-cli-$targetTriple
# Merge prod config into tauri.conf.json
if ! jq -s '.[0] * .[1]' \
packages/desktop/src-tauri/tauri.conf.json \
packages/desktop/src-tauri/tauri.prod.conf.json \
> packages/desktop/src-tauri/tauri.conf.json.tmp; then
echo "Error: failed to merge tauri.conf.json with tauri.prod.conf.json" >&2
exit 1
fi
mv packages/desktop/src-tauri/tauri.conf.json.tmp packages/desktop/src-tauri/tauri.conf.json
# Build the frontend
cd packages/desktop
# The 'build' script runs 'bun run typecheck && vite build'.
bun run build
popd
cp ${opencode}/bin/opencode packages/desktop/src-tauri/sidecars/opencode-cli-${stdenv.hostPlatform.rust.rustcTarget}
'';
# Tauri bundles the assets during the rust build phase (which happens after preBuild).
# It looks for them in the location specified in tauri.conf.json.
# see publish-tauri job in .github/workflows/publish.yml
tauriBuildFlags = [
"--config"
"tauri.prod.conf.json"
"--no-sign" # no code signing or auto updates
];
postInstall = lib.optionalString stdenv.isLinux ''
# Wrap the binary to ensure it finds the libraries
wrapProgram $out/bin/opencode-desktop \
--prefix LD_LIBRARY_PATH : ${
lib.makeLibraryPath [
gtk3
webkitgtk_4_1
librsvg
glib
libsoup_3
]
}
# FIXME: workaround for concerns about case insensitive filesystems
# should be removed once binary is renamed or decided otherwise
# darwin output is a .app bundle so no conflict
postFixup = lib.optionalString stdenv.hostPlatform.isLinux ''
mv $out/bin/OpenCode $out/bin/opencode-desktop
sed -i 's|^Exec=OpenCode$|Exec=opencode-desktop|' $out/share/applications/OpenCode.desktop
'';
meta = with lib; {
meta = {
description = "OpenCode Desktop App";
homepage = "https://opencode.ai";
license = licenses.mit;
maintainers = with maintainers; [ ];
license = lib.licenses.mit;
mainProgram = "opencode-desktop";
platforms = platforms.linux ++ platforms.darwin;
inherit (opencode.meta) platforms;
};
}
})
+6 -1
View File
@@ -1,3 +1,8 @@
{
"nodeModules": "sha256-2K+uwNPfgptAdZ8YVCCI+SJl84QHKosuQ07/OLGDPAA="
"nodeModules": {
"x86_64-linux": "sha256-fqwMFUpYQZrS7b+MnXQvRj1kemXlnILGAScJY+eWtD0=",
"aarch64-linux": "sha256-xsrV/gUVCKinzY4nx7NuceeLKx5pEdUWxeEm9GPHw34=",
"aarch64-darwin": "sha256-oH2cr1/Dfk6YToBon/mKMguvvj9WoE4E7MJjo/keL+A=",
"x86_64-darwin": "sha256-QvzBGDeqwGKC339KTDuh2X4DFGZF8AkpROx59nGbw0U="
}
}
-60
View File
@@ -1,60 +0,0 @@
{
hash,
lib,
stdenvNoCC,
bun,
cacert,
curl,
}:
args:
stdenvNoCC.mkDerivation {
pname = "opencode-node_modules";
inherit (args) version src;
impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ [
"GIT_PROXY_COMMAND"
"SOCKS_SERVER"
];
nativeBuildInputs = [
bun
cacert
curl
];
dontConfigure = true;
buildPhase = ''
runHook preBuild
export HOME=$(mktemp -d)
export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
bun install \
--cpu="*" \
--os="*" \
--frozen-lockfile \
--ignore-scripts \
--no-progress \
--linker=isolated
bun --bun ${args.canonicalizeScript}
bun --bun ${args.normalizeBinsScript}
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out
while IFS= read -r dir; do
rel="''${dir#./}"
dest="$out/$rel"
mkdir -p "$(dirname "$dest")"
cp -R "$dir" "$dest"
done < <(find . -type d -name node_modules -prune | sort)
runHook postInstall
'';
dontFixup = true;
outputHashAlgo = "sha256";
outputHashMode = "recursive";
outputHash = hash;
}
+85
View File
@@ -0,0 +1,85 @@
{
lib,
stdenvNoCC,
bun,
bunCpu ? if stdenvNoCC.hostPlatform.isAarch64 then "arm64" else "x64",
bunOs ? if stdenvNoCC.hostPlatform.isLinux then "linux" else "darwin",
rev ? "dirty",
hash ?
(lib.pipe ./hashes.json [
builtins.readFile
builtins.fromJSON
]).nodeModules.${stdenvNoCC.hostPlatform.system},
}:
let
packageJson = lib.pipe ../packages/opencode/package.json [
builtins.readFile
builtins.fromJSON
];
in
stdenvNoCC.mkDerivation {
pname = "opencode-node_modules";
version = "${packageJson.version}-${rev}";
src = lib.fileset.toSource {
root = ../.;
fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ../.)) (
lib.fileset.unions [
../packages
../bun.lock
../package.json
../patches
../install
]
);
};
impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ [
"GIT_PROXY_COMMAND"
"SOCKS_SERVER"
];
nativeBuildInputs = [
bun
];
dontConfigure = true;
buildPhase = ''
runHook preBuild
export HOME=$(mktemp -d)
export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
bun install \
--cpu="${bunCpu}" \
--os="${bunOs}" \
--frozen-lockfile \
--ignore-scripts \
--no-progress \
--linker=isolated
bun --bun ${./scripts/canonicalize-node-modules.ts}
bun --bun ${./scripts/normalize-bun-binaries.ts}
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out
find . -type d -name node_modules -exec cp -R --parents {} $out \;
runHook postInstall
'';
dontFixup = true;
outputHashAlgo = "sha256";
outputHashMode = "recursive";
outputHash = hash;
meta.platforms = [
"aarch64-linux"
"x86_64-linux"
"aarch64-darwin"
"x86_64-darwin"
];
}
+58 -100
View File
@@ -1,61 +1,48 @@
{
lib,
stdenvNoCC,
callPackage,
bun,
ripgrep,
sysctl,
makeBinaryWrapper,
models-dev,
ripgrep,
installShellFiles,
versionCheckHook,
writableTmpDirAsHomeHook,
node_modules ? callPackage ./node-modules.nix { },
}:
args:
let
inherit (args) scripts;
mkModules =
attrs:
args.mkNodeModules (
attrs
// {
canonicalizeScript = scripts + "/canonicalize-node-modules.ts";
normalizeBinsScript = scripts + "/normalize-bun-binaries.ts";
}
);
in
stdenvNoCC.mkDerivation (finalAttrs: {
pname = "opencode";
inherit (args) version src;
node_modules = mkModules {
inherit (finalAttrs) version src;
};
inherit (node_modules) version src;
inherit node_modules;
nativeBuildInputs = [
bun
installShellFiles
makeBinaryWrapper
models-dev
writableTmpDirAsHomeHook
];
env.MODELS_DEV_API_JSON = args.modelsDev;
env.OPENCODE_VERSION = args.version;
env.OPENCODE_CHANNEL = "stable";
dontConfigure = true;
configurePhase = ''
runHook preConfigure
cp -R ${finalAttrs.node_modules}/. .
runHook postConfigure
'';
env.MODELS_DEV_API_JSON = "${models-dev}/dist/_api.json";
env.OPENCODE_VERSION = finalAttrs.version;
env.OPENCODE_CHANNEL = "local";
buildPhase = ''
runHook preBuild
cp -r ${finalAttrs.node_modules}/node_modules .
cp -r ${finalAttrs.node_modules}/packages .
(
cd packages/opencode
chmod -R u+w ./node_modules
mkdir -p ./node_modules/@opencode-ai
rm -f ./node_modules/@opencode-ai/{script,sdk,plugin}
ln -s $(pwd)/../../packages/script ./node_modules/@opencode-ai/script
ln -s $(pwd)/../../packages/sdk/js ./node_modules/@opencode-ai/sdk
ln -s $(pwd)/../../packages/plugin ./node_modules/@opencode-ai/plugin
cp ${./bundle.ts} ./bundle.ts
chmod +x ./bundle.ts
bun run ./bundle.ts
)
cd ./packages/opencode
bun --bun ./script/build.ts --single --skip-install
bun --bun ./script/schema.ts schema.json
runHook postBuild
'';
@@ -63,76 +50,47 @@ stdenvNoCC.mkDerivation (finalAttrs: {
installPhase = ''
runHook preInstall
cd packages/opencode
if [ ! -d dist ]; then
echo "ERROR: dist directory missing after bundle step"
exit 1
fi
install -Dm755 dist/opencode-*/bin/opencode $out/bin/opencode
install -Dm644 schema.json $out/share/opencode/schema.json
mkdir -p $out/lib/opencode
cp -r dist $out/lib/opencode/
chmod -R u+w $out/lib/opencode/dist
# Select bundled worker assets deterministically (sorted find output)
worker_file=$(find "$out/lib/opencode/dist" -type f \( -path '*/tui/worker.*' -o -name 'worker.*' \) | sort | head -n1)
parser_worker_file=$(find "$out/lib/opencode/dist" -type f -name 'parser.worker.*' | sort | head -n1)
if [ -z "$worker_file" ]; then
echo "ERROR: bundled worker not found"
exit 1
fi
main_wasm=$(printf '%s\n' "$out"/lib/opencode/dist/tree-sitter-*.wasm | sort | head -n1)
wasm_list=$(find "$out/lib/opencode/dist" -maxdepth 1 -name 'tree-sitter-*.wasm' -print)
for patch_file in "$worker_file" "$parser_worker_file"; do
[ -z "$patch_file" ] && continue
[ ! -f "$patch_file" ] && continue
if [ -n "$wasm_list" ] && grep -q 'tree-sitter' "$patch_file"; then
# Rewrite wasm references to absolute store paths to avoid runtime resolve failures.
bun --bun ${scripts + "/patch-wasm.ts"} "$patch_file" "$main_wasm" $wasm_list
fi
done
mkdir -p $out/lib/opencode/node_modules
cp -r ../../node_modules/.bun $out/lib/opencode/node_modules/
mkdir -p $out/lib/opencode/node_modules/@opentui
mkdir -p $out/bin
makeWrapper ${bun}/bin/bun $out/bin/opencode \
--add-flags "run" \
--add-flags "$out/lib/opencode/dist/src/index.js" \
--prefix PATH : ${lib.makeBinPath [ ripgrep ]} \
--argv0 opencode
wrapProgram $out/bin/opencode \
--prefix PATH : ${
lib.makeBinPath (
[
ripgrep
]
# bun runs sysctl to detect if dunning on rosetta2
++ lib.optional stdenvNoCC.hostPlatform.isDarwin sysctl
)
}
runHook postInstall
'';
postInstall = ''
for pkg in $out/lib/opencode/node_modules/.bun/@opentui+core-* $out/lib/opencode/node_modules/.bun/@opentui+solid-* $out/lib/opencode/node_modules/.bun/@opentui+core@* $out/lib/opencode/node_modules/.bun/@opentui+solid@*; do
if [ -d "$pkg" ]; then
pkgName=$(basename "$pkg" | sed 's/@opentui+\([^@]*\)@.*/\1/')
ln -sf ../.bun/$(basename "$pkg")/node_modules/@opentui/$pkgName \
$out/lib/opencode/node_modules/@opentui/$pkgName
fi
done
postInstall = lib.optionalString (stdenvNoCC.buildPlatform.canExecute stdenvNoCC.hostPlatform) ''
# trick yargs into also generating zsh completions
installShellCompletion --cmd opencode \
--bash <($out/bin/opencode completion) \
--zsh <(SHELL=/bin/zsh $out/bin/opencode completion)
'';
dontFixup = true;
nativeInstallCheckInputs = [
versionCheckHook
writableTmpDirAsHomeHook
];
doInstallCheck = true;
versionCheckKeepEnvironment = [ "HOME" ];
versionCheckProgramArg = "--version";
passthru = {
jsonschema = "${placeholder "out"}/share/opencode/schema.json";
};
meta = {
description = "AI coding agent built for the terminal";
longDescription = ''
OpenCode is a terminal-based agent that can build anything.
It combines a TypeScript/JavaScript core with a Go-based TUI
to provide an interactive AI coding experience.
'';
homepage = "https://github.com/anomalyco/opencode";
description = "The open source coding agent";
homepage = "https://opencode.ai/";
license = lib.licenses.mit;
platforms = [
"aarch64-linux"
"x86_64-linux"
"aarch64-darwin"
"x86_64-darwin"
];
mainProgram = "opencode";
inherit (node_modules.meta) platforms;
};
})
-115
View File
@@ -1,115 +0,0 @@
import solidPlugin from "./packages/opencode/node_modules/@opentui/solid/scripts/solid-plugin"
import path from "path"
import fs from "fs"
const version = "@VERSION@"
const pkg = path.join(process.cwd(), "packages/opencode")
const parser = fs.realpathSync(path.join(pkg, "./node_modules/@opentui/core/parser.worker.js"))
const worker = "./src/cli/cmd/tui/worker.ts"
const target = process.env["BUN_COMPILE_TARGET"]
if (!target) {
throw new Error("BUN_COMPILE_TARGET not set")
}
process.chdir(pkg)
const manifestName = "opencode-assets.manifest"
const manifestPath = path.join(pkg, manifestName)
const readTrackedAssets = () => {
if (!fs.existsSync(manifestPath)) return []
return fs
.readFileSync(manifestPath, "utf8")
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0)
}
const removeTrackedAssets = () => {
for (const file of readTrackedAssets()) {
const filePath = path.join(pkg, file)
if (fs.existsSync(filePath)) {
fs.rmSync(filePath, { force: true })
}
}
}
const assets = new Set<string>()
const addAsset = async (p: string) => {
const file = path.basename(p)
const dest = path.join(pkg, file)
await Bun.write(dest, Bun.file(p))
assets.add(file)
}
removeTrackedAssets()
const result = await Bun.build({
conditions: ["browser"],
tsconfig: "./tsconfig.json",
plugins: [solidPlugin],
sourcemap: "external",
entrypoints: ["./src/index.ts", parser, worker],
define: {
OPENCODE_VERSION: `'@VERSION@'`,
OTUI_TREE_SITTER_WORKER_PATH: "/$bunfs/root/" + path.relative(pkg, parser).replace(/\\/g, "/"),
OPENCODE_CHANNEL: "'latest'",
},
compile: {
target,
outfile: "opencode",
execArgv: ["--user-agent=opencode/" + version, '--env-file=""', "--"],
windows: {},
},
})
if (!result.success) {
console.error("Build failed!")
for (const log of result.logs) {
console.error(log)
}
throw new Error("Compilation failed")
}
const assetOutputs = result.outputs?.filter((x) => x.kind === "asset") ?? []
for (const x of assetOutputs) {
await addAsset(x.path)
}
const bundle = await Bun.build({
entrypoints: [worker],
tsconfig: "./tsconfig.json",
plugins: [solidPlugin],
target: "bun",
outdir: "./.opencode-worker",
sourcemap: "none",
})
if (!bundle.success) {
console.error("Worker build failed!")
for (const log of bundle.logs) {
console.error(log)
}
throw new Error("Worker compilation failed")
}
const workerAssets = bundle.outputs?.filter((x) => x.kind === "asset") ?? []
for (const x of workerAssets) {
await addAsset(x.path)
}
const output = bundle.outputs.find((x) => x.kind === "entry-point")
if (!output) {
throw new Error("Worker build produced no entry-point output")
}
const dest = path.join(pkg, "opencode-worker.js")
await Bun.write(dest, Bun.file(output.path))
fs.rmSync(path.dirname(output.path), { recursive: true, force: true })
const list = Array.from(assets)
await Bun.write(manifestPath, list.length > 0 ? list.join("\n") + "\n" : "")
console.log("Build successful!")
-43
View File
@@ -1,43 +0,0 @@
#!/usr/bin/env bun
import fs from "fs"
import path from "path"
/**
* Rewrite tree-sitter wasm references inside a JS file to absolute paths.
* argv: [node, script, file, mainWasm, ...wasmPaths]
*/
const [, , file, mainWasm, ...wasmPaths] = process.argv
if (!file || !mainWasm) {
console.error("usage: patch-wasm <file> <mainWasm> [wasmPaths...]")
process.exit(1)
}
const content = fs.readFileSync(file, "utf8")
const byName = new Map<string, string>()
for (const wasm of wasmPaths) {
const name = path.basename(wasm)
byName.set(name, wasm)
}
let next = content
for (const [name, wasmPath] of byName) {
next = next.replaceAll(name, wasmPath)
}
next = next.replaceAll("tree-sitter.wasm", mainWasm).replaceAll("web-tree-sitter/tree-sitter.wasm", mainWasm)
// Collapse any relative prefixes before absolute store paths (e.g., "../../../..//nix/store/...")
const nixStorePrefix = process.env.NIX_STORE || "/nix/store"
next = next.replace(/(\.\/)+/g, "./")
next = next.replace(
new RegExp(`(\\.\\.\\/)+\\/{1,2}(${nixStorePrefix.replace(/^\//, "").replace(/\//g, "\\/")}[^"']+)`, "g"),
"/$2",
)
next = next.replace(new RegExp(`(["'])\\/{2,}(\\/${nixStorePrefix.replace(/\//g, "\\/")}[^"']+)(["'])`, "g"), "$1$2$3")
next = next.replace(new RegExp(`(["'])\\/\\/(${nixStorePrefix.replace(/\//g, "\\/")}[^"']+)(["'])`, "g"), "$1$2$3")
if (next !== content) fs.writeFileSync(file, next)
-112
View File
@@ -1,112 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
DUMMY="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
SYSTEM=${SYSTEM:-x86_64-linux}
DEFAULT_HASH_FILE=${MODULES_HASH_FILE:-nix/hashes.json}
HASH_FILE=${HASH_FILE:-$DEFAULT_HASH_FILE}
if [ ! -f "$HASH_FILE" ]; then
cat >"$HASH_FILE" <<EOF
{
"nodeModules": "$DUMMY"
}
EOF
fi
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
if ! git ls-files --error-unmatch "$HASH_FILE" >/dev/null 2>&1; then
git add -N "$HASH_FILE" >/dev/null 2>&1 || true
fi
fi
export DUMMY
export NIX_KEEP_OUTPUTS=1
export NIX_KEEP_DERIVATIONS=1
cleanup() {
rm -f "${JSON_OUTPUT:-}" "${BUILD_LOG:-}" "${TMP_EXPR:-}"
}
trap cleanup EXIT
write_node_modules_hash() {
local value="$1"
local temp
temp=$(mktemp)
jq --arg value "$value" '.nodeModules = $value' "$HASH_FILE" >"$temp"
mv "$temp" "$HASH_FILE"
}
TARGET="packages.${SYSTEM}.default"
MODULES_ATTR=".#packages.${SYSTEM}.default.node_modules"
CORRECT_HASH=""
DRV_PATH="$(nix eval --raw "${MODULES_ATTR}.drvPath")"
echo "Setting dummy node_modules outputHash for ${SYSTEM}..."
write_node_modules_hash "$DUMMY"
BUILD_LOG=$(mktemp)
JSON_OUTPUT=$(mktemp)
echo "Building node_modules for ${SYSTEM} to discover correct outputHash..."
echo "Attempting to realize derivation: ${DRV_PATH}"
REALISE_OUT=$(nix-store --realise "$DRV_PATH" --keep-failed 2>&1 | tee "$BUILD_LOG" || true)
BUILD_PATH=$(echo "$REALISE_OUT" | grep "^/nix/store/" | head -n1 || true)
if [ -n "$BUILD_PATH" ] && [ -d "$BUILD_PATH" ]; then
echo "Realized node_modules output: $BUILD_PATH"
CORRECT_HASH=$(nix hash path --sri "$BUILD_PATH" 2>/dev/null || true)
fi
if [ -z "$CORRECT_HASH" ]; then
CORRECT_HASH="$(grep -E 'got:\s+sha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | awk '{print $2}' | head -n1 || true)"
if [ -z "$CORRECT_HASH" ]; then
CORRECT_HASH="$(grep -A2 'hash mismatch' "$BUILD_LOG" | grep 'got:' | awk '{print $2}' | sed 's/sha256:/sha256-/' || true)"
fi
if [ -z "$CORRECT_HASH" ]; then
echo "Searching for kept failed build directory..."
KEPT_DIR=$(grep -oE "build directory.*'[^']+'" "$BUILD_LOG" | grep -oE "'/[^']+'" | tr -d "'" | head -n1)
if [ -z "$KEPT_DIR" ]; then
KEPT_DIR=$(grep -oE '/nix/var/nix/builds/[^ ]+' "$BUILD_LOG" | head -n1)
fi
if [ -n "$KEPT_DIR" ] && [ -d "$KEPT_DIR" ]; then
echo "Found kept build directory: $KEPT_DIR"
if [ -d "$KEPT_DIR/build" ]; then
HASH_PATH="$KEPT_DIR/build"
else
HASH_PATH="$KEPT_DIR"
fi
echo "Attempting to hash: $HASH_PATH"
ls -la "$HASH_PATH" || true
if [ -d "$HASH_PATH/node_modules" ]; then
CORRECT_HASH=$(nix hash path --sri "$HASH_PATH" 2>/dev/null || true)
echo "Computed hash from kept build: $CORRECT_HASH"
fi
fi
fi
fi
if [ -z "$CORRECT_HASH" ]; then
echo "Failed to determine correct node_modules hash for ${SYSTEM}."
echo "Build log:"
cat "$BUILD_LOG"
exit 1
fi
write_node_modules_hash "$CORRECT_HASH"
jq -e --arg hash "$CORRECT_HASH" '.nodeModules == $hash' "$HASH_FILE" >/dev/null
echo "node_modules hash updated for ${SYSTEM}: $CORRECT_HASH"
rm -f "$BUILD_LOG"
unset BUILD_LOG
+6 -2
View File
@@ -21,13 +21,14 @@
"packages/slack"
],
"catalog": {
"@types/bun": "1.3.4",
"@types/bun": "1.3.5",
"@octokit/rest": "22.0.0",
"@hono/zod-validator": "0.4.2",
"ulid": "3.0.1",
"@kobalte/core": "0.13.11",
"@types/luxon": "3.7.1",
"@types/node": "22.13.9",
"@types/semver": "7.7.1",
"@tsconfig/node22": "22.0.2",
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
@@ -36,13 +37,15 @@
"@solid-primitives/storage": "4.3.3",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
"ai": "5.0.97",
"dompurify": "3.3.1",
"ai": "5.0.119",
"hono": "4.10.7",
"hono-openapi": "1.1.2",
"fuzzysort": "3.1.0",
"luxon": "3.6.1",
"marked": "17.0.1",
"marked-shiki": "1.2.1",
"@playwright/test": "1.51.0",
"typescript": "5.8.2",
"@typescript/native-preview": "7.0.0-dev.20251207.1",
"zod": "4.1.8",
@@ -64,6 +67,7 @@
"@tsconfig/bun": "catalog:",
"husky": "9.1.7",
"prettier": "3.6.2",
"semver": "^7.6.0",
"sst": "3.17.23",
"turbo": "2.5.6"
},
+2
View File
@@ -1 +1,3 @@
src/assets/theme.css
e2e/test-results
e2e/playwright-report
+19 -2
View File
@@ -1,9 +1,15 @@
## Debugging
- To test the opencode app, use the playwright MCP server, the app is already
running at http://localhost:3000
- NEVER try to restart the app, or the server process, EVER.
## Local Dev
- `opencode dev web` proxies `https://app.opencode.ai`, so local UI/CSS changes will not show there.
- For local UI changes, run the backend and app dev servers separately.
- Backend (from `packages/opencode`): `bun run --conditions=browser ./src/index.ts serve --port 4096`
- App (from `packages/app`): `bun dev -- --port 4444`
- Open `http://localhost:4444` to verify UI changes (it targets the backend at `http://localhost:4096`).
## SolidJS
- Always prefer `createStore` over multiple `createSignal` calls
@@ -11,3 +17,14 @@
## Tool Calling
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
## Browser Automation
Use `agent-browser` for web automation. Run `agent-browser --help` for all commands.
Core workflow:
1. `agent-browser open <url>` - Navigate to page
2. `agent-browser snapshot -i` - Get interactive elements with refs (@e1, @e2)
3. `agent-browser click @e1` / `fill @e2 "text"` - Interact using refs
4. Re-snapshot after page changes
+19 -2
View File
@@ -1,8 +1,8 @@
## Usage
Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.
Dependencies for these templates are managed with [pnpm](https://pnpm.io) using `pnpm up -Lri`.
This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template.
This is the reason you see a `pnpm-lock.yaml`. That said, any package manager will work. This file can safely be removed once you clone a template.
```bash
$ npm install # or pnpm install or yarn install
@@ -29,6 +29,23 @@ It correctly bundles Solid in production mode and optimizes the build for the be
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
## E2E Testing
Playwright starts the Vite dev server automatically via `webServer`, and UI tests need an opencode backend (defaults to `localhost:4096`).
Use the local runner to create a temp sandbox, seed data, and run the tests.
```bash
bunx playwright install
bun run test:e2e:local
bun run test:e2e:local -- --grep "settings"
```
Environment options:
- `PLAYWRIGHT_SERVER_HOST` / `PLAYWRIGHT_SERVER_PORT` (backend address, default: `localhost:4096`)
- `PLAYWRIGHT_PORT` (Vite dev server port, default: `3000`)
- `PLAYWRIGHT_BASE_URL` (override base URL, default: `http://localhost:<PLAYWRIGHT_PORT>`)
## Deployment
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)
+45
View File
@@ -0,0 +1,45 @@
import { test, expect } from "./fixtures"
import { promptSelector } from "./utils"
test("context panel can be opened from the prompt", async ({ page, sdk, gotoSession }) => {
const title = `e2e smoke context ${Date.now()}`
const created = await sdk.session.create({ title }).then((r) => r.data)
if (!created?.id) throw new Error("Session create did not return an id")
const sessionID = created.id
try {
await sdk.session.promptAsync({
sessionID,
noReply: true,
parts: [
{
type: "text",
text: "seed context",
},
],
})
await expect
.poll(async () => {
const messages = await sdk.session.messages({ sessionID, limit: 1 }).then((r) => r.data ?? [])
return messages.length
})
.toBeGreaterThan(0)
await gotoSession(sessionID)
const contextButton = page
.locator('[data-component="button"]')
.filter({ has: page.locator('[data-component="progress-circle"]').first() })
.first()
await expect(contextButton).toBeVisible()
await contextButton.click()
const tabs = page.locator('[data-component="tabs"][data-variant="normal"]')
await expect(tabs.getByRole("tab", { name: "Context" })).toBeVisible()
} finally {
await sdk.session.delete({ sessionID }).catch(() => undefined)
}
})
+23
View File
@@ -0,0 +1,23 @@
import { test, expect } from "./fixtures"
import { modKey } from "./utils"
test("can open a file tab from the search palette", async ({ page, gotoSession }) => {
await gotoSession()
await page.keyboard.press(`${modKey}+P`)
const dialog = page.getByRole("dialog")
await expect(dialog).toBeVisible()
const input = dialog.getByRole("textbox").first()
await input.fill("package.json")
const fileItem = dialog.locator('[data-slot="list-item"][data-key^="file:"]').first()
await expect(fileItem).toBeVisible()
await fileItem.click()
await expect(dialog).toHaveCount(0)
const tabs = page.locator('[data-component="tabs"][data-variant="normal"]')
await expect(tabs.locator('[data-slot="tabs-trigger"]').first()).toBeVisible()
})
+40
View File
@@ -0,0 +1,40 @@
import { test as base, expect } from "@playwright/test"
import { createSdk, dirSlug, getWorktree, promptSelector, sessionPath } from "./utils"
type TestFixtures = {
sdk: ReturnType<typeof createSdk>
gotoSession: (sessionID?: string) => Promise<void>
}
type WorkerFixtures = {
directory: string
slug: string
}
export const test = base.extend<TestFixtures, WorkerFixtures>({
directory: [
async ({}, use) => {
const directory = await getWorktree()
await use(directory)
},
{ scope: "worker" },
],
slug: [
async ({ directory }, use) => {
await use(dirSlug(directory))
},
{ scope: "worker" },
],
sdk: async ({ directory }, use) => {
await use(createSdk(directory))
},
gotoSession: async ({ page, directory }, use) => {
const gotoSession = async (sessionID?: string) => {
await page.goto(sessionPath(directory, sessionID))
await expect(page.locator(promptSelector)).toBeVisible()
}
await use(gotoSession)
},
})
export { expect }
+21
View File
@@ -0,0 +1,21 @@
import { test, expect } from "./fixtures"
import { serverName } from "./utils"
test("home renders and shows core entrypoints", async ({ page }) => {
await page.goto("/")
await expect(page.getByRole("button", { name: "Open project" }).first()).toBeVisible()
await expect(page.getByRole("button", { name: serverName })).toBeVisible()
})
test("server picker dialog opens from home", async ({ page }) => {
await page.goto("/")
const trigger = page.getByRole("button", { name: serverName })
await expect(trigger).toBeVisible()
await trigger.click()
const dialog = page.getByRole("dialog")
await expect(dialog).toBeVisible()
await expect(dialog.getByRole("textbox").first()).toBeVisible()
})
+9
View File
@@ -0,0 +1,9 @@
import { test, expect } from "./fixtures"
import { dirPath, promptSelector } from "./utils"
test("project route redirects to /session", async ({ page, directory, slug }) => {
await page.goto(dirPath(directory))
await expect(page).toHaveURL(new RegExp(`/${slug}/session`))
await expect(page.locator(promptSelector)).toBeVisible()
})
+15
View File
@@ -0,0 +1,15 @@
import { test, expect } from "./fixtures"
import { modKey } from "./utils"
test("search palette opens and closes", async ({ page, gotoSession }) => {
await gotoSession()
await page.keyboard.press(`${modKey}+P`)
const dialog = page.getByRole("dialog")
await expect(dialog).toBeVisible()
await expect(dialog.getByRole("textbox").first()).toBeVisible()
await page.keyboard.press("Escape")
await expect(dialog).toHaveCount(0)
})
+62
View File
@@ -0,0 +1,62 @@
import { test, expect } from "./fixtures"
import { promptSelector } from "./utils"
function sessionIDFromUrl(url: string) {
const match = /\/session\/([^/?#]+)/.exec(url)
return match?.[1]
}
test("can send a prompt and receive a reply", async ({ page, sdk, gotoSession }) => {
test.setTimeout(120_000)
const pageErrors: string[] = []
const onPageError = (err: Error) => {
pageErrors.push(err.message)
}
page.on("pageerror", onPageError)
await gotoSession()
const token = `E2E_OK_${Date.now()}`
const prompt = page.locator(promptSelector)
await prompt.click()
await page.keyboard.type(`Reply with exactly: ${token}`)
await page.keyboard.press("Enter")
await expect(page).toHaveURL(/\/session\/[^/?#]+/, { timeout: 30_000 })
const sessionID = (() => {
const id = sessionIDFromUrl(page.url())
if (!id) throw new Error(`Failed to parse session id from url: ${page.url()}`)
return id
})()
try {
await expect
.poll(
async () => {
const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
return messages
.filter((m) => m.info.role === "assistant")
.flatMap((m) => m.parts)
.filter((p) => p.type === "text")
.map((p) => p.text)
.join("\n")
},
{ timeout: 90_000 },
)
.toContain(token)
const reply = page.locator('[data-slot="session-turn-summary-section"]').filter({ hasText: token }).first()
await expect(reply).toBeVisible({ timeout: 90_000 })
} finally {
page.off("pageerror", onPageError)
await sdk.session.delete({ sessionID }).catch(() => undefined)
}
if (pageErrors.length > 0) {
throw new Error(`Page error(s):\n${pageErrors.join("\n")}`)
}
})
+21
View File
@@ -0,0 +1,21 @@
import { test, expect } from "./fixtures"
import { promptSelector } from "./utils"
test("can open an existing session and type into the prompt", async ({ page, sdk, gotoSession }) => {
const title = `e2e smoke ${Date.now()}`
const created = await sdk.session.create({ title }).then((r) => r.data)
if (!created?.id) throw new Error("Session create did not return an id")
const sessionID = created.id
try {
await gotoSession(sessionID)
const prompt = page.locator(promptSelector)
await prompt.click()
await page.keyboard.type("hello from e2e")
await expect(prompt).toContainText("hello from e2e")
} finally {
await sdk.session.delete({ sessionID }).catch(() => undefined)
}
})
+21
View File
@@ -0,0 +1,21 @@
import { test, expect } from "./fixtures"
import { modKey } from "./utils"
test("sidebar can be collapsed and expanded", async ({ page, gotoSession }) => {
await gotoSession()
const main = page.locator("main")
const closedClass = /xl:border-l/
const isClosed = await main.evaluate((node) => node.className.includes("xl:border-l"))
if (isClosed) {
await page.keyboard.press(`${modKey}+B`)
await expect(main).not.toHaveClass(closedClass)
}
await page.keyboard.press(`${modKey}+B`)
await expect(main).toHaveClass(closedClass)
await page.keyboard.press(`${modKey}+B`)
await expect(main).not.toHaveClass(closedClass)
})
+16
View File
@@ -0,0 +1,16 @@
import { test, expect } from "./fixtures"
import { terminalSelector, terminalToggleKey } from "./utils"
test("terminal panel can be toggled", async ({ page, gotoSession }) => {
await gotoSession()
const terminal = page.locator(terminalSelector)
const initiallyOpen = await terminal.isVisible()
if (initiallyOpen) {
await page.keyboard.press(terminalToggleKey)
await expect(terminal).toHaveCount(0)
}
await page.keyboard.press(terminalToggleKey)
await expect(terminal).toBeVisible()
})
+38
View File
@@ -0,0 +1,38 @@
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import { base64Encode } from "@opencode-ai/util/encode"
export const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "localhost"
export const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
export const serverUrl = `http://${serverHost}:${serverPort}`
export const serverName = `${serverHost}:${serverPort}`
export const modKey = process.platform === "darwin" ? "Meta" : "Control"
export const terminalToggleKey = "Control+Backquote"
export const promptSelector = '[data-component="prompt-input"]'
export const terminalSelector = '[data-component="terminal"]'
export function createSdk(directory?: string) {
return createOpencodeClient({ baseUrl: serverUrl, directory, throwOnError: true })
}
export async function getWorktree() {
const sdk = createSdk()
const result = await sdk.path.get()
const data = result.data
if (!data?.worktree) throw new Error(`Failed to resolve a worktree from ${serverUrl}/path`)
return data.worktree
}
export function dirSlug(directory: string) {
return base64Encode(directory)
}
export function dirPath(directory: string) {
return `/${dirSlug(directory)}`
}
export function sessionPath(directory: string, sessionID?: string) {
return `${dirPath(directory)}/session${sessionID ? `/${sessionID}` : ""}`
}
+6 -36
View File
@@ -4,50 +4,20 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OpenCode</title>
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" href="/favicon-96x96-v2.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon-v2.svg" />
<link rel="shortcut icon" href="/favicon-v2.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-v2.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#F8F7F7" />
<meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
<!-- Theme preload script - applies cached theme to avoid FOUC -->
<script id="oc-theme-preload-script">
;(function () {
var themeId = localStorage.getItem("opencode-theme-id")
if (!themeId) return
var scheme = localStorage.getItem("opencode-color-scheme") || "system"
var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)
var mode = isDark ? "dark" : "light"
document.documentElement.dataset.theme = themeId
document.documentElement.dataset.colorScheme = mode
if (themeId === "oc-1") return
var css = localStorage.getItem("opencode-theme-css-" + themeId + "-" + mode)
if (css) {
var style = document.createElement("style")
style.id = "oc-theme-preload"
style.textContent =
":root{color-scheme:" +
mode +
";--text-mix-blend-mode:" +
(isDark ? "plus-lighter" : "multiply") +
";" +
css +
"}"
document.head.appendChild(style)
}
})()
</script>
<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
</head>
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" class="flex flex-col h-screen"></div>
<div id="root" class="flex flex-col h-dvh p-px"></div>
<script src="/src/entry.tsx" type="module"></script>
</body>
</html>
+9 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.1.1",
"version": "1.1.29",
"description": "",
"type": "module",
"exports": {
@@ -12,11 +12,17 @@
"start": "vite",
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
"serve": "vite preview",
"test": "playwright test",
"test:e2e": "playwright test",
"test:e2e:local": "bun script/e2e-local.ts",
"test:e2e:ui": "playwright test --ui",
"test:e2e:report": "playwright show-report e2e/playwright-report"
},
"license": "MIT",
"devDependencies": {
"@happy-dom/global-registrator": "20.0.11",
"@playwright/test": "1.57.0",
"@tailwindcss/vite": "catalog:",
"@tsconfig/bun": "1.0.9",
"@types/bun": "catalog:",
@@ -36,6 +42,7 @@
"@shikijs/transformers": "3.9.2",
"@solid-primitives/active-element": "2.1.3",
"@solid-primitives/audio": "1.4.2",
"@solid-primitives/i18n": "2.2.1",
"@solid-primitives/event-bus": "1.1.2",
"@solid-primitives/media": "2.3.3",
"@solid-primitives/resize-observer": "2.1.3",
+43
View File
@@ -0,0 +1,43 @@
import { defineConfig, devices } from "@playwright/test"
const port = Number(process.env.PLAYWRIGHT_PORT ?? 3000)
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://localhost:${port}`
const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "localhost"
const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
const command = `bun run dev -- --host 0.0.0.0 --port ${port}`
const reuse = !process.env.CI
export default defineConfig({
testDir: "./e2e",
outputDir: "./e2e/test-results",
timeout: 60_000,
expect: {
timeout: 10_000,
},
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
reporter: [["html", { outputFolder: "e2e/playwright-report", open: "never" }], ["line"]],
webServer: {
command,
url: baseURL,
reuseExistingServer: reuse,
timeout: 120_000,
env: {
VITE_OPENCODE_SERVER_HOST: serverHost,
VITE_OPENCODE_SERVER_PORT: serverPort,
},
},
use: {
baseURL,
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
})
+1
View File
@@ -0,0 +1 @@
../../ui/src/assets/favicon/apple-touch-icon-v2.png
+1
View File
@@ -0,0 +1 @@
../../ui/src/assets/favicon/favicon-96x96-v2.png
+1
View File
@@ -0,0 +1 @@
../../ui/src/assets/favicon/favicon-v2.ico
+1
View File
@@ -0,0 +1 @@
../../ui/src/assets/favicon/favicon-v2.svg
+28
View File
@@ -0,0 +1,28 @@
;(function () {
var themeId = localStorage.getItem("opencode-theme-id")
if (!themeId) return
var scheme = localStorage.getItem("opencode-color-scheme") || "system"
var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)
var mode = isDark ? "dark" : "light"
document.documentElement.dataset.theme = themeId
document.documentElement.dataset.colorScheme = mode
if (themeId === "oc-1") return
var css = localStorage.getItem("opencode-theme-css-" + themeId + "-" + mode)
if (css) {
var style = document.createElement("style")
style.id = "oc-theme-preload"
style.textContent =
":root{color-scheme:" +
mode +
";--text-mix-blend-mode:" +
(isDark ? "plus-lighter" : "multiply") +
";" +
css +
"}"
document.head.appendChild(style)
}
})()
+143
View File
@@ -0,0 +1,143 @@
import fs from "node:fs/promises"
import net from "node:net"
import os from "node:os"
import path from "node:path"
async function freePort() {
return await new Promise<number>((resolve, reject) => {
const server = net.createServer()
server.once("error", reject)
server.listen(0, () => {
const address = server.address()
if (!address || typeof address === "string") {
server.close(() => reject(new Error("Failed to acquire a free port")))
return
}
server.close((err) => {
if (err) {
reject(err)
return
}
resolve(address.port)
})
})
})
}
async function waitForHealth(url: string) {
const timeout = Date.now() + 120_000
const errors: string[] = []
while (Date.now() < timeout) {
const result = await fetch(url)
.then((r) => ({ ok: r.ok, error: undefined }))
.catch((error) => ({
ok: false,
error: error instanceof Error ? error.message : String(error),
}))
if (result.ok) return
if (result.error) errors.push(result.error)
await new Promise((r) => setTimeout(r, 250))
}
const last = errors.length ? ` (last error: ${errors[errors.length - 1]})` : ""
throw new Error(`Timed out waiting for server health: ${url}${last}`)
}
const appDir = process.cwd()
const repoDir = path.resolve(appDir, "../..")
const opencodeDir = path.join(repoDir, "packages", "opencode")
const modelsJson = path.join(opencodeDir, "test", "tool", "fixtures", "models-api.json")
const extraArgs = (() => {
const args = process.argv.slice(2)
if (args[0] === "--") return args.slice(1)
return args
})()
const [serverPort, webPort] = await Promise.all([freePort(), freePort()])
const sandbox = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-e2e-"))
const serverEnv = {
...process.env,
MODELS_DEV_API_JSON: modelsJson,
OPENCODE_DISABLE_MODELS_FETCH: "true",
OPENCODE_DISABLE_SHARE: "true",
OPENCODE_DISABLE_LSP_DOWNLOAD: "true",
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true",
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true",
OPENCODE_TEST_HOME: path.join(sandbox, "home"),
XDG_DATA_HOME: path.join(sandbox, "share"),
XDG_CACHE_HOME: path.join(sandbox, "cache"),
XDG_CONFIG_HOME: path.join(sandbox, "config"),
XDG_STATE_HOME: path.join(sandbox, "state"),
OPENCODE_E2E_PROJECT_DIR: repoDir,
OPENCODE_E2E_SESSION_TITLE: "E2E Session",
OPENCODE_E2E_MESSAGE: "Seeded for UI e2e",
OPENCODE_E2E_MODEL: "opencode/gpt-5-nano",
OPENCODE_CLIENT: "app",
} satisfies Record<string, string>
const runnerEnv = {
...serverEnv,
PLAYWRIGHT_SERVER_HOST: "127.0.0.1",
PLAYWRIGHT_SERVER_PORT: String(serverPort),
VITE_OPENCODE_SERVER_HOST: "127.0.0.1",
VITE_OPENCODE_SERVER_PORT: String(serverPort),
PLAYWRIGHT_PORT: String(webPort),
} satisfies Record<string, string>
const seed = Bun.spawn(["bun", "script/seed-e2e.ts"], {
cwd: opencodeDir,
env: serverEnv,
stdout: "inherit",
stderr: "inherit",
})
const seedExit = await seed.exited
if (seedExit !== 0) {
process.exit(seedExit)
}
Object.assign(process.env, serverEnv)
process.env.AGENT = "1"
process.env.OPENCODE = "1"
const log = await import("../../opencode/src/util/log")
const install = await import("../../opencode/src/installation")
await log.Log.init({
print: true,
dev: install.Installation.isLocal(),
level: "WARN",
})
const servermod = await import("../../opencode/src/server/server")
const inst = await import("../../opencode/src/project/instance")
const server = servermod.Server.listen({ port: serverPort, hostname: "127.0.0.1" })
console.log(`opencode server listening on http://127.0.0.1:${serverPort}`)
const result = await (async () => {
try {
await waitForHealth(`http://127.0.0.1:${serverPort}/global/health`)
const runner = Bun.spawn(["bun", "test:e2e", ...extraArgs], {
cwd: appDir,
env: runnerEnv,
stdout: "inherit",
stderr: "inherit",
})
return { code: await runner.exited }
} catch (error) {
return { error }
} finally {
await inst.Instance.disposeAll()
await server.stop()
}
})()
if ("error" in result) {
console.error(result.error)
process.exit(1)
}
process.exit(result.code)
+47
View File
@@ -242,6 +242,53 @@ describe("SerializeAddon", () => {
expect(term2.buffer.active.getLine(2)?.translateToString(true)).toBe("total 42")
})
test("serialized output should restore after Terminal.reset()", async () => {
const { term, addon } = createTerminal()
const content = [
"\x1b[1;32m\x1b[0m \x1b[34mcd\x1b[0m /some/path",
"\x1b[1;32m\x1b[0m \x1b[34mls\x1b[0m -la",
"total 42",
].join("\r\n")
await writeAndWait(term, content)
const serialized = addon.serialize()
const { term: term2 } = createTerminal()
terminals.push(term2)
term2.reset()
await writeAndWait(term2, serialized)
expect(term2.buffer.active.getLine(0)?.translateToString(true)).toContain("cd /some/path")
expect(term2.buffer.active.getLine(1)?.translateToString(true)).toContain("ls -la")
expect(term2.buffer.active.getLine(2)?.translateToString(true)).toBe("total 42")
})
test("alternate buffer should round-trip without garbage", async () => {
const { term, addon } = createTerminal(20, 5)
await writeAndWait(term, "normal\r\n")
await writeAndWait(term, "\x1b[?1049h\x1b[HALT")
expect(term.buffer.active.type).toBe("alternate")
const serialized = addon.serialize()
const { term: term2 } = createTerminal(20, 5)
terminals.push(term2)
await writeAndWait(term2, serialized)
expect(term2.buffer.active.type).toBe("alternate")
const line = term2.buffer.active.getLine(0)
expect(line?.translateToString(true)).toBe("ALT")
// Ensure a cell beyond content isn't garbage
const cellCode = line?.getCell(10)?.getCode()
expect(cellCode === 0 || cellCode === 32).toBe(true)
})
test("serialized output written to new terminal should match original colors", async () => {
const { term, addon } = createTerminal(40, 5)
+19 -23
View File
@@ -157,23 +157,6 @@ function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
abstract class BaseSerializeHandler {
constructor(protected readonly _buffer: IBuffer) {}
private _isRealContent(codepoint: number): boolean {
if (codepoint === 0) return false
if (codepoint >= 0xf000) return false
return true
}
private _findLastContentColumn(line: IBufferLine): number {
let lastContent = -1
for (let col = 0; col < line.length; col++) {
const cell = line.getCell(col)
if (cell && this._isRealContent(cell.getCode())) {
lastContent = col
}
}
return lastContent + 1
}
public serialize(range: IBufferRange, excludeFinalCursorPosition?: boolean): string {
let oldCell = this._buffer.getNullCell()
@@ -182,14 +165,14 @@ abstract class BaseSerializeHandler {
const startColumn = range.start.x
const endColumn = range.end.x
this._beforeSerialize(endRow - startRow, startRow, endRow)
this._beforeSerialize(endRow - startRow + 1, startRow, endRow)
for (let row = startRow; row <= endRow; row++) {
const line = this._buffer.getLine(row)
if (line) {
const startLineColumn = row === range.start.y ? startColumn : 0
const maxColumn = row === range.end.y ? endColumn : this._findLastContentColumn(line)
const endLineColumn = Math.min(maxColumn, line.length)
const endLineColumn = Math.min(endColumn, line.length)
for (let col = startLineColumn; col < endLineColumn; col++) {
const c = line.getCell(col)
if (!c) {
@@ -243,6 +226,13 @@ class StringSerializeHandler extends BaseSerializeHandler {
protected _beforeSerialize(rows: number, start: number, _end: number): void {
this._allRows = new Array<string>(rows)
this._allRowSeparators = new Array<string>(rows)
this._rowIndex = 0
this._currentRow = ""
this._nullCellCount = 0
this._cursorStyle = this._buffer.getNullCell()
this._lastContentCursorRow = start
this._lastCursorRow = start
this._firstRow = start
@@ -251,6 +241,11 @@ class StringSerializeHandler extends BaseSerializeHandler {
protected _rowEnd(row: number, isLastRow: boolean): void {
let rowSeparator = ""
if (this._nullCellCount > 0) {
this._currentRow += " ".repeat(this._nullCellCount)
this._nullCellCount = 0
}
if (!isLastRow) {
const nextLine = this._buffer.getLine(row + 1)
@@ -388,7 +383,8 @@ class StringSerializeHandler extends BaseSerializeHandler {
}
const codepoint = cell.getCode()
const isGarbage = codepoint >= 0xf000
const isInvalidCodepoint = codepoint > 0x10ffff || (codepoint >= 0xd800 && codepoint <= 0xdfff)
const isGarbage = isInvalidCodepoint || (codepoint >= 0xf000 && cell.getWidth() === 1)
const isEmptyCell = codepoint === 0 || cell.getChars() === "" || isGarbage
const sgrSeq = this._diffStyle(cell, this._cursorStyle)
@@ -397,7 +393,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (styleChanged) {
if (this._nullCellCount > 0) {
this._currentRow += `\u001b[${this._nullCellCount}C`
this._currentRow += " ".repeat(this._nullCellCount)
this._nullCellCount = 0
}
@@ -417,7 +413,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._nullCellCount += cell.getWidth()
} else {
if (this._nullCellCount > 0) {
this._currentRow += `\u001b[${this._nullCellCount}C`
this._currentRow += " ".repeat(this._nullCellCount)
this._nullCellCount = 0
}
+99 -71
View File
@@ -1,11 +1,12 @@
import "@/index.css"
import { ErrorBoundary, Show, type ParentProps } from "solid-js"
import { ErrorBoundary, Show, lazy, type ParentProps } from "solid-js"
import { Router, Route, Navigate } from "@solidjs/router"
import { MetaProvider } from "@solidjs/meta"
import { Font } from "@opencode-ai/ui/font"
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
import { CodeComponentProvider } from "@opencode-ai/ui/context/code"
import { I18nProvider } from "@opencode-ai/ui/context"
import { Diff } from "@opencode-ai/ui/diff"
import { Code } from "@opencode-ai/ui/code"
import { ThemeProvider } from "@opencode-ai/ui/theme"
@@ -14,36 +15,58 @@ import { PermissionProvider } from "@/context/permission"
import { LayoutProvider } from "@/context/layout"
import { GlobalSDKProvider } from "@/context/global-sdk"
import { ServerProvider, useServer } from "@/context/server"
import { SettingsProvider } from "@/context/settings"
import { TerminalProvider } from "@/context/terminal"
import { PromptProvider } from "@/context/prompt"
import { FileProvider } from "@/context/file"
import { NotificationProvider } from "@/context/notification"
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
import { CommandProvider } from "@/context/command"
import { LanguageProvider, useLanguage } from "@/context/language"
import { Logo } from "@opencode-ai/ui/logo"
import Layout from "@/pages/layout"
import Home from "@/pages/home"
import DirectoryLayout from "@/pages/directory-layout"
import Session from "@/pages/session"
import { ErrorPage } from "./pages/error"
import { iife } from "@opencode-ai/util/iife"
import { Suspense } from "solid-js"
const Home = lazy(() => import("@/pages/home"))
const Session = lazy(() => import("@/pages/session"))
const Loading = () => <div class="size-full" />
function UiI18nBridge(props: ParentProps) {
const language = useLanguage()
return <I18nProvider value={{ locale: language.locale, t: language.t }}>{props.children}</I18nProvider>
}
declare global {
interface Window {
__OPENCODE__?: { updaterEnabled?: boolean; port?: number }
__OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string }
}
}
const defaultServerUrl = iife(() => {
const param = new URLSearchParams(document.location.search).get("url")
if (param) return param
if (location.hostname.includes("opencode.ai")) return "http://localhost:4096"
if (window.__OPENCODE__) return `http://127.0.0.1:${window.__OPENCODE__.port}`
if (import.meta.env.DEV)
return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}`
return window.location.origin
})
export function AppBaseProviders(props: ParentProps) {
return (
<MetaProvider>
<Font />
<ThemeProvider>
<LanguageProvider>
<UiI18nBridge>
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
<DialogProvider>
<MarkedProvider>
<DiffComponentProvider component={Diff}>
<CodeComponentProvider component={Code}>{props.children}</CodeComponentProvider>
</DiffComponentProvider>
</MarkedProvider>
</DialogProvider>
</ErrorBoundary>
</UiI18nBridge>
</LanguageProvider>
</ThemeProvider>
</MetaProvider>
)
}
function ServerKey(props: ParentProps) {
const server = useServer()
@@ -54,62 +77,67 @@ function ServerKey(props: ParentProps) {
)
}
export function App() {
export function AppInterface(props: { defaultUrl?: string }) {
const defaultServerUrl = () => {
if (props.defaultUrl) return props.defaultUrl
if (location.hostname.includes("opencode.ai")) return "http://localhost:4096"
if (import.meta.env.DEV)
return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}`
return window.location.origin
}
return (
<MetaProvider>
<Font />
<ThemeProvider>
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
<DialogProvider>
<MarkedProvider>
<DiffComponentProvider component={Diff}>
<CodeComponentProvider component={Code}>
<ServerProvider defaultUrl={defaultServerUrl}>
<ServerKey>
<GlobalSDKProvider>
<GlobalSyncProvider>
<Router
root={(props) => (
<PermissionProvider>
<LayoutProvider>
<NotificationProvider>
<CommandProvider>
<Layout>{props.children}</Layout>
</CommandProvider>
</NotificationProvider>
</LayoutProvider>
</PermissionProvider>
)}
>
<Route path="/" component={Home} />
<Route path="/:dir" component={DirectoryLayout}>
<Route path="/" component={() => <Navigate href="session" />} />
<Route
path="/session/:id?"
component={(p) => (
<Show when={p.params.id ?? "new"} keyed>
<TerminalProvider>
<FileProvider>
<PromptProvider>
<Session />
</PromptProvider>
</FileProvider>
</TerminalProvider>
</Show>
)}
/>
</Route>
</Router>
</GlobalSyncProvider>
</GlobalSDKProvider>
</ServerKey>
</ServerProvider>
</CodeComponentProvider>
</DiffComponentProvider>
</MarkedProvider>
</DialogProvider>
</ErrorBoundary>
</ThemeProvider>
</MetaProvider>
<ServerProvider defaultUrl={defaultServerUrl()}>
<ServerKey>
<GlobalSDKProvider>
<GlobalSyncProvider>
<Router
root={(props) => (
<SettingsProvider>
<PermissionProvider>
<LayoutProvider>
<NotificationProvider>
<CommandProvider>
<Layout>{props.children}</Layout>
</CommandProvider>
</NotificationProvider>
</LayoutProvider>
</PermissionProvider>
</SettingsProvider>
)}
>
<Route
path="/"
component={() => (
<Suspense fallback={<Loading />}>
<Home />
</Suspense>
)}
/>
<Route path="/:dir" component={DirectoryLayout}>
<Route path="/" component={() => <Navigate href="session" />} />
<Route
path="/session/:id?"
component={(p) => (
<Show when={p.params.id ?? "new"} keyed>
<TerminalProvider>
<FileProvider>
<PromptProvider>
<Suspense fallback={<Loading />}>
<Session />
</Suspense>
</PromptProvider>
</FileProvider>
</TerminalProvider>
</Show>
)}
/>
</Route>
</Router>
</GlobalSyncProvider>
</GlobalSDKProvider>
</ServerKey>
</ServerProvider>
)
}
@@ -14,6 +14,7 @@ import { iife } from "@opencode-ai/util/iife"
import { createMemo, Match, onCleanup, onMount, Switch } from "solid-js"
import { createStore, produce } from "solid-js/store"
import { Link } from "@/components/link"
import { useLanguage } from "@/context/language"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
import { usePlatform } from "@/context/platform"
@@ -25,13 +26,14 @@ export function DialogConnectProvider(props: { provider: string }) {
const globalSync = useGlobalSync()
const globalSDK = useGlobalSDK()
const platform = usePlatform()
const language = useLanguage()
const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === props.provider)!)
const methods = createMemo(
() =>
globalSync.data.provider_auth[props.provider] ?? [
{
type: "api",
label: "API key",
label: language.t("provider.connect.method.apiKey"),
},
],
)
@@ -44,6 +46,12 @@ export function DialogConnectProvider(props: { provider: string }) {
const method = createMemo(() => (store.methodIndex !== undefined ? methods().at(store.methodIndex!) : undefined))
const methodLabel = (value?: { type?: string; label?: string }) => {
if (!value) return ""
if (value.type === "api") return language.t("provider.connect.method.apiKey")
return value.label ?? ""
}
async function selectMethod(index: number) {
const method = methods()[index]
setStore(
@@ -112,8 +120,8 @@ export function DialogConnectProvider(props: { provider: string }) {
showToast({
variant: "success",
icon: "circle-check",
title: `${provider().name} connected`,
description: `${provider().name} models are now available to use.`,
title: language.t("provider.connect.toast.connected.title", { provider: provider().name }),
description: language.t("provider.connect.toast.connected.description", { provider: provider().name }),
})
}
@@ -142,16 +150,18 @@ export function DialogConnectProvider(props: { provider: string }) {
<div class="text-16-medium text-text-strong">
<Switch>
<Match when={props.provider === "anthropic" && method()?.label?.toLowerCase().includes("max")}>
Login with Claude Pro/Max
{language.t("provider.connect.title.anthropicProMax")}
</Match>
<Match when={true}>Connect {provider().name}</Match>
<Match when={true}>{language.t("provider.connect.title", { provider: provider().name })}</Match>
</Switch>
</div>
</div>
<div class="px-2.5 pb-10 flex flex-col gap-6">
<Switch>
<Match when={store.methodIndex === undefined}>
<div class="text-14-regular text-text-base">Select login method for {provider().name}.</div>
<div class="text-14-regular text-text-base">
{language.t("provider.connect.selectMethod", { provider: provider().name })}
</div>
<div class="">
<List
ref={(ref) => {
@@ -169,7 +179,7 @@ export function DialogConnectProvider(props: { provider: string }) {
<div class="w-4 h-2 rounded-[1px] bg-input-base shadow-xs-border-base flex items-center justify-center">
<div class="w-2.5 h-0.5 bg-icon-strong-base hidden" data-slot="list-item-extra-icon" />
</div>
<span>{i.label}</span>
<span>{methodLabel(i)}</span>
</div>
)}
</List>
@@ -179,7 +189,7 @@ export function DialogConnectProvider(props: { provider: string }) {
<div class="text-14-regular text-text-base">
<div class="flex items-center gap-x-2">
<Spinner />
<span>Authorization in progress...</span>
<span>{language.t("provider.connect.status.inProgress")}</span>
</div>
</div>
</Match>
@@ -187,7 +197,7 @@ export function DialogConnectProvider(props: { provider: string }) {
<div class="text-14-regular text-text-base">
<div class="flex items-center gap-x-2">
<Icon name="circle-ban-sign" class="text-icon-critical-base" />
<span>Authorization failed: {store.error}</span>
<span>{language.t("provider.connect.status.failed", { error: store.error ?? "" })}</span>
</div>
</div>
</Match>
@@ -206,7 +216,7 @@ export function DialogConnectProvider(props: { provider: string }) {
const apiKey = formData.get("apiKey") as string
if (!apiKey?.trim()) {
setFormStore("error", "API key is required")
setFormStore("error", language.t("provider.connect.apiKey.required"))
return
}
@@ -227,25 +237,23 @@ export function DialogConnectProvider(props: { provider: string }) {
<Match when={provider().id === "opencode"}>
<div class="flex flex-col gap-4">
<div class="text-14-regular text-text-base">
OpenCode Zen gives you access to a curated set of reliable optimized models for coding
agents.
{language.t("provider.connect.opencodeZen.line1")}
</div>
<div class="text-14-regular text-text-base">
With a single API key you'll get access to models such as Claude, GPT, Gemini, GLM and more.
{language.t("provider.connect.opencodeZen.line2")}
</div>
<div class="text-14-regular text-text-base">
Visit{" "}
{language.t("provider.connect.opencodeZen.visit.prefix")}
<Link href="https://opencode.ai/zen" tabIndex={-1}>
opencode.ai/zen
</Link>{" "}
to collect your API key.
{language.t("provider.connect.opencodeZen.visit.link")}
</Link>
{language.t("provider.connect.opencodeZen.visit.suffix")}
</div>
</div>
</Match>
<Match when={true}>
<div class="text-14-regular text-text-base">
Enter your {provider().name} API key to connect your account and use {provider().name} models
in OpenCode.
{language.t("provider.connect.apiKey.description", { provider: provider().name })}
</div>
</Match>
</Switch>
@@ -253,8 +261,8 @@ export function DialogConnectProvider(props: { provider: string }) {
<TextField
autofocus
type="text"
label={`${provider().name} API key`}
placeholder="API key"
label={language.t("provider.connect.apiKey.label", { provider: provider().name })}
placeholder={language.t("provider.connect.apiKey.placeholder")}
name="apiKey"
value={formStore.value}
onChange={setFormStore.bind(null, "value")}
@@ -262,7 +270,7 @@ export function DialogConnectProvider(props: { provider: string }) {
error={formStore.error}
/>
<Button class="w-auto" type="submit" size="large" variant="primary">
Submit
{language.t("common.submit")}
</Button>
</form>
</div>
@@ -292,7 +300,7 @@ export function DialogConnectProvider(props: { provider: string }) {
const code = formData.get("code") as string
if (!code?.trim()) {
setFormStore("error", "Authorization code is required")
setFormStore("error", language.t("provider.connect.oauth.code.required"))
return
}
@@ -306,21 +314,24 @@ export function DialogConnectProvider(props: { provider: string }) {
await complete()
return
}
setFormStore("error", "Invalid authorization code")
setFormStore("error", language.t("provider.connect.oauth.code.invalid"))
}
return (
<div class="flex flex-col gap-6">
<div class="text-14-regular text-text-base">
Visit <Link href={store.authorization!.url}>this link</Link> to collect your authorization
code to connect your account and use {provider().name} models in OpenCode.
{language.t("provider.connect.oauth.code.visit.prefix")}
<Link href={store.authorization!.url}>
{language.t("provider.connect.oauth.code.visit.link")}
</Link>
{language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
</div>
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
<TextField
autofocus
type="text"
label={`${method()?.label} authorization code`}
placeholder="Authorization code"
label={language.t("provider.connect.oauth.code.label", { method: method()?.label ?? "" })}
placeholder={language.t("provider.connect.oauth.code.placeholder")}
name="code"
value={formStore.value}
onChange={setFormStore.bind(null, "value")}
@@ -328,7 +339,7 @@ export function DialogConnectProvider(props: { provider: string }) {
error={formStore.error}
/>
<Button class="w-auto" type="submit" size="large" variant="primary">
Submit
{language.t("common.submit")}
</Button>
</form>
</div>
@@ -361,13 +372,22 @@ export function DialogConnectProvider(props: { provider: string }) {
return (
<div class="flex flex-col gap-6">
<div class="text-14-regular text-text-base">
Visit <Link href={store.authorization!.url}>this link</Link> and enter the code below to
connect your account and use {provider().name} models in OpenCode.
{language.t("provider.connect.oauth.auto.visit.prefix")}
<Link href={store.authorization!.url}>
{language.t("provider.connect.oauth.auto.visit.link")}
</Link>
{language.t("provider.connect.oauth.auto.visit.suffix", { provider: provider().name })}
</div>
<TextField label="Confirmation code" class="font-mono" value={code()} readOnly copyable />
<TextField
label={language.t("provider.connect.oauth.auto.confirmationCode")}
class="font-mono"
value={code()}
readOnly
copyable
/>
<div class="text-14-regular text-text-base flex items-center gap-4">
<Spinner />
<span>Waiting for authorization...</span>
<span>{language.t("provider.connect.status.waiting")}</span>
</div>
</div>
)
@@ -7,18 +7,16 @@ import { createMemo, createSignal, For, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { useGlobalSDK } from "@/context/global-sdk"
import { type LocalProject, getAvatarColors } from "@/context/layout"
import { getFilename } from "@opencode-ai/util/path"
import { Avatar } from "@opencode-ai/ui/avatar"
import { useLanguage } from "@/context/language"
const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
function getFilename(input: string) {
const parts = input.split("/")
return parts[parts.length - 1] || input
}
export function DialogEditProject(props: { project: LocalProject }) {
const dialog = useDialog()
const globalSDK = useGlobalSDK()
const language = useLanguage()
const folderName = createMemo(() => getFilename(props.project.worktree))
const defaultName = createMemo(() => props.project.name || folderName())
@@ -26,16 +24,20 @@ export function DialogEditProject(props: { project: LocalProject }) {
const [store, setStore] = createStore({
name: defaultName(),
color: props.project.icon?.color || "pink",
iconUrl: props.project.icon?.url || "",
iconUrl: props.project.icon?.override || "",
saving: false,
})
const [dragOver, setDragOver] = createSignal(false)
const [iconHover, setIconHover] = createSignal(false)
function handleFileSelect(file: File) {
if (!file.type.startsWith("image/")) return
const reader = new FileReader()
reader.onload = (e) => setStore("iconUrl", e.target?.result as string)
reader.onload = (e) => {
setStore("iconUrl", e.target?.result as string)
setIconHover(false)
}
reader.readAsDataURL(file)
}
@@ -74,39 +76,46 @@ export function DialogEditProject(props: { project: LocalProject }) {
await globalSDK.client.project.update({
projectID: props.project.id,
name,
icon: { color: store.color, url: store.iconUrl },
icon: { color: store.color, override: store.iconUrl },
})
setStore("saving", false)
dialog.close()
}
return (
<Dialog title="Edit project">
<form onSubmit={handleSubmit} class="flex flex-col gap-6 px-2.5 pb-3">
<Dialog title={language.t("dialog.project.edit.title")} class="w-full max-w-[480px] mx-auto">
<form onSubmit={handleSubmit} class="flex flex-col gap-6 p-6 pt-0">
<div class="flex flex-col gap-4">
<TextField
autofocus
type="text"
label="Name"
label={language.t("dialog.project.edit.name")}
placeholder={folderName()}
value={store.name}
onChange={(v) => setStore("name", v)}
/>
<div class="flex flex-col gap-2">
<label class="text-12-medium text-text-weak">Icon</label>
<label class="text-12-medium text-text-weak">{language.t("dialog.project.edit.icon")}</label>
<div class="flex gap-3 items-start">
<div class="relative">
<div class="relative" onMouseEnter={() => setIconHover(true)} onMouseLeave={() => setIconHover(false)}>
<div
class="size-16 rounded-lg overflow-hidden border border-dashed transition-colors cursor-pointer"
class="relative size-16 rounded-md transition-colors cursor-pointer"
classList={{
"border-text-interactive-base bg-surface-info-base/20": dragOver(),
"border-border-base hover:border-border-strong": !dragOver(),
"overflow-hidden": !!store.iconUrl,
}}
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onClick={() => document.getElementById("icon-upload")?.click()}
onClick={() => {
if (store.iconUrl && iconHover()) {
clearIcon()
} else {
document.getElementById("icon-upload")?.click()
}
}}
>
<Show
when={store.iconUrl}
@@ -116,48 +125,87 @@ export function DialogEditProject(props: { project: LocalProject }) {
fallback={store.name || defaultName()}
{...getAvatarColors(store.color)}
class="size-full"
style={{ "font-size": "32px" }}
/>
</div>
}
>
<img src={store.iconUrl} alt="Project icon" class="size-full object-cover" />
<img
src={store.iconUrl}
alt={language.t("dialog.project.edit.icon.alt")}
class="size-full object-cover"
/>
</Show>
</div>
<Show when={store.iconUrl}>
<button
type="button"
class="absolute -top-1.5 -right-1.5 size-5 rounded-full bg-surface-raised-base border border-border-base flex items-center justify-center hover:bg-surface-raised-base-hover"
onClick={clearIcon}
>
<Icon name="close" class="size-3 text-icon-base" />
</button>
</Show>
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "64px",
height: "64px",
background: "rgba(0,0,0,0.6)",
"border-radius": "6px",
"z-index": 10,
"pointer-events": "none",
opacity: iconHover() && !store.iconUrl ? 1 : 0,
display: "flex",
"align-items": "center",
"justify-content": "center",
}}
>
<Icon name="cloud-upload" size="large" class="text-icon-invert-base" />
</div>
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "64px",
height: "64px",
background: "rgba(0,0,0,0.6)",
"border-radius": "6px",
"z-index": 10,
"pointer-events": "none",
opacity: iconHover() && store.iconUrl ? 1 : 0,
display: "flex",
"align-items": "center",
"justify-content": "center",
}}
>
<Icon name="trash" size="large" class="text-icon-invert-base" />
</div>
</div>
<input id="icon-upload" type="file" accept="image/*" class="hidden" onChange={handleInputChange} />
<div class="flex flex-col gap-1.5 text-12-regular text-text-weak">
<span>Click or drag an image</span>
<span>Recommended: 128x128px</span>
<div class="flex flex-col gap-1.5 text-12-regular text-text-weak self-center">
<span>{language.t("dialog.project.edit.icon.hint")}</span>
<span>{language.t("dialog.project.edit.icon.recommended")}</span>
</div>
</div>
</div>
<Show when={!store.iconUrl}>
<div class="flex flex-col gap-2">
<label class="text-12-medium text-text-weak">Color</label>
<div class="flex gap-2">
<label class="text-12-medium text-text-weak">{language.t("dialog.project.edit.color")}</label>
<div class="flex gap-1.5">
<For each={AVATAR_COLOR_KEYS}>
{(color) => (
<button
type="button"
class="relative size-8 rounded-md transition-all"
classList={{
"ring-2 ring-offset-2 ring-offset-surface-base ring-text-interactive-base":
"flex items-center justify-center size-10 p-0.5 rounded-lg overflow-hidden transition-colors cursor-default": true,
"bg-transparent border-2 border-icon-strong-base hover:bg-surface-base-hover":
store.color === color,
"bg-transparent border border-transparent hover:bg-surface-base-hover hover:border-border-weak-base":
store.color !== color,
}}
style={{ background: getAvatarColors(color).background }}
onClick={() => setStore("color", color)}
>
<Avatar fallback={store.name || defaultName()} {...getAvatarColors(color)} class="size-full" />
<Avatar
fallback={store.name || defaultName()}
{...getAvatarColors(color)}
class="size-full rounded"
/>
</button>
)}
</For>
@@ -168,10 +216,10 @@ export function DialogEditProject(props: { project: LocalProject }) {
<div class="flex justify-end gap-2">
<Button type="button" variant="ghost" size="large" onClick={() => dialog.close()}>
Cancel
{language.t("common.cancel")}
</Button>
<Button type="submit" variant="primary" size="large" disabled={store.saving}>
{store.saving ? "Saving..." : "Save"}
{store.saving ? language.t("common.saving") : language.t("common.save")}
</Button>
</div>
</form>
+104
View File
@@ -0,0 +1,104 @@
import { Component, createMemo } from "solid-js"
import { useNavigate, useParams } from "@solidjs/router"
import { useSync } from "@/context/sync"
import { useSDK } from "@/context/sdk"
import { usePrompt } from "@/context/prompt"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { extractPromptFromParts } from "@/utils/prompt"
import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client"
import { base64Encode } from "@opencode-ai/util/encode"
import { useLanguage } from "@/context/language"
interface ForkableMessage {
id: string
text: string
time: string
}
function formatTime(date: Date): string {
return date.toLocaleTimeString(undefined, { timeStyle: "short" })
}
export const DialogFork: Component = () => {
const params = useParams()
const navigate = useNavigate()
const sync = useSync()
const sdk = useSDK()
const prompt = usePrompt()
const dialog = useDialog()
const language = useLanguage()
const messages = createMemo((): ForkableMessage[] => {
const sessionID = params.id
if (!sessionID) return []
const msgs = sync.data.message[sessionID] ?? []
const result: ForkableMessage[] = []
for (const message of msgs) {
if (message.role !== "user") continue
const parts = sync.data.part[message.id] ?? []
const textPart = parts.find((x): x is SDKTextPart => x.type === "text" && !x.synthetic && !x.ignored)
if (!textPart) continue
result.push({
id: message.id,
text: textPart.text.replace(/\n/g, " ").slice(0, 200),
time: formatTime(new Date(message.time.created)),
})
}
return result.reverse()
})
const handleSelect = (item: ForkableMessage | undefined) => {
if (!item) return
const sessionID = params.id
if (!sessionID) return
const parts = sync.data.part[item.id] ?? []
const restored = extractPromptFromParts(parts, {
directory: sdk.directory,
attachmentName: language.t("common.attachment"),
})
dialog.close()
sdk.client.session.fork({ sessionID, messageID: item.id }).then((forked) => {
if (!forked.data) return
navigate(`/${base64Encode(sdk.directory)}/session/${forked.data.id}`)
requestAnimationFrame(() => {
prompt.set(restored)
})
})
}
return (
<Dialog title={language.t("command.session.fork")}>
<List
class="flex-1 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0"
search={{ placeholder: language.t("common.search.placeholder"), autofocus: true }}
emptyMessage={language.t("dialog.fork.empty")}
key={(x) => x.id}
items={messages}
filterKeys={["text"]}
onSelect={handleSelect}
>
{(item) => (
<div class="w-full flex items-center gap-2">
<span class="truncate flex-1 min-w-0 text-left" style={{ "font-weight": "400" }}>
{item.text}
</span>
<span class="text-text-weak shrink-0" style={{ "font-weight": "400" }}>
{item.time}
</span>
</div>
)}
</List>
</Dialog>
)
}
@@ -4,14 +4,16 @@ import { Switch } from "@opencode-ai/ui/switch"
import type { Component } from "solid-js"
import { useLocal } from "@/context/local"
import { popularProviders } from "@/hooks/use-providers"
import { useLanguage } from "@/context/language"
export const DialogManageModels: Component = () => {
const local = useLocal()
const language = useLanguage()
return (
<Dialog title="Manage models" description="Customize which models appear in the model selector.">
<Dialog title={language.t("dialog.model.manage")} description={language.t("dialog.model.manage.description")}>
<List
search={{ placeholder: "Search models", autofocus: true }}
emptyMessage="No model results"
search={{ placeholder: language.t("dialog.model.search.placeholder"), autofocus: true }}
emptyMessage={language.t("dialog.model.empty")}
key={(x) => `${x?.provider?.id}:${x?.id}`}
items={local.model.list()}
filterKeys={["provider.name", "name", "id"]}
@@ -6,6 +6,7 @@ import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { createMemo } from "solid-js"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
import { useLanguage } from "@/context/language"
interface DialogSelectDirectoryProps {
title?: string
@@ -17,6 +18,7 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
const sync = useGlobalSync()
const sdk = useGlobalSDK()
const dialog = useDialog()
const language = useLanguage()
const home = createMemo(() => sync.data.path.home)
const root = createMemo(() => sync.data.path.home || sync.data.path.directory)
@@ -81,10 +83,11 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
}
return (
<Dialog title={props.title ?? "Open project"}>
<Dialog title={props.title ?? language.t("command.project.open")}>
<List
search={{ placeholder: "Search folders", autofocus: true }}
emptyMessage="No folders found"
search={{ placeholder: language.t("dialog.directory.search.placeholder"), autofocus: true }}
emptyMessage={language.t("dialog.directory.empty")}
loadingMessage={language.t("common.loading")}
items={directories}
key={(x) => x}
onSelect={(path) => {
@@ -1,48 +1,194 @@
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { Keybind } from "@opencode-ai/ui/keybind"
import { List } from "@opencode-ai/ui/list"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { useParams } from "@solidjs/router"
import { createMemo } from "solid-js"
import { createMemo, createSignal, onCleanup, Show } from "solid-js"
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
import { useLayout } from "@/context/layout"
import { useFile } from "@/context/file"
import { useLanguage } from "@/context/language"
type EntryType = "command" | "file"
type Entry = {
id: string
type: EntryType
title: string
description?: string
keybind?: string
category: string
option?: CommandOption
path?: string
}
export function DialogSelectFile() {
const command = useCommand()
const language = useLanguage()
const layout = useLayout()
const file = useFile()
const dialog = useDialog()
const params = useParams()
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const tabs = createMemo(() => layout.tabs(sessionKey()))
const view = createMemo(() => layout.view(sessionKey()))
const state = { cleanup: undefined as (() => void) | void, committed: false }
const [grouped, setGrouped] = createSignal(false)
const common = [
"session.new",
"workspace.new",
"session.previous",
"session.next",
"terminal.toggle",
"review.toggle",
]
const limit = 5
const allowed = createMemo(() =>
command.options.filter(
(option) => !option.disabled && !option.id.startsWith("suggested.") && option.id !== "file.open",
),
)
const commandItem = (option: CommandOption): Entry => ({
id: "command:" + option.id,
type: "command",
title: option.title,
description: option.description,
keybind: option.keybind,
category: language.t("palette.group.commands"),
option,
})
const fileItem = (path: string): Entry => ({
id: "file:" + path,
type: "file",
title: path,
category: language.t("palette.group.files"),
path,
})
const list = createMemo(() => allowed().map(commandItem))
const picks = createMemo(() => {
const all = allowed()
const order = new Map(common.map((id, index) => [id, index]))
const picked = all.filter((option) => order.has(option.id))
const base = picked.length ? picked : all.slice(0, limit)
const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
return sorted.map(commandItem)
})
const recent = createMemo(() => {
const all = tabs().all()
const active = tabs().active()
const order = active ? [active, ...all.filter((item) => item !== active)] : all
const seen = new Set<string>()
const items: Entry[] = []
for (const item of order) {
const path = file.pathFromTab(item)
if (!path) continue
if (seen.has(path)) continue
seen.add(path)
items.push(fileItem(path))
}
return items.slice(0, limit)
})
const items = async (filter: string) => {
const query = filter.trim()
setGrouped(query.length > 0)
if (!query) return [...picks(), ...recent()]
const files = await file.searchFiles(query)
const entries = files.map(fileItem)
return [...list(), ...entries]
}
const handleMove = (item: Entry | undefined) => {
state.cleanup?.()
if (!item) return
if (item.type !== "command") return
state.cleanup = item.option?.onHighlight?.()
}
const open = (path: string) => {
const value = file.tab(path)
tabs().open(value)
file.load(path)
view().reviewPanel.open()
}
const handleSelect = (item: Entry | undefined) => {
if (!item) return
state.committed = true
state.cleanup = undefined
dialog.close()
if (item.type === "command") {
item.option?.onSelect?.("palette")
return
}
if (!item.path) return
open(item.path)
}
onCleanup(() => {
if (state.committed) return
state.cleanup?.()
})
return (
<Dialog title="Select file">
<Dialog class="pt-3 pb-0 !max-h-[480px]">
<List
search={{ placeholder: "Search files", autofocus: true }}
emptyMessage="No files found"
items={file.searchFiles}
key={(x) => x}
onSelect={(path) => {
if (path) {
const value = file.tab(path)
tabs().open(value)
file.load(path)
}
dialog.close()
search={{
placeholder: language.t("palette.search.placeholder"),
autofocus: true,
hideIcon: true,
class: "pl-3 pr-2 !mb-0",
}}
emptyMessage={language.t("palette.empty")}
loadingMessage={language.t("common.loading")}
items={items}
key={(item) => item.id}
filterKeys={["title", "description", "category"]}
groupBy={(item) => item.category}
onMove={handleMove}
onSelect={handleSelect}
>
{(i) => (
<div class="w-full flex items-center justify-between rounded-md">
<div class="flex items-center gap-x-3 grow min-w-0">
<FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
<div class="flex items-center text-14-regular">
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
{getDirectory(i)}
</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
{(item) => (
<Show
when={item.type === "command"}
fallback={
<div class="w-full flex items-center justify-between rounded-md pl-1">
<div class="flex items-center gap-x-3 grow min-w-0">
<FileIcon node={{ path: item.path ?? "", type: "file" }} class="shrink-0 size-4" />
<div class="flex items-center text-14-regular">
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
{getDirectory(item.path ?? "")}
</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(item.path ?? "")}</span>
</div>
</div>
</div>
}
>
<div class="w-full flex items-center justify-between gap-4 pl-1">
<div class="flex items-center gap-2 min-w-0">
<span class="text-14-regular text-text-strong whitespace-nowrap">{item.title}</span>
<Show when={item.description}>
<span class="text-14-regular text-text-weak truncate">{item.description}</span>
</Show>
</div>
<Show when={item.keybind}>
<Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "")}</Keybind>
</Show>
</div>
</div>
</Show>
)}
</List>
</Dialog>
@@ -4,10 +4,12 @@ import { useSDK } from "@/context/sdk"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { Switch } from "@opencode-ai/ui/switch"
import { useLanguage } from "@/context/language"
export const DialogSelectMcp: Component = () => {
const sync = useSync()
const sdk = useSDK()
const language = useLanguage()
const [loading, setLoading] = createSignal<string | null>(null)
const items = createMemo(() =>
@@ -34,10 +36,13 @@ export const DialogSelectMcp: Component = () => {
const totalCount = createMemo(() => items().length)
return (
<Dialog title="MCPs" description={`${enabledCount()} of ${totalCount()} enabled`}>
<Dialog
title={language.t("dialog.mcp.title")}
description={language.t("dialog.mcp.description", { enabled: enabledCount(), total: totalCount() })}
>
<List
search={{ placeholder: "Search", autofocus: true }}
emptyMessage="No MCPs configured"
search={{ placeholder: language.t("common.search.placeholder"), autofocus: true }}
emptyMessage={language.t("dialog.mcp.empty")}
key={(x) => x?.name ?? ""}
items={items}
filterKeys={["name", "status"]}
@@ -60,19 +65,21 @@ export const DialogSelectMcp: Component = () => {
<div class="flex items-center gap-2">
<span class="truncate">{i.name}</span>
<Show when={status() === "connected"}>
<span class="text-11-regular text-text-weaker">connected</span>
<span class="text-11-regular text-text-weaker">{language.t("mcp.status.connected")}</span>
</Show>
<Show when={status() === "failed"}>
<span class="text-11-regular text-text-weaker">failed</span>
<span class="text-11-regular text-text-weaker">{language.t("mcp.status.failed")}</span>
</Show>
<Show when={status() === "needs_auth"}>
<span class="text-11-regular text-text-weaker">needs auth</span>
<span class="text-11-regular text-text-weaker">{language.t("mcp.status.needs_auth")}</span>
</Show>
<Show when={status() === "disabled"}>
<span class="text-11-regular text-text-weaker">disabled</span>
<span class="text-11-regular text-text-weaker">{language.t("mcp.status.disabled")}</span>
</Show>
<Show when={loading() === i.name}>
<span class="text-11-regular text-text-weak">...</span>
<span class="text-11-regular text-text-weak">
{language.t("common.loading.ellipsis")}
</span>
</Show>
</div>
<Show when={error()}>
@@ -5,16 +5,20 @@ import type { IconName } from "@opencode-ai/ui/icons/provider"
import { List, type ListRef } from "@opencode-ai/ui/list"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { Tag } from "@opencode-ai/ui/tag"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { type Component, onCleanup, onMount, Show } from "solid-js"
import { useLocal } from "@/context/local"
import { popularProviders, useProviders } from "@/hooks/use-providers"
import { DialogConnectProvider } from "./dialog-connect-provider"
import { DialogSelectProvider } from "./dialog-select-provider"
import { ModelTooltip } from "./model-tooltip"
import { useLanguage } from "@/context/language"
export const DialogSelectModelUnpaid: Component = () => {
const local = useLocal()
const dialog = useDialog()
const providers = useProviders()
const language = useLanguage()
let listRef: ListRef | undefined
const handleKey = (e: KeyboardEvent) => {
@@ -30,14 +34,30 @@ export const DialogSelectModelUnpaid: Component = () => {
})
return (
<Dialog title="Select model">
<Dialog title={language.t("dialog.model.select.title")}>
<div class="flex flex-col gap-3 px-2.5">
<div class="text-14-medium text-text-base px-2.5">Free models provided by OpenCode</div>
<div class="text-14-medium text-text-base px-2.5">{language.t("dialog.model.unpaid.freeModels.title")}</div>
<List
ref={(ref) => (listRef = ref)}
items={local.model.list}
current={local.model.current()}
key={(x) => `${x.provider.id}:${x.id}`}
itemWrapper={(item, node) => (
<Tooltip
class="w-full"
placement="right-start"
gutter={12}
value={
<ModelTooltip
model={item}
latest={item.latest}
free={item.provider.id === "opencode" && (!item.cost || item.cost.input === 0)}
/>
}
>
{node}
</Tooltip>
)}
onSelect={(x) => {
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
recent: true,
@@ -48,9 +68,9 @@ export const DialogSelectModelUnpaid: Component = () => {
{(i) => (
<div class="w-full flex items-center gap-x-2.5">
<span>{i.name}</span>
<Tag>Free</Tag>
<Tag>{language.t("model.tag.free")}</Tag>
<Show when={i.latest}>
<Tag>Latest</Tag>
<Tag>{language.t("model.tag.latest")}</Tag>
</Show>
</div>
)}
@@ -61,7 +81,7 @@ export const DialogSelectModelUnpaid: Component = () => {
<div class="px-1.5 pb-1.5">
<div class="w-full rounded-sm border border-border-weak-base bg-surface-raised-base">
<div class="w-full flex flex-col items-start gap-4 px-1.5 pt-4 pb-4">
<div class="px-2 text-14-medium text-text-base">Add more models from popular providers</div>
<div class="px-2 text-14-medium text-text-base">{language.t("dialog.model.unpaid.addMore.title")}</div>
<div class="w-full">
<List
class="w-full px-0"
@@ -83,10 +103,10 @@ export const DialogSelectModelUnpaid: Component = () => {
<ProviderIcon data-slot="list-item-extra-icon" id={i.id as IconName} />
<span>{i.name}</span>
<Show when={i.id === "opencode"}>
<Tag>Recommended</Tag>
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
</Show>
<Show when={i.id === "anthropic"}>
<div class="text-14-regular text-text-weak">Connect with Claude Pro/Max or API key</div>
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.anthropic.note")}</div>
</Show>
</div>
)}
@@ -99,7 +119,7 @@ export const DialogSelectModelUnpaid: Component = () => {
dialog.show(() => <DialogSelectProvider />)
}}
>
View all providers
{language.t("dialog.provider.viewAll")}
</Button>
</div>
</div>
@@ -4,18 +4,24 @@ import { useLocal } from "@/context/local"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { popularProviders } from "@/hooks/use-providers"
import { Button } from "@opencode-ai/ui/button"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Tag } from "@opencode-ai/ui/tag"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { DialogSelectProvider } from "./dialog-select-provider"
import { DialogManageModels } from "./dialog-manage-models"
import { ModelTooltip } from "./model-tooltip"
import { useLanguage } from "@/context/language"
const ModelList: Component<{
provider?: string
class?: string
onSelect: () => void
action?: JSX.Element
}> = (props) => {
const local = useLocal()
const language = useLanguage()
const models = createMemo(() =>
local.model
@@ -27,8 +33,8 @@ const ModelList: Component<{
return (
<List
class={`flex-1 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0 ${props.class ?? ""}`}
search={{ placeholder: "Search models", autofocus: true }}
emptyMessage="No model results"
search={{ placeholder: language.t("dialog.model.search.placeholder"), autofocus: true, action: props.action }}
emptyMessage={language.t("dialog.model.empty")}
key={(x) => `${x.provider.id}:${x.id}`}
items={models}
current={local.model.current()}
@@ -36,14 +42,28 @@ const ModelList: Component<{
sortBy={(a, b) => a.name.localeCompare(b.name)}
groupBy={(x) => x.provider.name}
sortGroupsBy={(a, b) => {
if (a.category === "Recent" && b.category !== "Recent") return -1
if (b.category === "Recent" && a.category !== "Recent") return 1
const aProvider = a.items[0].provider.id
const bProvider = b.items[0].provider.id
if (popularProviders.includes(aProvider) && !popularProviders.includes(bProvider)) return -1
if (!popularProviders.includes(aProvider) && popularProviders.includes(bProvider)) return 1
return popularProviders.indexOf(aProvider) - popularProviders.indexOf(bProvider)
}}
itemWrapper={(item, node) => (
<Tooltip
class="w-full"
placement="right-start"
gutter={12}
value={
<ModelTooltip
model={item}
latest={item.latest}
free={item.provider.id === "opencode" && (!item.cost || item.cost.input === 0)}
/>
}
>
{node}
</Tooltip>
)}
onSelect={(x) => {
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
recent: true,
@@ -55,10 +75,10 @@ const ModelList: Component<{
<div class="w-full flex items-center gap-x-2 text-13-regular">
<span class="truncate">{i.name}</span>
<Show when={i.provider.id === "opencode" && (!i.cost || i.cost?.input === 0)}>
<Tag>Free</Tag>
<Tag>{language.t("model.tag.free")}</Tag>
</Show>
<Show when={i.latest}>
<Tag>Latest</Tag>
<Tag>{language.t("model.tag.latest")}</Tag>
</Show>
</div>
)}
@@ -71,14 +91,36 @@ export const ModelSelectorPopover: Component<{
children: JSX.Element
}> = (props) => {
const [open, setOpen] = createSignal(false)
const dialog = useDialog()
const handleManage = () => {
setOpen(false)
dialog.show(() => <DialogManageModels />)
}
const language = useLanguage()
return (
<Kobalte open={open()} onOpenChange={setOpen} placement="top-start" gutter={8}>
<Kobalte.Trigger as="div">{props.children}</Kobalte.Trigger>
<Kobalte.Portal>
<Kobalte.Content class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none">
<Kobalte.Title class="sr-only">Select model</Kobalte.Title>
<ModelList provider={props.provider} onSelect={() => setOpen(false)} class="p-1" />
<Kobalte.Content class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden">
<Kobalte.Title class="sr-only">{language.t("dialog.model.select.title")}</Kobalte.Title>
<ModelList
provider={props.provider}
onSelect={() => setOpen(false)}
class="p-1"
action={
<IconButton
icon="sliders"
variant="ghost"
iconSize="normal"
class="size-6"
aria-label={language.t("dialog.model.manage")}
title={language.t("dialog.model.manage")}
onClick={handleManage}
/>
}
/>
</Kobalte.Content>
</Kobalte.Portal>
</Kobalte>
@@ -87,10 +129,11 @@ export const ModelSelectorPopover: Component<{
export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
const dialog = useDialog()
const language = useLanguage()
return (
<Dialog
title="Select model"
title={language.t("dialog.model.select.title")}
action={
<Button
class="h-7 -my-1 text-14-medium"
@@ -98,7 +141,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
tabIndex={-1}
onClick={() => dialog.show(() => <DialogSelectProvider />)}
>
Connect provider
{language.t("command.provider.connect")}
</Button>
}
>
@@ -108,7 +151,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
class="ml-3 mt-5 mb-6 text-text-base self-start"
onClick={() => dialog.show(() => <DialogManageModels />)}
>
Manage models
{language.t("dialog.model.manage")}
</Button>
</Dialog>
)
@@ -7,28 +7,38 @@ import { Tag } from "@opencode-ai/ui/tag"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { IconName } from "@opencode-ai/ui/icons/provider"
import { DialogConnectProvider } from "./dialog-connect-provider"
import { useLanguage } from "@/context/language"
export const DialogSelectProvider: Component = () => {
const dialog = useDialog()
const providers = useProviders()
const language = useLanguage()
const popularGroup = () => language.t("dialog.provider.group.popular")
const otherGroup = () => language.t("dialog.provider.group.other")
return (
<Dialog title="Connect provider">
<Dialog title={language.t("command.provider.connect")}>
<List
search={{ placeholder: "Search providers", autofocus: true }}
search={{ placeholder: language.t("dialog.provider.search.placeholder"), autofocus: true }}
emptyMessage={language.t("dialog.provider.empty")}
activeIcon="plus-small"
key={(x) => x?.id}
items={providers.all}
items={() => {
language.locale()
return providers.all()
}}
filterKeys={["id", "name"]}
groupBy={(x) => (popularProviders.includes(x.id) ? "Popular" : "Other")}
groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())}
sortBy={(a, b) => {
if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
return a.name.localeCompare(b.name)
}}
sortGroupsBy={(a, b) => {
if (a.category === "Popular" && b.category !== "Popular") return -1
if (b.category === "Popular" && a.category !== "Popular") return 1
const popular = popularGroup()
if (a.category === popular && b.category !== popular) return -1
if (b.category === popular && a.category !== popular) return 1
return 0
}}
onSelect={(x) => {
@@ -41,10 +51,10 @@ export const DialogSelectProvider: Component = () => {
<ProviderIcon data-slot="list-item-extra-icon" id={i.id as IconName} />
<span>{i.name}</span>
<Show when={i.id === "opencode"}>
<Tag>Recommended</Tag>
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
</Show>
<Show when={i.id === "anthropic"}>
<div class="text-14-regular text-text-weak">Connect with Claude Pro/Max or API key</div>
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.anthropic.note")}</div>
</Show>
</div>
)}
@@ -1,14 +1,16 @@
import { createEffect, createMemo, onCleanup } from "solid-js"
import { createResource, createEffect, createMemo, onCleanup, Show } from "solid-js"
import { createStore, reconcile } from "solid-js/store"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { TextField } from "@opencode-ai/ui/text-field"
import { Button } from "@opencode-ai/ui/button"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server"
import { usePlatform } from "@/context/platform"
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import { useNavigate } from "@solidjs/router"
import { useLanguage } from "@/context/language"
type ServerStatus = { healthy: boolean; version?: string }
@@ -29,12 +31,15 @@ export function DialogSelectServer() {
const dialog = useDialog()
const server = useServer()
const platform = usePlatform()
const language = useLanguage()
const [store, setStore] = createStore({
url: "",
adding: false,
error: "",
status: {} as Record<string, ServerStatus | undefined>,
})
const [defaultUrl, defaultUrlActions] = createResource(() => platform.getDefaultServerUrl?.())
const isDesktop = platform.platform === "desktop"
const items = createMemo(() => {
const current = server.url
@@ -106,7 +111,7 @@ export function DialogSelectServer() {
setStore("adding", false)
if (!result.healthy) {
setStore("error", "Could not connect to server")
setStore("error", language.t("dialog.server.add.error"))
return
}
@@ -114,12 +119,16 @@ export function DialogSelectServer() {
select(value, true)
}
async function handleRemove(url: string) {
server.remove(url)
}
return (
<Dialog title="Servers" description="Switch which OpenCode server this app connects to.">
<Dialog title={language.t("dialog.server.title")} description={language.t("dialog.server.description")}>
<div class="flex flex-col gap-4 pb-4">
<List
search={{ placeholder: "Search servers", autofocus: true }}
emptyMessage="No servers yet"
search={{ placeholder: language.t("dialog.server.search.placeholder"), autofocus: true }}
emptyMessage={language.t("dialog.server.empty")}
items={sortedItems}
key={(x) => x}
current={current()}
@@ -128,36 +137,49 @@ export function DialogSelectServer() {
}}
>
{(i) => (
<div
class="flex items-center gap-2 min-w-0 flex-1"
classList={{ "opacity-50": store.status[i]?.healthy === false }}
>
<div class="flex items-center gap-2 min-w-0 flex-1 group/item">
<div
classList={{
"size-1.5 rounded-full shrink-0": true,
"bg-icon-success-base": store.status[i]?.healthy === true,
"bg-icon-critical-base": store.status[i]?.healthy === false,
"bg-border-weak-base": store.status[i] === undefined,
}}
/>
<span class="truncate">{serverDisplayName(i)}</span>
<span class="text-text-weak">{store.status[i]?.version}</span>
class="flex items-center gap-2 min-w-0 flex-1"
classList={{ "opacity-50": store.status[i]?.healthy === false }}
>
<div
classList={{
"size-1.5 rounded-full shrink-0": true,
"bg-icon-success-base": store.status[i]?.healthy === true,
"bg-icon-critical-base": store.status[i]?.healthy === false,
"bg-border-weak-base": store.status[i] === undefined,
}}
/>
<span class="truncate">{serverDisplayName(i)}</span>
<span class="text-text-weak">{store.status[i]?.version}</span>
</div>
<Show when={current() !== i && server.list.includes(i)}>
<IconButton
icon="circle-x"
variant="ghost"
class="bg-transparent transition-opacity shrink-0 hover:scale-110"
onClick={(e) => {
e.stopPropagation()
handleRemove(i)
}}
/>
</Show>
</div>
)}
</List>
<div class="mt-6 px-3 flex flex-col gap-1.5">
<div class="px-3">
<h3 class="text-14-regular text-text-weak">Add a server</h3>
<h3 class="text-14-regular text-text-weak">{language.t("dialog.server.add.title")}</h3>
</div>
<form onSubmit={handleSubmit}>
<div class="flex items-start gap-2">
<div class="flex-1 min-w-0 h-auto">
<TextField
type="text"
label="Server URL"
label={language.t("dialog.server.add.url")}
hideLabel
placeholder="http://localhost:4096"
placeholder={language.t("dialog.server.add.placeholder")}
value={store.url}
onChange={(v) => {
setStore("url", v)
@@ -168,11 +190,58 @@ export function DialogSelectServer() {
/>
</div>
<Button type="submit" variant="secondary" icon="plus-small" size="large" disabled={store.adding}>
{store.adding ? "Checking..." : "Add"}
{store.adding ? language.t("dialog.server.add.checking") : language.t("dialog.server.add.button")}
</Button>
</div>
</form>
</div>
<Show when={isDesktop}>
<div class="mt-6 px-3 flex flex-col gap-1.5">
<div class="px-3">
<h3 class="text-14-regular text-text-weak">{language.t("dialog.server.default.title")}</h3>
<p class="text-12-regular text-text-weak mt-1">{language.t("dialog.server.default.description")}</p>
</div>
<div class="flex items-center gap-2 px-3 py-2">
<Show
when={defaultUrl()}
fallback={
<Show
when={server.url}
fallback={
<span class="text-14-regular text-text-weak">{language.t("dialog.server.default.none")}</span>
}
>
<Button
variant="secondary"
size="small"
onClick={async () => {
await platform.setDefaultServerUrl?.(server.url)
defaultUrlActions.refetch(server.url)
}}
>
{language.t("dialog.server.default.set")}
</Button>
</Show>
}
>
<div class="flex items-center gap-2 flex-1 min-w-0">
<span class="truncate text-14-regular">{serverDisplayName(defaultUrl()!)}</span>
</div>
<Button
variant="ghost"
size="small"
onClick={async () => {
await platform.setDefaultServerUrl?.(null)
defaultUrlActions.refetch()
}}
>
{language.t("dialog.server.default.clear")}
</Button>
</Show>
</div>
</div>
</Show>
</div>
</Dialog>
)
@@ -0,0 +1,97 @@
import { Component } from "solid-js"
import { Dialog } from "@opencode-ai/ui/dialog"
import { Tabs } from "@opencode-ai/ui/tabs"
import { Icon } from "@opencode-ai/ui/icon"
import { useLanguage } from "@/context/language"
import { SettingsGeneral } from "./settings-general"
import { SettingsKeybinds } from "./settings-keybinds"
import { SettingsPermissions } from "./settings-permissions"
import { SettingsProviders } from "./settings-providers"
import { SettingsModels } from "./settings-models"
import { SettingsAgents } from "./settings-agents"
import { SettingsCommands } from "./settings-commands"
import { SettingsMcp } from "./settings-mcp"
export const DialogSettings: Component = () => {
const language = useLanguage()
return (
<Dialog size="x-large">
<Tabs orientation="vertical" variant="settings" defaultValue="general" class="h-full settings-dialog">
<Tabs.List>
<div
style={{
display: "flex",
"flex-direction": "column",
gap: "12px",
width: "100%",
"padding-top": "12px",
"padding-bottom": "12px",
}}
>
<Tabs.SectionTitle>{language.t("settings.section.desktop")}</Tabs.SectionTitle>
<div style={{ display: "flex", "flex-direction": "column", gap: "6px", width: "100%" }}>
<Tabs.Trigger value="general">
<Icon name="sliders" />
{language.t("settings.tab.general")}
</Tabs.Trigger>
<Tabs.Trigger value="shortcuts">
<Icon name="keyboard" />
{language.t("settings.tab.shortcuts")}
</Tabs.Trigger>
</div>
</div>
{/* <Tabs.SectionTitle>Server</Tabs.SectionTitle> */}
{/* <Tabs.Trigger value="permissions"> */}
{/* <Icon name="checklist" /> */}
{/* Permissions */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="providers"> */}
{/* <Icon name="server" /> */}
{/* Providers */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="models"> */}
{/* <Icon name="brain" /> */}
{/* Models */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="agents"> */}
{/* <Icon name="task" /> */}
{/* Agents */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="commands"> */}
{/* <Icon name="console" /> */}
{/* Commands */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="mcp"> */}
{/* <Icon name="mcp" /> */}
{/* MCP */}
{/* </Tabs.Trigger> */}
</Tabs.List>
<Tabs.Content value="general" class="no-scrollbar">
<SettingsGeneral />
</Tabs.Content>
<Tabs.Content value="shortcuts" class="no-scrollbar">
<SettingsKeybinds />
</Tabs.Content>
{/* <Tabs.Content value="permissions" class="no-scrollbar"> */}
{/* <SettingsPermissions /> */}
{/* </Tabs.Content> */}
{/* <Tabs.Content value="providers" class="no-scrollbar"> */}
{/* <SettingsProviders /> */}
{/* </Tabs.Content> */}
{/* <Tabs.Content value="models" class="no-scrollbar"> */}
{/* <SettingsModels /> */}
{/* </Tabs.Content> */}
{/* <Tabs.Content value="agents" class="no-scrollbar"> */}
{/* <SettingsAgents /> */}
{/* </Tabs.Content> */}
{/* <Tabs.Content value="commands" class="no-scrollbar"> */}
{/* <SettingsCommands /> */}
{/* </Tabs.Content> */}
{/* <Tabs.Content value="mcp" class="no-scrollbar"> */}
{/* <SettingsMcp /> */}
{/* </Tabs.Content> */}
</Tabs>
</Dialog>
)
}
@@ -0,0 +1,92 @@
import { Show, type Component } from "solid-js"
import { useLanguage } from "@/context/language"
type InputKey = "text" | "image" | "audio" | "video" | "pdf"
type InputMap = Record<InputKey, boolean>
type ModelInfo = {
id: string
name: string
provider: {
name: string
}
capabilities?: {
reasoning: boolean
input: InputMap
}
modalities?: {
input: Array<string>
}
reasoning?: boolean
limit: {
context: number
}
}
export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?: boolean }> = (props) => {
const language = useLanguage()
const sourceName = (model: ModelInfo) => {
const value = `${model.id} ${model.name}`.toLowerCase()
if (/claude|anthropic/.test(value)) return language.t("model.provider.anthropic")
if (/gpt|o[1-4]|codex|openai/.test(value)) return language.t("model.provider.openai")
if (/gemini|palm|bard|google/.test(value)) return language.t("model.provider.google")
if (/grok|xai/.test(value)) return language.t("model.provider.xai")
if (/llama|meta/.test(value)) return language.t("model.provider.meta")
return model.provider.name
}
const inputLabel = (value: string) => {
if (value === "text") return language.t("model.input.text")
if (value === "image") return language.t("model.input.image")
if (value === "audio") return language.t("model.input.audio")
if (value === "video") return language.t("model.input.video")
if (value === "pdf") return language.t("model.input.pdf")
return value
}
const title = () => {
const tags: Array<string> = []
if (props.latest) tags.push(language.t("model.tag.latest"))
if (props.free) tags.push(language.t("model.tag.free"))
const suffix = tags.length ? ` (${tags.join(", ")})` : ""
return `${sourceName(props.model)} ${props.model.name}${suffix}`
}
const inputs = () => {
if (props.model.capabilities) {
const input = props.model.capabilities.input
const order: Array<InputKey> = ["text", "image", "audio", "video", "pdf"]
const entries = order.filter((key) => input[key]).map((key) => inputLabel(key))
return entries.length ? entries.join(", ") : undefined
}
const raw = props.model.modalities?.input
if (!raw) return
const entries = raw.map((value) => inputLabel(value))
return entries.length ? entries.join(", ") : undefined
}
const reasoning = () => {
if (props.model.capabilities)
return props.model.capabilities.reasoning
? language.t("model.tooltip.reasoning.allowed")
: language.t("model.tooltip.reasoning.none")
return props.model.reasoning
? language.t("model.tooltip.reasoning.allowed")
: language.t("model.tooltip.reasoning.none")
}
const context = () =>
language.t("model.tooltip.context", { limit: props.model.limit.context.toLocaleString() })
return (
<div class="flex flex-col gap-1 py-1">
<div class="text-13-medium">{title()}</div>
<Show when={inputs()}>
{(value) => (
<div class="text-12-regular text-text-invert-base">
{language.t("model.tooltip.allows", { inputs: value() })}
</div>
)}
</Show>
<div class="text-12-regular text-text-invert-base">{reasoning()}</div>
<div class="text-12-regular text-text-invert-base">{context()}</div>
</div>
)
}
File diff suppressed because it is too large Load Diff
@@ -7,6 +7,7 @@ import { AssistantMessage } from "@opencode-ai/sdk/v2/client"
import { useLayout } from "@/context/layout"
import { useSync } from "@/context/sync"
import { useLanguage } from "@/context/language"
interface SessionContextUsageProps {
variant?: "button" | "indicator"
@@ -16,21 +17,25 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
const sync = useSync()
const params = useParams()
const layout = useLayout()
const language = useLanguage()
const variant = createMemo(() => props.variant ?? "button")
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const tabs = createMemo(() => layout.tabs(sessionKey()))
const view = createMemo(() => layout.view(sessionKey()))
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
const cost = createMemo(() => {
const locale = language.locale()
const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
return new Intl.NumberFormat("en-US", {
return new Intl.NumberFormat(locale, {
style: "currency",
currency: "USD",
}).format(total)
})
const context = createMemo(() => {
const locale = language.locale()
const last = messages().findLast((x) => {
if (x.role !== "assistant") return false
const total = x.tokens.input + x.tokens.output + x.tokens.reasoning + x.tokens.cache.read + x.tokens.cache.write
@@ -41,14 +46,14 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
const model = sync.data.provider.all.find((x) => x.id === last.providerID)?.models[last.modelID]
return {
tokens: total.toLocaleString(),
tokens: total.toLocaleString(locale),
percentage: model?.limit.context ? Math.round((total / model.limit.context) * 100) : null,
}
})
const openContext = () => {
if (!params.id) return
layout.review.open()
view().reviewPanel.open()
tabs().open("context")
tabs().setActive("context")
}
@@ -66,21 +71,21 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
<>
<div class="flex items-center gap-2">
<span class="text-text-invert-strong">{ctx().tokens}</span>
<span class="text-text-invert-base">Tokens</span>
<span class="text-text-invert-base">{language.t("context.usage.tokens")}</span>
</div>
<div class="flex items-center gap-2">
<span class="text-text-invert-strong">{ctx().percentage ?? 0}%</span>
<span class="text-text-invert-base">Usage</span>
<span class="text-text-invert-base">{language.t("context.usage.usage")}</span>
</div>
</>
)}
</Show>
<div class="flex items-center gap-2">
<span class="text-text-invert-strong">{cost()}</span>
<span class="text-text-invert-base">Cost</span>
<span class="text-text-invert-base">{language.t("context.usage.cost")}</span>
</div>
<Show when={variant() === "button"}>
<div class="text-11-regular text-text-invert-base mt-1">Click to view context</div>
<div class="text-11-regular text-text-invert-base mt-1">{language.t("context.usage.clickToView")}</div>
</Show>
</div>
)
@@ -1,9 +1,11 @@
import { createMemo, Show } from "solid-js"
import { useSync } from "@/context/sync"
import { useLanguage } from "@/context/language"
import { Tooltip } from "@opencode-ai/ui/tooltip"
export function SessionLspIndicator() {
const sync = useSync()
const language = useLanguage()
const lspStats = createMemo(() => {
const lsp = sync.data.lsp ?? []
@@ -15,7 +17,7 @@ export function SessionLspIndicator() {
const tooltipContent = createMemo(() => {
const lsp = sync.data.lsp ?? []
if (lsp.length === 0) return "No LSP servers"
if (lsp.length === 0) return language.t("lsp.tooltip.none")
return lsp.map((s) => s.name).join(", ")
})
@@ -30,7 +32,9 @@ export function SessionLspIndicator() {
"bg-icon-success-base": !lspStats().hasError && lspStats().connected > 0,
}}
/>
<span class="text-12-regular text-text-weak">{lspStats().connected} LSP</span>
<span class="text-12-regular text-text-weak">
{language.t("lsp.label.connected", { count: lspStats().connected })}
</span>
</div>
</Tooltip>
</Show>
@@ -11,6 +11,7 @@ import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
import { Code } from "@opencode-ai/ui/code"
import { Markdown } from "@opencode-ai/ui/markdown"
import type { AssistantMessage, Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client"
import { useLanguage } from "@/context/language"
interface SessionContextTabProps {
messages: () => Message[]
@@ -22,6 +23,7 @@ interface SessionContextTabProps {
export function SessionContextTab(props: SessionContextTabProps) {
const params = useParams()
const sync = useSync()
const language = useLanguage()
const ctx = createMemo(() => {
const last = props.messages().findLast((x) => {
@@ -59,8 +61,9 @@ export function SessionContextTab(props: SessionContextTabProps) {
})
const cost = createMemo(() => {
const locale = language.locale()
const total = props.messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
return new Intl.NumberFormat("en-US", {
return new Intl.NumberFormat(locale, {
style: "currency",
currency: "USD",
}).format(total)
@@ -89,18 +92,18 @@ export function SessionContextTab(props: SessionContextTabProps) {
const number = (value: number | null | undefined) => {
if (value === undefined) return "—"
if (value === null) return "—"
return value.toLocaleString()
return value.toLocaleString(language.locale())
}
const percent = (value: number | null | undefined) => {
if (value === undefined) return "—"
if (value === null) return "—"
return value.toString() + "%"
return value.toLocaleString(language.locale()) + "%"
}
const time = (value: number | undefined) => {
if (!value) return "—"
return DateTime.fromMillis(value).toLocaleString(DateTime.DATETIME_MED)
return DateTime.fromMillis(value).setLocale(language.locale()).toLocaleString(DateTime.DATETIME_MED)
}
const providerLabel = createMemo(() => {
@@ -172,7 +175,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
return [
{
key: "system",
label: "System",
label: language.t("context.breakdown.system"),
tokens: tokens.system,
width: pct(tokens.system),
percent: pctLabel(tokens.system),
@@ -180,7 +183,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
},
{
key: "user",
label: "User",
label: language.t("context.breakdown.user"),
tokens: tokens.user,
width: pct(tokens.user),
percent: pctLabel(tokens.user),
@@ -188,7 +191,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
},
{
key: "assistant",
label: "Assistant",
label: language.t("context.breakdown.assistant"),
tokens: tokens.assistant,
width: pct(tokens.assistant),
percent: pctLabel(tokens.assistant),
@@ -196,7 +199,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
},
{
key: "tool",
label: "Tool Calls",
label: language.t("context.breakdown.tool"),
tokens: tokens.tool,
width: pct(tokens.tool),
percent: pctLabel(tokens.tool),
@@ -204,7 +207,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
},
{
key: "other",
label: "Other",
label: language.t("context.breakdown.other"),
tokens: tokens.other,
width: pct(tokens.other),
percent: pctLabel(tokens.other),
@@ -243,22 +246,28 @@ export function SessionContextTab(props: SessionContextTabProps) {
const c = ctx()
const count = counts()
return [
{ label: "Session", value: props.info()?.title ?? params.id ?? "—" },
{ label: "Messages", value: count.all.toLocaleString() },
{ label: "Provider", value: providerLabel() },
{ label: "Model", value: modelLabel() },
{ label: "Context Limit", value: number(c?.limit) },
{ label: "Total Tokens", value: number(c?.total) },
{ label: "Usage", value: percent(c?.usage) },
{ label: "Input Tokens", value: number(c?.input) },
{ label: "Output Tokens", value: number(c?.output) },
{ label: "Reasoning Tokens", value: number(c?.reasoning) },
{ label: "Cache Tokens (read/write)", value: `${number(c?.cacheRead)} / ${number(c?.cacheWrite)}` },
{ label: "User Messages", value: count.user.toLocaleString() },
{ label: "Assistant Messages", value: count.assistant.toLocaleString() },
{ label: "Total Cost", value: cost() },
{ label: "Session Created", value: time(props.info()?.time.created) },
{ label: "Last Activity", value: time(c?.message.time.created) },
{ label: language.t("context.stats.session"), value: props.info()?.title ?? params.id ?? "—" },
{ label: language.t("context.stats.messages"), value: count.all.toLocaleString(language.locale()) },
{ label: language.t("context.stats.provider"), value: providerLabel() },
{ label: language.t("context.stats.model"), value: modelLabel() },
{ label: language.t("context.stats.limit"), value: number(c?.limit) },
{ label: language.t("context.stats.totalTokens"), value: number(c?.total) },
{ label: language.t("context.stats.usage"), value: percent(c?.usage) },
{ label: language.t("context.stats.inputTokens"), value: number(c?.input) },
{ label: language.t("context.stats.outputTokens"), value: number(c?.output) },
{ label: language.t("context.stats.reasoningTokens"), value: number(c?.reasoning) },
{
label: language.t("context.stats.cacheTokens"),
value: `${number(c?.cacheRead)} / ${number(c?.cacheWrite)}`,
},
{ label: language.t("context.stats.userMessages"), value: count.user.toLocaleString(language.locale()) },
{
label: language.t("context.stats.assistantMessages"),
value: count.assistant.toLocaleString(language.locale()),
},
{ label: language.t("context.stats.totalCost"), value: cost() },
{ label: language.t("context.stats.sessionCreated"), value: time(props.info()?.time.created) },
{ label: language.t("context.stats.lastActivity"), value: time(c?.message.time.created) },
] satisfies { label: string; value: JSX.Element }[]
})
@@ -371,7 +380,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
<Show when={breakdown().length > 0}>
<div class="flex flex-col gap-2">
<div class="text-12-regular text-text-weak">Context Breakdown</div>
<div class="text-12-regular text-text-weak">{language.t("context.breakdown.title")}</div>
<div class="h-2 w-full rounded-full bg-surface-base overflow-hidden flex">
<For each={breakdown()}>
{(segment) => (
@@ -396,16 +405,14 @@ export function SessionContextTab(props: SessionContextTabProps) {
)}
</For>
</div>
<div class="hidden text-11-regular text-text-weaker">
Approximate breakdown of input tokens. "Other" includes tool definitions and overhead.
</div>
<div class="hidden text-11-regular text-text-weaker">{language.t("context.breakdown.note")}</div>
</div>
</Show>
<Show when={systemPrompt()}>
{(prompt) => (
<div class="flex flex-col gap-2">
<div class="text-12-regular text-text-weak">System Prompt</div>
<div class="text-12-regular text-text-weak">{language.t("context.systemPrompt.title")}</div>
<div class="border border-border-base rounded-md bg-surface-base px-3 py-2">
<Markdown text={prompt()} class="text-12-regular" />
</div>
@@ -414,7 +421,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
</Show>
<div class="flex flex-col gap-2">
<div class="text-12-regular text-text-weak">Raw messages</div>
<div class="text-12-regular text-text-weak">{language.t("context.rawMessages.title")}</div>
<Accordion multiple>
<For each={props.messages()}>{(message) => <RawMessage message={message} />}</For>
</Accordion>
@@ -1,209 +1,342 @@
import { createEffect, createMemo, createResource, Show } from "solid-js"
import { A, useNavigate, useParams } from "@solidjs/router"
import { createEffect, createMemo, onCleanup, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { Portal } from "solid-js/web"
import { useParams } from "@solidjs/router"
import { useLayout } from "@/context/layout"
import { useCommand } from "@/context/command"
import { useServer } from "@/context/server"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useLanguage } from "@/context/language"
// import { useServer } from "@/context/server"
// import { useDialog } from "@opencode-ai/ui/context/dialog"
import { usePlatform } from "@/context/platform"
import { useSync } from "@/context/sync"
import { useGlobalSDK } from "@/context/global-sdk"
import { getFilename } from "@opencode-ai/util/path"
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
import { iife } from "@opencode-ai/util/iife"
import { base64Decode } from "@opencode-ai/util/encode"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Button } from "@opencode-ai/ui/button"
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { Select } from "@opencode-ai/ui/select"
import { Popover } from "@opencode-ai/ui/popover"
import { TextField } from "@opencode-ai/ui/text-field"
import { DialogSelectServer } from "@/components/dialog-select-server"
import { SessionLspIndicator } from "@/components/session-lsp-indicator"
import { SessionMcpIndicator } from "@/components/session-mcp-indicator"
import type { Session } from "@opencode-ai/sdk/v2/client"
import { Keybind } from "@opencode-ai/ui/keybind"
export function SessionHeader() {
const globalSDK = useGlobalSDK()
const layout = useLayout()
const params = useParams()
const navigate = useNavigate()
const command = useCommand()
const server = useServer()
const dialog = useDialog()
// const server = useServer()
// const dialog = useDialog()
const sync = useSync()
const platform = usePlatform()
const language = useLanguage()
const projectDirectory = createMemo(() => base64Decode(params.dir ?? ""))
const project = createMemo(() => {
const directory = projectDirectory()
if (!directory) return
return layout.projects.list().find((p) => p.worktree === directory || p.sandboxes?.includes(directory))
})
const name = createMemo(() => {
const current = project()
if (current) return current.name || getFilename(current.worktree)
return getFilename(projectDirectory())
})
const hotkey = createMemo(() => command.keybind("file.open"))
const sessions = createMemo(() => (sync.data.session ?? []).filter((s) => !s.parentID))
const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id))
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
const showReview = createMemo(() => !!currentSession()?.summary?.files)
const showShare = createMemo(() => shareEnabled() && !!currentSession())
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const view = createMemo(() => layout.view(sessionKey()))
function navigateToProject(directory: string) {
navigate(`/${base64Encode(directory)}`)
const [state, setState] = createStore({
share: false,
unshare: false,
copied: false,
timer: undefined as number | undefined,
})
const shareUrl = createMemo(() => currentSession()?.share?.url)
createEffect(() => {
const url = shareUrl()
if (url) return
if (state.timer) window.clearTimeout(state.timer)
setState({ copied: false, timer: undefined })
})
onCleanup(() => {
if (state.timer) window.clearTimeout(state.timer)
})
function shareSession() {
const session = currentSession()
if (!session || state.share) return
setState("share", true)
globalSDK.client.session
.share({ sessionID: session.id, directory: projectDirectory() })
.catch((error) => {
console.error("Failed to share session", error)
})
.finally(() => {
setState("share", false)
})
}
function navigateToSession(session: Session | undefined) {
if (!session) return
navigate(`/${params.dir}/session/${session.id}`)
function unshareSession() {
const session = currentSession()
if (!session || state.unshare) return
setState("unshare", true)
globalSDK.client.session
.unshare({ sessionID: session.id, directory: projectDirectory() })
.catch((error) => {
console.error("Failed to unshare session", error)
})
.finally(() => {
setState("unshare", false)
})
}
function copyLink() {
const url = shareUrl()
if (!url) return
navigator.clipboard
.writeText(url)
.then(() => {
if (state.timer) window.clearTimeout(state.timer)
setState("copied", true)
const timer = window.setTimeout(() => {
setState("copied", false)
setState("timer", undefined)
}, 3000)
setState("timer", timer)
})
.catch((error) => {
console.error("Failed to copy share link", error)
})
}
function viewShare() {
const url = shareUrl()
if (!url) return
platform.openLink(url)
}
const centerMount = createMemo(() => document.getElementById("opencode-titlebar-center"))
const rightMount = createMemo(() => document.getElementById("opencode-titlebar-right"))
return (
<header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex" data-tauri-drag-region>
<button
type="button"
class="xl:hidden w-12 shrink-0 flex items-center justify-center border-r border-border-weak-base hover:bg-surface-raised-base-hover active:bg-surface-raised-base-active transition-colors"
onClick={layout.mobileSidebar.toggle}
>
<Icon name="menu" size="small" />
</button>
<div class="px-4 flex items-center justify-between gap-4 w-full">
<div class="flex items-center gap-3 min-w-0">
<div class="flex items-center gap-2 min-w-0">
<div class="hidden xl:flex items-center gap-2">
<Select
options={layout.projects.list().map((project) => project.worktree)}
current={sync.project?.worktree ?? projectDirectory()}
label={(x) => getFilename(x)}
onSelect={(x) => (x ? navigateToProject(x) : undefined)}
class="text-14-regular text-text-base"
variant="ghost"
>
{/* @ts-ignore */}
{(i) => (
<div class="flex items-center gap-2">
<Icon name="folder" size="small" />
<div class="text-text-strong">{getFilename(i)}</div>
</div>
)}
</Select>
<div class="text-text-weaker">/</div>
</div>
<Select
options={sessions()}
current={currentSession()}
placeholder="New session"
label={(x) => x.title}
value={(x) => x.id}
onSelect={navigateToSession}
class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md"
variant="ghost"
/>
</div>
<Show when={currentSession()}>
<TooltipKeybind class="hidden xl:block" title="New session" keybind={command.keybind("session.new")}>
<IconButton as={A} href={`/${params.dir}/session`} icon="edit-small-2" variant="ghost" />
</TooltipKeybind>
</Show>
</div>
<div class="flex items-center gap-3">
<div class="hidden md:flex items-center gap-1">
<Button
size="small"
variant="ghost"
onClick={() => {
dialog.show(() => <DialogSelectServer />)
}}
<>
<Show when={centerMount()}>
{(mount) => (
<Portal mount={mount()}>
<button
type="button"
class="hidden md:flex w-[320px] p-1 pl-1.5 items-center gap-2 justify-between rounded-md border border-border-weak-base bg-surface-raised-base transition-colors cursor-default hover:bg-surface-raised-base-hover focus:bg-surface-raised-base-hover active:bg-surface-raised-base-active"
onClick={() => command.trigger("file.open")}
>
<div
classList={{
"size-1.5 rounded-full": true,
"bg-icon-success-base": server.healthy() === true,
"bg-icon-critical-base": server.healthy() === false,
"bg-border-weak-base": server.healthy() === undefined,
}}
/>
<Icon name="server" size="small" class="text-icon-weak" />
<span class="text-12-regular text-text-weak truncate max-w-[200px]">{server.name}</span>
</Button>
<SessionLspIndicator />
<SessionMcpIndicator />
</div>
<div class="flex items-center gap-1">
<Show when={currentSession()?.summary?.files}>
<TooltipKeybind
class="hidden md:block shrink-0"
title="Toggle review"
keybind={command.keybind("review.toggle")}
>
<Button variant="ghost" class="group/review-toggle size-6 p-0" onClick={layout.review.toggle}>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
name={layout.review.opened() ? "layout-right" : "layout-left"}
size="small"
class="group-hover/review-toggle:hidden"
/>
<Icon
name={layout.review.opened() ? "layout-right-partial" : "layout-left-partial"}
size="small"
class="hidden group-hover/review-toggle:inline-block"
/>
<Icon
name={layout.review.opened() ? "layout-right-full" : "layout-left-full"}
size="small"
class="hidden group-active/review-toggle:inline-block"
/>
</div>
</Button>
</TooltipKeybind>
</Show>
<TooltipKeybind
class="hidden md:block shrink-0"
title="Toggle terminal"
keybind={command.keybind("terminal.toggle")}
>
<Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
size="small"
name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
class="group-hover/terminal-toggle:hidden"
/>
<Icon
size="small"
name="layout-bottom-partial"
class="hidden group-hover/terminal-toggle:inline-block"
/>
<Icon
size="small"
name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
class="hidden group-active/terminal-toggle:inline-block"
/>
<div class="flex min-w-0 flex-1 items-center gap-2 overflow-visible">
<Icon name="magnifying-glass" size="normal" class="icon-base shrink-0" />
<span class="flex-1 min-w-0 text-14-regular text-text-weak truncate h-4.5 flex items-center">
{language.t("session.header.search.placeholder", { project: name() })}
</span>
</div>
<Show when={hotkey()}>{(keybind) => <Keybind class="shrink-0">{keybind()}</Keybind>}</Show>
</button>
</Portal>
)}
</Show>
<Show when={rightMount()}>
{(mount) => (
<Portal mount={mount()}>
<div class="flex items-center gap-3">
{/* <div class="hidden md:flex items-center gap-1"> */}
{/* <Button */}
{/* size="small" */}
{/* variant="ghost" */}
{/* onClick={() => { */}
{/* dialog.show(() => <DialogSelectServer />) */}
{/* }} */}
{/* > */}
{/* <div */}
{/* classList={{ */}
{/* "size-1.5 rounded-full": true, */}
{/* "bg-icon-success-base": server.healthy() === true, */}
{/* "bg-icon-critical-base": server.healthy() === false, */}
{/* "bg-border-weak-base": server.healthy() === undefined, */}
{/* }} */}
{/* /> */}
{/* <Icon name="server" size="small" class="text-icon-weak" /> */}
{/* <span class="text-12-regular text-text-weak truncate max-w-[200px]">{server.name}</span> */}
{/* </Button> */}
{/* <SessionLspIndicator /> */}
{/* <SessionMcpIndicator /> */}
{/* </div> */}
<div class="flex items-center gap-1">
<div
class="hidden md:block shrink-0"
classList={{
"opacity-0 pointer-events-none": !showReview(),
}}
aria-hidden={!showReview()}
>
<TooltipKeybind
title={language.t("command.review.toggle")}
keybind={command.keybind("review.toggle")}
>
<Button
variant="ghost"
class="group/review-toggle size-6 p-0"
onClick={() => view().reviewPanel.toggle()}
>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
size="small"
name={view().reviewPanel.opened() ? "layout-right-full" : "layout-right"}
class="group-hover/review-toggle:hidden"
/>
<Icon
size="small"
name="layout-right-partial"
class="hidden group-hover/review-toggle:inline-block"
/>
<Icon
size="small"
name={view().reviewPanel.opened() ? "layout-right" : "layout-right-full"}
class="hidden group-active/review-toggle:inline-block"
/>
</div>
</Button>
</TooltipKeybind>
</div>
</Button>
</TooltipKeybind>
</div>
<Show when={shareEnabled() && currentSession()}>
<Popover
title="Share session"
trigger={
<Tooltip class="shrink-0" value="Share session">
<IconButton icon="share" variant="ghost" class="" />
</Tooltip>
}
>
{iife(() => {
const [url] = createResource(
() => currentSession(),
async (session) => {
if (!session) return
let shareURL = session.share?.url
if (!shareURL) {
shareURL = await globalSDK.client.session
.share({ sessionID: session.id, directory: projectDirectory() })
.then((r) => r.data?.share?.url)
.catch((e) => {
console.error("Failed to share session", e)
return undefined
})
<TooltipKeybind
class="hidden md:block shrink-0"
title={language.t("command.terminal.toggle")}
keybind={command.keybind("terminal.toggle")}
>
<Button
variant="ghost"
class="group/terminal-toggle size-8 rounded-md"
onClick={() => view().terminal.toggle()}
>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
size="small"
name={view().terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
class="group-hover/terminal-toggle:hidden"
/>
<Icon
size="small"
name="layout-bottom-partial"
class="hidden group-hover/terminal-toggle:inline-block"
/>
<Icon
size="small"
name={view().terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
class="hidden group-active/terminal-toggle:inline-block"
/>
</div>
</Button>
</TooltipKeybind>
</div>
<div
class="flex items-center"
classList={{
"opacity-0 pointer-events-none": !showShare(),
}}
aria-hidden={!showShare()}
>
<Popover
title={language.t("session.share.popover.title")}
description={
shareUrl()
? language.t("session.share.popover.description.shared")
: language.t("session.share.popover.description.unshared")
}
trigger={
<Tooltip class="shrink-0" value={language.t("command.session.share")}>
<Button
variant="secondary"
classList={{ "rounded-r-none": shareUrl() !== undefined }}
style={{ scale: 1 }}
>
{language.t("session.share.action.share")}
</Button>
</Tooltip>
}
>
<div class="flex flex-col gap-2">
<Show
when={shareUrl()}
fallback={
<div class="flex">
<Button
size="large"
variant="primary"
class="w-1/2"
onClick={shareSession}
disabled={state.share}
>
{state.share
? language.t("session.share.action.publishing")
: language.t("session.share.action.publish")}
</Button>
</div>
}
>
<div class="flex flex-col gap-2 w-72">
<TextField value={shareUrl() ?? ""} readOnly copyable class="w-full" />
<div class="grid grid-cols-2 gap-2">
<Button
size="large"
variant="secondary"
class="w-full shadow-none border border-border-weak-base"
onClick={unshareSession}
disabled={state.unshare}
>
{state.unshare
? language.t("session.share.action.unpublishing")
: language.t("session.share.action.unpublish")}
</Button>
<Button
size="large"
variant="primary"
class="w-full"
onClick={viewShare}
disabled={state.unshare}
>
{language.t("session.share.action.view")}
</Button>
</div>
</div>
</Show>
</div>
</Popover>
<Show when={shareUrl()} fallback={<div class="size-6" aria-hidden="true" />}>
<Tooltip
value={
state.copied ? language.t("session.share.copy.copied") : language.t("session.share.copy.copyLink")
}
return shareURL
},
)
return <Show when={url()}>{(url) => <TextField value={url()} readOnly copyable class="w-72" />}</Show>
})}
</Popover>
</Show>
</div>
</div>
</header>
placement="top"
gutter={8}
>
<IconButton
icon={state.copied ? "check" : "copy"}
variant="secondary"
class="rounded-l-none"
onClick={copyLink}
disabled={state.unshare}
/>
</Tooltip>
</Show>
</div>
</div>
</Portal>
)}
</Show>
</>
)
}
@@ -1,6 +1,7 @@
import { Show, createMemo } from "solid-js"
import { DateTime } from "luxon"
import { useSync } from "@/context/sync"
import { useLanguage } from "@/context/language"
import { Icon } from "@opencode-ai/ui/icon"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { Select } from "@opencode-ai/ui/select"
@@ -15,6 +16,7 @@ interface NewSessionViewProps {
export function NewSessionView(props: NewSessionViewProps) {
const sync = useSync()
const language = useLanguage()
const sandboxes = createMemo(() => sync.project?.sandboxes ?? [])
const options = createMemo(() => [MAIN_WORKTREE, ...sandboxes(), CREATE_WORKTREE])
@@ -23,27 +25,37 @@ export function NewSessionView(props: NewSessionViewProps) {
if (options().includes(selection)) return selection
return MAIN_WORKTREE
})
const projectRoot = createMemo(() => sync.project?.worktree ?? sync.data.path.directory)
const isWorktree = createMemo(() => {
const project = sync.project
if (!project) return false
return sync.data.path.directory !== project.worktree
})
const label = (value: string) => {
if (value === MAIN_WORKTREE) {
if (isWorktree()) return language.t("session.new.worktree.main")
const branch = sync.data.vcs?.branch
if (branch) return `Current branch (${branch})`
return "Main branch"
if (branch) return language.t("session.new.worktree.mainWithBranch", { branch })
return language.t("session.new.worktree.main")
}
if (value === CREATE_WORKTREE) return "Create new worktree"
if (value === CREATE_WORKTREE) return language.t("session.new.worktree.create")
return getFilename(value)
}
return (
<div class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-200 mx-auto px-6">
<div class="text-20-medium text-text-weaker">New session</div>
<div
class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-200 mx-auto px-6"
style={{ "padding-bottom": "calc(var(--prompt-height, 11.25rem) + 64px)" }}
>
<div class="text-20-medium text-text-weaker">{language.t("command.session.new")}</div>
<div class="flex justify-center items-center gap-3">
<Icon name="folder" size="small" />
<div class="text-12-medium text-text-weak">
{getDirectory(sync.data.path.directory)}
<span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
<div class="text-12-medium text-text-weak select-text">
{getDirectory(projectRoot())}
<span class="text-text-strong">{getFilename(projectRoot())}</span>
</div>
</div>
<div class="flex justify-center items-center gap-1">
@@ -66,9 +78,11 @@ export function NewSessionView(props: NewSessionViewProps) {
<div class="flex justify-center items-center gap-3">
<Icon name="pencil-line" size="small" />
<div class="text-12-medium text-text-weak">
Last modified&nbsp;
{language.t("session.new.lastModified")}&nbsp;
<span class="text-text-strong">
{DateTime.fromMillis(project().time.updated ?? project().time.created).toRelative()}
{DateTime.fromMillis(project().time.updated ?? project().time.created)
.setLocale(language.locale())
.toRelative()}
</span>
</div>
</div>
@@ -7,6 +7,7 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
import { Tabs } from "@opencode-ai/ui/tabs"
import { getFilename } from "@opencode-ai/util/path"
import { useFile } from "@/context/file"
import { useLanguage } from "@/context/language"
export function FileVisual(props: { path: string; active?: boolean }): JSX.Element {
return (
@@ -25,6 +26,7 @@ export function FileVisual(props: { path: string; active?: boolean }): JSX.Eleme
export function SortableTab(props: { tab: string; onTabClose: (tab: string) => void }): JSX.Element {
const file = useFile()
const language = useLanguage()
const sortable = createSortable(props.tab)
const path = createMemo(() => file.pathFromTab(props.tab))
return (
@@ -34,11 +36,12 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
<Tabs.Trigger
value={props.tab}
closeButton={
<Tooltip value="Close tab" placement="bottom">
<Tooltip value={language.t("common.closeTab")} placement="bottom">
<IconButton icon="close" variant="ghost" onClick={() => props.onTabClose(props.tab)} />
</Tooltip>
}
hideCloseButton
onMiddleClick={() => props.onTabClose(props.tab)}
>
<Show when={path()}>{(p) => <FileVisual path={p()} />}</Show>
</Tabs.Trigger>
@@ -1,26 +1,164 @@
import type { JSX } from "solid-js"
import { createSignal, Show } from "solid-js"
import { createSortable } from "@thisbeyond/solid-dnd"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Tabs } from "@opencode-ai/ui/tabs"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
import { Icon } from "@opencode-ai/ui/icon"
import { useTerminal, type LocalPTY } from "@/context/terminal"
import { useLanguage } from "@/context/language"
export function SortableTerminalTab(props: { terminal: LocalPTY }): JSX.Element {
export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => void }): JSX.Element {
const terminal = useTerminal()
const language = useLanguage()
const sortable = createSortable(props.terminal.id)
const [editing, setEditing] = createSignal(false)
const [title, setTitle] = createSignal(props.terminal.title)
const [menuOpen, setMenuOpen] = createSignal(false)
const [menuPosition, setMenuPosition] = createSignal({ x: 0, y: 0 })
const [blurEnabled, setBlurEnabled] = createSignal(false)
const label = () => {
language.locale()
const number = props.terminal.titleNumber
if (Number.isFinite(number) && number > 0) {
return language.t("terminal.title.numbered", { number })
}
if (props.terminal.title) return props.terminal.title
return language.t("terminal.title")
}
const close = () => {
const count = terminal.all().length
terminal.close(props.terminal.id)
if (count === 1) {
props.onClose?.()
}
}
const focus = () => {
if (editing()) return
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur()
}
const wrapper = document.getElementById(`terminal-wrapper-${props.terminal.id}`)
const element = wrapper?.querySelector('[data-component="terminal"]') as HTMLElement
if (!element) return
const textarea = element.querySelector("textarea") as HTMLTextAreaElement
if (textarea) {
textarea.focus()
return
}
element.focus()
element.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true }))
}
const edit = (e?: Event) => {
if (e) {
e.stopPropagation()
e.preventDefault()
}
setBlurEnabled(false)
setTitle(props.terminal.title)
setEditing(true)
setTimeout(() => {
const input = document.getElementById(`terminal-title-input-${props.terminal.id}`) as HTMLInputElement
if (!input) return
input.focus()
input.select()
setTimeout(() => setBlurEnabled(true), 100)
}, 10)
}
const save = () => {
if (!blurEnabled()) return
const value = title().trim()
if (value && value !== props.terminal.title) {
terminal.update({ id: props.terminal.id, title: value })
}
setEditing(false)
}
const keydown = (e: KeyboardEvent) => {
if (e.key === "Enter") {
e.preventDefault()
save()
return
}
if (e.key === "Escape") {
e.preventDefault()
setEditing(false)
}
}
const menu = (e: MouseEvent) => {
e.preventDefault()
setMenuPosition({ x: e.clientX, y: e.clientY })
setMenuOpen(true)
}
return (
// @ts-ignore
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
<div class="relative h-full">
<Tabs.Trigger
value={props.terminal.id}
onClick={focus}
onMouseDown={(e) => e.preventDefault()}
onContextMenu={menu}
closeButton={
terminal.all().length > 1 && (
<IconButton icon="close" variant="ghost" onClick={() => terminal.close(props.terminal.id)} />
)
<IconButton
icon="close"
variant="ghost"
onClick={(e) => {
e.stopPropagation()
close()
}}
/>
}
>
{props.terminal.title}
<span onDblClick={edit} style={{ visibility: editing() ? "hidden" : "visible" }}>
{label()}
</span>
</Tabs.Trigger>
<Show when={editing()}>
<div class="absolute inset-0 flex items-center px-3 bg-muted z-10 pointer-events-auto">
<input
id={`terminal-title-input-${props.terminal.id}`}
type="text"
value={title()}
onInput={(e) => setTitle(e.currentTarget.value)}
onBlur={save}
onKeyDown={keydown}
onMouseDown={(e) => e.stopPropagation()}
class="bg-transparent border-none outline-none text-sm min-w-0 flex-1"
/>
</div>
</Show>
<DropdownMenu open={menuOpen()} onOpenChange={setMenuOpen}>
<DropdownMenu.Portal>
<DropdownMenu.Content
style={{
position: "fixed",
left: `${menuPosition().x}px`,
top: `${menuPosition().y}px`,
}}
>
<DropdownMenu.Item onSelect={edit}>
<Icon name="edit" class="w-4 h-4 mr-2" />
{language.t("common.rename")}
</DropdownMenu.Item>
<DropdownMenu.Item onSelect={close}>
<Icon name="close" class="w-4 h-4 mr-2" />
{language.t("common.close")}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
</div>
</div>
)
@@ -0,0 +1,15 @@
import { Component } from "solid-js"
import { useLanguage } from "@/context/language"
export const SettingsAgents: Component = () => {
const language = useLanguage()
return (
<div class="flex flex-col h-full overflow-y-auto">
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.agents.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.agents.description")}</p>
</div>
</div>
)
}
@@ -0,0 +1,15 @@
import { Component } from "solid-js"
import { useLanguage } from "@/context/language"
export const SettingsCommands: Component = () => {
const language = useLanguage()
return (
<div class="flex flex-col h-full overflow-y-auto">
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.commands.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.commands.description")}</p>
</div>
</div>
)
}
@@ -0,0 +1,297 @@
import { Component, createMemo, type JSX } from "solid-js"
import { Select } from "@opencode-ai/ui/select"
import { Switch } from "@opencode-ai/ui/switch"
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
import { useLanguage } from "@/context/language"
import { useSettings, monoFontFamily } from "@/context/settings"
import { playSound, SOUND_OPTIONS } from "@/utils/sound"
export const SettingsGeneral: Component = () => {
const theme = useTheme()
const language = useLanguage()
const settings = useSettings()
const themeOptions = createMemo(() =>
Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })),
)
const colorSchemeOptions = createMemo((): { value: ColorScheme; label: string }[] => [
{ value: "system", label: language.t("theme.scheme.system") },
{ value: "light", label: language.t("theme.scheme.light") },
{ value: "dark", label: language.t("theme.scheme.dark") },
])
const languageOptions = createMemo(() =>
language.locales.map((locale) => ({
value: locale,
label: language.label(locale),
})),
)
const fontOptions = [
{ value: "ibm-plex-mono", label: "font.option.ibmPlexMono" },
{ value: "cascadia-code", label: "font.option.cascadiaCode" },
{ value: "fira-code", label: "font.option.firaCode" },
{ value: "hack", label: "font.option.hack" },
{ value: "inconsolata", label: "font.option.inconsolata" },
{ value: "intel-one-mono", label: "font.option.intelOneMono" },
{ value: "jetbrains-mono", label: "font.option.jetbrainsMono" },
{ value: "meslo-lgs", label: "font.option.mesloLgs" },
{ value: "roboto-mono", label: "font.option.robotoMono" },
{ value: "source-code-pro", label: "font.option.sourceCodePro" },
{ value: "ubuntu-mono", label: "font.option.ubuntuMono" },
] as const
const fontOptionsList = [...fontOptions]
const soundOptions = [...SOUND_OPTIONS]
return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar" style={{ padding: "0 40px 40px 40px" }}>
<div
class="sticky top-0 z-10"
style={{
background:
"linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha) calc(100% - 24px), transparent)",
}}
>
<div class="flex flex-col gap-1 pt-6 pb-8">
<h2 class="text-16-medium text-text-strong">{language.t("settings.tab.general")}</h2>
</div>
</div>
<div class="flex flex-col gap-8 w-full">
{/* Appearance Section */}
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.appearance")}</h3>
<div class="bg-surface-raised-base px-4 rounded-lg">
<SettingsRow
title={language.t("settings.general.row.language.title")}
description={language.t("settings.general.row.language.description")}
>
<Select
options={languageOptions()}
current={languageOptions().find((o) => o.value === language.locale())}
value={(o) => o.value}
label={(o) => o.label}
onSelect={(option) => option && language.setLocale(option.value)}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.row.appearance.title")}
description={language.t("settings.general.row.appearance.description")}
>
<Select
options={colorSchemeOptions()}
current={colorSchemeOptions().find((o) => o.value === theme.colorScheme())}
value={(o) => o.value}
label={(o) => o.label}
onSelect={(option) => option && theme.setColorScheme(option.value)}
onHighlight={(option) => {
if (!option) return
theme.previewColorScheme(option.value)
return () => theme.cancelPreview()
}}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.row.theme.title")}
description={
<>
{language.t("settings.general.row.theme.description")}{" "}
<a href="#" class="text-text-interactive-base">
{language.t("common.learnMore")}
</a>
</>
}
>
<Select
options={themeOptions()}
current={themeOptions().find((o) => o.id === theme.themeId())}
value={(o) => o.id}
label={(o) => o.name}
onSelect={(option) => {
if (!option) return
theme.setTheme(option.id)
}}
onHighlight={(option) => {
if (!option) return
theme.previewTheme(option.id)
return () => theme.cancelPreview()
}}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.row.font.title")}
description={language.t("settings.general.row.font.description")}
>
<Select
options={fontOptionsList}
current={fontOptionsList.find((o) => o.value === settings.appearance.font())}
value={(o) => o.value}
label={(o) => language.t(o.label)}
onSelect={(option) => option && settings.appearance.setFont(option.value)}
variant="secondary"
size="small"
triggerVariant="settings"
triggerStyle={{ "font-family": monoFontFamily(settings.appearance.font()), "min-width": "180px" }}
>
{(option) => (
<span style={{ "font-family": monoFontFamily(option?.value) }}>
{option ? language.t(option.label) : ""}
</span>
)}
</Select>
</SettingsRow>
</div>
</div>
{/* System notifications Section */}
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.notifications")}</h3>
<div class="bg-surface-raised-base px-4 rounded-lg">
<SettingsRow
title={language.t("settings.general.notifications.agent.title")}
description={language.t("settings.general.notifications.agent.description")}
>
<Switch
checked={settings.notifications.agent()}
onChange={(checked) => settings.notifications.setAgent(checked)}
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.notifications.permissions.title")}
description={language.t("settings.general.notifications.permissions.description")}
>
<Switch
checked={settings.notifications.permissions()}
onChange={(checked) => settings.notifications.setPermissions(checked)}
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.notifications.errors.title")}
description={language.t("settings.general.notifications.errors.description")}
>
<Switch
checked={settings.notifications.errors()}
onChange={(checked) => settings.notifications.setErrors(checked)}
/>
</SettingsRow>
</div>
</div>
{/* Sound effects Section */}
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.sounds")}</h3>
<div class="bg-surface-raised-base px-4 rounded-lg">
<SettingsRow
title={language.t("settings.general.sounds.agent.title")}
description={language.t("settings.general.sounds.agent.description")}
>
<Select
options={soundOptions}
current={soundOptions.find((o) => o.id === settings.sounds.agent())}
value={(o) => o.id}
label={(o) => language.t(o.label)}
onHighlight={(option) => {
if (!option) return
playSound(option.src)
}}
onSelect={(option) => {
if (!option) return
settings.sounds.setAgent(option.id)
playSound(option.src)
}}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.sounds.permissions.title")}
description={language.t("settings.general.sounds.permissions.description")}
>
<Select
options={soundOptions}
current={soundOptions.find((o) => o.id === settings.sounds.permissions())}
value={(o) => o.id}
label={(o) => language.t(o.label)}
onHighlight={(option) => {
if (!option) return
playSound(option.src)
}}
onSelect={(option) => {
if (!option) return
settings.sounds.setPermissions(option.id)
playSound(option.src)
}}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.sounds.errors.title")}
description={language.t("settings.general.sounds.errors.description")}
>
<Select
options={soundOptions}
current={soundOptions.find((o) => o.id === settings.sounds.errors())}
value={(o) => o.id}
label={(o) => language.t(o.label)}
onHighlight={(option) => {
if (!option) return
playSound(option.src)
}}
onSelect={(option) => {
if (!option) return
settings.sounds.setErrors(option.id)
playSound(option.src)
}}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
</div>
</div>
</div>
</div>
)
}
interface SettingsRowProps {
title: string
description: string | JSX.Element
children: JSX.Element
}
const SettingsRow: Component<SettingsRowProps> = (props) => {
return (
<div class="flex items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
<div class="flex flex-col gap-0.5">
<span class="text-14-medium text-text-strong">{props.title}</span>
<span class="text-12-regular text-text-weak">{props.description}</span>
</div>
<div class="flex-shrink-0">{props.children}</div>
</div>
)
}
@@ -0,0 +1,437 @@
import { Component, For, Show, createMemo, createSignal, onCleanup, onMount } from "solid-js"
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { TextField } from "@opencode-ai/ui/text-field"
import { showToast } from "@opencode-ai/ui/toast"
import fuzzysort from "fuzzysort"
import { formatKeybind, parseKeybind, useCommand } from "@/context/command"
import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
const PALETTE_ID = "command.palette"
const DEFAULT_PALETTE_KEYBIND = "mod+shift+p"
type KeybindGroup = "General" | "Session" | "Navigation" | "Model and agent" | "Terminal" | "Prompt"
type KeybindMeta = {
title: string
group: KeybindGroup
}
const GROUPS: KeybindGroup[] = ["General", "Session", "Navigation", "Model and agent", "Terminal", "Prompt"]
type GroupKey =
| "settings.shortcuts.group.general"
| "settings.shortcuts.group.session"
| "settings.shortcuts.group.navigation"
| "settings.shortcuts.group.modelAndAgent"
| "settings.shortcuts.group.terminal"
| "settings.shortcuts.group.prompt"
const groupKey: Record<KeybindGroup, GroupKey> = {
General: "settings.shortcuts.group.general",
Session: "settings.shortcuts.group.session",
Navigation: "settings.shortcuts.group.navigation",
"Model and agent": "settings.shortcuts.group.modelAndAgent",
Terminal: "settings.shortcuts.group.terminal",
Prompt: "settings.shortcuts.group.prompt",
}
function groupFor(id: string): KeybindGroup {
if (id === PALETTE_ID) return "General"
if (id.startsWith("terminal.")) return "Terminal"
if (id.startsWith("model.") || id.startsWith("agent.") || id.startsWith("mcp.")) return "Model and agent"
if (id.startsWith("file.")) return "Navigation"
if (id.startsWith("prompt.")) return "Prompt"
if (
id.startsWith("session.") ||
id.startsWith("message.") ||
id.startsWith("permissions.") ||
id.startsWith("steps.") ||
id.startsWith("review.")
)
return "Session"
return "General"
}
function isModifier(key: string) {
return key === "Shift" || key === "Control" || key === "Alt" || key === "Meta"
}
function normalizeKey(key: string) {
if (key === ",") return "comma"
if (key === "+") return "plus"
if (key === " ") return "space"
return key.toLowerCase()
}
function recordKeybind(event: KeyboardEvent) {
if (isModifier(event.key)) return
const parts: string[] = []
const mod = IS_MAC ? event.metaKey : event.ctrlKey
if (mod) parts.push("mod")
if (IS_MAC && event.ctrlKey) parts.push("ctrl")
if (!IS_MAC && event.metaKey) parts.push("meta")
if (event.altKey) parts.push("alt")
if (event.shiftKey) parts.push("shift")
const key = normalizeKey(event.key)
if (!key) return
parts.push(key)
return parts.join("+")
}
function signatures(config: string | undefined) {
if (!config) return []
const sigs: string[] = []
for (const kb of parseKeybind(config)) {
const parts: string[] = []
if (kb.ctrl) parts.push("ctrl")
if (kb.alt) parts.push("alt")
if (kb.shift) parts.push("shift")
if (kb.meta) parts.push("meta")
if (kb.key) parts.push(kb.key)
if (parts.length === 0) continue
sigs.push(parts.join("+"))
}
return sigs
}
export const SettingsKeybinds: Component = () => {
const command = useCommand()
const language = useLanguage()
const settings = useSettings()
const [active, setActive] = createSignal<string | null>(null)
const [filter, setFilter] = createSignal("")
const stop = () => {
if (!active()) return
setActive(null)
command.keybinds(true)
}
const start = (id: string) => {
if (active() === id) {
stop()
return
}
if (active()) stop()
setActive(id)
command.keybinds(false)
}
const hasOverrides = createMemo(() => {
const keybinds = settings.current.keybinds as Record<string, string | undefined> | undefined
if (!keybinds) return false
return Object.values(keybinds).some((x) => typeof x === "string")
})
const resetAll = () => {
stop()
settings.keybinds.resetAll()
showToast({
title: language.t("settings.shortcuts.reset.toast.title"),
description: language.t("settings.shortcuts.reset.toast.description"),
})
}
const list = createMemo(() => {
language.locale()
const out = new Map<string, KeybindMeta>()
out.set(PALETTE_ID, { title: language.t("command.palette"), group: "General" })
for (const opt of command.catalog) {
if (opt.id.startsWith("suggested.")) continue
out.set(opt.id, { title: opt.title, group: groupFor(opt.id) })
}
for (const opt of command.options) {
if (opt.id.startsWith("suggested.")) continue
out.set(opt.id, { title: opt.title, group: groupFor(opt.id) })
}
const keybinds = settings.current.keybinds as Record<string, string | undefined> | undefined
if (keybinds) {
for (const [id, value] of Object.entries(keybinds)) {
if (typeof value !== "string") continue
if (out.has(id)) continue
out.set(id, { title: id, group: groupFor(id) })
}
}
return out
})
const title = (id: string) => list().get(id)?.title ?? ""
const grouped = createMemo(() => {
const map = list()
const out = new Map<KeybindGroup, string[]>()
for (const group of GROUPS) out.set(group, [])
for (const [id, item] of map) {
const ids = out.get(item.group)
if (!ids) continue
ids.push(id)
}
for (const group of GROUPS) {
const ids = out.get(group)
if (!ids) continue
ids.sort((a, b) => {
const at = map.get(a)?.title ?? ""
const bt = map.get(b)?.title ?? ""
return at.localeCompare(bt)
})
}
return out
})
const filtered = createMemo(() => {
const query = filter().toLowerCase().trim()
if (!query) return grouped()
const map = list()
const out = new Map<KeybindGroup, string[]>()
for (const group of GROUPS) out.set(group, [])
const items = Array.from(map.entries()).map(([id, meta]) => ({
id,
title: meta.title,
group: meta.group,
keybind: command.keybind(id) || "",
}))
const results = fuzzysort.go(query, items, {
keys: ["title", "keybind"],
threshold: -10000,
})
for (const result of results) {
const item = result.obj
const ids = out.get(item.group)
if (!ids) continue
ids.push(item.id)
}
return out
})
const hasResults = createMemo(() => {
for (const group of GROUPS) {
const ids = filtered().get(group) ?? []
if (ids.length > 0) return true
}
return false
})
const used = createMemo(() => {
const map = new Map<string, { id: string; title: string }[]>()
const add = (key: string, value: { id: string; title: string }) => {
const list = map.get(key)
if (!list) {
map.set(key, [value])
return
}
list.push(value)
}
const palette = settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND
for (const sig of signatures(palette)) {
add(sig, { id: PALETTE_ID, title: title(PALETTE_ID) })
}
const valueFor = (id: string) => {
const custom = settings.keybinds.get(id)
if (typeof custom === "string") return custom
const live = command.options.find((x) => x.id === id)
if (live?.keybind) return live.keybind
const meta = command.catalog.find((x) => x.id === id)
return meta?.keybind
}
for (const id of list().keys()) {
if (id === PALETTE_ID) continue
for (const sig of signatures(valueFor(id))) {
add(sig, { id, title: title(id) })
}
}
return map
})
const setKeybind = (id: string, keybind: string) => {
settings.keybinds.set(id, keybind)
}
onMount(() => {
const handle = (event: KeyboardEvent) => {
const id = active()
if (!id) return
event.preventDefault()
event.stopPropagation()
event.stopImmediatePropagation()
if (event.key === "Escape") {
stop()
return
}
const clear =
(event.key === "Backspace" || event.key === "Delete") &&
!event.ctrlKey &&
!event.metaKey &&
!event.altKey &&
!event.shiftKey
if (clear) {
setKeybind(id, "none")
stop()
return
}
const next = recordKeybind(event)
if (!next) return
const map = used()
const conflicts = new Map<string, string>()
for (const sig of signatures(next)) {
const list = map.get(sig) ?? []
for (const item of list) {
if (item.id === id) continue
conflicts.set(item.id, item.title)
}
}
if (conflicts.size > 0) {
showToast({
title: language.t("settings.shortcuts.conflict.title"),
description: language.t("settings.shortcuts.conflict.description", {
keybind: formatKeybind(next),
titles: [...conflicts.values()].join(", "),
}),
})
return
}
setKeybind(id, next)
stop()
}
document.addEventListener("keydown", handle, true)
onCleanup(() => {
document.removeEventListener("keydown", handle, true)
})
})
onCleanup(() => {
if (active()) command.keybinds(true)
})
return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar" style={{ padding: "0 40px 40px 40px" }}>
<div
class="sticky top-0 z-10"
style={{
background:
"linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha) calc(100% - 24px), transparent)",
}}
>
<div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
<div class="flex items-center justify-between gap-4">
<h2 class="text-16-medium text-text-strong">{language.t("settings.shortcuts.title")}</h2>
<Button size="small" variant="secondary" onClick={resetAll} disabled={!hasOverrides()}>
{language.t("settings.shortcuts.reset.button")}
</Button>
</div>
<div class="flex items-center gap-2 px-3 h-9 rounded-lg bg-surface-base">
<Icon name="magnifying-glass" class="text-icon-weak-base flex-shrink-0" />
<TextField
variant="ghost"
type="text"
value={filter()}
onChange={setFilter}
placeholder={language.t("settings.shortcuts.search.placeholder")}
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
class="flex-1"
/>
<Show when={filter()}>
<IconButton icon="circle-x" variant="ghost" onClick={() => setFilter("")} />
</Show>
</div>
</div>
</div>
<div class="flex flex-col gap-8 max-w-[720px]">
<For each={GROUPS}>
{(group) => (
<Show when={(filtered().get(group) ?? []).length > 0}>
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t(groupKey[group])}</h3>
<div class="bg-surface-raised-base px-4 rounded-lg">
<For each={filtered().get(group) ?? []}>
{(id) => (
<div class="flex items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
<span class="text-14-regular text-text-strong">{title(id)}</span>
<button
type="button"
classList={{
"h-8 px-3 rounded-md text-12-regular": true,
"bg-surface-base text-text-subtle hover:bg-surface-raised-base-hover active:bg-surface-raised-base-active":
active() !== id,
"border border-border-weak-base bg-surface-inset-base text-text-weak": active() === id,
}}
onClick={() => start(id)}
>
<Show
when={active() === id}
fallback={command.keybind(id) || language.t("settings.shortcuts.unassigned")}
>
{language.t("settings.shortcuts.pressKeys")}
</Show>
</button>
</div>
)}
</For>
</div>
</div>
</Show>
)}
</For>
<Show when={filter() && !hasResults()}>
<div class="flex flex-col items-center justify-center py-12 text-center">
<span class="text-14-regular text-text-weak">{language.t("settings.shortcuts.search.empty")}</span>
<Show when={filter()}>
<span class="text-14-regular text-text-strong mt-1">"{filter()}"</span>
</Show>
</div>
</Show>
</div>
</div>
)
}
@@ -0,0 +1,15 @@
import { Component } from "solid-js"
import { useLanguage } from "@/context/language"
export const SettingsMcp: Component = () => {
const language = useLanguage()
return (
<div class="flex flex-col h-full overflow-y-auto">
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.mcp.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.mcp.description")}</p>
</div>
</div>
)
}
@@ -0,0 +1,15 @@
import { Component } from "solid-js"
import { useLanguage } from "@/context/language"
export const SettingsModels: Component = () => {
const language = useLanguage()
return (
<div class="flex flex-col h-full overflow-y-auto">
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.models.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.models.description")}</p>
</div>
</div>
)
}
@@ -0,0 +1,234 @@
import { Select } from "@opencode-ai/ui/select"
import { showToast } from "@opencode-ai/ui/toast"
import { Component, For, createMemo, type JSX } from "solid-js"
import { useGlobalSync } from "@/context/global-sync"
import { useLanguage } from "@/context/language"
type PermissionAction = "allow" | "ask" | "deny"
type PermissionObject = Record<string, PermissionAction>
type PermissionValue = PermissionAction | PermissionObject | string[] | undefined
type PermissionMap = Record<string, PermissionValue>
type PermissionItem = {
id: string
title: string
description: string
}
const ACTIONS = [
{ value: "allow", label: "settings.permissions.action.allow" },
{ value: "ask", label: "settings.permissions.action.ask" },
{ value: "deny", label: "settings.permissions.action.deny" },
] as const
const ITEMS = [
{
id: "read",
title: "settings.permissions.tool.read.title",
description: "settings.permissions.tool.read.description",
},
{
id: "edit",
title: "settings.permissions.tool.edit.title",
description: "settings.permissions.tool.edit.description",
},
{
id: "glob",
title: "settings.permissions.tool.glob.title",
description: "settings.permissions.tool.glob.description",
},
{
id: "grep",
title: "settings.permissions.tool.grep.title",
description: "settings.permissions.tool.grep.description",
},
{
id: "list",
title: "settings.permissions.tool.list.title",
description: "settings.permissions.tool.list.description",
},
{
id: "bash",
title: "settings.permissions.tool.bash.title",
description: "settings.permissions.tool.bash.description",
},
{
id: "task",
title: "settings.permissions.tool.task.title",
description: "settings.permissions.tool.task.description",
},
{
id: "skill",
title: "settings.permissions.tool.skill.title",
description: "settings.permissions.tool.skill.description",
},
{
id: "lsp",
title: "settings.permissions.tool.lsp.title",
description: "settings.permissions.tool.lsp.description",
},
{
id: "todoread",
title: "settings.permissions.tool.todoread.title",
description: "settings.permissions.tool.todoread.description",
},
{
id: "todowrite",
title: "settings.permissions.tool.todowrite.title",
description: "settings.permissions.tool.todowrite.description",
},
{
id: "webfetch",
title: "settings.permissions.tool.webfetch.title",
description: "settings.permissions.tool.webfetch.description",
},
{
id: "websearch",
title: "settings.permissions.tool.websearch.title",
description: "settings.permissions.tool.websearch.description",
},
{
id: "codesearch",
title: "settings.permissions.tool.codesearch.title",
description: "settings.permissions.tool.codesearch.description",
},
{
id: "external_directory",
title: "settings.permissions.tool.external_directory.title",
description: "settings.permissions.tool.external_directory.description",
},
{
id: "doom_loop",
title: "settings.permissions.tool.doom_loop.title",
description: "settings.permissions.tool.doom_loop.description",
},
] as const
const VALID_ACTIONS = new Set<PermissionAction>(["allow", "ask", "deny"])
function toMap(value: unknown): PermissionMap {
if (value && typeof value === "object" && !Array.isArray(value)) return value as PermissionMap
const action = getAction(value)
if (action) return { "*": action }
return {}
}
function getAction(value: unknown): PermissionAction | undefined {
if (typeof value === "string" && VALID_ACTIONS.has(value as PermissionAction)) return value as PermissionAction
return
}
function getRuleDefault(value: unknown): PermissionAction | undefined {
const action = getAction(value)
if (action) return action
if (!value || typeof value !== "object" || Array.isArray(value)) return
return getAction((value as Record<string, unknown>)["*"])
}
export const SettingsPermissions: Component = () => {
const globalSync = useGlobalSync()
const language = useLanguage()
const actions = createMemo(
(): Array<{ value: PermissionAction; label: string }> =>
ACTIONS.map((action) => ({
value: action.value,
label: language.t(action.label),
})),
)
const permission = createMemo(() => {
return toMap(globalSync.data.config.permission)
})
const actionFor = (id: string): PermissionAction => {
const value = permission()[id]
const direct = getRuleDefault(value)
if (direct) return direct
const wildcard = getRuleDefault(permission()["*"])
if (wildcard) return wildcard
return "allow"
}
const setPermission = async (id: string, action: PermissionAction) => {
const before = globalSync.data.config.permission
const map = toMap(before)
const existing = map[id]
const nextValue =
existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing, "*": action } : action
globalSync.set("config", "permission", { ...map, [id]: nextValue })
globalSync.updateConfig({ permission: { [id]: nextValue } }).catch((err: unknown) => {
globalSync.set("config", "permission", before)
const message = err instanceof Error ? err.message : String(err)
showToast({ title: language.t("settings.permissions.toast.updateFailed.title"), description: message })
})
}
return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar">
<div
class="sticky top-0 z-10"
style={{
background:
"linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha) calc(100% - 24px), transparent)",
}}
>
<div class="flex flex-col gap-1 p-8 max-w-[720px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.permissions.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.permissions.description")}</p>
</div>
</div>
<div class="flex flex-col gap-6 p-8 pt-6 max-w-[720px]">
<div class="flex flex-col gap-2">
<h3 class="text-14-medium text-text-strong">{language.t("settings.permissions.section.tools")}</h3>
<div class="border border-border-weak-base rounded-lg overflow-hidden">
<For each={ITEMS}>
{(item) => (
<SettingsRow title={language.t(item.title)} description={language.t(item.description)}>
<Select
options={actions()}
current={actions().find((o) => o.value === actionFor(item.id))}
value={(o) => o.value}
label={(o) => o.label}
onSelect={(option) => option && setPermission(item.id, option.value)}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
)}
</For>
</div>
</div>
</div>
</div>
)
}
interface SettingsRowProps {
title: string
description: string
children: JSX.Element
}
const SettingsRow: Component<SettingsRowProps> = (props) => {
return (
<div class="flex items-center justify-between gap-4 px-4 py-3 border-b border-border-weak-base last:border-none">
<div class="flex flex-col gap-0.5">
<span class="text-14-medium text-text-strong">{props.title}</span>
<span class="text-12-regular text-text-weak">{props.description}</span>
</div>
<div class="flex-shrink-0">{props.children}</div>
</div>
)
}
@@ -0,0 +1,15 @@
import { Component } from "solid-js"
import { useLanguage } from "@/context/language"
export const SettingsProviders: Component = () => {
const language = useLanguage()
return (
<div class="flex flex-col h-full overflow-y-auto">
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.providers.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.providers.description")}</p>
</div>
</div>
)
}
+142 -75
View File
@@ -1,9 +1,10 @@
import { Ghostty, Terminal as Term, FitAddon } from "ghostty-web"
import type { Ghostty, Terminal as Term, FitAddon } from "ghostty-web"
import { ComponentProps, createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js"
import { useSDK } from "@/context/sdk"
import { monoFontFamily, useSettings } from "@/context/settings"
import { SerializeAddon } from "@/addons/serialize"
import { LocalPTY } from "@/context/terminal"
import { resolveThemeVariant, useTheme } from "@opencode-ai/ui/theme"
import { resolveThemeVariant, useTheme, withAlpha, type HexColor } from "@opencode-ai/ui/theme"
export interface TerminalProps extends ComponentProps<"div"> {
pty: LocalPTY
@@ -16,6 +17,7 @@ type TerminalColors = {
background: string
foreground: string
cursor: string
selectionBackground: string
}
const DEFAULT_TERMINAL_COLORS: Record<"light" | "dark", TerminalColors> = {
@@ -23,25 +25,32 @@ const DEFAULT_TERMINAL_COLORS: Record<"light" | "dark", TerminalColors> = {
background: "#fcfcfc",
foreground: "#211e1e",
cursor: "#211e1e",
selectionBackground: withAlpha("#211e1e", 0.2),
},
dark: {
background: "#191515",
foreground: "#d4d4d4",
cursor: "#d4d4d4",
selectionBackground: withAlpha("#d4d4d4", 0.25),
},
}
export const Terminal = (props: TerminalProps) => {
const sdk = useSDK()
const settings = useSettings()
const theme = useTheme()
let container!: HTMLDivElement
const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnectError"])
let ws: WebSocket
let term: Term
let ws: WebSocket | undefined
let term: Term | undefined
let ghostty: Ghostty
let serializeAddon: SerializeAddon
let fitAddon: FitAddon
let handleResize: () => void
let handleTextareaFocus: () => void
let handleTextareaBlur: () => void
let reconnect: number | undefined
let disposed = false
const getTerminalColors = (): TerminalColors => {
const mode = theme.mode()
@@ -51,12 +60,16 @@ export const Terminal = (props: TerminalProps) => {
const variant = mode === "dark" ? currentTheme.dark : currentTheme.light
if (!variant?.seeds) return fallback
const resolved = resolveThemeVariant(variant, mode === "dark")
const text = resolved["text-base"] ?? fallback.foreground
const text = resolved["text-stronger"] ?? fallback.foreground
const background = resolved["background-stronger"] ?? fallback.background
const alpha = mode === "dark" ? 0.25 : 0.2
const base = text.startsWith("#") ? (text as HexColor) : (fallback.foreground as HexColor)
const selectionBackground = withAlpha(base, alpha)
return {
background,
foreground: text,
cursor: text,
selectionBackground,
}
}
@@ -71,27 +84,19 @@ export const Terminal = (props: TerminalProps) => {
setOption("theme", colors)
})
const focusTerminal = () => term?.focus()
const copySelection = () => {
if (!term || !term.hasSelection()) return false
const selection = term.getSelection()
if (!selection) return false
const clipboard = navigator.clipboard
if (clipboard?.writeText) {
clipboard.writeText(selection).catch(() => {})
return true
}
if (!document.body) return false
const textarea = document.createElement("textarea")
textarea.value = selection
textarea.setAttribute("readonly", "")
textarea.style.position = "fixed"
textarea.style.opacity = "0"
document.body.appendChild(textarea)
textarea.select()
const copied = document.execCommand("copy")
document.body.removeChild(textarea)
return copied
createEffect(() => {
const font = monoFontFamily(settings.appearance.font())
if (!term) return
const setOption = (term as unknown as { setOption?: (key: string, value: string) => void }).setOption
if (!setOption) return
setOption("fontFamily", font)
})
const focusTerminal = () => {
const t = term
if (!t) return
t.focus()
setTimeout(() => t.textarea?.focus(), 0)
}
const handlePointerDown = () => {
const activeElement = document.activeElement
@@ -102,62 +107,115 @@ export const Terminal = (props: TerminalProps) => {
}
onMount(async () => {
ghostty = await Ghostty.load()
const mod = await import("ghostty-web")
ghostty = await mod.Ghostty.load()
ws = new WebSocket(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
term = new Term({
const url = new URL(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
if (window.__OPENCODE__?.serverPassword) {
url.username = "opencode"
url.password = window.__OPENCODE__?.serverPassword
}
const socket = new WebSocket(url)
ws = socket
const t = new mod.Terminal({
cursorBlink: true,
cursorStyle: "bar",
fontSize: 14,
fontFamily: "IBM Plex Mono, monospace",
fontFamily: monoFontFamily(settings.appearance.font()),
allowTransparency: true,
theme: terminalColors(),
scrollback: 10_000,
ghostty,
})
term.attachCustomKeyEventHandler((event) => {
const key = event.key.toLowerCase()
if (key === "c") {
const macCopy = event.metaKey && !event.ctrlKey && !event.altKey
const linuxCopy = event.ctrlKey && event.shiftKey && !event.metaKey
if ((macCopy || linuxCopy) && copySelection()) {
event.preventDefault()
return true
}
term = t
const copy = () => {
const selection = t.getSelection()
if (!selection) return false
const body = document.body
if (body) {
const textarea = document.createElement("textarea")
textarea.value = selection
textarea.setAttribute("readonly", "")
textarea.style.position = "fixed"
textarea.style.opacity = "0"
body.appendChild(textarea)
textarea.select()
const copied = document.execCommand("copy")
body.removeChild(textarea)
if (copied) return true
}
// allow for ctrl-` to toggle terminal in parent
if (event.ctrlKey && key === "`") {
event.preventDefault()
const clipboard = navigator.clipboard
if (clipboard?.writeText) {
clipboard.writeText(selection).catch(() => {})
return true
}
return false
}
t.attachCustomKeyEventHandler((event) => {
const key = event.key.toLowerCase()
if (event.ctrlKey && event.shiftKey && !event.metaKey && key === "c") {
copy()
return true
}
if (event.metaKey && !event.ctrlKey && !event.altKey && key === "c") {
if (!t.hasSelection()) return true
copy()
return true
}
// allow for ctrl-` to toggle terminal in parent
if (event.ctrlKey && key === "`") {
return true
}
return false
})
fitAddon = new FitAddon()
fitAddon = new mod.FitAddon()
serializeAddon = new SerializeAddon()
term.loadAddon(serializeAddon)
term.loadAddon(fitAddon)
t.loadAddon(serializeAddon)
t.loadAddon(fitAddon)
term.open(container)
t.open(container)
container.addEventListener("pointerdown", handlePointerDown)
handleTextareaFocus = () => {
t.options.cursorBlink = true
}
handleTextareaBlur = () => {
t.options.cursorBlink = false
}
t.textarea?.addEventListener("focus", handleTextareaFocus)
t.textarea?.addEventListener("blur", handleTextareaBlur)
focusTerminal()
if (local.pty.buffer) {
if (local.pty.rows && local.pty.cols) {
term.resize(local.pty.cols, local.pty.rows)
t.resize(local.pty.cols, local.pty.rows)
}
term.reset()
term.write(local.pty.buffer)
if (local.pty.scrollY) {
term.scrollToLine(local.pty.scrollY)
}
fitAddon.fit()
t.write(local.pty.buffer, () => {
if (local.pty.scrollY) {
t.scrollToLine(local.pty.scrollY)
}
fitAddon.fit()
})
}
fitAddon.observeResize()
handleResize = () => fitAddon.fit()
window.addEventListener("resize", handleResize)
term.onResize(async (size) => {
if (ws && ws.readyState === WebSocket.OPEN) {
t.onResize(async (size) => {
if (socket.readyState === WebSocket.OPEN) {
await sdk.client.pty
.update({
ptyID: local.pty.id,
@@ -169,40 +227,43 @@ export const Terminal = (props: TerminalProps) => {
.catch(() => {})
}
})
term.onData((data) => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(data)
t.onData((data) => {
if (socket.readyState === WebSocket.OPEN) {
socket.send(data)
}
})
term.onKey((key) => {
t.onKey((key) => {
if (key.key == "Enter") {
props.onSubmit?.()
}
})
// term.onScroll((ydisp) => {
// t.onScroll((ydisp) => {
// console.log("Scroll position:", ydisp)
// })
ws.addEventListener("open", () => {
console.log("WebSocket connected")
socket.addEventListener("open", () => {
sdk.client.pty
.update({
ptyID: local.pty.id,
size: {
cols: term.cols,
rows: term.rows,
cols: t.cols,
rows: t.rows,
},
})
.catch(() => {})
})
ws.addEventListener("message", (event) => {
term.write(event.data)
socket.addEventListener("message", (event) => {
t.write(event.data)
})
ws.addEventListener("error", (error) => {
socket.addEventListener("error", (error) => {
console.error("WebSocket error:", error)
props.onConnectError?.(error)
local.onConnectError?.(error)
})
ws.addEventListener("close", () => {
console.log("WebSocket disconnected")
socket.addEventListener("close", (event) => {
// Normal closure (code 1000) means PTY process exited - server event handles cleanup
// For other codes (network issues, server restart), trigger error handler
if (event.code !== 1000) {
local.onConnectError?.(new Error(`WebSocket closed abnormally: ${event.code}`))
}
})
})
@@ -211,18 +272,23 @@ export const Terminal = (props: TerminalProps) => {
window.removeEventListener("resize", handleResize)
}
container.removeEventListener("pointerdown", handlePointerDown)
if (serializeAddon && props.onCleanup) {
term?.textarea?.removeEventListener("focus", handleTextareaFocus)
term?.textarea?.removeEventListener("blur", handleTextareaBlur)
const t = term
if (serializeAddon && props.onCleanup && t) {
const buffer = serializeAddon.serialize()
props.onCleanup({
...local.pty,
buffer,
rows: term.rows,
cols: term.cols,
scrollY: term.getViewportY(),
rows: t.rows,
cols: t.cols,
scrollY: t.getViewportY(),
})
}
ws?.close()
term?.dispose()
t?.dispose()
})
return (
@@ -230,6 +296,7 @@ export const Terminal = (props: TerminalProps) => {
ref={container}
data-component="terminal"
data-prevent-autofocus
tabIndex={-1}
style={{ "background-color": terminalColors().background }}
classList={{
...(local.classList ?? {}),

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