Compare commits

..

863 Commits

Author SHA1 Message Date
Aiden Cline
3b6a21f173 wip 2025-12-28 18:14:31 -06:00
Aiden Cline
f7bdf0b74f wip 2025-12-28 18:14:31 -06:00
Aiden Cline
7539cbeea2 wip 2025-12-28 18:14:31 -06:00
Github Action
b683567c2c Update Nix flake.lock and hashes 2025-12-28 22:30:51 +00:00
Aiden Cline
6965805eeb adjust deps 2025-12-28 16:29:35 -06:00
Aiden Cline
7a511a1a25 wip 2025-12-28 01:20:18 -06:00
Aiden Cline
f3437d9a49 wip 2025-12-28 01:09:26 -06:00
Aiden Cline
ccf1826400 wip 2025-12-26 23:09:58 -06:00
Aiden Cline
f6b24d81d4 wip 2025-12-26 22:37:31 -06:00
Aiden Cline
3127f4f30e chore: kill some unused tools 2025-12-26 14:35:51 -06:00
Aiden Cline
d7a3a13034 test: add more tests to make sure that cwd is locked for read tool 2025-12-26 14:35:50 -06:00
Aiden Cline
a92af6b9ef tweak: bash tool description to avoid unnecessary 'cd &&' usage 2025-12-26 14:35:50 -06:00
Matt Silverlock
21e4fe3718 github: support issues and workflow_dispatch events (#6157) 2025-12-26 14:35:50 -06:00
Aiden Cline
fa64085aba ci: re-enable sync zed 2025-12-26 14:35:50 -06:00
Aiden Cline
06307dc8c3 ci: fix token for gh 2025-12-26 14:35:50 -06:00
Aiden Cline
1bf950179a ci: fix var 2025-12-26 14:35:50 -06:00
Aiden Cline
b112c8132c ci: update zed extension sync 2025-12-26 14:35:50 -06:00
Github Action
b279da3684 Update Nix flake.lock and hashes 2025-12-26 14:35:50 -06:00
GitHub Action
dcfeeb6919 chore: generate 2025-12-26 14:35:50 -06:00
Aiden Cline
0762a794ab Revert "feat(core): optional mdns service (#6192)"
This reverts commit 26e7043718.
2025-12-26 14:35:49 -06:00
Aiden Cline
f28628b744 chore: rm comments 2025-12-26 14:35:49 -06:00
Aiden Cline
336d165fd2 fix: adjust upgrade command to use gh releases page if not npm/bun/pnpm install method 2025-12-26 14:35:49 -06:00
Rohan Godha
af89e7f117 fix: opencode web baseURL error (#6181) 2025-12-26 14:35:49 -06:00
Ariane Emory
57ad8211d5 chore: kill the dead Polaris Alpha code (#6193) 2025-12-26 14:35:49 -06:00
ja
3fc23c3e8b feat: add shfmt formatter for shell scripts (#6204) 2025-12-26 14:35:49 -06:00
Aiden Cline
d6899cf9b0 tweak: make install script handle 404s better 2025-12-26 14:35:49 -06:00
Github Action
58c643f205 Update Nix flake.lock and hashes 2025-12-26 14:35:48 -06:00
GitHub Action
dcc5b7d6db chore: generate 2025-12-26 14:35:48 -06:00
Ayush Walekar
1c35e59e4f chore: createOpencodeServer expose logLevel (#6202) 2025-12-26 14:35:48 -06:00
Roberto Carvajal
c1d21f9b11 fix(dep): Update package.json - fix perplexity provider version (#6199)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-12-26 14:35:48 -06:00
Daniel Polito
822c20b320 Desktop: MCP UI (#6162)
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
2025-12-26 14:35:47 -06:00
GitHub Action
fbe676d1da chore: generate 2025-12-26 14:35:47 -06:00
Adam
3f3c0f370b feat(core): optional mdns service (#6192)
Co-authored-by: Github Action <action@github.com>
2025-12-26 14:35:47 -06:00
GitHub Action
3e4b8e2bf5 chore: generate 2025-12-26 14:35:47 -06:00
Didier Durand
ccd182f522 doc: fix typos in various files (#6196) 2025-12-26 14:35:47 -06:00
GitHub Action
bbf59fcb12 ignore: update download stats 2025-12-26 2025-12-26 14:35:46 -06:00
Aiden Cline
b40852abe3 tweak: update transform for gemini models so that topP and topK match gemini-cli values 2025-12-26 14:35:46 -06:00
GitHub Action
b6d8779c22 chore: generate 2025-12-26 14:35:46 -06:00
JackNorris
b479893c9a fix: only show diagnostics block when errors exist (#6175) 2025-12-26 14:35:46 -06:00
opencode
fbc9120dc3 release: v1.0.203 2025-12-26 14:35:45 -06:00
Dax Raad
95074ab541 prompt update to prevent searching via bash tool 2025-12-26 14:35:45 -06:00
Donghyun Shin
e98a2a5e47 fix(lsp): make JDTLS use the correct config directory on Windows (#6121) 2025-12-26 14:35:45 -06:00
GitHub Action
e60ef0286d chore: generate 2025-12-26 14:35:45 -06:00
Marco
a64e24a991 feat: haskell lsp support (#6141) 2025-12-26 14:35:45 -06:00
opencode
01ca39791d release: v1.0.202 2025-12-26 14:35:44 -06:00
Adam
13b6a47e77 chore: brain icon 2025-12-26 14:35:44 -06:00
Adam
6fab9d2ac3 fix(desktop): user message display 2025-12-26 14:35:44 -06:00
Adam
2381a83fc7 fix(desktop): padding 2025-12-26 14:35:44 -06:00
Adam
4ed0dc1dcc fix(desktop): move session context to top-right 2025-12-26 14:35:44 -06:00
Adam
01dd70eab1 fix(desktop): missing keybinds in tooltips 2025-12-26 14:35:44 -06:00
Adam
3c5529bf19 fix(desktop): markdown rendering perf 2025-12-26 14:35:44 -06:00
GitHub Action
9a2f955910 chore: generate 2025-12-26 14:35:44 -06:00
Adam
fefb9e5e0c fix(desktop): can't collapse project with active session 2025-12-26 14:35:44 -06:00
Adam
61bad1fd90 chore(ui): radio group primitive 2025-12-26 14:35:43 -06:00
Adam
e42d586460 feat(desktop): better indicator that session is busy 2025-12-26 14:35:43 -06:00
opencode
7c4d1e9443 release: v1.0.201 2025-12-26 14:35:43 -06:00
Adam
b5e5bf549e fix(desktop): so many prompt input fixes, merry christmas 2025-12-26 14:35:43 -06:00
GitHub Action
f91839889f ignore: update download stats 2025-12-25 2025-12-26 14:35:43 -06:00
GitHub Action
512b3088f7 chore: generate 2025-12-26 14:35:42 -06:00
Dax Raad
0b5df9c4c4 remove list tool 2025-12-26 14:35:42 -06:00
opencode
371a1fd115 release: v1.0.200 2025-12-26 14:35:42 -06:00
Adam
87d904422b fix(desktop): scroll jank in session turn and review 2025-12-26 14:35:42 -06:00
GitHub Action
8d85f975d8 chore: generate 2025-12-26 14:35:41 -06:00
Adam
37f98de6e6 fix(desktop): override agent model 2025-12-26 14:35:41 -06:00
Adam
1285f67b39 fix(desktop): reconcile session diff updates 2025-12-26 14:35:41 -06:00
opencode
4c44a09991 release: v1.0.199 2025-12-26 14:35:41 -06:00
Adam
7466c08277 chore: toast on file load error 2025-12-26 14:35:40 -06:00
Adam
d2da59f844 chore: cleanup dead code 2025-12-26 14:35:40 -06:00
Adam
52d81bdfd2 chore: show version on error page 2025-12-26 14:35:40 -06:00
Adam
b3ab52fe79 fix(desktop): show server connection failure 2025-12-26 14:35:40 -06:00
Ahmed Mansour
b39cf15833 fix: correct Content-Type headers for static assets on app.opencode.ai (#6113) 2025-12-26 14:35:40 -06:00
Connor Adams
cc39dbedf2 docs: update skills to use canonical ~/.config/opencode location (#6132) 2025-12-26 14:35:40 -06:00
Robb Tolliver
6242baea0a docs: Corrected the number of built-in subagents in documentation (#6133) 2025-12-26 14:35:40 -06:00
GitHub Action
b43a91db78 chore: generate 2025-12-26 14:35:40 -06:00
Dax Raad
19c1ed11b8 tui: disable tips display in home route 2025-12-26 14:35:40 -06:00
Dax Raad
b4d25efbb9 CI 2025-12-26 14:35:39 -06:00
opencode
fd56d8da8d release: v1.0.198 2025-12-26 14:35:39 -06:00
Dax Raad
8a1c3702a5 ci 2025-12-26 14:35:39 -06:00
GitHub Action
a0621840d4 chore: generate 2025-12-26 14:35:38 -06:00
Dax Raad
073fba09a4 ci 2025-12-26 14:35:38 -06:00
opencode
49fa4fce1c release: v1.0.197 2025-12-26 14:35:38 -06:00
Dax Raad
b59a021c94 Revert "feat: better styling for small screens (short and/or not wide) (#5968)"
This reverts commit ac371d2987.
2025-12-26 14:35:38 -06:00
GitHub Action
571a6ec6fc chore: generate 2025-12-26 14:35:37 -06:00
Patrick Schiel
7b89bb2a3b docs: add infos about server debugging (#6085) 2025-12-26 14:35:37 -06:00
opencode
f6a622d2fd release: v1.0.196 2025-12-26 14:35:37 -06:00
Adam
b47c9a4f9e fix(desktop): last text part streaming 2025-12-26 14:35:37 -06:00
Adam
807d61cb94 fix(desktop): render perf 2025-12-26 14:35:37 -06:00
Jay V
32cf23ffef docs: edits 2025-12-26 14:35:36 -06:00
Jay V
f2ceb55514 docs: add comprehensive CLI command documentation for agent, mcp, session, stats, and web commands 2025-12-26 14:35:36 -06:00
Jay V
cdc9c007bd docs: make MCP server documentation more scannable and add Sentry example 2025-12-26 14:35:36 -06:00
Adam
5de8dd131b chore: cleanup 2025-12-26 14:35:36 -06:00
Adam
a663ab81e5 fix(desktop): summary flicker 2025-12-26 14:35:36 -06:00
Adam
4154067dfd feat(desktop): show read tool args 2025-12-26 14:35:35 -06:00
GitHub Action
4dcd196135 chore: generate 2025-12-26 14:35:35 -06:00
Adam
7fe4e76666 fix(desktop): better session navigation, hide child sessions 2025-12-26 14:35:35 -06:00
opencode
c8b69d2ad0 release: v1.0.195 2025-12-26 14:35:35 -06:00
Adam
754d332692 chore: cleanup 2025-12-26 14:35:34 -06:00
GitHub Action
a2cd45ada1 chore: generate 2025-12-26 14:35:34 -06:00
Adam
a806023bdc refactor(ui): rewrite createAutoScroll with robust event tracking to fix sticky behavior 2025-12-26 14:35:33 -06:00
opencode
74a93f871f release: v1.0.194 2025-12-26 14:35:33 -06:00
Adam
59f1b41f73 chore: cleanup 2025-12-26 14:35:32 -06:00
Adam
d06aae5544 fix(desktop): session sort when multiple active 2025-12-26 14:35:32 -06:00
Adam
4b236451e9 fix(share): page title should be session title 2025-12-26 14:35:32 -06:00
GitHub Action
0ff29879de ignore: update download stats 2025-12-24 2025-12-26 14:35:32 -06:00
Adam
2ade0d8f3e fix(desktop): exclude deprecated models 2025-12-26 14:35:31 -06:00
Adam
78046d06e1 fix(desktop): auto-scroll 2025-12-26 14:35:31 -06:00
Adam
270505b0be fix: don't disable text selection 2025-12-26 14:35:31 -06:00
Github Action
c1cea004f4 Update Nix flake.lock and hashes 2025-12-26 14:35:31 -06:00
Adam
6cd9e5d3ac deps: update marked and marked-shiki 2025-12-26 14:35:30 -06:00
Adam
df04dfc957 fix(desktop): hang on backtracing-prone regex 2025-12-26 14:35:30 -06:00
Adam
fa53ab0cce fix(desktop): conditionally show review pane toggle 2025-12-26 14:35:30 -06:00
Ryan Vogel
f22dd0646e fix: remove SVG favicon to improve SEO (#5755) 2025-12-26 14:35:30 -06:00
Aiden Cline
73e5315077 docs: tweak lsp.mdx 2025-12-26 14:35:30 -06:00
opencode-agent[bot]
7ea4a63257 docs: experimental LSP tool (#5943)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-12-26 14:35:30 -06:00
opencode-agent[bot]
e94499d178 docs: skill tool/perm + parent keybind (#6001)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:35:29 -06:00
xiantang
05e8dfb8a2 docs: add Neovim to the list of editors (#6081) 2025-12-26 14:35:29 -06:00
Aiden Cline
e8ca1d1f7d tweak: retry err 2025-12-26 14:35:29 -06:00
GitHub Action
b8c2f9db59 chore: generate 2025-12-26 14:35:29 -06:00
Frank
245a63e3cf zen: sync 2025-12-26 14:35:29 -06:00
Abdelkader Boudih
0877ba302d feat(mcp): handle tools/list_changed notifications (#5913)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-12-26 14:35:29 -06:00
Aiden Cline
3f2aa25a94 test: add test for retry 2025-12-26 14:35:28 -06:00
Aiden Cline
fe91899428 chore: regen sdk 2025-12-26 14:35:28 -06:00
GitHub Action
e8bce0aaf2 chore: generate 2025-12-26 14:35:28 -06:00
Aiden Cline
8767cca3ea make 'The socket connection was closed unexpectedly' errors retryable 2025-12-26 14:35:28 -06:00
Rohan Mukherjee
6ef69dd905 chore: update AGENTS.md to ~150 lines (#5955) 2025-12-26 14:35:28 -06:00
David Hill
24f9ce30fc style: update current todo style (#6077) 2025-12-26 14:35:27 -06:00
rari404
523c47d285 feat(tui): console copy-to-clipboard via opentui (#5658)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:35:27 -06:00
OpeOginni
466ed745ab fix: resize textarea when pasting prompt less than 150 chars (#6070) 2025-12-26 14:35:27 -06:00
Matt Silverlock
1f2a4537bc providers: add Cloudflare AI Gateway (#5174) 2025-12-26 14:35:27 -06:00
Frank
0e50b4adea ci: adam is not a full stack engineer 2025-12-26 14:35:27 -06:00
Adam
e7cf8b0813 fix: remove desktop dup 2025-12-26 14:35:27 -06:00
Viktor Nagy
459db5c655 Update gitlab.mdx to use the 2.x component version (#6062) 2025-12-26 14:35:27 -06:00
Github Action
0e6be83872 Update Nix flake.lock and hashes 2025-12-26 14:35:26 -06:00
Aiden Cline
3612e09e95 Revert "Add animated braille spinner to terminal title when agent is running (#5984)"
This reverts commit 59b87f60f7.
2025-12-26 14:35:26 -06:00
Aiden Cline
67e74aef10 tweak: update import & pr commands to use new share link ur 2025-12-26 14:35:26 -06:00
GitHub Action
3f9ee3330f chore: generate 2025-12-26 14:35:26 -06:00
Jon Redeker
180e1f35ef Add opencode-shell-strategy plugin to ecosystem (#5995) 2025-12-26 14:35:26 -06:00
ja
e2e36adab7 feat(install): add standard CLI flags (--help, --version, --no-modify-path) (#5885) 2025-12-26 14:35:25 -06:00
David Hill
3b4519911e Add animated braille spinner to terminal title when agent is running (#5984)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
Co-authored-by: Github Action <action@github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:35:25 -06:00
GitHub Action
74cd5456b9 chore: generate 2025-12-26 14:35:25 -06:00
David Hill
6417a72f0b "Did you know?" start screen tips (#5982)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-12-26 14:35:25 -06:00
Daniel Gray
dfdde091f5 fix: favorites and recents stay visible when filtering models (#6053) 2025-12-26 14:35:25 -06:00
Matt Silverlock
72cb02f4d5 docs: add MCP OAuth debugging section (#6047) 2025-12-26 14:35:24 -06:00
Aiden Cline
7692af47c5 ci: update zed sync script 2025-12-26 14:35:24 -06:00
ja
441f5ba911 fix(tui): prevent keybinds from executing when dialog is open (#6017) 2025-12-26 14:35:24 -06:00
Frank
ad93850696 ci: fix 2025-12-26 14:35:24 -06:00
GitHub Action
c19a0c5d09 chore: generate 2025-12-26 14:35:24 -06:00
Rhys Sullivan
5d0fd6e7b9 [feat]: prompt stashing (#6021) 2025-12-26 14:35:24 -06:00
GitHub Action
7dc18c35c2 chore: generate 2025-12-26 14:35:24 -06:00
Daniel Polito
9c2d6c7f6a Fix Github Pull Request Event (#6037) 2025-12-26 14:35:23 -06:00
opencode
aafa717d4c release: v1.0.193 2025-12-26 14:35:23 -06:00
GitHub Action
327dd225a8 chore: generate 2025-12-26 14:35:23 -06:00
Sebastian Herrlinger
eb5c544e45 indent wrapped todo items properly 2025-12-26 14:35:23 -06:00
opencode
6d1a9227b9 release: v1.0.192 2025-12-26 14:35:22 -06:00
Frank
a73a36e533 zen: glm 4.7 2025-12-26 14:35:22 -06:00
Frank
2d6c3a886c ci: fix 2025-12-26 14:35:22 -06:00
Frank
b926c4f75a ci: fix 2025-12-26 14:35:22 -06:00
GitHub Action
e2b65f6ebd ignore: update download stats 2025-12-23 2025-12-26 14:35:22 -06:00
Sebastian Herrlinger
3341f3ae42 no intermediate autocomplete result to avoid flickering 2025-12-26 14:35:22 -06:00
GitHub Action
45a147858d chore: generate 2025-12-26 14:35:22 -06:00
Brendan Allan
38a67c2e5e desktop: kill_sidecar before update install on windows 2025-12-26 14:35:21 -06:00
Github Action
d5fd078a42 Update Nix flake.lock and hashes 2025-12-26 14:35:21 -06:00
GitHub Action
5ce08ba95a chore: generate 2025-12-26 14:35:21 -06:00
Adam
80333e13e1 deps: diffs, shiki updates 2025-12-26 14:35:20 -06:00
opencode
4a6facee03 release: v1.0.191 2025-12-26 14:35:20 -06:00
GitHub Action
0cf94b991a chore: generate 2025-12-26 14:35:20 -06:00
Brendan Allan
4c4bbc89e1 console: add AppImage download link 2025-12-26 14:35:19 -06:00
GitHub Action
dd4b348154 chore: generate 2025-12-26 14:35:19 -06:00
Matt Silverlock
9ffa3ab24e improve mcp CLI + ability to debug MCP oauth (#5980) 2025-12-26 14:35:19 -06:00
Aiden Cline
d1581fbcf2 ci: docs sync 2025-12-26 14:35:19 -06:00
opencode-agent[bot]
0c4b36a433 docs: new /global/health API (#6006)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2025-12-26 14:35:19 -06:00
lif
139eb94bba fix: handle Windows CRLF line endings in grep tool (#5948)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-26 14:35:18 -06:00
Joel Hooks
ce8ea03bd4 feat(plugin): allow compaction hook to replace prompt entirely (#5907) 2025-12-26 14:35:18 -06:00
Brendan Allan
0ccf6a8e2e ci: rename tauri -> desktop 2025-12-26 14:35:17 -06:00
Github Action
cc35060491 Update Nix flake.lock and hashes 2025-12-26 14:35:17 -06:00
Adam
11878623bb chore: rename packages/tauri -> packages/desktop 2025-12-26 14:35:17 -06:00
Github Action
ed59558070 Update Nix flake.lock and hashes 2025-12-26 14:35:16 -06:00
Adam
992327a05d chore: rename packages/desktop -> packages/app 2025-12-26 14:35:15 -06:00
GitHub Action
4ccd407259 chore: generate 2025-12-26 14:35:15 -06:00
Adam
3fef2d3b43 fix(desktop): better error messages on connection failure 2025-12-26 14:35:15 -06:00
Mohammad Alhashemi
30e6af1b2f feat(skill): add per-agent filtering to skill tool description (#6000) 2025-12-26 14:35:15 -06:00
Frank
1d71f33b71 zen: glm 4.7 2025-12-26 14:35:14 -06:00
Aiden Cline
e96fdcf042 feat: better styling for small screens (short and/or not wide) (#5968) 2025-12-26 14:35:14 -06:00
GitHub Action
2d22216826 chore: generate 2025-12-26 14:35:14 -06:00
Dax Raad
c81633aa3c tui: change task tool container to block layout for better subagent session display 2025-12-26 14:35:13 -06:00
opencode
f8563e901b release: v1.0.190 2025-12-26 14:35:13 -06:00
GitHub Action
6f4ddd15c9 chore: generate 2025-12-26 14:35:13 -06:00
Mohammad Alhashemi
d3eb85e330 feat: add native skill tool with permission system (#5930)
Co-authored-by: Dax Raad <d@ironbay.co>
2025-12-26 14:35:12 -06:00
opencode
86148821eb release: v1.0.189 2025-12-26 14:35:12 -06:00
GitHub Action
15c6052711 chore: generate 2025-12-26 14:35:11 -06:00
Jay V
b0c88929ed docs: edit gitlab 2025-12-26 14:35:11 -06:00
opencode
58a9cb901e release: v1.0.188 2025-12-26 14:35:11 -06:00
Github Action
997f84a5bd Update Nix flake.lock and hashes 2025-12-26 14:35:10 -06:00
Frank
4a347586d0 remove sharp 2025-12-26 14:35:10 -06:00
Josh Thomas
10745d5bef fix(tui): resize textarea if text inserted via appendPrompt TUI API (#5983) 2025-12-26 14:35:09 -06:00
Frank
35e6fc42b0 zen: add glm 4.7 2025-12-26 14:35:09 -06:00
Aiden Cline
523ed96df8 ignore: agents.md 2025-12-26 14:35:09 -06:00
Adam
834f144c52 feat(desktop): review pane toggle 2025-12-26 14:35:09 -06:00
Viktor Nagy
d5bf844e2c Add gitlab-opencode to GitLab docs
The current GitLab page describes OpenCode integration through GitLab Duo.

GitLab Duo is a paying functionality and is limited to workflows supported by GitLab.

GitLab-OpenCode is a community project that offers more flexiblity, better customization and easier setup to use OpenCode in GitLab. On the downside, it does not have the level of integration into GitLab as Duo does.
2025-12-26 14:35:09 -06:00
Frank
64a8852637 support glm 4.7 2025-12-26 14:35:09 -06:00
GitHub Action
73578d69cb chore: generate 2025-12-26 14:35:08 -06:00
Jay V
f4bd11a43d ignore: update GitHub stars to 41K and project stats to reflect current growth 2025-12-26 14:35:08 -06:00
opencode
82826c0dde release: v1.0.187 2025-12-26 14:35:07 -06:00
Aiden Cline
cc4a1e8849 ci: add failure case for changelog 2025-12-26 14:35:07 -06:00
Aiden Cline
de20383402 test: rm claude skills test 2025-12-26 14:35:07 -06:00
Aiden Cline
baee918ed0 fix: disable claude skill loading for now 2025-12-26 14:35:07 -06:00
Shpetim
67db1ef771 fix: stop auto execute on sendText vscode extension (#5994)
Co-authored-by: Shpetim <shpetim.alimi@ndbit.net>
2025-12-26 14:35:07 -06:00
Jon Redeker
1ac00cebea docs: add opencode-morph-fast-apply plugin to ecosystem (#5992) 2025-12-26 14:35:07 -06:00
Blake North
2fd23715f4 fix(providers.opencode): check config for api key in addition to auth (#5906) 2025-12-26 14:35:07 -06:00
Github Action
a9ae1ef2c1 Update Nix flake.lock and hashes 2025-12-26 14:35:06 -06:00
Aiden Cline
141d24c28c fix: bundle more providers to fix breaking ai sdk issue 2025-12-26 14:35:06 -06:00
Rohan Godha
f30992001f feat(tui): go to parent keybind for subagents (#5762) 2025-12-26 14:35:06 -06:00
GitHub Action
e79b9ad111 chore: generate 2025-12-26 14:35:06 -06:00
wienans
72a9e23926 Add OpenChamber to ecosystem documentation (#5978) 2025-12-26 14:35:05 -06:00
ja
21d81dcf4e feat(lsp): add Tinymist LSP support for Typst (#5933) 2025-12-26 14:35:05 -06:00
Github Action
811c8aac5e Update Nix flake.lock and hashes 2025-12-26 14:35:05 -06:00
Sebastian Herrlinger
2cf69ec114 upgrade opentui to v0.1.63, enabling kitty alternate keys by default 2025-12-26 14:35:05 -06:00
Tim Kleinschmidt
439af1740a support clojure projects with built-in lsp (#5975) 2025-12-26 14:35:04 -06:00
Shpetim
cf3d91e15b [FEATURE]: Show context usage in OpenCode Desktop Context usage (#5979) 2025-12-26 14:35:04 -06:00
Adam
344cdbb36b fix(prompt): better summary prompt 2025-12-26 14:35:04 -06:00
Daniel Polito
8ca4fdafd9 docs: Github Auto Pull Request Docs (#5974) 2025-12-26 14:35:04 -06:00
Adam
2fb212071b chore(desktop): auto scroll utility 2025-12-26 14:35:04 -06:00
GitHub Action
61be2c7f83 chore: generate 2025-12-26 14:35:04 -06:00
Will Marella
e81c496bb6 Add keybindable commands to navigate between user messages (#5078)
Co-authored-by: Will@Cambridge <willcambridge@MacBook-Pro-59.local>
Co-authored-by: Will@Cambridge <willcambridge@macbookpro.mynetworksettings.com>
2025-12-26 14:35:03 -06:00
Aiden Cline
c9a42f760f ci: tweak docs prompt 2025-12-26 14:35:03 -06:00
Daniel Polito
b6089e4371 GitHub pull request event (#5335) 2025-12-26 14:35:03 -06:00
Adam
f8b0ee9637 fix(share): expanded state and responsiveness 2025-12-26 14:35:03 -06:00
Aiden Cline
0b7b6d105e ci: limit to opencode repo 2025-12-26 14:35:03 -06:00
Lekë Dobruna
59cc15ecde fix: duplicate words in dialog options (#5944) 2025-12-26 14:35:03 -06:00
Adam
9236478182 fix(desktop): diff readability (colors) 2025-12-26 14:35:03 -06:00
Dax Raad
fa9edbe9d2 fix url for web 2025-12-26 14:35:02 -06:00
GitHub Action
99e04ad2ed chore: generate 2025-12-26 14:35:02 -06:00
Buck Evan
e70ae665d0 fix(read): narrow .env file blocking to not block .envrc (#5654)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-26 14:35:02 -06:00
opencode
ac03ab1e10 release: v1.0.186 2025-12-26 14:35:02 -06:00
Adam
cc572343be Revert "server: ensure frontend has correct port for PTY websocket connections (#5898)"
This reverts commit a05915ddc8.
2025-12-26 14:35:01 -06:00
Adam
b0c860a17b Revert "fix: server"
This reverts commit dbaac79039.
2025-12-26 14:35:01 -06:00
GitHub Action
bfcbdaceb7 ignore: update download stats 2025-12-22 2025-12-26 14:35:01 -06:00
Adam
73e8caa5e9 fix: server 2025-12-26 14:35:01 -06:00
Ashutosh Kumar
3c6c7c5278 server: ensure frontend has correct port for PTY websocket connections (#5898) 2025-12-26 14:35:00 -06:00
Adam
86d34aee8e Revert "fix: use current page port instead of hardcoded 4096 (#5949)"
This reverts commit d04a72a4ad.
2025-12-26 14:35:00 -06:00
Adam
dcee57f572 fix(desktop): cleanup auto scroll 2025-12-26 14:35:00 -06:00
Adam
249431d84a feat(desktop): mobile responsiveness 2025-12-26 14:34:59 -06:00
Adam
806c21c535 fix(desktop): filter child sessions from header 2025-12-26 14:34:59 -06:00
Adam
309e594e41 feat(desktop): better task tool rendering 2025-12-26 14:34:58 -06:00
lif
355323f180 fix: use current page port instead of hardcoded 4096 (#5949)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-26 14:34:58 -06:00
Aaron Iker
9f4c5d6736 feat: polish dialog & list styles for the desktop app, add fixed logos from models.dev (#5925) 2025-12-26 14:34:58 -06:00
Brendan Allan
24ed94bc1f ci: fix tauri build args 2025-12-26 14:34:58 -06:00
Brendan Allan
599da3ee8f ci: verbose build and re-enable appimage 2025-12-26 14:34:57 -06:00
GitHub Action
b1cd7d617b chore: generate 2025-12-26 14:34:57 -06:00
Brendan Allan
ddab63be61 ci: run prepare step for tauri build 2025-12-26 14:34:57 -06:00
Brendan Allan
18ff962f6e ci: try downloading artifact in desktop prepare 2025-12-26 14:34:57 -06:00
NN708
5a84872777 feat(desktop): arm64 build for linux (#5935) 2025-12-26 14:34:57 -06:00
Brendan Allan
1282f3457d ci: replace with just upload-artifact whole dir 2025-12-26 14:34:57 -06:00
Brendan Allan
2ea105735d ci: import bun shell 2025-12-26 14:34:56 -06:00
Brendan Allan
2e7086e321 try uploading artifacts in workflow 2025-12-26 14:34:56 -06:00
Brendan Allan
64a50bb030 remove actions artifact uploading 2025-12-26 14:34:56 -06:00
Github Action
320a434ae7 Update Nix flake.lock and hashes 2025-12-26 14:34:56 -06:00
GitHub Action
09875f5984 chore: generate 2025-12-26 14:34:56 -06:00
Brendan Allan
89ce5d9ea2 ci: try to upload cli artifacts 2025-12-26 14:34:55 -06:00
Aiden Cline
330e4a7c73 feat: add experimental lsp tool (#5886) 2025-12-26 14:34:55 -06:00
Luo Chen
79134dd16a feat: add nixd as lsp for nix language (#5929) 2025-12-26 14:34:55 -06:00
opencode-agent[bot]
10e454481c docs: Agent Skills (#5931)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-12-26 14:34:55 -06:00
Valerio Di Maggio
d9b1b876e5 fix: ensure installation commands are using .quiet (#5758)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:34:55 -06:00
Ben Vargas
e21c6d93e0 docs: add ai-sdk-provider-opencode-sdk to ecosystem (#5772) 2025-12-26 14:34:55 -06:00
Aiden Cline
7eba950c6d ci: update docs prompt 2025-12-26 14:34:55 -06:00
Aiden Cline
53149554c8 ci: add automatic doc update workflow 2025-12-26 14:34:55 -06:00
Neil Daquioag
2dc9528867 fix: support clipboard image paste (Ctrl+V) on Windows (#5919) 2025-12-26 14:34:54 -06:00
Aiden Cline
b7a9cc5fd3 tweak: adjust minimax m2 topK and add minimax m2.1 topP 2025-12-26 14:34:54 -06:00
Noam Bressler
a8f5e69ede fix: Perform snapshot in cases finish-step is not reached (#5912)
Co-authored-by: noamzbr <noamzbr@users.noreply.github.com>
2025-12-26 14:34:54 -06:00
Matt Silverlock
6fe028dd21 prompts: improve built-in /review prompt (#5918) 2025-12-26 14:34:54 -06:00
YeonGyu-Kim
6d6bc0140b feat(server): expose auto param in session.summarize for plugins (#5924) 2025-12-26 14:34:54 -06:00
GitHub Action
a22a67b8bc chore: generate 2025-12-26 14:34:54 -06:00
Dax
f2ddf5a7d4 feat: add Agent Skills support (#5921) 2025-12-26 14:34:54 -06:00
opencode
d4cb80a5e1 release: v1.0.185 2025-12-26 14:34:53 -06:00
Dax Raad
221093a52b core: fix LSP server binary installation and shell command execution
- Ensure proper file permissions are set for installed LSP binaries on non-Windows platforms
- Add error handling for shell command execution in prompt system to prevent crashes
2025-12-26 14:34:53 -06:00
Aiden Cline
80271f939c tweak: update kimi-k2 and kimi-k2-thinking to use recommended temperature values 2025-12-26 14:34:53 -06:00
Dax Raad
0cc373de7b tui: fix SDK context usage and server port fallback
- Update SDK context to return client instead of event for proper usage
- Add server port fallback to 4096 when port 0 is specified but unavailable
- Fix SDK event listener usage in TUI app
2025-12-26 14:34:53 -06:00
Github Action
d3f42d5302 Update Nix flake.lock and hashes 2025-12-26 14:34:52 -06:00
GitHub Action
d0fb6e72fe chore: generate 2025-12-26 14:34:52 -06:00
Sherlock Holmes
9e9eaabe68 fix(deps): add missing @opencode-ai/plugin to dependencies (#5797) 2025-12-26 14:34:52 -06:00
Nalin Singh
c04c92778b feat: add syntax highlighting for .ets files (#5889) 2025-12-26 14:34:52 -06:00
Adam Hosker
76b0ac2204 fix: prevent stats workflow from running on forks (#5897) 2025-12-26 14:34:52 -06:00
Abdelkader Boudih
a81cd3c8c4 fix: use official MCP SDK for better tool schema handling (#5463) 2025-12-26 14:34:51 -06:00
GitHub Action
3bd665c5b6 chore: generate 2025-12-26 14:34:51 -06:00
Matt Silverlock
2fa7b01072 github: support schedule events (#5810) 2025-12-26 14:34:51 -06:00
GitHub Action
84a2180d0b ignore: update download stats 2025-12-21 2025-12-26 14:34:51 -06:00
opencode
6b21504dbc release: v1.0.184 2025-12-26 14:34:50 -06:00
Adam
244afdf4ef fix(desktop): layout regression 2025-12-26 14:34:50 -06:00
Adam
8bc49ccf2c fix(desktop): better keybind tooltips 2025-12-26 14:34:50 -06:00
Adam
0f5ce6fb04 fix(desktop): todo tool title 2025-12-26 14:34:50 -06:00
Adam
0d1f8aa40e fix(desktop): allow text selection 2025-12-26 14:34:50 -06:00
GitHub Action
eb6ae39917 chore: generate 2025-12-26 14:34:50 -06:00
Adam
c57e4dc411 fix(desktop): incorrect state dir on macos 2025-12-26 14:34:50 -06:00
opencode
61be42e08c release: v1.0.183 2025-12-26 14:34:49 -06:00
Adam
9f1ed1c186 fix(desktop): better error reporting 2025-12-26 14:34:49 -06:00
Adam
1cd4cafc35 fix(desktop): non-latin file paths failed 2025-12-26 14:34:49 -06:00
Adam
0ec1e14c63 fix(desktop): file loading errors 2025-12-26 14:34:49 -06:00
Github Action
bfeef85d59 Update Nix flake.lock and hashes 2025-12-26 14:34:49 -06:00
Christopher Tso
31de4fc223 Make CLI build script Windows-friendly (#5835) 2025-12-26 14:34:48 -06:00
Aiden Cline
267447018e core: add verification that at least 1 primary agent is enabled, add regression tests (#5881) 2025-12-26 14:34:48 -06:00
GitHub Action
53d9c745cd chore: generate 2025-12-26 14:34:48 -06:00
opencode
bfe2ddf81a release: v1.0.182 2025-12-26 14:34:47 -06:00
Aiden Cline
72bf8b4b6d fix: regression where config would error despite valid agents 2025-12-26 14:34:47 -06:00
opencode
a0c279d75e release: v1.0.181 2025-12-26 14:34:47 -06:00
Adam
0e8303c016 fix(desktop): layout 2025-12-26 14:34:46 -06:00
Adam
7d88443ca8 feat(desktop): new layout 2025-12-26 14:34:46 -06:00
YuY801103
85ef34ca06 docs: add Traditional Chinese (Taiwan) README translation (#5861)
Co-authored-by: Yu <YuY801103@users.noreply.github.com>
2025-12-26 14:34:46 -06:00
Ryan Vogel
3fdeee126a docs: clarify model ID format for OpenCode provider (#5854) 2025-12-26 14:34:46 -06:00
Aiden Cline
7654839159 tweak: better error message if no primary agents are enabled 2025-12-26 14:34:46 -06:00
Ryan Vogel
af66701f44 feat(docs): adding .md to docs pages shows raw markdown (#5823) 2025-12-26 14:34:46 -06:00
shamil2
25e25a2110 feat: add Catppuccin Frappé theme (#5821)
Co-authored-by: shamil2 <shamil2@users.noreply.github.com>
2025-12-26 14:34:45 -06:00
ja
f57538ebba docs: add name property to model configuration example (#5853) 2025-12-26 14:34:45 -06:00
Shpetim
cbe06c47b3 fix: system theme flicker (#5842)
Co-authored-by: Shpetim <shpetim.alimi@ndbit.net>
2025-12-26 14:34:45 -06:00
Frank
cdfdd63bee zen: sync 2025-12-26 14:34:45 -06:00
Matt Silverlock
753b7362a3 feat: support configuring a default_agent across all API/user surfaces (#5843)
Co-authored-by: observerw <observerw@users.noreply.github.com>
2025-12-26 14:34:44 -06:00
Aiden Cline
bd24e1a715 ci: adjust review agent prompt to discourage bad diffs 2025-12-26 14:34:44 -06:00
lif
b1e7975bf8 fix: add transform case for gemini if mcp tool has missing array items (#5846) 2025-12-26 14:34:44 -06:00
ja
2d47d5cf83 fix: prioritize session list loading when resuming with -c (#5816)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:34:44 -06:00
Ryan Cassidy
1e867702a3 feat: add cursor theme (#5850) 2025-12-26 14:34:44 -06:00
GitHub Action
2a3b9bde24 chore: generate 2025-12-26 14:34:44 -06:00
opencode
2fec3c1859 release: v1.0.180 2025-12-26 14:34:43 -06:00
Dax Raad
c60ef697f2 ci 2025-12-26 14:34:43 -06:00
opencode
eb84c5820f release: v1.0.179 2025-12-26 14:34:43 -06:00
Dax Raad
b7c3d0dd9d ci 2025-12-26 14:34:43 -06:00
GitHub Action
e5954a97b4 chore: generate 2025-12-26 14:34:42 -06:00
Dax Raad
13e1091f0f ci 2025-12-26 14:34:42 -06:00
opencode
0d94abc69a release: v1.0.178 2025-12-26 14:34:42 -06:00
Dax Raad
5d5c2523b8 ci 2025-12-26 14:34:42 -06:00
Tommy D. Rossi
5bac846e98 feat: add endpoints to delete and update message parts (#5433) 2025-12-26 14:34:41 -06:00
GitHub Action
997cf6e207 chore: generate 2025-12-26 14:34:41 -06:00
opencode
8dfdd09dcb release: v1.0.177 2025-12-26 14:34:41 -06:00
Dax Raad
1024724cc6 ci 2025-12-26 14:34:40 -06:00
opencode
2774e4f6fd release: v1.0.176 2025-12-26 14:34:40 -06:00
Adam
29b22b46db fix(desktop): sidebar UX issues 2025-12-26 14:34:40 -06:00
Adam
9d75fbd1b2 fix(desktop): task rendering perf 2025-12-26 14:34:40 -06:00
GitHub Action
6557fa2e0b chore: generate 2025-12-26 14:34:40 -06:00
Adam
6e5d7fcae3 fix(desktop): show last text part when summarized 2025-12-26 14:34:39 -06:00
Adam
2912ba9526 fix(desktop): performance with lots of session changes 2025-12-26 14:34:39 -06:00
Adam
8ec421a3be fix(desktop): event reconnect gaps 2025-12-26 14:34:39 -06:00
GitHub Action
b279b5022c ignore: update download stats 2025-12-20 2025-12-26 14:34:39 -06:00
opencode
78364f0137 release: v1.0.175 2025-12-26 14:34:38 -06:00
Adam
67a7483505 fix(desktop): perf tweaks 2025-12-26 14:34:38 -06:00
Adam
a023273d9e fix(desktop): add retries to init promises 2025-12-26 14:34:38 -06:00
Adam
64dd604ea0 fix: types 2025-12-26 14:34:38 -06:00
Adam
34d9c657e8 fix: shouldEncode 2025-12-26 14:34:38 -06:00
Adam
1fcacfd889 fix(desktop): perf stuff 2025-12-26 14:34:38 -06:00
Adam
979a50ecfe fix(desktop): removed projects 2025-12-26 14:34:38 -06:00
Frank
901801d3d8 zen: add minimax m2.1 2025-12-26 14:34:37 -06:00
GitHub Action
f5468a0399 chore: generate 2025-12-26 14:34:37 -06:00
Frank
b9dd35bfcc zen: add minimax m2.1 2025-12-26 14:34:37 -06:00
opencode
192975e07e release: v1.0.174 2025-12-26 14:34:37 -06:00
GitHub Action
669a7e0897 chore: generate 2025-12-26 14:34:36 -06:00
Aiden Cline
1eaa71e236 fix: file permissions 2025-12-26 14:34:36 -06:00
opencode
583f0aa5ef release: v1.0.173 2025-12-26 14:34:36 -06:00
Ariane Emory
6c159d4f78 feat(tui): reinsert forked message text in prompt text input box when forking session (resolves #5495) (#5545)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-26 14:34:35 -06:00
GitHub Action
160b26f360 chore: generate 2025-12-26 14:34:35 -06:00
Aiden Cline
f85a57d4af ci: fix archive 2025-12-26 14:34:35 -06:00
opencode
38eb36f4f6 release: v1.0.172 2025-12-26 14:34:34 -06:00
Aiden Cline
01ddf7deb6 ci: fix undefined 2025-12-26 14:34:34 -06:00
Aiden Cline
be47071ce5 ci: fix release notes 2025-12-26 14:34:34 -06:00
Aiden Cline
7bc6ac85a3 Revert "tweak: better release notes (grouped changelog) (#5768)"
This reverts commit b99afdad91.
2025-12-26 14:34:34 -06:00
Aiden Cline
65ca3c463a Revert "tweak: DevEx to run changelog independently (#5774)"
This reverts commit 7f8e799392.
2025-12-26 14:34:33 -06:00
Aiden Cline
d30536efa6 Revert "ci: gemini 3 flash doesnt exist in pinned cicd version (#5776)"
This reverts commit 382905602c.
2025-12-26 14:34:33 -06:00
Aiden Cline
1682fdf0d1 test: fixture cleanup 2025-12-26 14:34:33 -06:00
Aiden Cline
d2919b1d74 test: fix test case 2025-12-26 14:34:33 -06:00
Kaspar
72c4cadb22 tweak: Make LSP message more accurate when LSPs disabled (#5814) 2025-12-26 14:34:33 -06:00
Aiden Cline
35787ffaed chore: rm dead code 2025-12-26 14:34:33 -06:00
Aiden Cline
c0fe626748 ci: fix publish auth failure 2025-12-26 14:34:32 -06:00
1XD
3306d8f666 docs: replace deprecated mise ubi backend with github backend (#5811) 2025-12-26 14:34:32 -06:00
Github Action
5e8b1d8f4a Update Nix flake.lock and hashes 2025-12-26 14:34:31 -06:00
Frank
9ce16ac197 zen: sync 2025-12-26 14:34:31 -06:00
Frank
8597343df0 zen: sync 2025-12-26 14:34:31 -06:00
Frank
47444b398b zen: sync 2025-12-26 14:34:31 -06:00
Cameron
b95be947c6 Desktop file encoding issue (#5490) 2025-12-26 14:34:30 -06:00
Adam
f17e4fbaf0 chore: cleanup 2025-12-26 14:34:30 -06:00
Adam
fd9e54978a fix(desktop): auto-scroll and session perf 2025-12-26 14:34:30 -06:00
Adam
59e6454823 fix(desktop): don't use tauri http for sse events 2025-12-26 14:34:30 -06:00
Adam
8199a3b5e5 fix(desktop): error height 2025-12-26 14:34:30 -06:00
Github Action
881465a7d0 Update Nix flake.lock and hashes 2025-12-26 14:34:29 -06:00
Aiden Cline
feb9f9dbd6 tweak: use fetch instead of octokit for now 2025-12-26 14:34:29 -06:00
Steven T. Cramer
5c47adb782 docs: add Windows Terminal Shift+Enter configuration guide (#5788) 2025-12-26 14:34:29 -06:00
Brendan Allan
bf653d44d1 ci: separate standalone publishing from dependent publishing (#5634)
Co-authored-by: GitHub Action <action@github.com>
2025-12-26 14:34:28 -06:00
Dax Raad
71af1859fa core: prevent file system scanning when in root directory to avoid unnecessary operations 2025-12-26 14:34:28 -06:00
Aiden Cline
f084a0cfee ci: change token for gh release to allow discord job to actually trigger see: https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow 2025-12-26 14:34:28 -06:00
GitHub Action
28d2ae5208 chore: generate 2025-12-26 14:34:27 -06:00
José Valim
cdb7949280 Do not include hidden agents in ACP (#5791) 2025-12-26 14:34:27 -06:00
Github Action
f02d1ef4a1 Update Nix flake.lock and hashes 2025-12-26 14:34:27 -06:00
Sebastian Herrlinger
59968ebd25 upgrade opentui to v0.1.62, enabling textarea mouse scroll and cursor set 2025-12-26 14:34:26 -06:00
Github Action
a346d22c25 Update Nix flake.lock and hashes 2025-12-26 14:34:26 -06:00
opencode
f15db51fe0 release: v1.0.170 2025-12-26 14:34:26 -06:00
Adam
92dfbdc08b fix(desktop): error handling 2025-12-26 14:34:25 -06:00
Adam
923bad1f70 chore: logging 2025-12-26 14:34:25 -06:00
Adam
551c5283bd fix(desktop): separate prompt history for shell 2025-12-26 14:34:25 -06:00
Adam
a05851b0ea fix(desktop): don't navigate prompt history if dirty 2025-12-26 14:34:25 -06:00
Sebastian Herrlinger
d888a318c5 user messages as markdown with toggle 2025-12-26 14:34:25 -06:00
GitHub Action
cfa95461b2 ignore: update download stats 2025-12-19 2025-12-26 14:34:25 -06:00
Github Action
182cf2d878 Update Nix flake.lock and hashes 2025-12-26 14:34:24 -06:00
Brendan Allan
6473b3f91a Update SolidStart and bring back HttpHeader usage (#5355)
Co-authored-by: Github Action <action@github.com>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
2025-12-26 14:34:24 -06:00
Sherlock Holmes
6c8a7f05be feat(tui): implement smooth scrolling for autocomplete dropdown navigation (#5559)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-12-26 14:34:24 -06:00
Eric Shirley
d8a450ce17 lsp: add oxlint server (#5570) 2025-12-26 14:34:24 -06:00
Aiden Cline
d96fa874b0 docs: contributing 2025-12-26 14:34:24 -06:00
Aiden Cline
29541578b1 ci: only run generate for dev 2025-12-26 14:34:24 -06:00
Luke Parker
4c805fb327 ci: gemini 3 flash doesnt exist in pinned cicd version (#5776) 2025-12-26 14:34:24 -06:00
GitHub Action
906bb8821c chore: generate 2025-12-26 14:34:23 -06:00
Matt Silverlock
df93dbe347 fix: use correct octokit API for PR review comment reactions (#5778) 2025-12-26 14:34:23 -06:00
GitHub Action
dc7562189b chore: generate 2025-12-26 14:34:23 -06:00
Brendan Allan
e88ef5b294 tauri: remove pinch-to-zoom on window 2025-12-26 14:34:23 -06:00
Basit Mustafa
16b7e85256 docs(ecosystem): add opencode-zellij-namer plugin (#5771) 2025-12-26 14:34:23 -06:00
Luke Parker
5e403d3a9e tweak: DevEx to run changelog independently (#5774) 2025-12-26 14:34:22 -06:00
opencode
d058c0aa2d release: v1.0.169 2025-12-26 14:34:22 -06:00
Adam
ba081d64ad fix(desktop): shell mode 2025-12-26 14:34:22 -06:00
Adam
24005e3fc8 fix(desktop): extra reqs 2025-12-26 14:34:21 -06:00
Aiden Cline
9f3942d7da ci: better err msg for generate workflow 2025-12-26 14:34:21 -06:00
Luke Parker
536b4e9055 tweak: better release notes (grouped changelog) (#5768) 2025-12-26 14:34:21 -06:00
Aiden Cline
4d1d499c93 fix: better api call error msgs in some cases 2025-12-26 14:34:21 -06:00
GitHub Action
f5c5eff3ac chore: generate 2025-12-26 14:34:21 -06:00
Rohan Godha
2e0c6d5274 feat(tui): click on subagents to open them (#5761)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:34:21 -06:00
opencode
39e4b85273 release: v1.0.168 2025-12-26 14:34:20 -06:00
Github Action
0b4e84b9fa Update Nix flake.lock and hashes 2025-12-26 14:34:20 -06:00
GitHub Action
72105666cc chore: generate 2025-12-26 14:34:20 -06:00
Adam
2e3c122462 fix: id 2025-12-26 14:34:19 -06:00
Adam
80935a2dd1 fix(desktop): expanded states 2025-12-26 14:34:19 -06:00
Adam
a858dae20b chore: cleanup 2025-12-26 14:34:19 -06:00
Adam
2e431a0269 fix(desktop): smaller max-width when review open 2025-12-26 14:34:19 -06:00
Aiden Cline
66aee1bffd ci: fix generate 2025-12-26 14:34:19 -06:00
Aiden Cline
0c2bf6870b ci: fix file perm 2025-12-26 14:34:19 -06:00
Aiden Cline
e73b8cf171 ci: handle case where generate.yml fails better 2025-12-26 14:34:19 -06:00
Aiden Cline
0c4d68e9d3 tweak: more retry cases 2025-12-26 14:34:19 -06:00
Aiden Cline
92270f582a chore: rm dead code 2025-12-26 14:34:18 -06:00
GitHub Action
9aabebd644 chore: format code 2025-12-26 14:34:18 -06:00
OpeOginni
859ee9904d feat: add experimental support for Ty language server (#5575) 2025-12-26 14:34:18 -06:00
Adam
d02b54085e fix(desktop): markdown styles 2025-12-26 14:34:18 -06:00
Adam
65be48a14d fix(desktop): don't show image button in shell mode 2025-12-26 14:34:18 -06:00
Frank
0b44adf551 zen: cleanup headers 2025-12-26 14:34:18 -06:00
Aiden Cline
eed4a950c9 rm interleaved thinking filter for certain kimi k2 thinking model providers that were bugged 2025-12-26 14:34:18 -06:00
Adam
cc932a5f4f fix(desktop): submit prompt 2025-12-26 14:34:18 -06:00
GitHub Action
d4c89df601 chore: format code 2025-12-26 14:34:17 -06:00
Matt Silverlock
f707f6254e github: add OIDC_BASE_URL for custom GitHub App installs (#5756) 2025-12-26 14:34:17 -06:00
Adam
de933a68c9 fix(desktop): checkbox render in safari fml 2025-12-26 14:34:17 -06:00
Adam
24a9f95cc7 fix(desktop): rendering shell mode messages 2025-12-26 14:34:17 -06:00
Adam
6469ab5f95 fix(desktop): error styles 2025-12-26 14:34:16 -06:00
Adam
c31545351a fix(desktop): prompt history nav, optimistic prompt dup 2025-12-26 14:34:16 -06:00
Adam
51e5bc3e2b fix(desktop): session ordered by most recent 2025-12-26 14:34:16 -06:00
Adam
6759a50bee feat(desktop): shell mode 2025-12-26 14:34:16 -06:00
Adam
71e92a7e76 chore: cleanup 2025-12-26 14:34:15 -06:00
GitHub Action
ac25f3c738 chore: format code 2025-12-26 14:34:15 -06:00
Ariane Emory
52d0dbb2be feat(tui): fork slash command for keyboard-friendly session forking (resolves #5599) (#5610)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-26 14:34:15 -06:00
Jeon Suyeol
83b3cf6135 fix(command): validate model before executing slash command (#5740) 2025-12-26 14:34:15 -06:00
Frank
b67b876b00 Revert "add client header"
This reverts commit 2fb89161c8.
2025-12-26 14:34:15 -06:00
barış
e1b7bdb35f docs: fix typos (#5753) 2025-12-26 14:34:15 -06:00
Aiden Cline
198ccc5ef7 ci: fix discord 2025-12-26 14:34:15 -06:00
Aiden Cline
04665c8c53 bump bun version 2025-12-26 14:34:14 -06:00
Daniel Polito
89ef686546 Improve Github Action Hallucinations (#5751) 2025-12-26 14:34:14 -06:00
Frank
dc1cc77ee1 add client header 2025-12-26 14:34:14 -06:00
GitHub Action
f5a1cdad2f ignore: update download stats 2025-12-18 2025-12-26 14:34:14 -06:00
Brendan Allan
9c1dbcdaae tauri: disable pinch zoom on linux (#5735) 2025-12-26 14:34:14 -06:00
Brendan Allan
26e80c6ce3 tauri: configure display backends more correctly on linux (#5730) 2025-12-26 14:34:13 -06:00
GitHub Action
de9aee8f40 chore: format code 2025-12-26 14:34:13 -06:00
Adam
d34464593c fix(desktop): disable pinch to zoom 2025-12-26 14:34:13 -06:00
Adam
1ece7ebe50 feat(desktop): custom update toast 2025-12-26 14:34:12 -06:00
opencode
97c6bd5ab2 release: v1.0.167 2025-12-26 14:34:12 -06:00
shuv
81d367bd8e fix: handle empty directory query parameter in server middleware (#5732) 2025-12-26 14:34:12 -06:00
Brendan Allan
ea99db7d72 tauri: say OpenCode Server instead of OpenCode CLI 2025-12-26 14:34:11 -06:00
Brendan Allan
8ecd7566c1 tauri: server spawn fail dialog w/ copy logs button (#5729) 2025-12-26 14:34:11 -06:00
Frank
ede5871cf4 zen: error handling for stream requests 2025-12-26 14:34:11 -06:00
GitHub Action
9aebc4d976 chore: format code 2025-12-26 14:34:11 -06:00
Jeon Suyeol
0a1ef150c6 docs: add OPENCODE_DISABLE_TERMINAL_TITLE to environment variables (#5725) 2025-12-26 14:34:11 -06:00
Jake Nelson
e1b2fd5044 feat(tui): add option to disable terminal title (#5713) 2025-12-26 14:34:10 -06:00
Frank
f84e54517f zen: error handling for stream requests 2025-12-26 14:34:10 -06:00
Rohan Mukherjee
71a1aa5b16 MCP improvements (#5699) 2025-12-26 14:34:10 -06:00
Jay V
f0b2dfce6e docs: add legal pages with privacy policy and terms of service links 2025-12-26 14:34:10 -06:00
GitHub Action
9e842e5fbf chore: format code 2025-12-26 14:34:10 -06:00
Ryan Vogel
7becca83ae docs: add opencode.cafe to ecosystem page (#5714) 2025-12-26 14:34:10 -06:00
opencode
7b333eb352 release: v1.0.166 2025-12-26 14:34:09 -06:00
Adam
7091299de5 chore: cleanup 2025-12-26 14:34:09 -06:00
Adam
09172e9a55 fix: better init error messages 2025-12-26 14:34:09 -06:00
Adam
371a47ca11 fix: auto-scroll 2025-12-26 14:34:09 -06:00
GitHub Action
e5c6960b4c chore: format code 2025-12-26 14:34:09 -06:00
Sercan Sagman
193303a325 fix(tui): exclude reverted assistant reply when copying last message (#5705)
Signed-off-by: assagman <ahmetsercansagman@gmail.com>
2025-12-26 14:34:08 -06:00
opencode
836a7f5d83 release: v1.0.165 2025-12-26 14:34:08 -06:00
Adam
b4b633eee3 feat(desktop): startup errors shown 2025-12-26 14:34:08 -06:00
Adam
2f6e4e7e77 feat(desktop): optimistic prompt submit 2025-12-26 14:34:07 -06:00
Aiden Cline
a43981fa66 fix: better error messages 2025-12-26 14:34:07 -06:00
Aiden Cline
8b344dd8d1 fix: prevent 1 from showing when preparing write 2025-12-26 14:34:07 -06:00
Nalin Singh
ddb1105a94 feat: add viewportOptions to scrollbox for padding adjustments to avoid scrollbar overlap (#5703) 2025-12-26 14:34:07 -06:00
Spoon
6b3e0923fe UI: show plugins in /status (#4515)
Co-authored-by: GitHub Action <action@github.com>
2025-12-26 14:34:07 -06:00
Nalin Singh
8a19de4672 fix: prevent session list selection from jumping to active session when confirming delete (#5666) 2025-12-26 14:34:07 -06:00
Joel Hooks
247d1b34cf feat(plugin): add experimental.session.compacting hook for pre-compaction context injection (#5698)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-12-26 14:34:07 -06:00
Github Action
3e11f1d3ec Update Nix flake.lock and hashes 2025-12-26 14:34:07 -06:00
Adam
b860ff8901 chore: localStorage -> tauri store 2025-12-26 14:34:06 -06:00
Brendan Allan
9c2ee8320e console: use download proxy to rename mac and windows installers (#5697)
Co-authored-by: GitHub Action <action@github.com>
2025-12-26 14:34:06 -06:00
Ravi Kumar
c83f2f3676 fix(tui): resolve session_status TypeError (#5520) 2025-12-26 14:34:06 -06:00
Aiden Cline
b9eb55b772 fix: keep session dialog open if deleting session 2025-12-26 14:34:06 -06:00
Aiden Cline
c7a7fced41 fix: remove needless tui event publish on session delete 2025-12-26 14:34:06 -06:00
GitHub Action
91d1b65920 chore: format code 2025-12-26 14:34:06 -06:00
Adam
59ddf2a823 fix: sticky visual issues 2025-12-26 14:34:06 -06:00
Aiden Cline
befd8b59ee ci: add windows label to triage bot 2025-12-26 14:34:06 -06:00
GitHub Action
aa51979592 chore: format code 2025-12-26 14:34:05 -06:00
Brendan Allan
5a9b52e92e console: add /download/[platform] endpoint 2025-12-26 14:34:05 -06:00
Qio
4222758324 add OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX to override 32k default (#5679)
Co-authored-by: qio <handsomehust@gmail.com>
2025-12-26 14:34:05 -06:00
Adam
edd7032cb8 feat(desktop): inter and ibm plex mono 2025-12-26 14:34:05 -06:00
Adam
eff31afd33 feat(desktop): inter and ibm plex mono 2025-12-26 14:34:05 -06:00
Paolo Ricciuti
d1fc208e4a fix: send mcpName as state if authUrl doesn't have state (#5681) 2025-12-26 14:34:05 -06:00
Aiden Cline
628cab0f6b test: add regression test for setCacheKey option 2025-12-26 14:34:05 -06:00
Spoon
bf1f0a52c0 batch: enable edit, todoread, clarify error message, minor tool description change (#5659) 2025-12-26 14:34:05 -06:00
Rhys Sullivan
61aecc92ea fix: change subagent navigation order to newest-to-oldest (#5680) 2025-12-26 14:34:05 -06:00
Shantur Rathore
a652fe9076 fix: config option setCacheKey not being respected (#5686) 2025-12-26 14:34:04 -06:00
Aiden Cline
92f4fdc568 docs: update share link 2025-12-26 14:34:04 -06:00
Frank
c64c29821a zen: add gemini 3 flash 2025-12-26 14:34:04 -06:00
Brendan Allan
5b65e14c2b tauri: nsis header and sidebar 2025-12-26 14:34:04 -06:00
Brendan Allan
3f8f6177d4 tauri: update nsis icon 2025-12-26 14:34:04 -06:00
Stoufiler
3a46b78994 docs: Sort LSP Server list (#5688) 2025-12-26 14:34:04 -06:00
Rohan Mukherjee
d14f767381 feat: added lucent-orng theme (#5678)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2025-12-26 14:34:04 -06:00
Sachnun
355e027ea6 fix: remove unsupported parameter from bash tool description (#5676) 2025-12-26 14:34:04 -06:00
Brendan Allan
f4e851d9a1 tauri: return after update failures 2025-12-26 14:34:04 -06:00
GitHub Action
5524c9c6dc chore: format code 2025-12-26 14:34:04 -06:00
Brendan Allan
566e0f75a4 tauri: only alert on update failure when triggered manually 2025-12-26 14:34:03 -06:00
Brendan Allan
daf387861d tauri: dev icons + separate prod config (#5691)
Co-authored-by: GitHub Action <action@github.com>
2025-12-26 14:34:03 -06:00
Adam
c0b9f1c147 fix: command shortcuts 2025-12-26 14:34:03 -06:00
Github Action
978c6a30f6 Update Nix flake.lock and hashes 2025-12-26 14:34:03 -06:00
Amadeus Demarzi
96719bdf52 Diffs Performance Improvements (#5653)
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
2025-12-26 14:34:03 -06:00
GitHub Action
561e9f35ae ignore: update download stats 2025-12-17 2025-12-26 14:34:03 -06:00
David Hill
201b3f4014 wip: auto-detect OS and show desktop download button 2025-12-26 14:34:03 -06:00
David Hill
b3e173fb8f fix: website cta button 2025-12-26 14:34:03 -06:00
Adam
9f6032f250 chore: cleanup 2025-12-26 14:34:02 -06:00
Adam
6362cff7cb feat(desktop): share sessions 2025-12-26 14:34:02 -06:00
Adam
0ae1c170c2 feat(desktop): lsp diagnostics displayed 2025-12-26 14:34:02 -06:00
Github Action
6afe7337a9 Update Nix flake.lock and hashes 2025-12-26 14:34:02 -06:00
GitHub Action
831ef65a41 chore: format code 2025-12-26 14:34:02 -06:00
Sebastian Herrlinger
797271e2e2 upgrade opentui to v0.1.61 2025-12-26 14:34:01 -06:00
Jeon Suyeol
f268d81e16 Add availability to disable terminal title using OPENCODE_DISABLE_TERMINAL_TITLE env (#5661) 2025-12-26 14:34:01 -06:00
Dax Raad
1b28bd68f6 ci: update publish workflow configuration 2025-12-26 14:34:01 -06:00
GitHub Action
798dee27d0 chore: format code 2025-12-26 14:34:01 -06:00
Spoon
463bf7ab3a plugin(hook): add task tool execution hooks and command context tracking (#5642) 2025-12-26 14:34:01 -06:00
Aiden Cline
e4d7bb4869 ci: tweak triage 2025-12-26 14:34:01 -06:00
Matt Silverlock
80f1173652 github: add configurable mentions input (#5655) 2025-12-26 14:34:00 -06:00
Adam
be7b9c05a5 tui: increase session width to accommodate longer code blocks and improve readability 2025-12-26 14:34:00 -06:00
David Hill
31897bf974 fix: load more button 2025-12-26 14:34:00 -06:00
Aiden Cline
09a2d3b8b7 ci: fix missing pkg issue 2025-12-26 14:34:00 -06:00
Dax Raad
f80e555a58 ci: fix release draft configuration to prevent automatic draft flag 2025-12-26 14:34:00 -06:00
GitHub Action
59a618ddcb chore: format code 2025-12-26 14:33:59 -06:00
Adam
1cbc9e252c fix(share): content wasn't centered 2025-12-26 14:33:59 -06:00
opencode
9e98bd3283 release: v1.0.164 2025-12-26 14:33:59 -06:00
Adam
15ca31370d fix(desktop): prompt history nav 2025-12-26 14:33:59 -06:00
Aiden Cline
d9a6000a33 fix: user invoked subtasks causing tool_use or missing thinking signa… (#5650) 2025-12-26 14:33:59 -06:00
Adam
daaf88b8e4 fix(desktop): auto-scroll 2025-12-26 14:33:59 -06:00
Adam
c15b581131 fix(desktop): focus prompt input after dialog close 2025-12-26 14:33:59 -06:00
Adam
67973728ef fix(desktop): prompt history navigation 2025-12-26 14:33:58 -06:00
Adam
3d7bdfc52e fix: auto-scroll 2025-12-26 14:33:58 -06:00
Adam
02c8c2cb67 fix: working logic 2025-12-26 14:33:58 -06:00
Adam
53c386e2e5 fix: prompt input multi line input 2025-12-26 14:33:58 -06:00
Adam
196ce5af40 fix: defensive audio init 2025-12-26 14:33:58 -06:00
Eric Guo
299e88b9cf feat(desktop): Loading more session number per project by button (#5616)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:33:58 -06:00
matvey
0302b8317b feat: add experimental oxfmt formatter (#5620) 2025-12-26 14:33:57 -06:00
Aiden Cline
cc7fa6af83 ci: include desktop & tauri in release notes 2025-12-26 14:33:57 -06:00
Aiden Cline
5dd09b13b1 ci: fix branch name 2025-12-26 14:33:57 -06:00
Aiden Cline
77c7b094a8 fix: git branch filewatcher, add flag to completely disable watcher 2025-12-26 14:33:57 -06:00
Aiden Cline
2fdf6e1029 ci: fix triage 2025-12-26 14:33:57 -06:00
Aiden Cline
c3f1e631a0 ci: auto tag github action once a change is shipped for it 2025-12-26 14:33:57 -06:00
Dax Raad
9c8b670886 core: update plugin dependency and config loading for .opencode directory support 2025-12-26 14:33:57 -06:00
Dax Raad
d0cb59c000 core: fix Tauri desktop app SSE connection timeout
- Add heartbeat events to /global/event and /event SSE endpoints
- Send server.heartbeat event every 30s to prevent WKWebView 60s timeout
- Fixes desktop app disconnecting from global events after 1 minute
2025-12-26 14:33:57 -06:00
Dax Raad
9dbbdcc555 fix 2025-12-26 14:33:57 -06:00
Dax Raad
8b3e0422ee ignore: update opencode plugin dependency 2025-12-26 14:33:57 -06:00
Dax Raad
13e26ff18a core: ensure desktop app loads user shell environment variables
Changes shell spawn flags from -l to -il so that ~/.zshrc and
~/.bashrc are sourced when starting the desktop app on macOS
and Linux. This fixes missing PATH and other environment
variables that users expect to be available.
2025-12-26 14:33:57 -06:00
Adam
183cbc1981 feat(desktop): show retries 2025-12-26 14:33:57 -06:00
Adam
25ca8cec4d fix: layout badness 2025-12-26 14:33:56 -06:00
Adam
01261e7f79 fix: defensive audio init 2025-12-26 14:33:56 -06:00
Adam
e598524b40 feat(desktop): show write tool output 2025-12-26 14:33:56 -06:00
Adam
582d9b4187 chore: cleanup 2025-12-26 14:33:56 -06:00
Github Action
babba4e6db Update Nix flake.lock and hashes 2025-12-26 14:33:56 -06:00
Dax Raad
419db64fc9 chore: update opencode plugin dependencies and fix tauri sidecar path 2025-12-26 14:33:56 -06:00
Dax Raad
f386601cbd tui: fix autocomplete file loading and update dependencies 2025-12-26 14:33:55 -06:00
David Hill
12d5a83ba3 fix: remove the selected state from button when select deselected 2025-12-26 14:33:55 -06:00
David Hill
cc17c3338e wip: add active state to open select 2025-12-26 14:33:55 -06:00
Fran Zekan
3460e29107 fix: enable shell alias expansion in ! command (#5621) 2025-12-26 14:33:55 -06:00
GitHub Action
e11051a29a chore: format code 2025-12-26 14:33:55 -06:00
shekohex
debba2ab37 docs: fix typo in Google Antigravity github link (#5625) 2025-12-26 14:33:54 -06:00
jinzhongjia
91b7c3bfa4 docs: Add new project entry for opencode.nvim frontend (#5626) 2025-12-26 14:33:54 -06:00
Connor Adams
8c15365133 docs: add homebrew command for opencode-desktop (#5631)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:33:54 -06:00
Aiden Cline
b4db2b1bdd fix: github install cmd if repo has . in it 2025-12-26 14:33:54 -06:00
David Hill
5570b9f05f fix: breadcrumb dropdown position left aligned 2025-12-26 14:33:53 -06:00
David Hill
370aa7b515 Revert "wip: make the default container wider"
This reverts commit 1f18f389c0.
2025-12-26 14:33:53 -06:00
Tommy D. Rossi
e8c2e494d0 fix: use system prompt field from prompt input (#5633)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-26 14:33:53 -06:00
GitHub Action
434e12666b chore: format code 2025-12-26 14:33:53 -06:00
Aiden Cline
1de89153d8 ci: tweak triage 2025-12-26 14:33:52 -06:00
Brendan Allan
22192a2e92 Keep release a draft until all builds are finished (#5632)
Co-authored-by: opencode <opencode@sst.dev>
Co-authored-by: GitHub Action <action@github.com>
2025-12-26 14:33:52 -06:00
David Hill
73b6464998 wip: make the default container wider 2025-12-26 14:33:52 -06:00
Simon D'Morias
3f571c6117 fix: preserve argument boundaries in run command (#4979) 2025-12-26 14:33:51 -06:00
David Hill
4126084dfd Revert "fix: strip parentheses from file paths generated by llm"
This reverts commit 6c1a1a77b7.
2025-12-26 14:33:51 -06:00
GitHub Action
5529d1b741 ignore: update download stats 2025-12-16 2025-12-26 14:33:51 -06:00
GitHub Action
3555acbe78 chore: format code 2025-12-26 14:33:51 -06:00
Brendan Allan
331ece741f tauri: explicitly kill sidecar before updater relaunch 2025-12-26 14:33:51 -06:00
David Hill
039f853d80 fix: strip parentheses from file paths generated by llm 2025-12-26 14:33:51 -06:00
David Hill
38a0f49595 fix: font size updates 2025-12-26 14:33:50 -06:00
David Hill
f82ac7a7b8 wip: font-size updates 2025-12-26 14:33:50 -06:00
GitHub Action
a8ff5b5174 chore: format code 2025-12-26 14:33:50 -06:00
Brendan Allan
3b1b621d00 tauri: macos-only app menu 2025-12-26 14:33:50 -06:00
Aiden Cline
200ea62f56 fix: small bug w/ install script 2025-12-26 14:33:50 -06:00
Aiden Cline
158f68070f chore: centralize dep to catalog & fix typos 2025-12-26 14:33:49 -06:00
Aiden Cline
aea35df988 ci: Update issue assignment and labeling guidelines
Clarified assignment responsibilities and labeling for issues related to OpenCode tools.
2025-12-26 14:33:49 -06:00
Dax Raad
73b44d198e tui: fix dialog select items taking up 2 lines when truncated
Prevents text wrapping in dialog select options by removing wrapMode,
ensuring truncated text stays on single line and maintains proper timestamp visibility
2025-12-26 14:33:49 -06:00
Luke Parker
3b75ffdc41 fix: debounce LSP diagnostics to get complete results (#5600) 2025-12-26 14:33:48 -06:00
DS
bf8210d750 fix: rejoin system prompt if experimental plugin hook triggers to preserve caching (#5550)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-26 14:33:48 -06:00
Lucas Duailibe
033212c201 make install script use tmp dir (#5601) 2025-12-26 14:33:48 -06:00
Aiden Cline
dc5df2cd10 ci: cheaper model 2025-12-26 14:33:48 -06:00
Aiden Cline
ca2f281a30 ci: ignore 2025-12-26 14:33:48 -06:00
GitHub Action
85ba9b7831 chore: format code 2025-12-26 14:33:48 -06:00
Aiden Cline
1f3129fe96 ci: auto triage issues 2025-12-26 14:33:48 -06:00
opencode
b0c241a59f release: v1.0.162 2025-12-26 14:33:47 -06:00
GitHub Action
780e32bc2b chore: format code 2025-12-26 14:33:47 -06:00
Dax Raad
133e0983d6 tui: update dialog context and server to use new single dialog system 2025-12-26 14:33:47 -06:00
Dax Raad
1f24cccd66 tui: refactor dialog system to use single active dialog instead of stack 2025-12-26 14:33:47 -06:00
opencode
3774fafb26 release: v1.0.161 2025-12-26 14:33:46 -06:00
Adam
c0842ec113 fix: undefined events 2025-12-26 14:33:46 -06:00
opencode-agent[bot]
2560cba9ef docs: models --refresh flag in cli.mdx (#5596)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2025-12-26 14:33:46 -06:00
Luke Parker
9b470429de fix(win32): Normalise LSP paths on windows (fixes lua) (#5597) 2025-12-26 14:33:46 -06:00
Aiden Cline
e8dba6264f tweak: add .catch for extractZip calls 2025-12-26 14:33:46 -06:00
GitHub Action
bb47a1e0f8 chore: format code 2025-12-26 14:33:45 -06:00
Luke Parker
7e9f77117f fix(win32): Missing LSP can now unzip on windows (#5594) 2025-12-26 14:33:45 -06:00
opencode
ca7ac43cff release: v1.0.160 2025-12-26 14:33:45 -06:00
Dax Raad
471ec0350c tui: fix dialog replacement to prevent nested dialogs from showing simultaneously 2025-12-26 14:33:44 -06:00
Dax Raad
90077b6dd0 tui: fix model selection dialog to properly replace current dialog instead of creating nested dialogs 2025-12-26 14:33:44 -06:00
Luke Parker
5fc0a55f54 fix(win32): correct ElixirLS extension typo (#5590) 2025-12-26 14:33:44 -06:00
GitHub Action
3ad2a40e89 chore: format code 2025-12-26 14:33:43 -06:00
Luke Parker
59da2e16b2 fix(win32): use path.delimiter for PATH separator in LSP server lookups (#5589) 2025-12-26 14:33:43 -06:00
opencode
a9a5cf0a76 release: v1.0.159 2025-12-26 14:33:43 -06:00
Dax Raad
e9f899cb9a fix dialog root complexity 2025-12-26 14:33:42 -06:00
Luke Parker
8b976833a3 fix(windows): opencode github install (#5587) 2025-12-26 14:33:42 -06:00
Ariane Emory
fc5493e8bd fix: restore ability to bind keys for model_cycle_favorite model_cycle_favorite_reverse (resolves #5198) (#5202) 2025-12-26 14:33:42 -06:00
opencode-agent[bot]
ecef4d2a92 docs: enabled_providers docs section (#5586)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:33:42 -06:00
Adam
2771367dc4 fix: multiline user input 2025-12-26 14:33:42 -06:00
Adam
e0ca160360 fix: keybinds for agent and model selection 2025-12-26 14:33:42 -06:00
Adam
94d6c1bc92 fix: landing page CLS hero jump down 2025-12-26 14:33:41 -06:00
Adam
f58c96fea5 fix: session nav on homepage 2025-12-26 14:33:41 -06:00
Adam
f4336b504a fix: default steps expanded unless done 2025-12-26 14:33:41 -06:00
Adam
acd2d77e47 fix: landing page CLS hero jump down 2025-12-26 14:33:41 -06:00
Aiden Cline
c0aaba9f65 tweak: add model flag support for agent create command 2025-12-26 14:33:41 -06:00
Jay V
58a37e72e7 docs: make project names clickable links in ecosystem documentation 2025-12-26 14:33:41 -06:00
Adam
589cdc40e3 fix: missing event type (global) 2025-12-26 14:33:41 -06:00
Ariane Emory
b77826f3c5 fix: record shell mode in history (resolves #5454) (#5551) 2025-12-26 14:33:40 -06:00
GitHub Action
46b04f1163 chore: format code 2025-12-26 14:33:40 -06:00
Aiden Cline
31ddd0798a add ability to set topK 2025-12-26 14:33:40 -06:00
opencode
813c330c47 release: v1.0.158 2025-12-26 14:33:40 -06:00
Adam
6205f0bde0 fix: modal search 2025-12-26 14:33:40 -06:00
justfortheloveof
c28709827f tweak: prioritize fuzzysort results that start with user input (#5571) 2025-12-26 14:33:40 -06:00
Adam
ce7f4c0e6e fix: allow for non-vcs projects in desktop 2025-12-26 14:33:40 -06:00
GitHub Action
362502cf6f chore: format code 2025-12-26 14:33:39 -06:00
Dax Raad
5d29af3b45 core: fix server response handling to prevent connection timeouts 2025-12-26 14:33:39 -06:00
opencode
a13af69de5 release: v1.0.157 2025-12-26 14:33:39 -06:00
Dax Raad
653c181312 ci: fix tauri updater version mismatch by checking out the release tag 2025-12-26 14:33:39 -06:00
Dax Raad
8dca9051f1 ci: add app bundle target to fix macOS updater by generating .app.tar.gz files 2025-12-26 14:33:39 -06:00
GitHub Action
6f56b69264 chore: format code 2025-12-26 14:33:39 -06:00
Dax Raad
64b8f0a1a9 core: fix message caching for Anthropic models to improve response consistency 2025-12-26 14:33:38 -06:00
opencode
6fd6eecbf5 release: v1.0.156 2025-12-26 14:33:38 -06:00
Adam
74d4a6bcb1 fix: connect provider on homepage 2025-12-26 14:33:38 -06:00
Adam
47eaef468f fix: terminal in desktop 2025-12-26 14:33:38 -06:00
Adam
1d68c34be6 fix: image attachments in desktop 2025-12-26 14:33:37 -06:00
Github Action
9fb67310bb Update Nix flake.lock and hashes 2025-12-26 14:33:37 -06:00
Dax Raad
f23fc83d3c docs: update header navigation to include desktop download 2025-12-26 14:33:37 -06:00
Adam
8138c36343 fix: share page 2025-12-26 14:33:36 -06:00
Dax Raad
9ffaaf9bbf docs: restore desktop beta banner to homepage 2025-12-26 14:33:36 -06:00
GitHub Action
52bced424f chore: format code 2025-12-26 14:33:36 -06:00
Nalin Singh
ba1cd2777a feat: add F# language server support (#5549) 2025-12-26 14:33:35 -06:00
opencode
f0da095552 release: v1.0.155 2025-12-26 14:33:35 -06:00
Dax Raad
ccaf562c9e 20min 2025-12-26 14:33:35 -06:00
Adam
973162c444 chore: update stats 2025-12-26 14:33:34 -06:00
GitHub Action
be261c6cdb chore: format code 2025-12-26 14:33:34 -06:00
Adam
27da9c1852 chore: cleanup 2025-12-26 14:33:34 -06:00
Adam
779311d064 wip(desktop): progress 2025-12-26 14:33:34 -06:00
Adam
25d6d17296 wip(desktop): progress 2025-12-26 14:33:33 -06:00
Adam
ab48ff0471 wip(desktop): progress 2025-12-26 14:33:33 -06:00
Adam
1078ae03a0 wip(desktop): progress 2025-12-26 14:33:33 -06:00
Adam
a4aa758043 chore: cleanup 2025-12-26 14:33:33 -06:00
Adam
9536746fb7 chore: cleanup 2025-12-26 14:33:33 -06:00
Adam
b088180464 feat(desktop): custom commands 2025-12-26 14:33:33 -06:00
Adam
211524cb0b wip(desktop): progress 2025-12-26 14:33:33 -06:00
Adam
c7b8b7c49e wip(desktop): progress 2025-12-26 14:33:33 -06:00
Adam
105cfa8d4e wip(desktop): progress 2025-12-26 14:33:32 -06:00
Adam
eab1b44f66 wip(desktop): progress 2025-12-26 14:33:32 -06:00
Adam
668c932631 wip(desktop): progress 2025-12-26 14:33:32 -06:00
Adam
5fb20a52a7 wip(desktop): progress 2025-12-26 14:33:32 -06:00
Adam
73b74581f8 wip(desktop): progress 2025-12-26 14:33:32 -06:00
Adam
9ac9396aec wip(desktop): progress 2025-12-26 14:33:32 -06:00
Adam
3df9f708b3 Revert "wip(desktop): session turn state consolidation"
This reverts commit 453f862616dc4d3ac90680581cde279e118b0da1.
2025-12-26 14:33:32 -06:00
Adam
dc65a46fb8 wip(desktop): progress 2025-12-26 14:33:32 -06:00
Adam
71cb4c3ddc wip(desktop): progress 2025-12-26 14:33:32 -06:00
Adam
0a556bb646 wip(desktop): progress 2025-12-26 14:33:31 -06:00
Adam
c8e1950522 wip(desktop): session turn state consolidation 2025-12-26 14:33:31 -06:00
Adam
32bbc07e36 wip(desktop): progress 2025-12-26 14:33:31 -06:00
Adam
c76a00fc3f wip(desktop): progress 2025-12-26 14:33:31 -06:00
Adam
ce4d0d76ea wip(desktop): progress 2025-12-26 14:33:31 -06:00
Adam
df844dc408 wip(desktop): progress 2025-12-26 14:33:31 -06:00
Adam
10276e8b3f wip(desktop): progress 2025-12-26 14:33:30 -06:00
opencode
ea1c063d98 release: v1.0.154 2025-12-26 14:33:30 -06:00
Dax Raad
83249e69f3 ci: update publish workflow concurrency to include version inputs and upgrade ubuntu runner to 24.04 2025-12-26 14:33:30 -06:00
GitHub Action
b612b939c9 chore: format code 2025-12-26 14:33:30 -06:00
Dax Raad
0ffc67792c core: reorganize agent configuration to separate primary agents (build, plan) from subagents 2025-12-26 14:33:30 -06:00
Dax Raad
37d0ef3362 ci: fix AppImage build hanging by using portable appimage format
- Add appimage target back to tauri.conf.json
- Force reinstall tauri-cli from feat/truly-portable-appimage branch
- Add 10 minute timeout to prevent indefinite hangs
- Add logging to verify correct tauri-cli version is installed

This fixes the issue where AppImage builds would hang at 'Running input plugin: gtk' by using the new portable appimage format that bypasses linuxdeploy entirely.
2025-12-26 14:33:30 -06:00
René
62e5e42707 docs: Fix Wakatime repository link in ecosystem.mdx (#5552)
Co-authored-by: Github Action <action@github.com>
2025-12-26 14:33:29 -06:00
David Hill
f8af042811 fix: font size updates 2025-12-26 14:33:29 -06:00
David Hill
45d85a71c6 fix: add tooltip to close review tab 2025-12-26 14:33:29 -06:00
David Hill
cd76512659 fix: prompt input using border shadow 2025-12-26 14:33:29 -06:00
David Hill
9a4e7319df fix: replace agents dropdown with shadow border 2025-12-26 14:33:29 -06:00
David Hill
c6ed81fa8d fix: fix width and padding on agent select
- conditionally hide the role=presentation element because it was adding extra gap before the first agent
2025-12-26 14:33:28 -06:00
David Hill
ba4cd97d16 fix: hide prompt input send tooltip when the send button is disabled 2025-12-26 14:33:28 -06:00
David Hill
bc2d2297bb fix: avatar and icon size in sidebar 2025-12-26 14:33:28 -06:00
David Hill
731233c96d fix: remove blue border from prompt input 2025-12-26 14:33:28 -06:00
GitHub Action
bcb5840537 ignore: update download stats 2025-12-15 2025-12-26 14:33:28 -06:00
René
0bf1c40eff Provider fix, anthropic Errorhandling if empty image file is read (#5521) 2025-12-26 14:33:28 -06:00
Nalin Singh
48eeb4e80d fix: input lip visibility for transparent themes (#5544) 2025-12-26 14:33:28 -06:00
Spoon
64552fe7e1 fix(edit): add per-file lock to prevent read-before-write race (#4388) 2025-12-26 14:33:28 -06:00
Aiden Cline
c7b5831a0f ignore: fix debug var in last commit 2025-12-26 14:33:27 -06:00
opencode-agent[bot]
70a8efc843 Add dismiss button to Getting Started box (#5543)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-12-26 14:33:27 -06:00
DS
b98bd90d50 feat: restore experimental.chat.messages.transform and add experimental.chat.system.transform hooks (#5542) 2025-12-26 14:33:27 -06:00
Aiden Cline
c985ef172d ci: switch model 2025-12-26 14:33:27 -06:00
Brendan Allan
83a320a2e7 tauri: use correct sidecar name 2025-12-26 14:33:27 -06:00
Brendan Allan
cc82a2711b tauri: rename sidecar to opencode-cli 2025-12-26 14:33:26 -06:00
Aiden Cline
7b83294590 ci: smart oc 2025-12-26 14:33:26 -06:00
Ravi Kumar
4e4a24b769 fix(session): fix unshare command not clearing share state (#5523) 2025-12-26 14:33:26 -06:00
GitHub Action
9fbf6d4bd6 chore: format code 2025-12-26 14:33:25 -06:00
Adam
f27b737038 fix: test 2025-12-26 14:33:25 -06:00
Adam
5246ecfc88 fix: update sdk 2025-12-26 14:33:25 -06:00
Adam
d1c3bb2616 wip(desktop): progress 2025-12-26 14:33:25 -06:00
Adam
e9f8b7d91f wip(desktop): progress 2025-12-26 14:33:24 -06:00
Adam
bdd5e63529 wip(desktop): progress 2025-12-26 14:33:24 -06:00
Adam
7584bd0d71 wip(desktop): progress 2025-12-26 14:33:24 -06:00
Adam
60fadb253a wip(desktop): progress 2025-12-26 14:33:23 -06:00
Adam
de635e3c51 wip(desktop): progress 2025-12-26 14:33:23 -06:00
Adam
6c0638c73b fix(desktop): auto scroll 2025-12-26 14:33:22 -06:00
Adam
238ca62ba7 wip(desktop): progress 2025-12-26 14:33:22 -06:00
Adam
d8aba3a35f fix(desktop): layout fixes 2025-12-26 14:33:22 -06:00
Mark Jaquith
8a518c8b2a feat(cli): auto-submit prompt when using --prompt flag (#4510) 2025-12-26 14:33:21 -06:00
Dax Raad
bc7f81c1a3 fix share link 2025-12-26 14:33:21 -06:00
Dax Raad
c8d735de6c ci: fix test failures in CI by pre-populating models cache
Tests were failing in CI because the models.json cache file doesn't exist
and the data() macro fallback only works at build time, not runtime.
The preload now pre-fetches models.json and disables the background
refresh to prevent race conditions during test execution.
2025-12-26 14:33:21 -06:00
Dax Raad
f4e1fa28b8 fix desktop updater 2025-12-26 14:33:21 -06:00
Dax
262d836dd7 LLM cleanup (#5462)
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:33:21 -06:00
Ravi Kumar
01c2ba2e4a fix(tui): --continue selects wrong session (#5513) 2025-12-26 14:33:21 -06:00
GitHub Action
3b59308282 chore: format code 2025-12-26 14:33:21 -06:00
Martijn Baay
b6714c645e feat: add experimental.continue_loop_on_deny config option (#4729)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-12-26 14:33:20 -06:00
Dax Raad
0b60e82266 ignore: simplify download page to use GitHub latest redirect URLs 2025-12-26 14:33:20 -06:00
GitHub Action
321354529b chore: format code 2025-12-26 14:33:20 -06:00
Dax Raad
3efb17e91e disable app image 2025-12-26 14:33:20 -06:00
opencode
df1bc0e9bb release: v1.0.153 2025-12-26 14:33:19 -06:00
Aiden Cline
ac843d39ea tweak: fallback to provider default for temperature 2025-12-26 14:33:19 -06:00
GitHub Action
dbfdb54315 chore: format code 2025-12-26 14:33:19 -06:00
Nalin Singh
2816e84f0a fix: ensure input borders are drawn in transparent themes (#5524) 2025-12-26 14:33:19 -06:00
GitHub Action
5b7581ebbe chore: format code 2025-12-26 14:33:19 -06:00
Sellers Crisp
62bcdf3144 feat: add server_error, rate_limit, and no_kv_space retry logic to accommodate Foundry API issues (#5527)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:33:19 -06:00
shekohex
9746d89cc1 docs: add opencode-pty and opencode-google-antigravity-auth plugins to the echosystem (#5530) 2025-12-26 14:33:18 -06:00
Lawrence Sarpong
74fb054040 Add Gleam LSP and formatter (#5514) 2025-12-26 14:33:18 -06:00
Aymvn
527836a90b docs: Fix Wakatime link in ecosystem documentation (#5528) 2025-12-26 14:33:18 -06:00
GitHub Action
481147c946 ignore: update download stats 2025-12-14 2025-12-26 14:33:18 -06:00
Aiden Cline
3babb33f8d rm unnecessary code 2025-12-26 14:33:18 -06:00
Aiden Cline
1192e16278 add topK function to transform, add temp defaults for glm and minimax 2025-12-26 14:33:18 -06:00
YeonGyu-Kim
e59d479947 fix(ui): guard Node reference for SSR compatibility in isTriggerTitle (#5509) 2025-12-26 14:33:18 -06:00
Brendan Allan
2980eff805 tauri: change mainBinaryName to just OpenCode 2025-12-26 14:33:17 -06:00
Brendan Allan
72c0af5b6c tauri: bring back appimage 2025-12-26 14:33:17 -06:00
Sachnun
deaf35707d fix(tui): open parent session instead of subagent on continue flag (#5503) 2025-12-26 14:33:17 -06:00
Zhou Rui
73b9705410 docs: Add opencode-websearch-cited to plugin list (#5501) 2025-12-26 14:33:17 -06:00
Adam
782ed12244 fix: sort models 2025-12-26 14:33:17 -06:00
Adam
696d14167c fix: use opencode icon 2025-12-26 14:33:17 -06:00
Adam
e9eb799464 chore: cleanup 2025-12-26 14:33:17 -06:00
Adam
05846ed29d chore: cleanup 2025-12-26 14:33:17 -06:00
Tommy D. Rossi
d80634642c fix: limit LSP diagnostics to prevent context window waste (#5480)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:33:17 -06:00
Aiden Cline
d7f32a57ff docs: networking 2025-12-26 14:33:17 -06:00
Aiden Cline
51267f256e chore: reduce duplication of field in transform 2025-12-26 14:33:17 -06:00
Adam
860b62a6e8 chore: cleanup 2025-12-26 14:33:17 -06:00
Adam
c952505526 fix(desktop): archive button 2025-12-26 14:33:17 -06:00
Adam
1f8ac107e9 fix(desktop): auto scroll 2025-12-26 14:33:17 -06:00
Adam
eceac1e727 feat(desktop): show richer status when thinking 2025-12-26 14:33:16 -06:00
Adam
4202298422 fix: css scroll jitter 2025-12-26 14:33:16 -06:00
Adam
43e50b713e fix: don't rotate placeholders in session 2025-12-26 14:33:16 -06:00
Adam
f7544b729c fix: don't open shell by default 2025-12-26 14:33:16 -06:00
Github Action
b84530821e Update Nix flake.lock and hashes 2025-12-26 14:33:16 -06:00
Adam
c808815ec8 chore: cleanup 2025-12-26 14:33:16 -06:00
Adam
280d309a52 fix(desktop): terminal light mode 2025-12-26 14:33:15 -06:00
Adam
a8d7b8e946 feat(desktop): message history 2025-12-26 14:33:15 -06:00
Adam
7a494abea9 fix: session turn scroll 2025-12-26 14:33:15 -06:00
Felipe Oduardo Sierra
48ff68512a add ARM64 Docker image support (#5483) 2025-12-26 14:33:15 -06:00
Github Action
deb6e37cc1 Update Nix flake.lock and hashes 2025-12-26 14:33:15 -06:00
Aiden Cline
5257b22135 bump bun version & set flags this time 2025-12-26 14:33:15 -06:00
GitHub Action
b376d5b622 chore: format code 2025-12-26 14:33:14 -06:00
Jan-Niklas W.
484416b503 docs: fix title for JetBrains ACP config file (#5479) 2025-12-26 14:33:14 -06:00
YeonGyu-Kim
8ed42c8bbb docs: add oh-my-opencode to plugins list (#5481) 2025-12-26 14:33:14 -06:00
GitHub Action
91a6f77ff0 chore: format code 2025-12-26 14:33:14 -06:00
rari404
84975a5d82 feat: add dockerfile language server (#5252)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:33:14 -06:00
GitHub Action
74319df2e3 ignore: update download stats 2025-12-13 2025-12-26 14:33:14 -06:00
rari404
a9c179db7a feat: add texlab language server and latexindent formatter (#5251) 2025-12-26 14:33:14 -06:00
GitHub Action
e85d53f2a9 chore: format code 2025-12-26 14:33:14 -06:00
Jan-Niklas W.
06e93668ee docs: JetBrains IDEs to ACP config docs page (#5465) 2025-12-26 14:33:14 -06:00
Matt Silverlock
a837cef3cb github: support GITHUB_TOKEN + skip OIDC (#5459) 2025-12-26 14:33:14 -06:00
Charles Cooper
5ee52d2f84 fix: add --session flag to attach command (#5460)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-26 14:33:14 -06:00
David Hill
f1ceee1f2e fix: mute the project path in the sidebar that proceeds the final directory 2025-12-26 14:33:14 -06:00
Dax Raad
8e7269d985 fix 2025-12-26 14:33:14 -06:00
GitHub Action
829d1bc084 chore: format code 2025-12-26 14:33:14 -06:00
Aiden Cline
10e0cd619d docs: add env vars sections 2025-12-26 14:33:13 -06:00
opencode
c8e64f208a release: v1.0.152 2025-12-26 14:33:13 -06:00
Adam
5ddc0fd901 Revert "fix: archive button"
This reverts commit bc3286de46.
2025-12-26 14:33:13 -06:00
Aiden Cline
9bd8c6d3d3 shell tweaks, better handling for windows (#5455)
Co-authored-by: GitHub Action <action@github.com>
2025-12-26 14:33:13 -06:00
Adam
11100c4ba2 fix: max height on bash tool 2025-12-26 14:33:13 -06:00
Adam
7bc072c9ad fix: archive button 2025-12-26 14:33:12 -06:00
Dax Raad
e2530833e3 desktop: fix build on Linux and Windows by making macOS title bar styling conditional 2025-12-26 14:33:12 -06:00
Sebastian Herrlinger
cd3f2412e8 only exit app when prompt is empty, otherwise fallthrough, fix #5457 2025-12-26 14:33:12 -06:00
GitHub Action
e8a20cfe9e chore: format code 2025-12-26 14:33:12 -06:00
Dan Brown
071a87177a Shell: No -l in fallback, for max compatibility (#5452) 2025-12-26 14:33:12 -06:00
opencode
c1fda41fad release: v1.0.151 2025-12-26 14:33:11 -06:00
Github Action
fa275b0bf9 Update Nix flake.lock and hashes 2025-12-26 14:33:11 -06:00
Adam
1e3cd35cb8 fix: desktop layout 2025-12-26 14:33:11 -06:00
Adam
4425f13430 fix: desktop layout 2025-12-26 14:33:11 -06:00
Adam
5c2f616c73 fix: desktop layout 2025-12-26 14:33:11 -06:00
Adam
04d71a1820 fix: desktop layout 2025-12-26 14:33:10 -06:00
Adam
11062f83ae fix: desktop layout 2025-12-26 14:33:10 -06:00
Adam
0925ea6c7e fix: desktop layout 2025-12-26 14:33:10 -06:00
Adam
fe1e0b52c7 fix: desktop layout 2025-12-26 14:33:09 -06:00
Adam
ac8f4ab678 fix: desktop layout 2025-12-26 14:33:09 -06:00
Adam
72d1aadea1 fix: desktop layout 2025-12-26 14:33:09 -06:00
Adam
4ca8dcf79d fix: desktop layout 2025-12-26 14:33:09 -06:00
Adam
db8ee3471b chore: cleanup 2025-12-26 14:33:08 -06:00
Adam
c06b78859a chore: cleanup 2025-12-26 14:33:08 -06:00
Adam
b319187151 wip: desktop timeline changes 2025-12-26 14:33:08 -06:00
Dax Raad
4746338e8e only gen summary if diffs 2025-12-26 14:33:08 -06:00
Aiden Cline
5221251b5c tweak: 5.1 -> 5. in transform 2025-12-26 14:33:08 -06:00
GitHub Action
e6520dd6a9 chore: format code 2025-12-26 14:33:08 -06:00
David Hill
845a21da35 fix: mute the whole prompt area when leader key is active 2025-12-26 14:33:08 -06:00
Dax Raad
b41333d30f reuse existing server query 2025-12-26 14:33:07 -06:00
Dax Raad
f9c24ba266 sync 2025-12-26 14:33:07 -06:00
Dax Raad
0d6eba5392 sync 2025-12-26 14:33:07 -06:00
Aiden Cline
9abbd74a51 tweak: 5.1 -> 5. reasoning effort match 2025-12-26 14:33:07 -06:00
Frank
376639f6b8 Zen: add gpt5.2 2025-12-26 14:33:06 -06:00
Luke Parker
f31151deaf fix: osascript for clipboard typo (#5430) 2025-12-26 14:33:06 -06:00
Adam
22487904ab fix: desktop layout 2025-12-26 14:33:06 -06:00
Jeremy Osih
8b1b411989 Change tooltip text from 'Open file' to 'New Terminal' (#5435) 2025-12-26 14:33:06 -06:00
Adam
70b6c4dfc9 fix: tweak missing colors 2025-12-26 14:33:06 -06:00
Adam
03be72bb2b chore: cleanup 2025-12-26 14:33:06 -06:00
xu0o0
c4c36ea54b acp: fix internal error on /compact (#5424) 2025-12-26 14:33:06 -06:00
Brendan Allan
3d751c0da1 tauri: add basic custom titlebar (#5438) 2025-12-26 14:33:05 -06:00
Adam
385ac04c1d feat(desktop): archive sessions 2025-12-26 14:33:05 -06:00
Adam
7cdd33199e fix(desktop): audio stuff 2025-12-26 14:33:05 -06:00
Adam
1a83ffdf01 fix(desktop): homedir aware path on home 2025-12-26 14:33:05 -06:00
GitHub Action
44084dac5f ignore: update download stats 2025-12-12 2025-12-26 14:33:05 -06:00
Github Action
28066ecfbd Update Nix flake.lock and hashes 2025-12-26 14:33:04 -06:00
Adam
b2d028693d feat(desktop): basic alerting 2025-12-26 14:33:04 -06:00
GitHub Action
dfe6c58b04 chore: format code 2025-12-26 14:33:04 -06:00
David Hill
644e360a63 fix: increase font-size-small to 13px 2025-12-26 14:33:04 -06:00
David Hill
8cee140196 fix: make syntax colors have more contrast 2025-12-26 14:33:04 -06:00
Brendan Allan
c301703f89 tauri: create window with full screen size 2025-12-26 14:33:04 -06:00
Github Action
57b7f112d0 Update Nix flake.lock and hashes 2025-12-26 14:33:04 -06:00
GitHub Action
e45d014b7b chore: format code 2025-12-26 14:33:03 -06:00
Brendan Allan
9dfacb8dd1 tauri: initialise store and window-state plugins 2025-12-26 14:33:03 -06:00
Viktor Forsman
ddf44233e7 fix: debug lsp diagnostics cmd for certain lsps (#5420) 2025-12-26 14:33:03 -06:00
Frank
010d1298c0 Zen: sync 2025-12-26 14:33:03 -06:00
Rhys Sullivan
729eca8095 [feat]: show indicator for in progress chats in the sessions list (#5417) 2025-12-26 14:33:03 -06:00
Sachnun
aed32dd840 fix(tui): restore input on timeline revert and show newest first (#5366) 2025-12-26 14:33:02 -06:00
GitHub Action
24c9d2b891 chore: format code 2025-12-26 14:33:02 -06:00
Sachnun
2062dd1086 fix(server): make time field optional in session update validator (#5372) 2025-12-26 14:33:02 -06:00
xu0o0
3f580ef53e acp: replay conversation history in session/load (#5385) 2025-12-26 14:33:02 -06:00
opencode-agent[bot]
fb9ea51018 Removed cache mention from webfetch prompt. (#5412)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2025-12-26 14:33:02 -06:00
Zeno Jiricek
807c421248 docs: Add opencode-md-table-formatter and plugin template (#5405) 2025-12-26 14:33:02 -06:00
Koichi Nakayamada
8992072b95 fix(tui): ensure fatal error UI is readable in light mode (#5387) 2025-12-26 14:33:02 -06:00
David Hill
41319a2885 wip: desktop sidebar icon updates 2025-12-26 14:33:02 -06:00
David Hill
c316ef35d9 fix: make the logo on the home screen non-selectable 2025-12-26 14:33:01 -06:00
GitHub Action
86f9c3acce chore: format code 2025-12-11 23:08:04 +00:00
Aiden Cline
bacf705ee5 wip 2025-12-11 17:07:22 -06:00
451 changed files with 9152 additions and 3516 deletions

69
.github/workflows/docs-update.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: Docs Update
on:
schedule:
- cron: "0 */12 * * *"
workflow_dispatch:
jobs:
update-docs:
if: github.repository == 'sst/opencode'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
id-token: write
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch full history to access commits
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Get recent commits
id: commits
run: |
COMMITS=$(git log --since="4 hours ago" --pretty=format:"- %h %s" 2>/dev/null || echo "")
if [ -z "$COMMITS" ]; then
echo "No commits in the last 4 hours"
echo "has_commits=false" >> $GITHUB_OUTPUT
else
echo "has_commits=true" >> $GITHUB_OUTPUT
{
echo "list<<EOF"
echo "$COMMITS"
echo "EOF"
} >> $GITHUB_OUTPUT
fi
- name: Run opencode
if: steps.commits.outputs.has_commits == 'true'
uses: sst/opencode/github@latest
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
with:
model: opencode/gpt-5.2
agent: docs
prompt: |
Review the following commits from the last 4 hours and identify any new features that may need documentation.
<recent_commits>
${{ steps.commits.outputs.list }}
</recent_commits>
Steps:
1. For each commit that looks like a new feature or significant change:
- Read the changed files to understand what was added
- Check if the feature is already documented in packages/web/src/content/docs/*
2. If you find undocumented features:
- Update the relevant documentation files in packages/web/src/content/docs/*
- Follow the existing documentation style and structure
- Make sure to document the feature clearly with examples where appropriate
3. If all new features are already documented, report that no updates are needed
4. If you are creating a new documentation file be sure to update packages/web/astro.config.mjs too.
Focus on user-facing features and API changes. Skip internal refactors, bug fixes, and test updates unless they affect user-facing behavior.
Don't feel the need to document every little thing. It is perfectly okay to make 0 changes at all.
Try to keep documentation only for large features or changes that already have a good spot to be documented.

View File

@@ -79,6 +79,12 @@ jobs:
AUR_KEY: ${{ secrets.AUR_KEY }}
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
NPM_CONFIG_PROVENANCE: false
- uses: actions/upload-artifact@v4
with:
name: opencode-cli
path: packages/opencode/dist
outputs:
release: ${{ steps.publish.outputs.release }}
tag: ${{ steps.publish.outputs.tag }}
@@ -99,6 +105,8 @@ jobs:
target: x86_64-pc-windows-msvc
- host: blacksmith-4vcpu-ubuntu-2404
target: x86_64-unknown-linux-gnu
- host: blacksmith-4vcpu-ubuntu-2404-arm
target: aarch64-unknown-linux-gnu
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v3
@@ -143,13 +151,12 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
workspaces: packages/tauri/src-tauri
workspaces: packages/desktop/src-tauri
shared-key: ${{ matrix.settings.target }}
- name: Prepare
if: inputs.bump || inputs.version
run: |
cd packages/tauri
cd packages/desktop
bun ./scripts/prepare.ts
env:
OPENCODE_VERSION: ${{ needs.publish.outputs.version }}
@@ -159,6 +166,7 @@ jobs:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
RUST_TARGET: ${{ matrix.settings.target }}
GH_TOKEN: ${{ github.token }}
GITHUB_RUN_ID: ${{ github.run_id }}
# Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released
- name: Install tauri-cli from portable appimage branch
@@ -183,10 +191,10 @@ jobs:
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
with:
projectPath: packages/tauri
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
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 }}

View File

@@ -5,8 +5,11 @@ on:
- cron: "0 12 * * *" # Run daily at 12:00 UTC
workflow_dispatch: # Allow manual trigger
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
stats:
if: github.repository == 'sst/opencode'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write

View File

@@ -2,8 +2,8 @@ name: "sync-zed-extension"
on:
workflow_dispatch:
# release:
# types: [published]
release:
types: [published]
jobs:
zed:
@@ -31,4 +31,4 @@ jobs:
run: |
./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }}
env:
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
ZED_EXTENSIONS_PAT: ${{ secrets.ZED_EXTENSIONS_PAT }}

View File

@@ -6,6 +6,8 @@ You are an expert technical documentation writer
You are not verbose
Use a relaxed and friendly tone
The title of the page should be a word or a 2-3 word phrase
The description should be one short line, should not start with "The", should

View File

@@ -0,0 +1,6 @@
---
name: test-skill
description: use this when asked to test skill
---
woah this is a test skill

View File

@@ -4,31 +4,4 @@
## Tool Calling
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE. Here is an example illustrating how to execute 3 parallel file reads in this chat environment:
json
{
"recipient_name": "multi_tool_use.parallel",
"parameters": {
"tool_uses": [
{
"recipient_name": "functions.read",
"parameters": {
"filePath": "path/to/file.tsx"
}
},
{
"recipient_name": "functions.read",
"parameters": {
"filePath": "path/to/file.ts"
}
},
{
"recipient_name": "functions.read",
"parameters": {
"filePath": "path/to/file.md"
}
}
]
}
}
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.

View File

@@ -53,12 +53,12 @@ your debugger via that URL. Other methods can result in breakpoints being mapped
Caveats:
- `*.tsx` files won't have their breakpoints correctly mapped. This seems due to Bun currently not supporting source maps on code transformed
via `BunPlugin`s (currently necessary due to our dependency on `@opentui/solid`). Currently, the best you can do in terms of debugging `*.tsx`
files is writing a `debugger;` statement. Debugging facilities like stepping won't work, but at least you will be informed if a specific code
is triggered.
- If you want to run the OpenCode TUI and have breakpoints triggered in the server code, you might need to run `bun dev spawn` instead of
the usual `bun dev`. This is because `bun dev` runs the server in a worker thread and breakpoints might not work there.
- If `spawn` does not work for you, you can debug the server separately:
- Debug server: `bun run --inspect=ws://localhost:6499/ ./src/index.ts serve --port 4096`,
then attach TUI with `opencode attach http://localhost:4096`
- Debug TUI: `bun run --inspect=ws://localhost:6499/ --conditions=browser ./src/index.ts`
Other tips and tricks:

View File

@@ -79,7 +79,7 @@ you can switch between these using the `Tab` key.
- Asks permission before running bash commands
- Ideal for exploring unfamiliar codebases or planning changes
Also, included is a **general** subagent for complex searches and multi-step tasks.
Also, included is a **general** subagent for complex searches and multistep tasks.
This is used internally and can be invoked using `@general` in messages.
Learn more about [agents](https://opencode.ai/docs/agents).
@@ -98,7 +98,7 @@ If you are working on a project that's related to OpenCode and is using "opencod
### FAQ
#### How is this different than Claude Code?
#### How is this different from Claude Code?
It's very similar to Claude Code in terms of capability. Here are the key differences:

115
README.zh-TW.md Normal file
View File

@@ -0,0 +1,115 @@
<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/sst/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/sst/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 bucket add extras; scoop install extras/opencode # Windows
choco install opencode # Windows
brew install opencode # macOS 與 Linux
paru -S opencode-bin # Arch Linux
mise use -g github:sst/opencode # 任何作業系統
nix run nixpkgs#opencode # 或使用 github:sst/opencode 以取得最新開發分支
```
> [!TIP]
> 安裝前請先移除 0.1.x 以前的舊版本。
### 桌面應用程式 (BETA)
OpenCode 也提供桌面版應用程式。您可以直接從 [發佈頁面 (releases page)](https://github.com/sst/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
```
#### 安裝目錄
安裝腳本會依據以下優先順序決定安裝路徑:
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** - 預設模式,具備完整權限的 Agent適用於開發工作。
- **plan** - 唯讀模式,適用於程式碼分析與探索。
- 預設禁止修改檔案。
- 執行 bash 指令前會詢問權限。
- 非常適合用來探索陌生的程式碼庫或規劃變更。
此外OpenCode 還包含一個 **general** 子 Agent用於處理複雜搜尋與多步驟任務。此 Agent 供系統內部使用,亦可透過在訊息中輸入 `@general` 來呼叫。
了解更多關於 [Agents](https://opencode.ai/docs/agents) 的資訊。
### 線上文件
關於如何設定 OpenCode 的詳細資訊,請參閱我們的 [**官方文件**](https://opencode.ai/docs)。
### 參與貢獻
如果您有興趣參與 OpenCode 的開發,請在提交 Pull Request 前先閱讀我們的 [貢獻指南 (Contributing Docs)](./CONTRIBUTING.md)。
### 基於 OpenCode 進行開發
如果您正在開發與 OpenCode 相關的專案,並在名稱中使用了 "opencode"(例如 "opencode-dashboard" 或 "opencode-mobile"),請在您的 README 中加入聲明,說明該專案並非由 OpenCode 團隊開發,且與我們沒有任何隸屬關係。
### 常見問題 (FAQ)
#### 這跟 Claude Code 有什麼不同?
在功能面上與 Claude Code 非常相似。以下是關鍵差異:
- 100% 開源。
- 不綁定特定的服務提供商。雖然我們推薦使用透過 [OpenCode Zen](https://opencode.ai/zen) 提供的模型,但 OpenCode 也可搭配 Claude, OpenAI, Google 甚至本地模型使用。隨著模型不斷演進,彼此間的差距會縮小且價格會下降,因此具備「不限廠商 (provider-agnostic)」的特性至關重要。
- 內建 LSP (語言伺服器協定) 支援。
- 專注於終端機介面 (TUI)。OpenCode 由 Neovim 愛好者與 [terminal.shop](https://terminal.shop) 的創作者打造;我們將不斷挑戰終端機介面的極限。
- 客戶端/伺服器架構 (Client/Server Architecture)。這讓 OpenCode 能夠在您的電腦上運行的同時,由行動裝置進行遠端操控。這意味著 TUI 前端只是眾多可能的客戶端之一。
#### 另一個同名的 Repo 是什麼?
另一個名稱相近的儲存庫與本專案無關。您可以點此[閱讀背後的故事](https://x.com/thdxr/status/1933561254481666466)。
---
**加入我們的社群** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)

View File

@@ -176,3 +176,9 @@
| 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) |

699
bun.lock

File diff suppressed because it is too large Load Diff

6
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1766025857,
"narHash": "sha256-Lav5jJazCW4mdg1iHcROpuXqmM94BWJvabLFWaJVJp0=",
"lastModified": 1766870016,
"narHash": "sha256-fHmxAesa6XNqnIkcS6+nIHuEmgd/iZSP/VXxweiEuQw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "def3da69945bbe338c373fddad5a1bb49cf199ce",
"rev": "5c2bc52fb9f8c264ed6c93bd20afa2ff5e763dce",
"type": "github"
},
"original": {

View File

@@ -574,10 +574,13 @@ async function subscribeSessionEvents() {
}
async function summarize(response: string) {
const payload = useContext().payload as IssueCommentEvent
try {
return await chat(`Summarize the following in less than 40 characters:\n\n${response}`)
} catch (e) {
if (isScheduleEvent()) {
return "Scheduled task changes"
}
const payload = useContext().payload as IssueCommentEvent
return `Fix issue: ${payload.issue.title}`
}
}

View File

@@ -44,3 +44,12 @@ new sst.cloudflare.x.Astro("Web", {
VITE_API_URL: api.url.apply((url) => url!),
},
})
new sst.cloudflare.StaticSite("WebApp", {
domain: "app." + domain,
path: "packages/app",
build: {
command: "bun turbo build",
output: "./dist",
},
})

View File

@@ -103,6 +103,7 @@ const ZEN_MODELS = [
new sst.Secret("ZEN_MODELS3"),
new sst.Secret("ZEN_MODELS4"),
new sst.Secret("ZEN_MODELS5"),
new sst.Secret("ZEN_MODELS6"),
]
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
@@ -118,6 +119,7 @@ const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
////////////////
const bucket = new sst.cloudflare.Bucket("ZenData")
const bucketNew = new sst.cloudflare.Bucket("ZenDataNew")
const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
@@ -136,6 +138,7 @@ new sst.cloudflare.x.SolidStart("Console", {
path: "packages/console/app",
link: [
bucket,
bucketNew,
database,
AUTH_API_URL,
STRIPE_WEBHOOK_SECRET,

View File

@@ -1,10 +0,0 @@
import { domain } from "./stage"
new sst.cloudflare.StaticSite("Desktop", {
domain: "desktop." + domain,
path: "packages/desktop",
build: {
command: "bun turbo build",
output: "./dist",
},
})

124
install
View File

@@ -7,7 +7,51 @@ RED='\033[0;31m'
ORANGE='\033[38;5;214m'
NC='\033[0m' # No Color
usage() {
cat <<EOF
OpenCode Installer
Usage: install.sh [options]
Options:
-h, --help Display this help message
-v, --version <version> Install a specific version (e.g., 1.0.180)
--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
EOF
}
requested_version=${VERSION:-}
no_modify_path=false
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-v|--version)
if [[ -n "${2:-}" ]]; then
requested_version="$2"
shift 2
else
echo -e "${RED}Error: --version requires a version argument${NC}"
exit 1
fi
;;
--no-modify-path)
no_modify_path=true
shift
;;
*)
echo -e "${ORANGE}Warning: Unknown option '$1'${NC}" >&2
shift
;;
esac
done
raw_os=$(uname -s)
os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
@@ -111,8 +155,18 @@ if [ -z "$requested_version" ]; then
exit 1
fi
else
# Strip leading 'v' if present
requested_version="${requested_version#v}"
url="https://github.com/sst/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/sst/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/sst/opencode/releases${NC}"
exit 1
fi
fi
print_message() {
@@ -304,42 +358,42 @@ case $current_shell in
;;
esac
config_file=""
for file in $config_files; do
if [[ -f $file ]]; then
config_file=$file
break
if [[ "$no_modify_path" != "true" ]]; then
config_file=""
for file in $config_files; do
if [[ -f $file ]]; then
config_file=$file
break
fi
done
if [[ -z $config_file ]]; then
print_message warning "No config file found for $current_shell. You may need to manually add to PATH:"
print_message info " export PATH=$INSTALL_DIR:\$PATH"
elif [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
case $current_shell in
fish)
add_to_path "$config_file" "fish_add_path $INSTALL_DIR"
;;
zsh)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
bash)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
ash)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
sh)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
*)
export PATH=$INSTALL_DIR:$PATH
print_message warning "Manually add the directory to $config_file (or similar):"
print_message info " export PATH=$INSTALL_DIR:\$PATH"
;;
esac
fi
done
if [[ -z $config_file ]]; then
print_message error "No config file found for $current_shell. Checked files: ${config_files[@]}"
exit 1
fi
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
case $current_shell in
fish)
add_to_path "$config_file" "fish_add_path $INSTALL_DIR"
;;
zsh)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
bash)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
ash)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
sh)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
*)
export PATH=$INSTALL_DIR:$PATH
print_message warning "Manually add the directory to $config_file (or similar):"
print_message info " export PATH=$INSTALL_DIR:\$PATH"
;;
esac
fi
if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then

View File

@@ -1,3 +1,3 @@
{
"nodeModules": "sha256-cpXmqJQJeFj3eED/aOb4YLUdkZFV//7u4f0STBxzUhk="
"nodeModules": "sha256-d10pu1tpwBbZ4YZqG0WcaK6W1+K2+Q0wdPjuI1rVIpY="
}

View File

@@ -31,7 +31,7 @@
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/diffs": "1.0.0-beta.3",
"@pierre/diffs": "1.0.2",
"@solid-primitives/storage": "4.3.3",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
@@ -40,10 +40,13 @@
"hono-openapi": "1.1.2",
"fuzzysort": "3.1.0",
"luxon": "3.6.1",
"marked": "17.0.1",
"marked-shiki": "1.2.1",
"typescript": "5.8.2",
"@typescript/native-preview": "7.0.0-dev.20251207.1",
"zod": "4.1.8",
"remeda": "2.26.0",
"shiki": "3.20.0",
"solid-list": "0.3.0",
"tailwindcss": "4.1.11",
"virtua": "0.42.3",
@@ -56,6 +59,7 @@
}
},
"devDependencies": {
"@actions/artifact": "5.0.1",
"@tsconfig/bun": "catalog:",
"husky": "9.1.7",
"prettier": "3.6.2",
@@ -64,6 +68,7 @@
},
"dependencies": {
"@aws-sdk/client-s3": "3.933.0",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"typescript": "catalog:"
@@ -80,7 +85,6 @@
"trustedDependencies": [
"esbuild",
"protobufjs",
"sharp",
"tree-sitter",
"tree-sitter-bash",
"web-tree-sitter"

1
packages/app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
src/assets/theme.css

34
packages/app/README.md Normal file
View File

@@ -0,0 +1,34 @@
## Usage
Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `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.
```bash
$ npm install # or pnpm install or yarn install
```
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
## Available Scripts
In the project directory, you can run:
### `npm run dev` or `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
### `npm run build`
Builds the app for production to the `dist` folder.<br>
It correctly bundles Solid in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
## Deployment
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)

View File

@@ -14,7 +14,7 @@
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
</head>
<body class="antialiased overscroll-none select-none text-12-regular overflow-hidden">
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
<script>
;(function () {
const savedTheme = localStorage.getItem("theme") || "oc-1"
@@ -23,6 +23,6 @@
</script>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" class="flex flex-col h-screen"></div>
<script src="/src/index.tsx" type="module"></script>
<script src="/src/entry.tsx" type="module"></script>
</body>
</html>

62
packages/app/package.json Normal file
View File

@@ -0,0 +1,62 @@
{
"name": "@opencode-ai/app",
"version": "1.0.203",
"description": "",
"type": "module",
"exports": {
".": "./src/index.ts",
"./vite": "./vite.js"
},
"scripts": {
"typecheck": "tsgo -b",
"start": "vite",
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
},
"license": "MIT",
"devDependencies": {
"@happy-dom/global-registrator": "20.0.11",
"@tailwindcss/vite": "catalog:",
"@tsconfig/bun": "1.0.9",
"@types/bun": "catalog:",
"@types/luxon": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plugin-icons-spritesheet": "3.0.1",
"vite-plugin-solid": "catalog:"
},
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@shikijs/transformers": "3.9.2",
"@solid-primitives/active-element": "2.1.3",
"@solid-primitives/audio": "1.4.2",
"@solid-primitives/event-bus": "1.1.2",
"@solid-primitives/media": "2.3.3",
"@solid-primitives/resize-observer": "2.1.3",
"@solid-primitives/scroll": "2.1.3",
"@solid-primitives/storage": "catalog:",
"@solid-primitives/websocket": "1.3.1",
"@solidjs/meta": "catalog:",
"@solidjs/router": "catalog:",
"@thisbeyond/solid-dnd": "0.7.5",
"diff": "catalog:",
"fuzzysort": "catalog:",
"ghostty-web": "0.3.0",
"luxon": "catalog:",
"marked": "catalog:",
"marked-shiki": "catalog:",
"remeda": "catalog:",
"shiki": "catalog:",
"solid-js": "catalog:",
"solid-list": "catalog:",
"tailwindcss": "catalog:",
"virtua": "catalog:",
"zod": "catalog:"
}
}

View File

@@ -0,0 +1,17 @@
/assets/*.js
Content-Type: application/javascript
/assets/*.mjs
Content-Type: application/javascript
/assets/*.css
Content-Type: text/css
/*.js
Content-Type: application/javascript
/*.mjs
Content-Type: application/javascript
/*.css
Content-Type: text/css

View File

@@ -21,6 +21,7 @@ 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"
declare global {
interface Window {
@@ -28,14 +29,17 @@ declare global {
}
}
const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
const port = window.__OPENCODE__?.port ?? import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
const url = iife(() => {
const param = new URLSearchParams(document.location.search).get("url")
if (param) return param
const url =
new URLSearchParams(document.location.search).get("url") ||
(location.hostname.includes("opencode.ai") || location.hostname.includes("localhost")
? `http://${host}:${port}`
: "/")
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 App() {
return (

View File

@@ -1,24 +1,24 @@
import type { ProviderAuthAuthorization } from "@opencode-ai/sdk/v2/client"
import { Button } from "@opencode-ai/ui/button"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
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 { Spinner } from "@opencode-ai/ui/spinner"
import { TextField } from "@opencode-ai/ui/text-field"
import { showToast } from "@opencode-ai/ui/toast"
import { iife } from "@opencode-ai/util/iife"
import { createMemo, Match, onCleanup, onMount, Switch } from "solid-js"
import { createStore, produce } from "solid-js/store"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useGlobalSync } from "@/context/global-sync"
import { useGlobalSDK } from "@/context/global-sdk"
import { usePlatform } from "@/context/platform"
import { ProviderAuthAuthorization } from "@opencode-ai/sdk/v2/client"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List, ListRef } from "@opencode-ai/ui/list"
import { Button } from "@opencode-ai/ui/button"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { TextField } from "@opencode-ai/ui/text-field"
import { Spinner } from "@opencode-ai/ui/spinner"
import { Icon } from "@opencode-ai/ui/icon"
import { showToast } from "@opencode-ai/ui/toast"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { IconName } from "@opencode-ai/ui/icons/provider"
import { iife } from "@opencode-ai/util/iife"
import { Link } from "@/components/link"
import { DialogSelectProvider } from "./dialog-select-provider"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
import { usePlatform } from "@/context/platform"
import { DialogSelectModel } from "./dialog-select-model"
import { DialogSelectProvider } from "./dialog-select-provider"
export function DialogConnectProvider(props: { provider: string }) {
const dialog = useDialog()
@@ -154,7 +154,9 @@ export function DialogConnectProvider(props: { provider: string }) {
<div class="text-14-regular text-text-base">Select login method for {provider().name}.</div>
<div class="">
<List
ref={(ref) => (listRef = ref)}
ref={(ref) => {
listRef = ref
}}
items={methods}
key={(m) => m?.label}
onSelect={async (method, index) => {
@@ -163,7 +165,7 @@ export function DialogConnectProvider(props: { provider: string }) {
}}
>
{(i) => (
<div class="w-full flex items-center gap-x-4">
<div class="w-full flex items-center gap-x-2">
<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>
@@ -175,7 +177,7 @@ export function DialogConnectProvider(props: { provider: string }) {
</Match>
<Match when={store.state === "pending"}>
<div class="text-14-regular text-text-base">
<div class="flex items-center gap-x-4">
<div class="flex items-center gap-x-2">
<Spinner />
<span>Authorization in progress...</span>
</div>
@@ -183,7 +185,7 @@ export function DialogConnectProvider(props: { provider: string }) {
</Match>
<Match when={store.state === "error"}>
<div class="text-14-regular text-text-base">
<div class="flex items-center gap-x-4">
<div class="flex items-center gap-x-2">
<Icon name="circle-ban-sign" class="text-icon-critical-base" />
<span>Authorization failed: {store.error}</span>
</div>

View File

@@ -1,16 +1,15 @@
import { Component } from "solid-js"
import { useLocal } from "@/context/local"
import { popularProviders } from "@/hooks/use-providers"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { Switch } from "@opencode-ai/ui/switch"
import type { Component } from "solid-js"
import { useLocal } from "@/context/local"
import { popularProviders } from "@/hooks/use-providers"
export const DialogManageModels: Component = () => {
const local = useLocal()
return (
<Dialog title="Manage models" description="Customize which models appear in the model selector.">
<List
class="px-2.5"
search={{ placeholder: "Search models", autofocus: true }}
emptyMessage="No model results"
key={(x) => `${x?.provider?.id}:${x?.id}`}
@@ -27,16 +26,24 @@ export const DialogManageModels: Component = () => {
}}
onSelect={(x) => {
if (!x) return
const visible = local.model.visible({ modelID: x.id, providerID: x.provider.id })
const visible = local.model.visible({
modelID: x.id,
providerID: x.provider.id,
})
local.model.setVisibility({ modelID: x.id, providerID: x.provider.id }, !visible)
}}
>
{(i) => (
<div class="w-full flex items-center justify-between gap-x-2.5">
<div class="w-full flex items-center justify-between gap-x-3">
<span>{i.name}</span>
<div onClick={(e) => e.stopPropagation()}>
<Switch
checked={!!local.model.visible({ modelID: i.id, providerID: i.provider.id })}
checked={
!!local.model.visible({
modelID: i.id,
providerID: i.provider.id,
})
}
onChange={(checked) => {
local.model.setVisibility({ modelID: i.id, providerID: i.provider.id }, checked)
}}

View File

@@ -1,12 +1,12 @@
import { useLocal } from "@/context/local"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { useLayout } from "@/context/layout"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { FileIcon } from "@opencode-ai/ui/file-icon"
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 { useLayout } from "@/context/layout"
import { useLocal } from "@/context/local"
export function DialogSelectFile() {
const layout = useLayout()
@@ -18,7 +18,6 @@ export function DialogSelectFile() {
return (
<Dialog title="Select file">
<List
class="px-2.5"
search={{ placeholder: "Search files", autofocus: true }}
emptyMessage="No files found"
items={local.file.searchFiles}
@@ -32,7 +31,7 @@ export function DialogSelectFile() {
>
{(i) => (
<div class="w-full flex items-center justify-between rounded-md">
<div class="flex items-center gap-x-2 grow min-w-0">
<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">

View File

@@ -0,0 +1,91 @@
import { Component, createMemo, createSignal, Show } from "solid-js"
import { useSync } from "@/context/sync"
import { useSDK } from "@/context/sdk"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { Switch } from "@opencode-ai/ui/switch"
export const DialogSelectMcp: Component = () => {
const sync = useSync()
const sdk = useSDK()
const [loading, setLoading] = createSignal<string | null>(null)
const items = createMemo(() =>
Object.entries(sync.data.mcp ?? {})
.map(([name, status]) => ({ name, status: status.status }))
.sort((a, b) => a.name.localeCompare(b.name)),
)
const toggle = async (name: string) => {
if (loading()) return
setLoading(name)
const status = sync.data.mcp[name]
if (status?.status === "connected") {
await sdk.client.mcp.disconnect({ name })
} else {
await sdk.client.mcp.connect({ name })
}
const result = await sdk.client.mcp.status()
if (result.data) sync.set("mcp", result.data)
setLoading(null)
}
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)
const totalCount = createMemo(() => items().length)
return (
<Dialog title="MCPs" description={`${enabledCount()} of ${totalCount()} enabled`}>
<List
search={{ placeholder: "Search", autofocus: true }}
emptyMessage="No MCPs configured"
key={(x) => x?.name ?? ""}
items={items}
filterKeys={["name", "status"]}
sortBy={(a, b) => a.name.localeCompare(b.name)}
onSelect={(x) => {
if (x) toggle(x.name)
}}
>
{(i) => {
const mcpStatus = () => sync.data.mcp[i.name]
const status = () => mcpStatus()?.status
const error = () => {
const s = mcpStatus()
return s?.status === "failed" ? s.error : undefined
}
const enabled = () => status() === "connected"
return (
<div class="w-full flex items-center justify-between gap-x-3">
<div class="flex flex-col gap-0.5 min-w-0">
<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>
</Show>
<Show when={status() === "failed"}>
<span class="text-11-regular text-text-weaker">failed</span>
</Show>
<Show when={status() === "needs_auth"}>
<span class="text-11-regular text-text-weaker">needs auth</span>
</Show>
<Show when={status() === "disabled"}>
<span class="text-11-regular text-text-weaker">disabled</span>
</Show>
<Show when={loading() === i.name}>
<span class="text-11-regular text-text-weak">...</span>
</Show>
</div>
<Show when={error()}>
<span class="text-11-regular text-text-weaker truncate">{error()}</span>
</Show>
</div>
<div onClick={(e) => e.stopPropagation()}>
<Switch checked={enabled()} disabled={loading() === i.name} onChange={() => toggle(i.name)} />
</div>
</div>
)
}}
</List>
</Dialog>
)
}

View File

@@ -1,15 +1,15 @@
import { Component, onCleanup, onMount, Show } from "solid-js"
import { useLocal } from "@/context/local"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { popularProviders, useProviders } from "@/hooks/use-providers"
import { Button } from "@opencode-ai/ui/button"
import { Tag } from "@opencode-ai/ui/tag"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List, ListRef } from "@opencode-ai/ui/list"
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 { IconName } from "@opencode-ai/ui/icons/provider"
import { DialogSelectProvider } from "./dialog-select-provider"
import { Tag } from "@opencode-ai/ui/tag"
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"
export const DialogSelectModelUnpaid: Component = () => {
const local = useLocal()
@@ -64,7 +64,7 @@ export const DialogSelectModelUnpaid: Component = () => {
<div class="px-2 text-14-medium text-text-base">Add more models from popular providers</div>
<div class="w-full">
<List
class="w-full"
class="w-full px-0"
key={(x) => x?.id}
items={providers.popular}
activeIcon="plus-small"
@@ -79,17 +79,8 @@ export const DialogSelectModelUnpaid: Component = () => {
}}
>
{(i) => (
<div class="w-full flex items-center gap-x-4">
<ProviderIcon
data-slot="list-item-extra-icon"
id={i.id as IconName}
// TODO: clean this up after we update icon in models.dev
classList={{
"text-icon-weak-base": true,
"size-4 mx-0.5": i.id === "opencode",
"size-5": i.id !== "opencode",
}}
/>
<div class="w-full flex items-center gap-x-3">
<ProviderIcon data-slot="list-item-extra-icon" id={i.id as IconName} />
<span>{i.name}</span>
<Show when={i.id === "opencode"}>
<Tag>Recommended</Tag>

View File

@@ -35,7 +35,6 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
}
>
<List
class="px-2.5"
search={{ placeholder: "Search models", autofocus: true }}
emptyMessage="No model results"
key={(x) => `${x.provider.id}:${x.id}`}
@@ -61,7 +60,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
}}
>
{(i) => (
<div class="w-full flex items-center gap-x-2.5">
<div class="w-full flex items-center gap-x-3">
<span>{i.name}</span>
<Show when={i.provider.id === "opencode" && (!i.cost || i.cost?.input === 0)}>
<Tag>Free</Tag>

View File

@@ -15,7 +15,6 @@ export const DialogSelectProvider: Component = () => {
return (
<Dialog title="Connect provider">
<List
class="px-2.5"
search={{ placeholder: "Search providers", autofocus: true }}
activeIcon="plus-small"
key={(x) => x?.id}
@@ -38,17 +37,8 @@ export const DialogSelectProvider: Component = () => {
}}
>
{(i) => (
<div class="px-1.25 w-full flex items-center gap-x-4">
<ProviderIcon
data-slot="list-item-extra-icon"
id={i.id as IconName}
// TODO: clean this up after we update icon in models.dev
classList={{
"text-icon-weak-base": true,
"size-4 mx-0.5": i.id === "opencode",
"size-5": i.id !== "opencode",
}}
/>
<div class="px-1.25 w-full flex items-center gap-x-3">
<ProviderIcon data-slot="list-item-extra-icon" id={i.id as IconName} />
<span>{i.name}</span>
<Show when={i.id === "opencode"}>
<Tag>Recommended</Tag>

View File

@@ -2,7 +2,7 @@ import { useLocal, type LocalFile } from "@/context/local"
import { Collapsible } from "@opencode-ai/ui/collapsible"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { For, Match, Switch, Show, type ComponentProps, type ParentProps } from "solid-js"
import { For, Match, Switch, type ComponentProps, type ParentProps } from "solid-js"
import { Dynamic } from "solid-js/web"
export default function FileTree(props: {
@@ -57,14 +57,14 @@ export default function FileTree(props: {
"text-text-muted/40": p.node.ignored,
"text-text-muted/80": !p.node.ignored,
// "!text-text": local.file.active()?.path === p.node.path,
"!text-primary": local.file.changed(p.node.path),
// "!text-primary": local.file.changed(p.node.path),
}}
>
{p.node.name}
</span>
<Show when={local.file.changed(p.node.path)}>
<span class="ml-auto mr-1 w-1.5 h-1.5 rounded-full bg-primary/50 shrink-0" />
</Show>
{/* <Show when={local.file.changed(p.node.path)}> */}
{/* <span class="ml-auto mr-1 w-1.5 h-1.5 rounded-full bg-primary/50 shrink-0" /> */}
{/* </Show> */}
</Dynamic>
)

View File

@@ -10,6 +10,7 @@ import { Select } from "@opencode-ai/ui/select"
import { TextField } from "@opencode-ai/ui/text-field"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { base64Decode } from "@opencode-ai/util/encode"
import { useCommand } from "@/context/command"
import { getFilename } from "@opencode-ai/util/path"
import { A, useParams } from "@solidjs/router"
import { createMemo, createResource, Show } from "solid-js"
@@ -19,19 +20,29 @@ import { iife } from "@opencode-ai/util/iife"
export function Header(props: {
navigateToProject: (directory: string) => void
navigateToSession: (session: Session | undefined) => void
onMobileMenuToggle?: () => void
}) {
const globalSync = useGlobalSync()
const globalSDK = useGlobalSDK()
const layout = useLayout()
const params = useParams()
const command = useCommand()
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={props.onMobileMenuToggle}
>
<Icon name="menu" size="small" />
</button>
<A
href="/"
classList={{
"hidden xl:flex": true,
"w-12 shrink-0 px-4 py-3.5": true,
"flex items-center justify-start self-stretch": true,
"items-center justify-start self-stretch": true,
"border-r border-border-weak-base": true,
}}
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
@@ -44,30 +55,32 @@ export function Header(props: {
{(directory) => {
const currentDirectory = createMemo(() => base64Decode(directory()))
const store = createMemo(() => globalSync.child(currentDirectory())[0])
const sessions = createMemo(() => store().session ?? [])
const sessions = createMemo(() => (store().session ?? []).filter((s) => !s.parentID))
const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
const shareEnabled = createMemo(() => store().config.share !== "disabled")
return (
<>
<div class="flex items-center gap-3">
<div class="flex items-center gap-2">
<Select
options={layout.projects.list().map((project) => project.worktree)}
current={currentDirectory()}
label={(x) => getFilename(x)}
onSelect={(x) => (x ? props.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 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={currentDirectory()}
label={(x) => getFilename(x)}
onSelect={(x) => (x ? props.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()}
@@ -75,23 +88,64 @@ export function Header(props: {
label={(x) => x.title}
value={(x) => x.id}
onSelect={props.navigateToSession}
class="text-14-regular text-text-base max-w-md"
class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md"
variant="ghost"
/>
</div>
<Show when={currentSession()}>
<Button as={A} href={`/${params.dir}/session`} icon="plus-small">
New session
</Button>
<Tooltip
class="hidden xl:block"
value={
<div class="flex items-center gap-2">
<span>New session</span>
<span class="text-icon-base text-12-medium">{command.keybind("session.new")}</span>
</div>
}
>
<Button as={A} href={`/${params.dir}/session`} icon="plus-small">
New session
</Button>
</Tooltip>
</Show>
</div>
<div class="flex items-center gap-4">
<Show when={currentSession()?.summary?.files}>
<Tooltip
class="hidden md:block shrink-0"
value={
<div class="flex items-center gap-2">
<span>Toggle review</span>
<span class="text-icon-base text-12-medium">{command.keybind("review.toggle")}</span>
</div>
}
>
<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>
</Tooltip>
</Show>
<Tooltip
class="shrink-0"
class="hidden md:block shrink-0"
value={
<div class="flex items-center gap-2">
<span>Toggle terminal</span>
<span class="text-icon-base text-12-medium">Ctrl `</span>
<span class="text-icon-base text-12-medium">{command.keybind("terminal.toggle")}</span>
</div>
}
>

View File

@@ -19,9 +19,10 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
import { DialogSelectModel } from "@/components/dialog-select-model"
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
import { useProviders } from "@/hooks/use-providers"
import { useCommand, formatKeybind } from "@/context/command"
import { useCommand } from "@/context/command"
import { persisted } from "@/utils/persist"
import { Identifier } from "@/utils/id"
import { SessionContextUsage } from "@/components/session-context-usage"
const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"]
const ACCEPTED_FILE_TYPES = [...ACCEPTED_IMAGE_TYPES, "application/pdf"]
@@ -102,7 +103,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
imageAttachments: ImageAttachmentPart[]
mode: "normal" | "shell"
applyingHistory: boolean
userHasEdited: boolean
}>({
popover: null,
historyIndex: -1,
@@ -112,7 +112,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
imageAttachments: [],
mode: "normal",
applyingHistory: false,
userHasEdited: false,
})
const MAX_HISTORY = 100
@@ -149,7 +148,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const applyHistoryPrompt = (p: Prompt, position: "start" | "end") => {
const length = position === "start" ? 0 : promptLength(p)
setStore("applyingHistory", true)
setStore("userHasEdited", false)
prompt.set(p, length)
requestAnimationFrame(() => {
editorRef.focus()
@@ -278,11 +276,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
})
createEffect(() => {
if (isFocused()) {
handleInput()
} else {
setStore("popover", null)
}
if (!isFocused()) setStore("popover", null)
})
const handleFileSelect = (path: string | undefined) => {
@@ -362,7 +356,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
() => prompt.current(),
(currentParts) => {
const domParts = parseFromDOM()
if (isPromptEqual(currentParts, domParts)) return
const normalized = Array.from(editorRef.childNodes).every((node) => {
if (node.nodeType === Node.TEXT_NODE) return true
if (node.nodeType !== Node.ELEMENT_NODE) return false
return (node as HTMLElement).dataset.type === "file"
})
if (normalized && isPromptEqual(currentParts, domParts)) return
const selection = window.getSelection()
let cursorPosition: number | null = null
@@ -394,34 +393,21 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
)
const parseFromDOM = (): Prompt => {
const newParts: Prompt = []
const parts: Prompt = []
let position = 0
let buffer = ""
const pushText = (content: string) => {
const flushText = () => {
const content = buffer.replace(/\r\n?/g, "\n")
buffer = ""
if (!content) return
newParts.push({ type: "text", content, start: position, end: position + content.length })
parts.push({ type: "text", content, start: position, end: position + content.length })
position += content.length
}
const rangeText = (range: Range) => {
const fragment = range.cloneContents()
const container = document.createElement("div")
container.append(fragment)
return container.innerText
}
const files = Array.from(editorRef.querySelectorAll<HTMLElement>("[data-type=file]"))
let last: HTMLElement | undefined
files.forEach((file) => {
const before = document.createRange()
before.selectNodeContents(editorRef)
if (last) before.setStartAfter(last)
before.setEndBefore(file)
pushText(rangeText(before))
const pushFile = (file: HTMLElement) => {
const content = file.textContent ?? ""
newParts.push({
parts.push({
type: "file",
path: file.dataset.path!,
content,
@@ -429,16 +415,44 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
end: position + content.length,
})
position += content.length
last = file
}
const visit = (node: Node) => {
if (node.nodeType === Node.TEXT_NODE) {
buffer += node.textContent ?? ""
return
}
if (node.nodeType !== Node.ELEMENT_NODE) return
const el = node as HTMLElement
if (el.dataset.type === "file") {
flushText()
pushFile(el)
return
}
if (el.tagName === "BR") {
buffer += "\n"
return
}
for (const child of Array.from(el.childNodes)) {
visit(child)
}
}
const children = Array.from(editorRef.childNodes)
children.forEach((child, index) => {
const isBlock = child.nodeType === Node.ELEMENT_NODE && ["DIV", "P"].includes((child as HTMLElement).tagName)
visit(child)
if (isBlock && index < children.length - 1) {
buffer += "\n"
}
})
const after = document.createRange()
after.selectNodeContents(editorRef)
if (last) after.setStartAfter(last)
pushText(rangeText(after))
flushText()
if (newParts.length === 0) newParts.push(...DEFAULT_PROMPT)
return newParts
if (parts.length === 0) parts.push(...DEFAULT_PROMPT)
return parts
}
const handleInput = () => {
@@ -451,7 +465,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (shouldReset) {
setStore("popover", null)
setStore("userHasEdited", false)
if (store.historyIndex >= 0 && !store.applyingHistory) {
setStore("historyIndex", -1)
setStore("savedPrompt", null)
@@ -486,10 +499,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
setStore("savedPrompt", null)
}
if (!store.applyingHistory) {
setStore("userHasEdited", true)
}
prompt.set(rawParts, cursorPosition)
}
@@ -515,27 +524,39 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const gap = document.createTextNode(" ")
const range = selection.getRangeAt(0)
if (atMatch) {
let runningLength = 0
const setEdge = (edge: "start" | "end", offset: number) => {
let remaining = offset
const nodes = Array.from(editorRef.childNodes)
const walker = document.createTreeWalker(editorRef, NodeFilter.SHOW_TEXT, null)
let currentNode = walker.nextNode()
while (currentNode) {
const textContent = currentNode.textContent || ""
if (runningLength + textContent.length >= atMatch.index!) {
const localStart = atMatch.index! - runningLength
const localEnd = cursorPosition - runningLength
if (currentNode === range.startContainer || runningLength + textContent.length >= cursorPosition) {
range.setStart(currentNode, localStart)
range.setEnd(currentNode, Math.min(localEnd, textContent.length))
break
}
for (const node of nodes) {
const length = node.textContent?.length ?? 0
const isText = node.nodeType === Node.TEXT_NODE
const isFile = node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).dataset.type === "file"
if (isText && remaining <= length) {
if (edge === "start") range.setStart(node, remaining)
if (edge === "end") range.setEnd(node, remaining)
return
}
runningLength += textContent.length
currentNode = walker.nextNode()
if (isFile && remaining <= length) {
if (edge === "start" && remaining === 0) range.setStartBefore(node)
if (edge === "start" && remaining > 0) range.setStartAfter(node)
if (edge === "end" && remaining === 0) range.setEndBefore(node)
if (edge === "end" && remaining > 0) range.setEndAfter(node)
return
}
remaining -= length
}
}
if (atMatch) {
const start = atMatch.index ?? cursorPosition - atMatch[0].length
setEdge("start", start)
setEdge("end", cursorPosition)
}
range.deleteContents()
range.insertNode(gap)
range.insertNode(pill)
@@ -583,8 +604,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}
const navigateHistory = (direction: "up" | "down") => {
if (store.userHasEdited) return false
const entries = store.mode === "shell" ? shellHistory.entries : history.entries
const current = store.historyIndex
@@ -691,6 +710,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
return
}
if (event.key === "Enter" && event.shiftKey) {
addPart({ type: "text", content: "\n", start: 0, end: 0 })
event.preventDefault()
return
}
if (event.key === "Enter" && !event.shiftKey) {
handleSubmit(event)
}
@@ -716,7 +740,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
addToHistory(currentPrompt, store.mode)
setStore("historyIndex", -1)
setStore("savedPrompt", null)
setStore("userHasEdited", false)
let existing = info()
if (!existing) {
@@ -889,8 +912,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
custom
</span>
</Show>
<Show when={cmd.keybind}>
<span class="text-12-regular text-text-subtle">{formatKeybind(cmd.keybind!)}</span>
<Show when={command.keybind(cmd.id)}>
<span class="text-12-regular text-text-subtle">{command.keybind(cmd.id)}</span>
</Show>
</div>
</button>
@@ -966,18 +989,21 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
onInput={handleInput}
onKeyDown={handleKeyDown}
classList={{
"w-full px-5 py-3 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
"[&>[data-type=file]]:text-icon-info-active": true,
"w-full px-5 py-3 pr-12 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
"[&_[data-type=file]]:text-icon-info-active": true,
"font-mono!": store.mode === "shell",
}}
/>
<Show when={!prompt.dirty() && store.imageAttachments.length === 0}>
<div class="absolute top-0 left-0 px-5 py-3 text-14-regular text-text-weak pointer-events-none">
<div class="absolute top-0 inset-x-0 px-5 py-3 pr-12 text-14-regular text-text-weak pointer-events-none whitespace-nowrap truncate">
{store.mode === "shell"
? "Enter shell command..."
: `Ask anything... "${PLACEHOLDERS[store.placeholder]}"`}
</div>
</Show>
<div class="absolute top-4.5 right-4">
<SessionContextUsage />
</div>
</div>
<div class="relative p-3 flex items-center justify-between">
<div class="flex items-center justify-start gap-1">
@@ -990,26 +1016,48 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</div>
</Match>
<Match when={store.mode === "normal"}>
<Select
options={local.agent.list().map((agent) => agent.name)}
current={local.agent.current().name}
onSelect={local.agent.set}
class="capitalize"
variant="ghost"
/>
<Button
as="div"
variant="ghost"
onClick={() =>
dialog.show(() =>
providers.paid().length > 0 ? <DialogSelectModel /> : <DialogSelectModelUnpaid />,
)
<Tooltip
placement="top"
value={
<div class="flex items-center gap-2">
<span>Cycle agent</span>
<span class="text-icon-base text-12-medium">{command.keybind("agent.cycle")}</span>
</div>
}
>
{local.model.current()?.name ?? "Select model"}
<span class="ml-0.5 text-text-weak text-12-regular">{local.model.current()?.provider.name}</span>
<Icon name="chevron-down" size="small" />
</Button>
<Select
options={local.agent.list().map((agent) => agent.name)}
current={local.agent.current().name}
onSelect={local.agent.set}
class="capitalize"
variant="ghost"
/>
</Tooltip>
<Tooltip
placement="top"
value={
<div class="flex items-center gap-2">
<span>Choose model</span>
<span class="text-icon-base text-12-medium">{command.keybind("model.choose")}</span>
</div>
}
>
<Button
as="div"
variant="ghost"
onClick={() =>
dialog.show(() =>
providers.paid().length > 0 ? <DialogSelectModel /> : <DialogSelectModelUnpaid />,
)
}
>
{local.model.current()?.name ?? "Select model"}
<span class="hidden md:block ml-0.5 text-text-weak text-12-regular">
{local.model.current()?.provider.name}
</span>
<Icon name="chevron-down" size="small" />
</Button>
</Tooltip>
</Match>
</Switch>
</div>

View File

@@ -0,0 +1,63 @@
import { createMemo, Show } from "solid-js"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
import { useSync } from "@/context/sync"
import { useParams } from "@solidjs/router"
import { AssistantMessage } from "@opencode-ai/sdk/v2"
export function SessionContextUsage() {
const sync = useSync()
const params = useParams()
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
const cost = createMemo(() => {
const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(total)
})
const context = createMemo(() => {
const last = messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage
if (!last) return
const total =
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(),
percentage: model?.limit.context ? Math.round((total / model.limit.context) * 100) : null,
}
})
return (
<Show when={context?.()}>
{(ctx) => (
<Tooltip
value={
<div class="flex flex-col gap-1">
<div class="flex gap-3">
<span class="opacity-70 text-right flex-1">Tokens</span>
<span class="text-left flex-1">{ctx().tokens}</span>
</div>
<div class="flex gap-3">
<span class="opacity-70 text-right flex-1">Usage</span>
<span class="text-left flex-1">{ctx().percentage ?? 0}%</span>
</div>
<div class="flex gap-3">
<span class="opacity-70 text-right flex-1">Cost</span>
<span class="text-left flex-1">{cost()}</span>
</div>
</div>
}
placement="top"
>
<div class="flex items-center gap-1.5">
<ProgressCircle size={16} strokeWidth={2} percentage={ctx().percentage ?? 0} />
{/* <span class="text-12-medium text-text-weak">{`${ctx().percentage ?? 0}%`}</span> */}
</div>
</Tooltip>
)}
</Show>
)
}

View File

@@ -0,0 +1,40 @@
import { createMemo, Show } from "solid-js"
import { Icon } from "@opencode-ai/ui/icon"
import { useSync } from "@/context/sync"
import { Tooltip } from "@opencode-ai/ui/tooltip"
export function SessionLspIndicator() {
const sync = useSync()
const lspStats = createMemo(() => {
const lsp = sync.data.lsp ?? []
const connected = lsp.filter((s) => s.status === "connected").length
const hasError = lsp.some((s) => s.status === "error")
const total = lsp.length
return { connected, hasError, total }
})
const tooltipContent = createMemo(() => {
const lsp = sync.data.lsp ?? []
if (lsp.length === 0) return "No LSP servers"
return lsp.map((s) => s.name).join(", ")
})
return (
<Show when={lspStats().total > 0}>
<Tooltip placement="top" value={tooltipContent()}>
<div class="flex items-center gap-1 px-2 cursor-default select-none">
<Icon
name="code"
size="small"
classList={{
"text-icon-critical-base": lspStats().hasError,
"text-icon-success-base": !lspStats().hasError && lspStats().connected > 0,
}}
/>
<span class="text-12-regular text-text-weak">{lspStats().connected} LSP</span>
</div>
</Tooltip>
</Show>
)
}

View File

@@ -0,0 +1,36 @@
import { createMemo, Show } from "solid-js"
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useSync } from "@/context/sync"
import { DialogSelectMcp } from "@/components/dialog-select-mcp"
export function SessionMcpIndicator() {
const sync = useSync()
const dialog = useDialog()
const mcpStats = createMemo(() => {
const mcp = sync.data.mcp ?? {}
const entries = Object.entries(mcp)
const enabled = entries.filter(([, status]) => status.status === "connected").length
const failed = entries.some(([, status]) => status.status === "failed")
const total = entries.length
return { enabled, failed, total }
})
return (
<Show when={mcpStats().total > 0}>
<Button variant="ghost" onClick={() => dialog.show(() => <DialogSelectMcp />)}>
<Icon
name="mcp"
size="small"
classList={{
"text-icon-critical-base": mcpStats().failed,
"text-icon-success-base": !mcpStats().failed && mcpStats().enabled > 0,
}}
/>
<span class="text-12-regular text-text-weak">{mcpStats().enabled} MCP</span>
</Button>
</Show>
)
}

View File

@@ -0,0 +1,14 @@
import { Show, type ParentProps } from "solid-js"
import { usePlatform } from "@/context/platform"
export function StatusBar(props: ParentProps) {
const platform = usePlatform()
return (
<div class="h-8 w-full shrink-0 flex items-center justify-between px-2 border-t border-border-weak-base bg-background-base">
<Show when={platform.version}>
<span class="text-12-regular text-text-weak">v{platform.version}</span>
</Show>
<div class="flex items-center">{props.children}</div>
</div>
)
}

View File

@@ -119,7 +119,6 @@ function DialogCommand(props: { options: CommandOption[] }) {
return (
<Dialog title="Commands">
<List
class="px-2.5"
search={{ placeholder: "Search commands", autofocus: true }}
emptyMessage="No commands found"
items={() => props.options.filter((x) => !x.id.startsWith("suggested.") || !x.disabled)}
@@ -226,6 +225,11 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
}
}
},
keybind(id: string) {
const option = options().find((x) => x.id === id || x.id === "suggested." + id)
if (!option?.keybind) return ""
return formatKeybind(option.keybind)
},
show: showPalette,
keybinds(enabled: boolean) {
setSuspendCount((count) => count + (enabled ? -1 : 1))

View File

@@ -5,8 +5,6 @@ import {
type Part,
type Config,
type Path,
type File,
type FileNode,
type Project,
type FileDiff,
type Todo,
@@ -14,6 +12,8 @@ import {
type ProviderListResponse,
type ProviderAuthResponse,
type Command,
type McpStatus,
type LspStatus,
createOpencodeClient,
} from "@opencode-ai/sdk/v2/client"
import { createStore, produce, reconcile } from "solid-js/store"
@@ -43,6 +43,10 @@ type State = {
todo: {
[sessionID: string]: Todo[]
}
mcp: {
[name: string]: McpStatus
}
lsp: LspStatus[]
limit: number
message: {
[sessionID: string]: Message[]
@@ -50,8 +54,6 @@ type State = {
part: {
[messageID: string]: Part[]
}
node: FileNode[]
changes: File[]
}
function createGlobalSync() {
@@ -89,11 +91,11 @@ function createGlobalSync() {
session_status: {},
session_diff: {},
todo: {},
mcp: {},
lsp: [],
limit: 5,
message: {},
part: {},
node: [],
changes: [],
})
children[directory] = createStore(globalStore.children[directory])
bootstrapInstance(directory)
@@ -136,15 +138,27 @@ function createGlobalSync() {
})
const load = {
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
provider: () => sdk.provider.list().then((x) => setStore("provider", x.data!)),
provider: () =>
sdk.provider.list().then((x) => {
const data = x.data!
setStore("provider", {
...data,
all: data.all.map((provider) => ({
...provider,
models: Object.fromEntries(
Object.entries(provider.models).filter(([, info]) => info.status !== "deprecated"),
),
})),
})
}),
path: () => sdk.path.get().then((x) => setStore("path", x.data!)),
agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
command: () => sdk.command.list().then((x) => setStore("command", x.data ?? [])),
session: () => loadSessions(directory),
status: () => sdk.session.status().then((x) => setStore("session_status", x.data!)),
config: () => sdk.config.get().then((x) => setStore("config", x.data!)),
changes: () => sdk.file.status().then((x) => setStore("changes", x.data!)),
node: () => sdk.file.list({ path: "/" }).then((x) => setStore("node", x.data!)),
mcp: () => sdk.mcp.status().then((x) => setStore("mcp", x.data ?? {})),
lsp: () => sdk.lsp.status().then((x) => setStore("lsp", x.data ?? [])),
}
await Promise.all(Object.values(load).map((p) => retry(p).catch((e) => setGlobalStore("error", e))))
.then(() => setStore("ready", true))
@@ -211,13 +225,13 @@ function createGlobalSync() {
break
}
case "session.diff":
setStore("session_diff", event.properties.sessionID, event.properties.diff)
setStore("session_diff", event.properties.sessionID, reconcile(event.properties.diff, { key: "file" }))
break
case "todo.updated":
setStore("todo", event.properties.sessionID, event.properties.todos)
setStore("todo", event.properties.sessionID, reconcile(event.properties.todos))
break
case "session.status": {
setStore("session_status", event.properties.sessionID, event.properties.status)
setStore("session_status", event.properties.sessionID, reconcile(event.properties.status))
break
}
case "message.updated": {
@@ -295,6 +309,18 @@ function createGlobalSync() {
})
async function bootstrap() {
const health = await globalSDK.client.global
.health()
.then((x) => x.data)
.catch(() => undefined)
if (!health?.healthy) {
setGlobalStore(
"error",
new Error(`Could not connect to server. Is there a server running at \`${globalSDK.url}\`?`),
)
return
}
return Promise.all([
retry(() =>
globalSDK.client.path.get().then((x) => {
@@ -311,7 +337,16 @@ function createGlobalSync() {
),
retry(() =>
globalSDK.client.provider.list().then((x) => {
setGlobalStore("provider", x.data ?? {})
const data = x.data!
setGlobalStore("provider", {
...data,
all: data.all.map((provider) => ({
...provider,
models: Object.fromEntries(
Object.entries(provider.models).filter(([, info]) => info.status !== "deprecated"),
),
})),
})
}),
),
retry(() =>

View File

@@ -47,7 +47,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
height: 280,
},
review: {
state: "pane" as "pane" | "tab",
opened: true,
},
session: {
width: 600,
},
sessionTabs: {} as Record<string, SessionTabs>,
}),
@@ -108,10 +111,12 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setStore("projects", (x) => x.filter((x) => x.worktree !== directory))
},
expand(directory: string) {
setStore("projects", (x) => x.map((x) => (x.worktree === directory ? { ...x, expanded: true } : x)))
const index = store.projects.findIndex((x) => x.worktree === directory)
if (index !== -1) setStore("projects", index, "expanded", true)
},
collapse(directory: string) {
setStore("projects", (x) => x.map((x) => (x.worktree === directory ? { ...x, expanded: false } : x)))
const index = store.projects.findIndex((x) => x.worktree === directory)
if (index !== -1) setStore("projects", index, "expanded", false)
},
move(directory: string, toIndex: number) {
setStore("projects", (projects) => {
@@ -157,12 +162,25 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
},
review: {
state: createMemo(() => store.review?.state ?? "closed"),
pane() {
setStore("review", "state", "pane")
opened: createMemo(() => store.review?.opened ?? true),
open() {
setStore("review", "opened", true)
},
tab() {
setStore("review", "state", "tab")
close() {
setStore("review", "opened", false)
},
toggle() {
setStore("review", "opened", (x) => !x)
},
},
session: {
width: createMemo(() => store.session?.width ?? 600),
resize(width: number) {
if (!store.session) {
setStore("session", { width })
} else {
setStore("session", "width", width)
}
},
},
tabs(sessionKey: string) {
@@ -186,14 +204,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
}
},
async open(tab: string) {
if (tab === "chat") {
if (!store.sessionTabs[sessionKey]) {
setStore("sessionTabs", sessionKey, { all: [], active: undefined })
} else {
setStore("sessionTabs", sessionKey, "active", undefined)
}
return
}
const current = store.sessionTabs[sessionKey] ?? { all: [] }
if (tab !== "review") {
if (!current.all.includes(tab)) {

View File

@@ -1,5 +1,5 @@
import { createStore, produce, reconcile } from "solid-js/store"
import { batch, createEffect, createMemo } from "solid-js"
import { batch, createMemo } from "solid-js"
import { filter, firstBy, flat, groupBy, mapValues, pipe, uniqueBy, values } from "remeda"
import type { FileContent, FileNode, Model, Provider, File as FileStatus } from "@opencode-ai/sdk/v2"
import { createSimpleContext } from "@opencode-ai/ui/context"
@@ -9,6 +9,7 @@ import { base64Encode } from "@opencode-ai/util/encode"
import { useProviders } from "@/hooks/use-providers"
import { DateTime } from "luxon"
import { persisted } from "@/utils/persist"
import { showToast } from "@opencode-ai/ui/toast"
export type LocalFile = FileNode &
Partial<{
@@ -61,24 +62,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}
}
// Automatically update model when agent changes
createEffect(() => {
const value = agent.current()
if (value.model) {
if (isModelValid(value.model))
model.set({
providerID: value.model.providerID,
modelID: value.model.modelID,
})
// else
// toast.show({
// type: "warning",
// message: `Agent ${value.name}'s configured model ${value.model.providerID}/${value.model.modelID} is not valid`,
// duration: 3000,
// })
}
})
const agent = (() => {
const list = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent" && !x.hidden))
const [store, setStore] = createStore<{
@@ -276,11 +259,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const [store, setStore] = createStore<{
node: Record<string, LocalFile>
}>({
node: Object.fromEntries(sync.data.node.map((x) => [x.path, x])),
node: {}, // Object.fromEntries(sync.data.node.map((x) => [x.path, x])),
})
const changeset = createMemo(() => new Set(sync.data.changes.map((f) => f.path)))
const changes = createMemo(() => Array.from(changeset()).sort((a, b) => a.localeCompare(b)))
// const changeset = createMemo(() => new Set(sync.data.changes.map((f) => f.path)))
// const changes = createMemo(() => Array.from(changeset()).sort((a, b) => a.localeCompare(b)))
// createEffect((prev: FileStatus[]) => {
// const removed = prev.filter((p) => !sync.data.changes.find((c) => c.path === p.path))
@@ -308,16 +291,16 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
// return sync.data.changes
// }, sync.data.changes)
const changed = (path: string) => {
const node = store.node[path]
if (node?.status) return true
const set = changeset()
if (set.has(path)) return true
for (const p of set) {
if (p.startsWith(path ? path + "/" : "")) return true
}
return false
}
// const changed = (path: string) => {
// const node = store.node[path]
// if (node?.status) return true
// const set = changeset()
// if (set.has(path)) return true
// for (const p of set) {
// if (p.startsWith(path ? path + "/" : "")) return true
// }
// return false
// }
// const resetNode = (path: string) => {
// setStore("node", path, {
@@ -336,17 +319,26 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const load = async (path: string) => {
const relativePath = relative(path)
await sdk.client.file.read({ path: relativePath }).then((x) => {
if (!store.node[relativePath]) return
setStore(
"node",
relativePath,
produce((draft) => {
draft.loaded = true
draft.content = x.data
}),
)
})
await sdk.client.file
.read({ path: relativePath })
.then((x) => {
if (!store.node[relativePath]) return
setStore(
"node",
relativePath,
produce((draft) => {
draft.loaded = true
draft.content = x.data
}),
)
})
.catch((e) => {
showToast({
variant: "error",
title: "Failed to load file",
description: e.message,
})
})
}
const fetch = async (path: string) => {
@@ -360,7 +352,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const init = async (path: string) => {
const relativePath = relative(path)
if (!store.node[relativePath]) await fetch(path)
if (store.node[relativePath].loaded) return
if (store.node[relativePath]?.loaded) return
return load(relativePath)
}
@@ -380,7 +372,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
context.addActive()
if (options?.pinned) setStore("node", path, "pinned", true)
if (options?.view && store.node[relativePath].view === undefined) setStore("node", path, "view", options.view)
if (store.node[relativePath].loaded) return
if (store.node[relativePath]?.loaded) return
return load(relativePath)
}
@@ -466,8 +458,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
setChangeIndex(path: string, index: number | undefined) {
setStore("node", path, "selectedChange", index)
},
changes,
changed,
// changes,
// changed,
children(path: string) {
return Object.values(store.node).filter(
(x) =>

View File

@@ -5,6 +5,9 @@ export type Platform = {
/** Platform discriminator */
platform: "web" | "tauri"
/** App version */
version?: string
/** Open a URL in the default browser */
openLink(url: string): void

View File

@@ -2,6 +2,7 @@
import { render } from "solid-js/web"
import { App } from "@/app"
import { Platform, PlatformProvider } from "@/context/platform"
import pkg from "../package.json"
const root = document.getElementById("root")
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
@@ -12,6 +13,7 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
const platform: Platform = {
platform: "web",
version: pkg.version,
openLink(url: string) {
window.open(url, "_blank")
},

View File

@@ -1,7 +1,7 @@
import { TextField } from "@opencode-ai/ui/text-field"
import { Logo } from "@opencode-ai/ui/logo"
import { Button } from "@opencode-ai/ui/button"
import { Component } from "solid-js"
import { Component, Show } from "solid-js"
import { usePlatform } from "@/context/platform"
import { Icon } from "@opencode-ai/ui/icon"
@@ -62,12 +62,54 @@ function formatInitError(error: InitError): string {
}
}
function formatError(error: unknown): string {
function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): string {
if (!error) return "Unknown error"
if (isInitError(error)) return formatInitError(error)
if (error instanceof Error) return `${error.name}: ${error.message}\n\n${error.stack}`
if (typeof error === "string") return error
return JSON.stringify(error, null, 2)
if (isInitError(error)) {
const message = formatInitError(error)
if (depth > 0 && parentMessage === message) return ""
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
return indent + message
}
if (error instanceof Error) {
const isDuplicate = depth > 0 && parentMessage === error.message
const parts: string[] = []
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
if (!isDuplicate) {
// Stack already includes error name and message, so prefer it
parts.push(indent + (error.stack ?? `${error.name}: ${error.message}`))
} else if (error.stack) {
// Duplicate message - only show the stack trace lines (skip message)
const trace = error.stack.split("\n").slice(1).join("\n").trim()
if (trace) {
parts.push(trace)
}
}
if (error.cause) {
const causeResult = formatErrorChain(error.cause, depth + 1, error.message)
if (causeResult) {
parts.push(causeResult)
}
}
return parts.join("\n\n")
}
if (typeof error === "string") {
if (depth > 0 && parentMessage === error) return ""
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
return indent + error
}
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
return indent + JSON.stringify(error, null, 2)
}
function formatError(error: unknown): string {
return formatErrorChain(error, 0)
}
interface ErrorPageProps {
@@ -96,16 +138,21 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
<Button size="large" onClick={platform.restart}>
Restart
</Button>
<div class="flex items-center justify-center gap-1">
Please report this error to the OpenCode team
<button
type="button"
class="flex items-center text-text-interactive-base gap-1"
onClick={() => platform.openLink("https://opencode.ai/desktop-feedback")}
>
<div>on Discord</div>
<Icon name="discord" class="text-text-interactive-base" />
</button>
<div class="flex flex-col items-center gap-2">
<div class="flex items-center justify-center gap-1">
Please report this error to the OpenCode team
<button
type="button"
class="flex items-center text-text-interactive-base gap-1"
onClick={() => platform.openLink("https://opencode.ai/desktop-feedback")}
>
<div>on Discord</div>
<Icon name="discord" class="text-text-interactive-base" />
</button>
</div>
<Show when={platform.version}>
<p class="text-xs text-text-weak">Version: {platform.version}</p>
</Show>
</div>
</div>
</div>

View File

@@ -1,4 +1,17 @@
import { createEffect, createMemo, For, Match, onMount, ParentProps, Show, Switch, type JSX } from "solid-js"
import {
createEffect,
createMemo,
createSignal,
For,
Match,
onCleanup,
onMount,
ParentProps,
Show,
Switch,
untrack,
type JSX,
} from "solid-js"
import { DateTime } from "luxon"
import { A, useNavigate, useParams } from "@solidjs/router"
import { useLayout, getAvatarColors, LocalProject } from "@/context/layout"
@@ -42,9 +55,29 @@ export default function Layout(props: ParentProps) {
const [store, setStore] = createStore({
lastSession: {} as { [directory: string]: string },
activeDraggable: undefined as string | undefined,
mobileSidebarOpen: false,
mobileProjectsExpanded: {} as Record<string, boolean>,
})
const mobileSidebar = {
open: () => store.mobileSidebarOpen,
show: () => setStore("mobileSidebarOpen", true),
hide: () => setStore("mobileSidebarOpen", false),
toggle: () => setStore("mobileSidebarOpen", (x) => !x),
}
const mobileProjects = {
expanded: (directory: string) => store.mobileProjectsExpanded[directory] ?? true,
expand: (directory: string) => setStore("mobileProjectsExpanded", directory, true),
collapse: (directory: string) => setStore("mobileProjectsExpanded", directory, false),
}
let scrollContainerRef: HTMLDivElement | undefined
const xlQuery = window.matchMedia("(min-width: 1280px)")
const [isLargeViewport, setIsLargeViewport] = createSignal(xlQuery.matches)
const handleViewportChange = (e: MediaQueryListEvent) => setIsLargeViewport(e.matches)
xlQuery.addEventListener("change", handleViewportChange)
onCleanup(() => xlQuery.removeEventListener("change", handleViewportChange))
const params = useParams()
const globalSDK = useGlobalSDK()
@@ -84,42 +117,31 @@ export default function Layout(props: ParentProps) {
}
})
function flattenSessions(sessions: Session[]): Session[] {
const childrenMap = new Map<string, Session[]>()
for (const session of sessions) {
if (session.parentID) {
const children = childrenMap.get(session.parentID) ?? []
children.push(session)
childrenMap.set(session.parentID, children)
}
}
const result: Session[] = []
function visit(session: Session) {
result.push(session)
for (const child of childrenMap.get(session.id) ?? []) {
visit(child)
}
}
for (const session of sessions) {
if (!session.parentID) visit(session)
}
return result
function sortSessions(a: Session, b: Session) {
const now = Date.now()
const oneMinuteAgo = now - 60 * 1000
const aUpdated = a.time.updated ?? a.time.created
const bUpdated = b.time.updated ?? b.time.created
const aRecent = aUpdated > oneMinuteAgo
const bRecent = bUpdated > oneMinuteAgo
if (aRecent && bRecent) return a.id.localeCompare(b.id)
if (aRecent && !bRecent) return -1
if (!aRecent && bRecent) return 1
return bUpdated - aUpdated
}
function scrollToSession(sessionId: string) {
if (!scrollContainerRef) return
const element = scrollContainerRef.querySelector(`[data-session-id="${sessionId}"]`)
if (element) {
element.scrollIntoView({ block: "center", behavior: "smooth" })
element.scrollIntoView({ block: "nearest", behavior: "smooth" })
}
}
function projectSessions(directory: string) {
if (!directory) return []
const sessions = globalSync
.child(directory)[0]
.session.toSorted((a, b) => (b.time.updated ?? b.time.created) - (a.time.updated ?? a.time.created))
return flattenSessions(sessions ?? [])
const sessions = globalSync.child(directory)[0].session.toSorted(sortSessions)
return (sessions ?? []).filter((s) => !s.parentID)
}
const currentSessions = createMemo(() => {
@@ -259,11 +281,13 @@ export default function Layout(props: ParentProps) {
if (!directory) return
const lastSession = store.lastSession[directory]
navigate(`/${base64Encode(directory)}${lastSession ? `/session/${lastSession}` : ""}`)
mobileSidebar.hide()
}
function navigateToSession(session: Session | undefined) {
if (!session) return
navigate(`/${params.dir}/session/${session?.id}`)
mobileSidebar.hide()
}
function openProject(directory: string, navigate = true) {
@@ -297,13 +321,20 @@ export default function Layout(props: ParentProps) {
createEffect(() => {
if (!params.dir || !params.id) return
const directory = base64Decode(params.dir)
setStore("lastSession", directory, params.id)
notification.session.markViewed(params.id)
const id = params.id
setStore("lastSession", directory, id)
notification.session.markViewed(id)
untrack(() => layout.projects.expand(directory))
requestAnimationFrame(() => scrollToSession(id))
})
createEffect(() => {
const sidebarWidth = layout.sidebar.opened() ? layout.sidebar.width() : 48
document.documentElement.style.setProperty("--dialog-left-margin", `${sidebarWidth}px`)
if (isLargeViewport()) {
const sidebarWidth = layout.sidebar.opened() ? layout.sidebar.width() : 48
document.documentElement.style.setProperty("--dialog-left-margin", `${sidebarWidth}px`)
} else {
document.documentElement.style.setProperty("--dialog-left-margin", "0px")
}
})
function getDraggableId(event: unknown): string | undefined {
@@ -417,12 +448,9 @@ export default function Layout(props: ParentProps) {
session: Session
slug: string
project: LocalProject
depth?: number
childrenMap: Map<string, Session[]>
mobile?: boolean
}): JSX.Element => {
const notification = useNotification()
const depth = props.depth ?? 0
const children = createMemo(() => props.childrenMap.get(props.session.id) ?? [])
const updated = createMemo(() => DateTime.fromMillis(props.session.time.updated))
const notifications = createMemo(() => notification.session.unseen(props.session.id))
const hasError = createMemo(() => notifications().some((n) => n.type === "error"))
@@ -437,15 +465,20 @@ export default function Layout(props: ParentProps) {
data-session-id={props.session.id}
class="group/session relative w-full pr-2 py-1 rounded-md cursor-default transition-colors
hover:bg-surface-raised-base-hover focus-within:bg-surface-raised-base-hover has-[.active]:bg-surface-raised-base-hover"
style={{ "padding-left": `${16 + depth * 12}px` }}
style={{ "padding-left": "16px" }}
>
<Tooltip placement="right" value={props.session.title} gutter={10}>
<Tooltip placement={props.mobile ? "bottom" : "right"} value={props.session.title} gutter={10}>
<A
href={`${props.slug}/session/${props.session.id}`}
class="flex flex-col min-w-0 text-left w-full focus:outline-none"
>
<div class="flex items-center self-stretch gap-6 justify-between transition-[padding] group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7">
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
<span
classList={{
"text-14-regular text-text-strong overflow-hidden text-ellipsis truncate": true,
"animate-pulse": isWorking(),
}}
>
{props.session.title}
</span>
<div class="shrink-0 group-hover/session:hidden group-active/session:hidden group-focus-within/session:hidden">
@@ -486,66 +519,54 @@ export default function Layout(props: ParentProps) {
</A>
</Tooltip>
<div class="hidden group-hover/session:flex group-active/session:flex group-focus-within/session:flex text-text-base gap-1 items-center absolute top-1 right-1">
<Tooltip placement="right" value="Archive session">
<Tooltip
placement={props.mobile ? "bottom" : "right"}
value={
<div class="flex items-center gap-2">
<span>Archive session</span>
<span class="text-icon-base text-12-medium">{command.keybind("session.archive")}</span>
</div>
}
>
<IconButton icon="archive" variant="ghost" onClick={() => archiveSession(props.session)} />
</Tooltip>
</div>
</div>
<For each={children()}>
{(child) => (
<SessionItem
session={child}
slug={props.slug}
project={props.project}
depth={depth + 1}
childrenMap={props.childrenMap}
/>
)}
</For>
</>
)
}
const SortableProject = (props: { project: LocalProject }): JSX.Element => {
const SortableProject = (props: { project: LocalProject; mobile?: boolean }): JSX.Element => {
const sortable = createSortable(props.project.worktree)
const showExpanded = createMemo(() => props.mobile || layout.sidebar.opened())
const slug = createMemo(() => base64Encode(props.project.worktree))
const name = createMemo(() => getFilename(props.project.worktree))
const [store, setProjectStore] = globalSync.child(props.project.worktree)
const sessions = createMemo(() =>
store.session.toSorted((a, b) => (b.time.updated ?? b.time.created) - (a.time.updated ?? a.time.created)),
)
const sessions = createMemo(() => store.session.toSorted(sortSessions))
const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID))
const childSessionsByParent = createMemo(() => {
const map = new Map<string, Session[]>()
for (const session of sessions()) {
if (session.parentID) {
const children = map.get(session.parentID) ?? []
children.push(session)
map.set(session.parentID, children)
}
}
return map
})
const hasMoreSessions = createMemo(() => store.session.length >= store.limit)
const loadMoreSessions = async () => {
setProjectStore("limit", (limit) => limit + 5)
await globalSync.project.loadSessions(props.project.worktree)
}
const isExpanded = createMemo(() =>
props.mobile ? mobileProjects.expanded(props.project.worktree) : props.project.expanded,
)
const handleOpenChange = (open: boolean) => {
if (open) layout.projects.expand(props.project.worktree)
else layout.projects.collapse(props.project.worktree)
if (props.mobile) {
if (open) mobileProjects.expand(props.project.worktree)
else mobileProjects.collapse(props.project.worktree)
} else {
if (open) layout.projects.expand(props.project.worktree)
else layout.projects.collapse(props.project.worktree)
}
}
return (
// @ts-ignore
<div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
<Switch>
<Match when={layout.sidebar.opened()}>
<Collapsible
variant="ghost"
open={props.project.expanded}
class="gap-2 shrink-0"
onOpenChange={handleOpenChange}
>
<Match when={showExpanded()}>
<Collapsible variant="ghost" open={isExpanded()} class="gap-2 shrink-0" onOpenChange={handleOpenChange}>
<Button
as={"div"}
variant="ghost"
@@ -556,7 +577,7 @@ export default function Layout(props: ParentProps) {
project={props.project}
class="group-hover/session:hidden"
expandable
notify={!props.project.expanded}
notify={!isExpanded()}
/>
<span class="truncate text-14-medium text-text-strong">{name()}</span>
</Collapsible.Trigger>
@@ -571,7 +592,15 @@ export default function Layout(props: ParentProps) {
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
<Tooltip placement="top" value="New session">
<Tooltip
placement="top"
value={
<div class="flex items-center gap-2">
<span>New session</span>
<span class="text-icon-base text-12-medium">{command.keybind("session.new")}</span>
</div>
}
>
<IconButton as={A} href={`${slug()}/session`} icon="plus-small" variant="ghost" />
</Tooltip>
</div>
@@ -580,12 +609,7 @@ export default function Layout(props: ParentProps) {
<nav class="hidden @[4rem]:flex w-full flex-col gap-1.5">
<For each={rootSessions()}>
{(session) => (
<SessionItem
session={session}
slug={slug()}
project={props.project}
childrenMap={childSessionsByParent()}
/>
<SessionItem session={session} slug={slug()} project={props.project} mobile={props.mobile} />
)}
</For>
<Show when={rootSessions().length === 0}>
@@ -595,7 +619,7 @@ export default function Layout(props: ParentProps) {
>
<div class="flex items-center self-stretch w-full">
<div class="flex-1 min-w-0">
<Tooltip placement="right" value="New session">
<Tooltip placement={props.mobile ? "bottom" : "right"} value="New session">
<A
href={`${slug()}/session`}
class="flex flex-col gap-1 min-w-0 text-left w-full focus:outline-none"
@@ -650,31 +674,23 @@ export default function Layout(props: ParentProps) {
)
}
return (
<div class="relative flex-1 min-h-0 flex flex-col">
<Header navigateToProject={navigateToProject} navigateToSession={navigateToSession} />
<div class="flex-1 min-h-0 flex">
<div
classList={{
"relative @container w-12 pb-5 shrink-0 bg-background-base": true,
"flex flex-col gap-5.5 items-start self-stretch justify-between": true,
"border-r border-border-weak-base contain-strict": true,
}}
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
>
<Show when={layout.sidebar.opened()}>
<ResizeHandle
direction="horizontal"
size={layout.sidebar.width()}
min={150}
max={window.innerWidth * 0.3}
collapseThreshold={80}
onResize={layout.sidebar.resize}
onCollapse={layout.sidebar.close}
/>
</Show>
<div class="flex flex-col items-start self-stretch gap-4 p-2 min-h-0 overflow-hidden">
<Tooltip class="shrink-0" placement="right" value="Toggle sidebar" inactive={layout.sidebar.opened()}>
const SidebarContent = (sidebarProps: { mobile?: boolean }) => {
const expanded = () => sidebarProps.mobile || layout.sidebar.opened()
return (
<>
<div class="flex flex-col items-start self-stretch gap-4 p-2 min-h-0 overflow-hidden">
<Show when={!sidebarProps.mobile}>
<Tooltip
class="shrink-0"
placement="right"
value={
<div class="flex items-center gap-2">
<span>Toggle sidebar</span>
<span class="text-icon-base text-12-medium">{command.keybind("sidebar.toggle")}</span>
</div>
}
inactive={expanded()}
>
<Button
variant="ghost"
size="large"
@@ -705,101 +721,162 @@ export default function Layout(props: ParentProps) {
</Show>
</Button>
</Tooltip>
<DragDropProvider
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
collisionDetector={closestCenter}
</Show>
<DragDropProvider
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
collisionDetector={closestCenter}
>
<DragDropSensors />
<ConstrainDragXAxis />
<div
ref={(el) => {
if (!sidebarProps.mobile) scrollContainerRef = el
}}
class="w-full min-w-8 flex flex-col gap-2 min-h-0 overflow-y-auto no-scrollbar"
>
<DragDropSensors />
<ConstrainDragXAxis />
<div
ref={scrollContainerRef}
class="w-full min-w-8 flex flex-col gap-2 min-h-0 overflow-y-auto no-scrollbar"
>
<SortableProvider ids={layout.projects.list().map((p) => p.worktree)}>
<For each={layout.projects.list()}>{(project) => <SortableProject project={project} />}</For>
</SortableProvider>
</div>
<DragOverlay>
<ProjectDragOverlay />
</DragOverlay>
</DragDropProvider>
</div>
<div class="flex flex-col gap-1.5 self-stretch items-start shrink-0 px-2 py-3">
<Switch>
<Match when={!providers.paid().length && layout.sidebar.opened()}>
<div class="rounded-md bg-background-stronger shadow-xs-border-base">
<div class="p-3 flex flex-col gap-2">
<div class="text-12-medium text-text-strong">Getting started</div>
<div class="text-text-base">OpenCode includes free models so you can start immediately.</div>
<div class="text-text-base">Connect any provider to use models, inc. Claude, GPT, Gemini etc.</div>
</div>
<Tooltip placement="right" value="Connect provider" inactive={layout.sidebar.opened()}>
<Button
class="flex w-full text-left justify-start text-12-medium text-text-strong stroke-[1.5px] rounded-lg rounded-t-none shadow-none border-t border-border-weak-base pl-2.25 pb-px"
size="large"
icon="plus"
onClick={connectProvider}
>
<Show when={layout.sidebar.opened()}>Connect provider</Show>
</Button>
</Tooltip>
<SortableProvider ids={layout.projects.list().map((p) => p.worktree)}>
<For each={layout.projects.list()}>
{(project) => <SortableProject project={project} mobile={sidebarProps.mobile} />}
</For>
</SortableProvider>
</div>
<DragOverlay>
<ProjectDragOverlay />
</DragOverlay>
</DragDropProvider>
</div>
<div class="flex flex-col gap-1.5 self-stretch items-start shrink-0 px-2 py-3">
<Switch>
<Match when={!providers.paid().length && expanded()}>
<div class="rounded-md bg-background-stronger shadow-xs-border-base">
<div class="p-3 flex flex-col gap-2">
<div class="text-12-medium text-text-strong">Getting started</div>
<div class="text-text-base">OpenCode includes free models so you can start immediately.</div>
<div class="text-text-base">Connect any provider to use models, inc. Claude, GPT, Gemini etc.</div>
</div>
</Match>
<Match when={true}>
<Tooltip placement="right" value="Connect provider" inactive={layout.sidebar.opened()}>
<Tooltip placement="right" value="Connect provider" inactive={expanded()}>
<Button
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
variant="ghost"
class="flex w-full text-left justify-start text-12-medium text-text-strong stroke-[1.5px] rounded-lg rounded-t-none shadow-none border-t border-border-weak-base pl-2.25 pb-px"
size="large"
icon="plus"
onClick={connectProvider}
>
<Show when={layout.sidebar.opened()}>Connect provider</Show>
Connect provider
</Button>
</Tooltip>
</Match>
</Switch>
<Show when={platform.openDirectoryPickerDialog}>
<Tooltip placement="right" value="Open project" inactive={layout.sidebar.opened()}>
</div>
</Match>
<Match when={true}>
<Tooltip placement="right" value="Connect provider" inactive={expanded()}>
<Button
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
variant="ghost"
size="large"
icon="folder-add-left"
onClick={chooseProject}
icon="plus"
onClick={connectProvider}
>
<Show when={layout.sidebar.opened()}>Open project</Show>
<Show when={expanded()}>Connect provider</Show>
</Button>
</Tooltip>
</Show>
{/* <Tooltip placement="right" value="Settings" inactive={layout.sidebar.opened()}> */}
{/* <Button */}
{/* disabled */}
{/* class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px] rounded-lg px-2" */}
{/* variant="ghost" */}
{/* size="large" */}
{/* icon="settings-gear" */}
{/* > */}
{/* <Show when={layout.sidebar.opened()}>Settings</Show> */}
{/* </Button> */}
{/* </Tooltip> */}
<Tooltip placement="right" value="Share feedback" inactive={layout.sidebar.opened()}>
</Match>
</Switch>
<Show when={platform.openDirectoryPickerDialog}>
<Tooltip
placement="right"
value={
<div class="flex items-center gap-2">
<span>Open project</span>
<Show when={!sidebarProps.mobile}>
<span class="text-icon-base text-12-medium">{command.keybind("project.open")}</span>
</Show>
</div>
}
inactive={expanded()}
>
<Button
as={"a"}
href="https://opencode.ai/desktop-feedback"
target="_blank"
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
variant="ghost"
size="large"
icon="bubble-5"
icon="folder-add-left"
onClick={chooseProject}
>
<Show when={layout.sidebar.opened()}>Share feedback</Show>
<Show when={expanded()}>Open project</Show>
</Button>
</Tooltip>
</Show>
<Tooltip placement="right" value="Share feedback" inactive={expanded()}>
<Button
as={"a"}
href="https://opencode.ai/desktop-feedback"
target="_blank"
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
variant="ghost"
size="large"
icon="bubble-5"
>
<Show when={expanded()}>Share feedback</Show>
</Button>
</Tooltip>
</div>
</>
)
}
return (
<div class="relative flex-1 min-h-0 flex flex-col">
<Header
navigateToProject={navigateToProject}
navigateToSession={navigateToSession}
onMobileMenuToggle={mobileSidebar.toggle}
/>
<div class="flex-1 min-h-0 flex">
<div
classList={{
"hidden xl:flex": true,
"relative @container w-12 pb-5 shrink-0 bg-background-base": true,
"flex-col gap-5.5 items-start self-stretch justify-between": true,
"border-r border-border-weak-base contain-strict": true,
}}
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
>
<Show when={layout.sidebar.opened()}>
<ResizeHandle
direction="horizontal"
size={layout.sidebar.width()}
min={150}
max={window.innerWidth * 0.3}
collapseThreshold={80}
onResize={layout.sidebar.resize}
onCollapse={layout.sidebar.close}
/>
</Show>
<SidebarContent />
</div>
<div class="xl:hidden">
<div
classList={{
"fixed inset-0 bg-black/50 z-40 transition-opacity duration-200": true,
"opacity-100 pointer-events-auto": mobileSidebar.open(),
"opacity-0 pointer-events-none": !mobileSidebar.open(),
}}
onClick={(e) => {
if (e.target === e.currentTarget) mobileSidebar.hide()
}}
/>
<div
classList={{
"@container fixed inset-y-0 left-0 z-50 w-72 bg-background-base border-r border-border-weak-base flex flex-col gap-5.5 items-start self-stretch justify-between pt-12 pb-5 transition-transform duration-200 ease-out": true,
"translate-x-0": mobileSidebar.open(),
"-translate-x-full": !mobileSidebar.open(),
}}
onClick={(e) => e.stopPropagation()}
>
<SidebarContent mobile />
</div>
</div>
<main class="size-full overflow-x-hidden flex flex-col items-start contain-strict">{props.children}</main>
</div>
<Toast.Region />

View File

@@ -12,6 +12,7 @@ import {
createRenderEffect,
batch,
} from "solid-js"
import { Dynamic } from "solid-js/web"
import { useLocal, type LocalFile } from "@/context/local"
import { createStore } from "solid-js/store"
@@ -22,11 +23,11 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
import { Icon } from "@opencode-ai/ui/icon"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { Tabs } from "@opencode-ai/ui/tabs"
import { useCodeComponent } from "@opencode-ai/ui/context/code"
import { SessionTurn } from "@opencode-ai/ui/session-turn"
import { createAutoScroll } from "@opencode-ai/ui/hooks"
import { SessionMessageRail } from "@opencode-ai/ui/session-message-rail"
import { SessionReview } from "@opencode-ai/ui/session-review"
import {
@@ -48,13 +49,17 @@ import { checksum } from "@opencode-ai/util/encode"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { DialogSelectFile } from "@/components/dialog-select-file"
import { DialogSelectModel } from "@/components/dialog-select-model"
import { DialogSelectMcp } from "@/components/dialog-select-mcp"
import { useCommand } from "@/context/command"
import { useNavigate, useParams } from "@solidjs/router"
import { AssistantMessage, UserMessage } from "@opencode-ai/sdk/v2"
import { UserMessage } from "@opencode-ai/sdk/v2"
import { useSDK } from "@/context/sdk"
import { usePrompt } from "@/context/prompt"
import { extractPromptFromParts } from "@/utils/prompt"
import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd"
import { StatusBar } from "@/components/status-bar"
import { SessionMcpIndicator } from "@/components/session-mcp-indicator"
import { SessionLspIndicator } from "@/components/session-lsp-indicator"
export default function Page() {
const layout = useLayout()
@@ -71,7 +76,6 @@ export default function Page() {
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const tabs = createMemo(() => layout.tabs(sessionKey()))
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
const revertMessageID = createMemo(() => info()?.revert?.messageID)
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
@@ -80,7 +84,6 @@ export default function Page() {
.filter((m) => m.role === "user")
.sort((a, b) => a.id.localeCompare(b.id)),
)
// Visible user messages excludes reverted messages (those >= revertMessageID)
const visibleUserMessages = createMemo(() => {
const revert = revertMessageID()
if (!revert) return userMessages()
@@ -88,15 +91,24 @@ export default function Page() {
})
const lastUserMessage = createMemo(() => visibleUserMessages()?.at(-1))
const [messageStore, setMessageStore] = createStore<{ messageId?: string }>({})
const [store, setStore] = createStore({
clickTimer: undefined as number | undefined,
activeDraggable: undefined as string | undefined,
activeTerminalDraggable: undefined as string | undefined,
userInteracted: false,
stepsExpanded: true,
mobileStepsExpanded: {} as Record<string, boolean>,
messageId: undefined as string | undefined,
})
const activeMessage = createMemo(() => {
if (!messageStore.messageId) return lastUserMessage()
if (!store.messageId) return lastUserMessage()
// If the stored message is no longer visible (e.g., was reverted), fall back to last visible
const found = visibleUserMessages()?.find((m) => m.id === messageStore.messageId)
const found = visibleUserMessages()?.find((m) => m.id === store.messageId)
return found ?? lastUserMessage()
})
const setActiveMessage = (message: UserMessage | undefined) => {
setMessageStore("messageId", message?.id)
setStore("messageId", message?.id)
}
function navigateMessageByOffset(offset: number) {
@@ -118,34 +130,8 @@ export default function Page() {
setActiveMessage(msgs[targetIndex])
}
const last = createMemo(
() => messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage,
)
const model = createMemo(() =>
last() ? sync.data.provider.all.find((x) => x.id === last().providerID)?.models[last().modelID] : undefined,
)
const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : []))
const tokens = createMemo(() => {
if (!last()) return
const t = last().tokens
return t.input + t.output + t.reasoning + t.cache.read + t.cache.write
})
const context = createMemo(() => {
const total = tokens()
const limit = model()?.limit.context
if (!total || !limit) return 0
return Math.round((total / limit) * 100)
})
const [store, setStore] = createStore({
clickTimer: undefined as number | undefined,
activeDraggable: undefined as string | undefined,
activeTerminalDraggable: undefined as string | undefined,
userInteracted: false,
stepsExpanded: true,
})
let inputRef!: HTMLDivElement
createEffect(() => {
@@ -166,7 +152,7 @@ export default function Page() {
() => visibleUserMessages().at(-1)?.id,
(lastId, prevLastId) => {
if (lastId && prevLastId && lastId > prevLastId) {
setMessageStore("messageId", undefined)
setStore("messageId", undefined)
}
},
{ defer: true },
@@ -239,6 +225,14 @@ export default function Page() {
slash: "terminal",
onSelect: () => layout.terminal.toggle(),
},
{
id: "review.toggle",
title: "Toggle review",
description: "Show or hide the review panel",
category: "View",
keybind: "mod+shift+r",
onSelect: () => layout.review.toggle(),
},
{
id: "terminal.new",
title: "New terminal",
@@ -284,6 +278,15 @@ export default function Page() {
slash: "model",
onSelect: () => dialog.show(() => <DialogSelectModel />),
},
{
id: "mcp.toggle",
title: "Toggle MCPs",
description: "Toggle MCPs",
category: "MCP",
keybind: "mod+;",
slash: "mcp",
onSelect: () => dialog.show(() => <DialogSelectMcp />),
},
{
id: "agent.cycle",
title: "Cycle agent",
@@ -551,279 +554,321 @@ export default function Page() {
)
}
const wide = createMemo(() => layout.review.state() === "tab" || !diffs().length)
const showTabs = createMemo(() => layout.review.opened() && (diffs().length > 0 || tabs().all().length > 0))
const mobileWorking = createMemo(() => status().type !== "idle")
const mobileAutoScroll = createAutoScroll({
working: mobileWorking,
onUserInteracted: () => setStore("userInteracted", true),
})
const MobileTurns = () => (
<div
ref={mobileAutoScroll.scrollRef}
onScroll={mobileAutoScroll.handleScroll}
onClick={mobileAutoScroll.handleInteraction}
class="relative mt-2 min-w-0 w-full h-full overflow-y-auto no-scrollbar pb-12"
>
<div ref={mobileAutoScroll.contentRef} class="flex flex-col gap-45 items-start justify-start mt-4">
<For each={visibleUserMessages()}>
{(message) => (
<SessionTurn
sessionID={params.id!}
messageID={message.id}
stepsExpanded={store.mobileStepsExpanded[message.id] ?? false}
onStepsExpandedToggle={() => setStore("mobileStepsExpanded", message.id, (x) => !x)}
onUserInteracted={() => setStore("userInteracted", true)}
classes={{
root: "min-w-0 w-full relative",
content:
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
container: "px-4",
}}
/>
)}
</For>
</div>
</div>
)
const NewSessionView = () => (
<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="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>
</div>
<Show when={sync.project}>
{(project) => (
<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;
<span class="text-text-strong">
{DateTime.fromMillis(project().time.updated ?? project().time.created).toRelative()}
</span>
</div>
</div>
)}
</Show>
</div>
)
const DesktopSessionContent = () => (
<Switch>
<Match when={params.id}>
<div class="flex items-start justify-start h-full min-h-0">
<SessionMessageRail
messages={visibleUserMessages()}
current={activeMessage()}
onMessageSelect={setActiveMessage}
wide={!showTabs()}
/>
<Show when={activeMessage()}>
<SessionTurn
sessionID={params.id!}
messageID={activeMessage()!.id}
stepsExpanded={store.stepsExpanded}
onStepsExpandedToggle={() => setStore("stepsExpanded", (x) => !x)}
onUserInteracted={() => setStore("userInteracted", true)}
classes={{
root: "pb-20 flex-1 min-w-0",
content: "pb-20",
container:
"w-full " +
(!showTabs() ? "max-w-200 mx-auto px-6" : visibleUserMessages().length > 1 ? "pr-6 pl-18" : "px-6"),
}}
/>
</Show>
</div>
</Match>
<Match when={true}>
<NewSessionView />
</Match>
</Switch>
)
return (
<div class="relative bg-background-base size-full overflow-x-hidden flex flex-col">
<div class="min-h-0 grow w-full">
<DragDropProvider
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
collisionDetector={closestCenter}
>
<DragDropSensors />
<ConstrainDragYAxis />
<Tabs value={tabs().active() ?? "chat"} onChange={tabs().open}>
<div class="sticky top-0 shrink-0 flex">
<Tabs.List>
<Tabs.Trigger value="chat">
<div class="flex gap-x-[17px] items-center">
<div>Session</div>
<Tooltip
value={`${new Intl.NumberFormat("en-US", {
notation: "compact",
compactDisplay: "short",
}).format(tokens() ?? 0)} Tokens`}
class="flex items-center gap-1.5"
>
<ProgressCircle percentage={context() ?? 0} />
<div class="text-14-regular text-text-weak text-left w-7">{context() ?? 0}%</div>
</Tooltip>
</div>
</Tabs.Trigger>
<Show when={layout.review.state() === "tab" && diffs().length}>
<Tabs.Trigger
value="review"
closeButton={
<Tooltip value="Close tab" placement="bottom">
<IconButton icon="collapse" size="normal" variant="ghost" onClick={layout.review.pane} />
</Tooltip>
}
>
<div class="flex items-center gap-3">
<Show when={diffs()}>
<DiffChanges changes={diffs()} variant="bars" />
</Show>
<div class="flex items-center gap-1.5">
<div>Review</div>
<Show when={info()?.summary?.files}>
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
{info()?.summary?.files ?? 0}
</div>
</Show>
</div>
</div>
</Tabs.Trigger>
</Show>
<SortableProvider ids={tabs().all() ?? []}>
<For each={tabs().all() ?? []}>
{(tab) => <SortableTab tab={tab} onTabClick={handleTabClick} onTabClose={tabs().close} />}
</For>
</SortableProvider>
<div class="bg-background-base h-full flex items-center justify-center border-b border-border-weak-base px-3">
<Tooltip value="Open file" class="flex items-center">
<IconButton
icon="plus-small"
variant="ghost"
iconSize="large"
onClick={() => dialog.show(() => <DialogSelectFile />)}
/>
</Tooltip>
</div>
</Tabs.List>
<div class="relative bg-background-base size-full overflow-hidden flex flex-col">
<div class="md:hidden flex-1 min-h-0 flex flex-col bg-background-stronger">
<Switch>
<Match when={!params.id}>
<div class="flex-1 min-h-0 overflow-hidden">
<NewSessionView />
</div>
<Tabs.Content
value="chat"
class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden contain-strict"
>
<div
classList={{
"w-full flex-1 min-h-0": true,
grid: layout.review.state() === "tab",
flex: layout.review.state() === "pane",
}}
>
<div
classList={{
"relative shrink-0 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full": true,
"max-w-146 mx-auto": !wide(),
}}
>
<Switch>
<Match when={params.id}>
<div class="flex items-start justify-start h-full min-h-0">
<SessionMessageRail
messages={visibleUserMessages()}
current={activeMessage()}
onMessageSelect={setActiveMessage}
wide={wide()}
/>
<Show when={activeMessage()}>
<SessionTurn
sessionID={params.id!}
messageID={activeMessage()!.id}
stepsExpanded={store.stepsExpanded}
onStepsExpandedToggle={() => setStore("stepsExpanded", (x) => !x)}
onUserInteracted={() => setStore("userInteracted", true)}
classes={{
root: "pb-20 flex-1 min-w-0",
content: "pb-20",
container:
"w-full " +
(wide()
? "max-w-200 mx-auto px-6"
: visibleUserMessages().length > 1
? "pr-6 pl-18"
: "px-6"),
}}
/>
</Show>
</div>
</Match>
<Match when={true}>
<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="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>
</div>
<Show when={sync.project}>
{(project) => (
<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;
<span class="text-text-strong">
{DateTime.fromMillis(project().time.updated ?? project().time.created).toRelative()}
</span>
</div>
</div>
)}
</Show>
</div>
</Match>
</Switch>
<div class="absolute inset-x-0 bottom-8 flex flex-col justify-center items-center z-50">
<div class="w-full max-w-200 px-6">
<PromptInput
ref={(el) => {
inputRef = el
}}
/>
</div>
</div>
</div>
<Show when={layout.review.state() === "pane" && diffs().length}>
<div
classList={{
"relative grow pt-3 flex-1 min-h-0 border-l border-border-weak-base contain-strict": true,
}}
>
<SessionReview
classes={{
root: "pb-20",
header: "px-6",
container: "px-6",
}}
diffs={diffs()}
actions={
<Tooltip value="Open in tab">
<IconButton
icon="expand"
variant="ghost"
onClick={() => {
layout.review.tab()
tabs().setActive("review")
}}
/>
</Tooltip>
}
/>
</div>
</Show>
</div>
</Tabs.Content>
<Show when={layout.review.state() === "tab" && diffs().length}>
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden contain-strict">
<div
classList={{
"relative pt-3 flex-1 min-h-0 overflow-hidden": true,
}}
>
</Match>
<Match when={diffs().length > 0}>
<Tabs class="flex-1 min-h-0 flex flex-col pb-28">
<Tabs.List>
<Tabs.Trigger value="session" class="w-1/2" classes={{ button: "w-full" }}>
Session
</Tabs.Trigger>
<Tabs.Trigger value="review" class="w-1/2 !border-r-0" classes={{ button: "w-full" }}>
{diffs().length} Files Changed
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="session" class="flex-1 !overflow-hidden">
<MobileTurns />
</Tabs.Content>
<Tabs.Content forceMount value="review" class="flex-1 !overflow-hidden hidden data-[selected]:block">
<div class="relative h-full mt-6 overflow-y-auto no-scrollbar">
<SessionReview
classes={{
root: "pb-40",
header: "px-6",
container: "px-6",
}}
diffs={diffs()}
split
classes={{
root: "pb-32",
header: "px-4",
container: "px-4",
}}
/>
</div>
</Tabs.Content>
</Show>
<For each={tabs().all()}>
{(tab) => {
const [file] = createResource(
() => tab,
async (tab) => {
if (tab.startsWith("file://")) {
return local.file.node(tab.replace("file://", ""))
}
return undefined
},
)
return (
<Tabs.Content value={tab} class="select-text mt-3">
<Switch>
<Match when={file()}>
{(f) => (
<Dynamic
component={codeComponent}
file={{
name: f().path,
contents: f().content?.content ?? "",
cacheKey: checksum(f().content?.content ?? ""),
}}
overflow="scroll"
class="pb-40"
/>
)}
</Match>
</Switch>
</Tabs.Content>
)
}}
</For>
</Tabs>
<DragOverlay>
<Show when={store.activeDraggable}>
{(draggedFile) => {
const [file] = createResource(
() => draggedFile(),
async (tab) => {
if (tab.startsWith("file://")) {
return local.file.node(tab.replace("file://", ""))
}
return undefined
},
)
return (
<div class="relative px-6 h-12 flex items-center bg-background-stronger border-x border-border-weak-base border-b border-b-transparent">
<Show when={file()}>{(f) => <FileVisual active file={f()} />}</Show>
</div>
)
}}
</Show>
</DragOverlay>
</DragDropProvider>
<Show when={tabs().active()}>
<div class="absolute inset-x-0 px-6 max-w-200 flex flex-col justify-center items-center z-50 mx-auto bottom-8">
</Tabs>
</Match>
<Match when={true}>
<div class="flex-1 min-h-0 overflow-hidden">
<MobileTurns />
</div>
</Match>
</Switch>
<div class="absolute inset-x-0 bottom-4 flex flex-col justify-center items-center z-50 px-4">
<div class="w-full">
<PromptInput
ref={(el) => {
inputRef = el
}}
/>
</div>
</div>
</div>
<div class="hidden md:flex min-h-0 grow w-full">
<div
class="@container relative shrink-0 py-3 flex flex-col gap-6 min-h-0 h-full bg-background-stronger"
style={{ width: showTabs() ? `${layout.session.width()}px` : "100%" }}
>
<div class="flex-1 min-h-0 overflow-hidden">
<DesktopSessionContent />
</div>
<div class="absolute inset-x-0 bottom-8 flex flex-col justify-center items-center z-50">
<div
classList={{
"w-full px-6": true,
"max-w-200": !showTabs(),
}}
>
<PromptInput
ref={(el) => {
inputRef = el
}}
/>
</div>
</div>
<Show when={showTabs()}>
<ResizeHandle
direction="horizontal"
size={layout.session.width()}
min={450}
max={window.innerWidth * 0.45}
onResize={layout.session.resize}
/>
</Show>
</div>
<Show when={showTabs()}>
<div class="relative flex-1 min-w-0 h-full border-l border-border-weak-base">
<DragDropProvider
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
collisionDetector={closestCenter}
>
<DragDropSensors />
<ConstrainDragYAxis />
<Tabs value={tabs().active() ?? "review"} onChange={tabs().open}>
<div class="sticky top-0 shrink-0 flex">
<Tabs.List>
<Show when={diffs().length}>
<Tabs.Trigger value="review">
<div class="flex items-center gap-3">
<Show when={diffs()}>
<DiffChanges changes={diffs()} variant="bars" />
</Show>
<div class="flex items-center gap-1.5">
<div>Review</div>
<Show when={info()?.summary?.files}>
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
{info()?.summary?.files ?? 0}
</div>
</Show>
</div>
</div>
</Tabs.Trigger>
</Show>
<SortableProvider ids={tabs().all() ?? []}>
<For each={tabs().all() ?? []}>
{(tab) => <SortableTab tab={tab} onTabClick={handleTabClick} onTabClose={tabs().close} />}
</For>
</SortableProvider>
<div class="bg-background-base h-full flex items-center justify-center border-b border-border-weak-base px-3">
<Tooltip
value={
<div class="flex items-center gap-2">
<span>Open file</span>
<span class="text-icon-base text-12-medium">{command.keybind("file.open")}</span>
</div>
}
class="flex items-center"
>
<IconButton
icon="plus-small"
variant="ghost"
iconSize="large"
onClick={() => dialog.show(() => <DialogSelectFile />)}
/>
</Tooltip>
</div>
</Tabs.List>
</div>
<Show when={diffs().length}>
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden contain-strict">
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
<SessionReview
classes={{
root: "pb-40",
header: "px-6",
container: "px-6",
}}
diffs={diffs()}
split
/>
</div>
</Tabs.Content>
</Show>
<For each={tabs().all()}>
{(tab) => {
const [file] = createResource(
() => tab,
async (tab) => {
if (tab.startsWith("file://")) {
return local.file.node(tab.replace("file://", ""))
}
return undefined
},
)
return (
<Tabs.Content value={tab} class="select-text mt-3">
<Switch>
<Match when={file()}>
{(f) => (
<Dynamic
component={codeComponent}
file={{
name: f().path,
contents: f().content?.content ?? "",
cacheKey: checksum(f().content?.content ?? ""),
}}
overflow="scroll"
class="pb-40"
/>
)}
</Match>
</Switch>
</Tabs.Content>
)
}}
</For>
</Tabs>
<DragOverlay>
<Show when={store.activeDraggable}>
{(draggedFile) => {
const [file] = createResource(
() => draggedFile(),
async (tab) => {
if (tab.startsWith("file://")) {
return local.file.node(tab.replace("file://", ""))
}
return undefined
},
)
return (
<div class="relative px-6 h-12 flex items-center bg-background-stronger border-x border-border-weak-base border-b border-b-transparent">
<Show when={file()}>{(f) => <FileVisual active file={f()} />}</Show>
</div>
)
}}
</Show>
</DragOverlay>
</DragDropProvider>
</div>
</Show>
</div>
<Show when={layout.terminal.opened()}>
<div
class="relative w-full flex flex-col shrink-0 border-t border-border-weak-base"
class="hidden md:flex relative w-full flex-col shrink-0 border-t border-border-weak-base"
style={{ height: `${layout.terminal.height()}px` }}
>
<ResizeHandle
@@ -849,7 +894,15 @@ export default function Page() {
<For each={terminal.all()}>{(pty) => <SortableTerminalTab terminal={pty} />}</For>
</SortableProvider>
<div class="h-full flex items-center justify-center">
<Tooltip value="New Terminal" class="flex items-center">
<Tooltip
value={
<div class="flex items-center gap-2">
<span>New terminal</span>
<span class="text-icon-base text-12-medium">{command.keybind("terminal.new")}</span>
</div>
}
class="flex items-center"
>
<IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={terminal.new} />
</Tooltip>
</div>
@@ -881,6 +934,10 @@ export default function Page() {
</DragDropProvider>
</div>
</Show>
<StatusBar>
<SessionLspIndicator />
<SessionMcpIndicator />
</StatusBar>
</div>
)
}

View File

@@ -1,5 +1,7 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"composite": true,
"target": "ESNext",
"module": "ESNext",
"skipLibCheck": true,
@@ -9,12 +11,16 @@
"jsx": "preserve",
"jsxImportSource": "solid-js",
"allowJs": true,
"resolveJsonModule": true,
"strict": true,
"noEmit": false,
"emitDeclarationOnly": true,
"outDir": "node_modules/.ts-dist",
"isolatedModules": true,
"noEmit": true,
"emitDeclarationOnly": false,
"outDir": "node_modules/.ts-dist"
"paths": {
"@/*": ["./src/*"]
}
},
"references": [{ "path": "../desktop" }],
"include": ["src"]
"include": ["src", "package.json"],
"exclude": ["dist", "ts-dist"]
}

View File

@@ -0,0 +1,15 @@
import { defineConfig } from "vite"
import desktopPlugin from "./vite"
export default defineConfig({
plugins: [desktopPlugin] as any,
server: {
host: "0.0.0.0",
allowedHosts: true,
port: 3000,
},
build: {
target: "esnext",
sourcemap: true,
},
})

View File

@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.0.180",
"version": "1.0.203",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",

View File

@@ -9,8 +9,8 @@ export const config = {
github: {
repoUrl: "https://github.com/sst/opencode",
starsFormatted: {
compact: "38K",
full: "38,000",
compact: "41K",
full: "41,000",
},
},
@@ -22,8 +22,8 @@ export const config = {
// Static stats (used on landing page)
stats: {
contributors: "400",
commits: "5,000",
contributors: "450",
commits: "6,000",
monthlyUsers: "400,000",
},
} as const

View File

@@ -6,6 +6,7 @@ const assetNames: Record<string, string> = {
"darwin-x64-dmg": "opencode-desktop-darwin-x64.dmg",
"windows-x64-nsis": "opencode-desktop-windows-x64.exe",
"linux-x64-deb": "opencode-desktop-linux-amd64.deb",
"linux-x64-appimage": "opencode-desktop-linux-amd64.AppImage",
"linux-x64-rpm": "opencode-desktop-linux-x86_64.rpm",
} satisfies Record<DownloadPlatform, string>

View File

@@ -244,6 +244,22 @@ export default function Download() {
Download
</a>
</div>
<div data-component="download-row">
<div data-component="download-info">
<span data-slot="icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M4.34591 22.7088C5.61167 22.86 7.03384 23.6799 8.22401 23.8247C9.42058 23.9758 9.79086 23.0098 9.79086 23.0098C9.79086 23.0098 11.1374 22.7088 12.553 22.6741C13.97 22.6344 15.3113 22.9688 15.3113 22.9688C15.3113 22.9688 15.5714 23.5646 16.057 23.8247C16.5426 24.0898 17.588 24.1257 18.258 23.4198C18.9293 22.7088 20.7204 21.8132 21.7261 21.2533C22.7382 20.6922 22.5525 19.8364 21.917 19.5763C21.2816 19.3163 20.7614 18.9063 20.8011 18.1196C20.8357 17.3394 20.24 16.8193 20.24 16.8193C20.24 16.8193 20.7614 15.1025 20.2759 13.6805C19.7903 12.2648 18.1889 9.98819 16.9577 8.27657C15.7266 6.55985 16.7719 4.5779 15.651 2.04503C14.5299 -0.491656 11.623 -0.341713 10.0562 0.739505C8.4893 1.8208 8.96968 4.50225 9.04526 5.77447C9.12084 7.04022 9.07985 7.94598 8.93509 8.27146C8.79033 8.60198 7.77951 9.80243 7.1082 10.8081C6.43818 11.819 5.95254 13.906 5.46187 14.7669C4.98142 15.6228 5.31711 16.403 5.31711 16.403C5.31711 16.403 4.98149 16.5182 4.71628 17.0795C4.45616 17.6342 3.93601 17.8993 2.99948 18.0801C2.06934 18.2709 2.06934 18.8705 2.29357 19.5419C2.51902 20.2119 2.29357 20.5873 2.03346 21.4431C1.77342 22.2988 3.07506 22.5588 4.34591 22.7088ZM17.5034 18.805C18.1683 19.0958 19.124 18.691 19.4149 18.4001C19.7045 18.1106 19.9094 17.6801 19.9094 17.6801C19.9094 17.6801 20.2002 17.8249 20.1707 18.2848C20.14 18.7512 20.3706 19.4161 20.8062 19.6467C21.2418 19.876 21.9067 20.1963 21.5621 20.5166C21.211 20.8369 19.2688 21.6183 18.6885 22.2282C18.1132 22.8341 17.3573 23.33 16.8974 23.1839C16.4324 23.0391 16.0262 22.4037 16.2261 21.4736C16.4324 20.5473 16.6066 19.5313 16.5771 18.951C16.5464 18.3707 16.4324 17.5892 16.5771 17.4738C16.7219 17.3598 16.9525 17.4148 16.9525 17.4148C16.9525 17.4148 16.8371 18.5156 17.5034 18.805ZM13.1885 3.12632C13.829 3.12632 14.3454 3.76175 14.3454 4.54324C14.3454 5.09798 14.0853 5.57844 13.7048 5.80906C13.6087 5.76937 13.5087 5.72449 13.3986 5.67832C13.6292 5.56434 13.7893 5.27352 13.7893 4.93783C13.7893 4.49844 13.519 4.13714 13.1794 4.13714C12.8489 4.13714 12.5734 4.49836 12.5734 4.93783C12.5734 5.09806 12.6132 5.25813 12.6785 5.38369C12.4786 5.30293 12.298 5.23383 12.1532 5.17874C12.0776 4.98781 12.0328 4.77257 12.0328 4.54331C12.0328 3.76183 12.5478 3.12632 13.1885 3.12632ZM11.6024 5.56823C11.9176 5.62331 12.7835 5.9987 13.1039 6.11398C13.4242 6.22415 13.7791 6.4291 13.7445 6.63413C13.7048 6.84548 13.5395 6.84548 13.1039 7.1107C12.6735 7.37082 11.7331 7.95116 11.432 7.99085C11.1322 8.03055 10.9618 7.86141 10.6415 7.65516C10.3211 7.44503 9.72039 6.95436 9.87147 6.69432C9.87147 6.69432 10.3416 6.33432 10.5467 6.14986C10.7517 5.95893 11.2821 5.50925 11.6024 5.56823ZM10.2213 3.35185C10.726 3.35185 11.1373 3.95268 11.1373 4.69318C11.1373 4.82773 11.1219 4.95322 11.0976 5.07878C10.972 5.11847 10.8466 5.18385 10.726 5.28891C10.6671 5.33889 10.612 5.38369 10.5621 5.43367C10.6415 5.28381 10.6722 5.06857 10.6363 4.84305C10.5672 4.44335 10.2968 4.14743 10.0316 4.18712C9.76511 4.232 9.60625 4.5984 9.67033 5.00327C9.74081 5.41325 10.0059 5.7091 10.2763 5.6643C10.2917 5.6592 10.3058 5.65409 10.3211 5.64891C10.1918 5.77447 10.0713 5.88464 9.94576 5.97432C9.58065 5.80388 9.31033 5.29402 9.31033 4.69318C9.31041 3.94758 9.71521 3.35185 10.2213 3.35185ZM7.40915 13.045C7.9293 12.2251 8.26492 10.4328 8.78507 9.83702C9.31041 9.24259 9.71521 7.97554 9.53075 7.41569C9.53075 7.41569 10.6517 8.75702 11.432 8.53668C12.2135 8.31116 13.97 7.00571 14.23 7.22994C14.4901 7.45539 16.727 12.375 16.9525 13.9419C17.178 15.5074 16.8026 16.7041 16.8026 16.7041C16.8026 16.7041 15.9468 16.4785 15.8366 16.9987C15.7264 17.524 15.7264 19.4265 15.7264 19.4265C15.7264 19.4265 14.5695 21.0279 12.7784 21.2931C10.9874 21.5532 10.0905 21.3636 10.0905 21.3636L9.08481 20.2118C9.08481 20.2118 9.86637 20.0965 9.75612 19.3112C9.64595 18.531 7.36801 17.4496 6.95803 16.4785C6.5482 15.5073 6.8826 13.8662 7.40915 13.045ZM2.9802 18.9204C3.06988 18.5361 4.23056 18.5361 4.67643 18.2657C5.12229 17.9954 5.21189 17.219 5.57197 17.0141C5.92679 16.804 6.58279 17.5496 6.85311 17.9697C7.11833 18.3797 8.13433 20.1721 8.54942 20.6179C8.96961 21.0676 9.35528 21.6633 9.23483 22.1988C9.12084 22.7343 8.48923 23.1251 8.48923 23.1251C7.92427 23.2993 6.34843 22.619 5.63231 22.3192C4.9162 22.0182 3.09433 21.9284 2.8599 21.6633C2.61906 21.393 2.97517 20.7972 3.06995 20.2322C3.15445 19.6609 2.8893 19.306 2.9802 18.9204Z"
fill="currentColor"
/>
</svg>
</span>
<span>Linux (.AppImage)</span>
</div>
<a href={getDownloadHref("linux-x64-appimage")} data-component="action-button">
Download
</a>
</div>
</div>
</section>

View File

@@ -1 +1,4 @@
export type DownloadPlatform = `darwin-${"x64" | "aarch64"}-dmg` | "windows-x64-nsis" | `linux-x64-${"deb" | "rpm"}`
export type DownloadPlatform =
| `darwin-${"x64" | "aarch64"}-dmg`
| "windows-x64-nsis"
| `linux-x64-${"deb" | "rpm" | "appimage"}`

View File

@@ -19,17 +19,23 @@ export function createDataDumper(sessionId: string, requestId: string, projectId
if (!data.modelName) return
const timestamp = new Date().toISOString().replace(/[^0-9]/g, "")
const year = timestamp.substring(0, 4)
const month = timestamp.substring(4, 6)
const day = timestamp.substring(6, 8)
const hour = timestamp.substring(8, 10)
const minute = timestamp.substring(10, 12)
const second = timestamp.substring(12, 14)
waitUntil(
Resource.ZenData.put(
`data/${data.modelName}/${sessionId}/${requestId}.json`,
Resource.ZenDataNew.put(
`data/${data.modelName}/${year}/${month}/${day}/${hour}/${minute}/${second}/${requestId}.json`,
JSON.stringify({ timestamp, ...data }),
),
)
waitUntil(
Resource.ZenData.put(
`meta/${data.modelName}/${timestamp}/${requestId}.json`,
Resource.ZenDataNew.put(
`meta/${data.modelName}/${sessionId}/${requestId}.json`,
JSON.stringify({ timestamp, ...metadata }),
),
)

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.0.180",
"version": "1.0.203",
"private": true,
"type": "module",
"dependencies": {

View File

@@ -17,14 +17,16 @@ const value2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[
const value3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1]
const value4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1]
const value5 = lines.find((line) => line.startsWith("ZEN_MODELS5"))?.split("=")[1]
const value6 = lines.find((line) => line.startsWith("ZEN_MODELS6"))?.split("=")[1]
if (!value1) throw new Error("ZEN_MODELS1 not found")
if (!value2) throw new Error("ZEN_MODELS2 not found")
if (!value3) throw new Error("ZEN_MODELS3 not found")
if (!value4) throw new Error("ZEN_MODELS4 not found")
if (!value5) throw new Error("ZEN_MODELS5 not found")
if (!value6) throw new Error("ZEN_MODELS6 not found")
// validate value
ZenData.validate(JSON.parse(value1 + value2 + value3 + value4 + value5))
ZenData.validate(JSON.parse(value1 + value2 + value3 + value4 + value5 + value6))
// update the secret
await $`bun sst secret set ZEN_MODELS1 ${value1} --stage ${stage}`
@@ -32,3 +34,4 @@ await $`bun sst secret set ZEN_MODELS2 ${value2} --stage ${stage}`
await $`bun sst secret set ZEN_MODELS3 ${value3} --stage ${stage}`
await $`bun sst secret set ZEN_MODELS4 ${value4} --stage ${stage}`
await $`bun sst secret set ZEN_MODELS5 ${value5} --stage ${stage}`
await $`bun sst secret set ZEN_MODELS6 ${value6} --stage ${stage}`

View File

@@ -17,14 +17,15 @@ const value2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[
const value3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1]
const value4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1]
const value5 = lines.find((line) => line.startsWith("ZEN_MODELS5"))?.split("=")[1]
const value6 = lines.find((line) => line.startsWith("ZEN_MODELS6"))?.split("=")[1]
if (!value1) throw new Error("ZEN_MODELS1 not found")
if (!value2) throw new Error("ZEN_MODELS2 not found")
if (!value3) throw new Error("ZEN_MODELS3 not found")
if (!value4) throw new Error("ZEN_MODELS4 not found")
if (!value5) throw new Error("ZEN_MODELS5 not found")
if (!value6) throw new Error("ZEN_MODELS6 not found")
// validate value
ZenData.validate(JSON.parse(value1 + value2 + value3 + value4 + value5))
ZenData.validate(JSON.parse(value1 + value2 + value3 + value4 + value5 + value6))
// update the secret
await $`bun sst secret set ZEN_MODELS1 ${value1}`
@@ -32,3 +33,4 @@ await $`bun sst secret set ZEN_MODELS2 ${value2}`
await $`bun sst secret set ZEN_MODELS3 ${value3}`
await $`bun sst secret set ZEN_MODELS4 ${value4}`
await $`bun sst secret set ZEN_MODELS5 ${value5}`
await $`bun sst secret set ZEN_MODELS6 ${value6}`

View File

@@ -15,16 +15,20 @@ const oldValue2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=
const oldValue3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1]
const oldValue4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1]
const oldValue5 = lines.find((line) => line.startsWith("ZEN_MODELS5"))?.split("=")[1]
const oldValue6 = lines.find((line) => line.startsWith("ZEN_MODELS6"))?.split("=")[1]
if (!oldValue1) throw new Error("ZEN_MODELS1 not found")
if (!oldValue2) throw new Error("ZEN_MODELS2 not found")
if (!oldValue3) throw new Error("ZEN_MODELS3 not found")
if (!oldValue4) throw new Error("ZEN_MODELS4 not found")
if (!oldValue5) throw new Error("ZEN_MODELS5 not found")
if (!oldValue6) throw new Error("ZEN_MODELS6 not found")
// store the prettified json to a temp file
const filename = `models-${Date.now()}.json`
const tempFile = Bun.file(path.join(os.tmpdir(), filename))
await tempFile.write(JSON.stringify(JSON.parse(oldValue1 + oldValue2 + oldValue3 + oldValue4 + oldValue5), null, 2))
await tempFile.write(
JSON.stringify(JSON.parse(oldValue1 + oldValue2 + oldValue3 + oldValue4 + oldValue5 + oldValue6), null, 2),
)
console.log("tempFile", tempFile.name)
// open temp file in vim and read the file on close
@@ -33,15 +37,17 @@ const newValue = JSON.stringify(JSON.parse(await tempFile.text()))
ZenData.validate(JSON.parse(newValue))
// update the secret
const chunk = Math.ceil(newValue.length / 5)
const chunk = Math.ceil(newValue.length / 6)
const newValue1 = newValue.slice(0, chunk)
const newValue2 = newValue.slice(chunk, chunk * 2)
const newValue3 = newValue.slice(chunk * 2, chunk * 3)
const newValue4 = newValue.slice(chunk * 3, chunk * 4)
const newValue5 = newValue.slice(chunk * 4)
const newValue5 = newValue.slice(chunk * 4, chunk * 5)
const newValue6 = newValue.slice(chunk * 5)
await $`bun sst secret set ZEN_MODELS1 ${newValue1}`
await $`bun sst secret set ZEN_MODELS2 ${newValue2}`
await $`bun sst secret set ZEN_MODELS3 ${newValue3}`
await $`bun sst secret set ZEN_MODELS4 ${newValue4}`
await $`bun sst secret set ZEN_MODELS5 ${newValue5}`
await $`bun sst secret set ZEN_MODELS6 ${newValue6}`

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