Compare commits

..

827 Commits

Author SHA1 Message Date
opencode
427157c683 release: v1.0.168 2025-12-18 21:55:29 +00:00
Github Action
a0ab3d98b7 Update Nix flake.lock and hashes 2025-12-18 21:48:53 +00:00
GitHub Action
c8de766913 chore: generate 2025-12-18 21:47:59 +00:00
Adam
d57b963141 fix: id 2025-12-18 15:47:21 -06:00
Adam
0ebcaff927 fix(desktop): expanded states 2025-12-18 15:47:20 -06:00
Adam
15931fa170 chore: cleanup 2025-12-18 15:47:20 -06:00
Adam
af4087d7b5 fix(desktop): smaller max-width when review open 2025-12-18 15:47:20 -06:00
Aiden Cline
323ea1040c ci: fix generate 2025-12-18 15:23:23 -06:00
Aiden Cline
1fe87b0233 ci: fix file perm 2025-12-18 14:39:44 -06:00
Aiden Cline
8d11df1b3b ci: handle case where generate.yml fails better 2025-12-18 14:33:40 -06:00
Aiden Cline
ecc5050838 tweak: more retry cases 2025-12-18 13:59:37 -06:00
Aiden Cline
606cf3b6f2 chore: rm dead code 2025-12-18 13:59:37 -06:00
GitHub Action
67cfd7f06b chore: format code 2025-12-18 19:38:25 +00:00
OpeOginni
ab9ac7c87a feat: add experimental support for Ty language server (#5575) 2025-12-18 13:37:48 -06:00
Adam
ee9f979613 fix(desktop): markdown styles 2025-12-18 13:03:14 -06:00
Adam
228b6444f8 fix(desktop): don't show image button in shell mode 2025-12-18 13:03:14 -06:00
Frank
9998efdae2 zen: cleanup headers 2025-12-18 13:47:31 -05:00
Aiden Cline
9427f56e1a rm interleaved thinking filter for certain kimi k2 thinking model providers that were bugged 2025-12-18 12:26:27 -06:00
Adam
a6dd35d73d fix(desktop): submit prompt 2025-12-18 12:03:21 -06:00
GitHub Action
faeaafa5f5 chore: format code 2025-12-18 17:31:49 +00:00
Matt Silverlock
8b298a233e github: add OIDC_BASE_URL for custom GitHub App installs (#5756) 2025-12-18 11:31:13 -06:00
Adam
6f43d03043 fix(desktop): checkbox render in safari fml 2025-12-18 11:16:33 -06:00
Adam
c868a4088d fix(desktop): rendering shell mode messages 2025-12-18 11:16:33 -06:00
Adam
83d8a88c90 fix(desktop): error styles 2025-12-18 11:16:33 -06:00
Adam
268f37f8c9 fix(desktop): prompt history nav, optimistic prompt dup 2025-12-18 11:16:33 -06:00
Adam
b0aaf04957 fix(desktop): session ordered by most recent 2025-12-18 11:16:32 -06:00
Adam
b7875256f3 feat(desktop): shell mode 2025-12-18 11:16:32 -06:00
Adam
7bc47fb904 chore: cleanup 2025-12-18 11:16:32 -06:00
GitHub Action
5cf8e54372 chore: format code 2025-12-18 16:39:21 +00:00
Ariane Emory
7437ccd6f4 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-18 10:38:19 -06:00
Jeon Suyeol
4bf882ba81 fix(command): validate model before executing slash command (#5740) 2025-12-18 10:35:40 -06:00
Frank
d5dcc55a47 Revert "add client header"
This reverts commit 2fb89161c8.
2025-12-18 11:21:22 -05:00
barış
e1925f4fe8 docs: fix typos (#5753) 2025-12-18 09:56:37 -06:00
Aiden Cline
ee3d034e16 ci: fix discord 2025-12-18 09:56:08 -06:00
Aiden Cline
257a4d5b86 bump bun version 2025-12-18 09:47:42 -06:00
Daniel Polito
1fc5836f64 Improve Github Action Hallucinations (#5751) 2025-12-18 09:40:04 -06:00
Frank
2fb89161c8 add client header 2025-12-18 10:33:25 -05:00
GitHub Action
251fbc0a99 ignore: update download stats 2025-12-18 2025-12-18 12:16:54 +00:00
Brendan Allan
0da901a188 tauri: disable pinch zoom on linux (#5735) 2025-12-18 05:13:09 -06:00
Brendan Allan
17221e6ffe tauri: configure display backends more correctly on linux (#5730) 2025-12-18 18:34:12 +08:00
GitHub Action
cc9f88ac8f chore: format code 2025-12-18 10:28:55 +00:00
Adam
fe65ed6a61 fix(desktop): disable pinch to zoom 2025-12-18 04:28:03 -06:00
Adam
e37a75a411 feat(desktop): custom update toast 2025-12-18 04:26:21 -06:00
opencode
194ff4919c release: v1.0.167 2025-12-18 09:45:33 +00:00
shuv
83843a794f fix: handle empty directory query parameter in server middleware (#5732) 2025-12-18 03:27:50 -06:00
Brendan Allan
235a60d3c2 tauri: say OpenCode Server instead of OpenCode CLI 2025-12-18 17:18:46 +08:00
Brendan Allan
b70d186bd1 tauri: server spawn fail dialog w/ copy logs button (#5729) 2025-12-18 03:17:31 -06:00
Frank
647331de28 zen: error handling for stream requests 2025-12-18 00:47:37 -05:00
GitHub Action
57ef115375 chore: format code 2025-12-18 04:30:54 +00:00
Jeon Suyeol
942498211f docs: add OPENCODE_DISABLE_TERMINAL_TITLE to environment variables (#5725) 2025-12-17 22:30:21 -06:00
Jake Nelson
e789fcf5e5 feat(tui): add option to disable terminal title (#5713) 2025-12-17 22:30:01 -06:00
Frank
b9fb180bc6 zen: error handling for stream requests 2025-12-17 22:55:44 -05:00
Rohan Mukherjee
7427b887f9 MCP improvements (#5699) 2025-12-17 20:49:45 -06:00
Jay V
289b2b6a51 docs: add legal pages with privacy policy and terms of service links 2025-12-17 20:17:33 -05:00
GitHub Action
49b4b5907e chore: format code 2025-12-18 01:09:02 +00:00
Ryan Vogel
f82442c123 docs: add opencode.cafe to ecosystem page (#5714) 2025-12-17 19:08:29 -06:00
opencode
e682cc9daf release: v1.0.166 2025-12-17 22:08:15 +00:00
Adam
d359e086a4 chore: cleanup 2025-12-17 16:04:41 -06:00
Adam
f949755367 fix: better init error messages 2025-12-17 16:04:40 -06:00
Adam
a168d854f4 fix: auto-scroll 2025-12-17 16:04:40 -06:00
GitHub Action
31645f5578 chore: format code 2025-12-17 22:03:43 +00:00
Sercan Sagman
a1b68daa9a fix(tui): exclude reverted assistant reply when copying last message (#5705)
Signed-off-by: assagman <ahmetsercansagman@gmail.com>
2025-12-17 16:03:06 -06:00
opencode
ca65da2d9e release: v1.0.165 2025-12-17 21:46:21 +00:00
Adam
e48d804d84 feat(desktop): startup errors shown 2025-12-17 15:42:55 -06:00
Adam
b4209582fb feat(desktop): optimistic prompt submit 2025-12-17 15:42:55 -06:00
Aiden Cline
dbdea2f659 fix: better error messages 2025-12-17 15:32:44 -06:00
Aiden Cline
a50ab4b5b5 fix: prevent 1 from showing when preparing write 2025-12-17 15:25:04 -06:00
Nalin Singh
4d7c3f56fa feat: add viewportOptions to scrollbox for padding adjustments to avoid scrollbar overlap (#5703) 2025-12-17 15:09:41 -06:00
Spoon
16b41d2bea UI: show plugins in /status (#4515)
Co-authored-by: GitHub Action <action@github.com>
2025-12-17 15:01:52 -06:00
Nalin Singh
a8c499ae8f fix: prevent session list selection from jumping to active session when confirming delete (#5666) 2025-12-17 14:35:46 -06:00
Joel Hooks
24430287c5 feat(plugin): add experimental.session.compacting hook for pre-compaction context injection (#5698)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-12-17 13:57:09 -06:00
Github Action
1f52731255 Update Nix flake.lock and hashes 2025-12-17 19:13:06 +00:00
Adam
4a3ba58f65 chore: localStorage -> tauri store 2025-12-17 13:11:02 -06:00
Brendan Allan
2a3a8a1ec2 console: use download proxy to rename mac and windows installers (#5697)
Co-authored-by: GitHub Action <action@github.com>
2025-12-18 01:59:23 +08:00
Ravi Kumar
69e562125d fix(tui): resolve session_status TypeError (#5520) 2025-12-17 11:38:05 -06:00
Aiden Cline
b5e97eb338 fix: keep session dialog open if deleting session 2025-12-17 11:29:55 -06:00
Aiden Cline
16e6941495 fix: remove needless tui event publish on session delete 2025-12-17 11:29:55 -06:00
GitHub Action
f033e0317e chore: format code 2025-12-17 17:02:36 +00:00
Adam
ddd88f92cc fix: sticky visual issues 2025-12-17 11:02:00 -06:00
Aiden Cline
99101edc13 ci: add windows label to triage bot 2025-12-17 11:01:53 -06:00
GitHub Action
6e85a07977 chore: format code 2025-12-17 16:59:57 +00:00
Brendan Allan
be1a3536ae console: add /download/[platform] endpoint 2025-12-18 00:59:16 +08:00
Qio
1e4bfbcf6f add OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX to override 32k default (#5679)
Co-authored-by: qio <handsomehust@gmail.com>
2025-12-17 10:35:43 -06:00
Adam
204e3bf382 feat(desktop): inter and ibm plex mono 2025-12-17 10:30:35 -06:00
Adam
8fb014a48d feat(desktop): inter and ibm plex mono 2025-12-17 10:30:03 -06:00
Paolo Ricciuti
57c3cf1f8b fix: send mcpName as state if authUrl doesn't have state (#5681) 2025-12-17 10:26:23 -06:00
Aiden Cline
f9d0850c5e test: add regression test for setCacheKey option 2025-12-17 10:24:53 -06:00
Spoon
8864da7a77 batch: enable edit, todoread, clarify error message, minor tool description change (#5659) 2025-12-17 10:23:35 -06:00
Rhys Sullivan
1b39199083 fix: change subagent navigation order to newest-to-oldest (#5680) 2025-12-17 10:22:57 -06:00
Shantur Rathore
b8204c0bb7 fix: config option setCacheKey not being respected (#5686) 2025-12-17 10:20:10 -06:00
Aiden Cline
fe8c5c143e docs: update share link 2025-12-17 10:18:30 -06:00
Frank
d6f86e9bb7 zen: add gemini 3 flash 2025-12-17 11:10:58 -05:00
Brendan Allan
bf00b2bfc9 tauri: nsis header and sidebar 2025-12-18 00:02:16 +08:00
Brendan Allan
382ec8fb2c tauri: update nsis icon 2025-12-17 23:40:52 +08:00
Stoufiler
6454adcd69 docs: Sort LSP Server list (#5688) 2025-12-17 09:14:26 -06:00
Rohan Mukherjee
99548554d7 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-17 09:10:35 -06:00
Sachnun
751899eeec fix: remove unsupported parameter from bash tool description (#5676) 2025-12-17 08:59:42 -06:00
Brendan Allan
f8df1d3185 tauri: return after update failures 2025-12-17 22:54:54 +08:00
GitHub Action
b07a47fc89 chore: format code 2025-12-17 14:52:05 +00:00
Brendan Allan
c6f84f32d7 tauri: only alert on update failure when triggered manually 2025-12-17 22:51:14 +08:00
Brendan Allan
ebe25c3e9a tauri: dev icons + separate prod config (#5691)
Co-authored-by: GitHub Action <action@github.com>
2025-12-17 22:23:03 +08:00
Adam
65d7fc3ccd fix: command shortcuts 2025-12-17 07:36:53 -06:00
Github Action
4f3037d803 Update Nix flake.lock and hashes 2025-12-17 13:35:05 +00:00
Amadeus Demarzi
5c490c51ed Diffs Performance Improvements (#5653)
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
2025-12-17 07:33:46 -06:00
GitHub Action
5da1c0087b ignore: update download stats 2025-12-17 2025-12-17 12:04:41 +00:00
David Hill
4375149e63 wip: auto-detect OS and show desktop download button 2025-12-17 11:43:04 +00:00
David Hill
b695d3b6bb fix: website cta button 2025-12-17 11:21:57 +00:00
Adam
d7e133732c chore: cleanup 2025-12-17 03:58:16 -06:00
Adam
494e6fff01 feat(desktop): share sessions 2025-12-17 03:47:49 -06:00
Adam
0c7a297b1d feat(desktop): lsp diagnostics displayed 2025-12-17 03:47:48 -06:00
Github Action
9b1f9007c3 Update Nix flake.lock and hashes 2025-12-17 08:41:07 +00:00
GitHub Action
34ef5f4ece chore: format code 2025-12-17 08:40:22 +00:00
Sebastian Herrlinger
73ad20b90c upgrade opentui to v0.1.61 2025-12-17 09:38:04 +01:00
Jeon Suyeol
340e80257a Add availability to disable terminal title using OPENCODE_DISABLE_TERMINAL_TITLE env (#5661) 2025-12-16 23:57:03 -06:00
Dax Raad
c23ea2a211 ci: update publish workflow configuration 2025-12-16 21:01:44 -05:00
GitHub Action
a5f964aec6 chore: format code 2025-12-17 01:28:42 +00:00
Spoon
b8a8fb0de6 plugin(hook): add task tool execution hooks and command context tracking (#5642) 2025-12-16 19:28:09 -06:00
Aiden Cline
a6a8f41fd3 ci: tweak triage 2025-12-16 19:27:44 -06:00
Matt Silverlock
c137babea3 github: add configurable mentions input (#5655) 2025-12-16 19:14:50 -06:00
Adam
db2abc1b2c tui: increase session width to accommodate longer code blocks and improve readability 2025-12-16 17:31:26 -06:00
David Hill
a0f9f8dabb fix: load more button 2025-12-16 23:23:18 +00:00
Aiden Cline
8a185aa678 ci: fix missing pkg issue 2025-12-16 16:31:46 -06:00
Dax Raad
29aaf4f000 ci: fix release draft configuration to prevent automatic draft flag 2025-12-16 17:17:06 -05:00
GitHub Action
fc940dfcfb chore: format code 2025-12-16 22:07:10 +00:00
Adam
2f2ea98937 fix(share): content wasn't centered 2025-12-16 16:06:28 -06:00
opencode
ef0fa2007b release: v1.0.164 2025-12-16 21:47:41 +00:00
Adam
f07d4b933c fix(desktop): prompt history nav 2025-12-16 15:42:35 -06:00
Aiden Cline
5f57cee8e4 fix: user invoked subtasks causing tool_use or missing thinking signa… (#5650) 2025-12-16 15:42:21 -06:00
Adam
1755a3fe07 fix(desktop): auto-scroll 2025-12-16 15:32:14 -06:00
Adam
99680baf83 fix(desktop): focus prompt input after dialog close 2025-12-16 15:25:00 -06:00
Adam
9aa5460a0e fix(desktop): prompt history navigation 2025-12-16 15:10:44 -06:00
Adam
b4014e5baa fix: auto-scroll 2025-12-16 15:10:43 -06:00
Adam
96e4dcb521 fix: working logic 2025-12-16 15:10:43 -06:00
Adam
7e682a95c4 fix: prompt input multi line input 2025-12-16 15:10:43 -06:00
Adam
5eeba76bc5 fix: defensive audio init 2025-12-16 15:10:43 -06:00
Eric Guo
a2c91ebc32 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-16 14:50:33 -06:00
matvey
1aee8b49e1 feat: add experimental oxfmt formatter (#5620) 2025-12-16 14:43:14 -06:00
Aiden Cline
984f17ddd7 ci: include desktop & tauri in release notes 2025-12-16 14:37:33 -06:00
Aiden Cline
d556143e3b ci: fix branch name 2025-12-16 14:35:42 -06:00
Aiden Cline
7e3ad770ac fix: git branch filewatcher, add flag to completely disable watcher 2025-12-16 14:31:09 -06:00
Aiden Cline
87524de265 ci: fix triage 2025-12-16 14:15:50 -06:00
Aiden Cline
ee10d9b898 ci: auto tag github action once a change is shipped for it 2025-12-16 14:13:51 -06:00
Dax Raad
bbd36e8441 core: update plugin dependency and config loading for .opencode directory support 2025-12-16 15:06:50 -05:00
Dax Raad
4e2d1acf7d 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-16 14:45:03 -05:00
Dax Raad
40d63cd1e3 fix 2025-12-16 14:25:12 -05:00
Dax Raad
77b2331428 ignore: update opencode plugin dependency 2025-12-16 14:21:19 -05:00
Dax Raad
2b7e2edee5 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-16 14:21:19 -05:00
Adam
28aba35ff9 feat(desktop): show retries 2025-12-16 13:19:32 -06:00
Adam
89219a77f7 fix: layout badness 2025-12-16 12:53:12 -06:00
Adam
20e3a74bad fix: defensive audio init 2025-12-16 12:53:12 -06:00
Adam
ff690350b1 feat(desktop): show write tool output 2025-12-16 12:53:11 -06:00
Adam
ebefb26e8f chore: cleanup 2025-12-16 12:53:11 -06:00
Dax Raad
0b1ee9ddd9 Merge remote-tracking branch 'origin/dev' into dev 2025-12-16 13:52:50 -05:00
Dax Raad
79599f351e chore: update opencode plugin dependencies and fix tauri sidecar path 2025-12-16 13:52:40 -05:00
Github Action
8c9f6b1d3e Update Nix flake.lock and hashes 2025-12-16 18:38:59 +00:00
Dax Raad
83bcb9e95b tui: fix autocomplete file loading and update dependencies 2025-12-16 13:37:22 -05:00
David Hill
96b9ff8d0e fix: remove the selected state from button when select deselected 2025-12-16 17:41:56 +00:00
David Hill
0af2254856 wip: add active state to open select 2025-12-16 17:35:04 +00:00
Fran Zekan
c2944024a8 fix: enable shell alias expansion in ! command (#5621) 2025-12-16 11:32:31 -06:00
GitHub Action
5be4bda90f chore: format code 2025-12-16 17:30:08 +00:00
shekohex
b78e2db013 docs: fix typo in Google Antigravity github link (#5625) 2025-12-16 11:29:31 -06:00
jinzhongjia
3f4d1121a4 docs: Add new project entry for opencode.nvim frontend (#5626) 2025-12-16 11:29:06 -06:00
Connor Adams
def910021d 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-16 11:28:40 -06:00
Aiden Cline
3ac42e9632 fix: github install cmd if repo has . in it 2025-12-16 11:19:18 -06:00
David Hill
9c26bb7c6c fix: breadcrumb dropdown position left aligned 2025-12-16 17:16:51 +00:00
David Hill
53f20f7612 Revert "wip: make the default container wider"
This reverts commit 1f18f389c0.
2025-12-16 17:16:51 +00:00
Tommy D. Rossi
11b3927dc2 fix: use system prompt field from prompt input (#5633)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-16 10:52:22 -06:00
David Hill
a190eda2c8 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-16 16:36:15 +00:00
David Hill
1f18f389c0 wip: make the default container wider 2025-12-16 16:35:57 +00:00
GitHub Action
84e56ee614 chore: format code 2025-12-16 16:10:36 +00:00
Aiden Cline
59329a414d ci: tweak triage 2025-12-16 10:09:51 -06:00
Brendan Allan
452c991f58 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-16 23:34:44 +08:00
Simon D'Morias
be8116e2ea fix: preserve argument boundaries in run command (#4979) 2025-12-16 06:21:51 -06:00
David Hill
f0ed1e38c9 Revert "fix: strip parentheses from file paths generated by llm"
This reverts commit 6c1a1a77b7.
2025-12-16 12:12:01 +00:00
GitHub Action
ac0f1dbbdd ignore: update download stats 2025-12-16 2025-12-16 12:04:54 +00:00
GitHub Action
275a352e81 chore: format code 2025-12-16 11:51:08 +00:00
David Hill
9f3bc0e352 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-16 11:50:25 +00:00
David Hill
6c1a1a77b7 fix: strip parentheses from file paths generated by llm 2025-12-16 11:50:23 +00:00
David Hill
2e21c62320 fix: font size updates 2025-12-16 11:48:52 +00:00
David Hill
19c6fec4d1 wip: font-size updates 2025-12-16 11:12:08 +00:00
Brendan Allan
4779d99a13 tauri: explicitly kill sidecar before updater relaunch 2025-12-16 19:01:37 +08:00
David Hill
05e0759878 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-16 10:54:51 +00:00
David Hill
2330ec6dc3 fix: font size updates 2025-12-16 10:54:50 +00:00
GitHub Action
75e5130cf8 chore: format code 2025-12-16 10:33:49 +00:00
Brendan Allan
87efd27459 tauri: macos-only app menu 2025-12-16 18:33:04 +08:00
Aiden Cline
62f080b0e4 fix: small bug w/ install script 2025-12-16 00:20:05 -06:00
Aiden Cline
ae3990a557 chore: centralize dep to catalog & fix typos 2025-12-15 23:07:55 -06:00
Aiden Cline
d7b5b431d6 ci: Update issue assignment and labeling guidelines
Clarified assignment responsibilities and labeling for issues related to OpenCode tools.
2025-12-15 22:11:30 -06:00
Dax Raad
e2fbd098d2 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-15 22:57:52 -05:00
Luke Parker
ef78fd8bae fix: debounce LSP diagnostics to get complete results (#5600) 2025-12-15 21:26:59 -06:00
DS
72ebaeb8f7 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-15 20:00:26 -06:00
Lucas Duailibe
0dc62d5dad make install script use tmp dir (#5601) 2025-12-15 19:58:07 -06:00
Aiden Cline
d118782a10 ci: cheaper model 2025-12-15 19:24:30 -06:00
Aiden Cline
ff05647350 ci: ignore 2025-12-15 19:18:39 -06:00
GitHub Action
0e1c711c4e chore: format code 2025-12-16 01:16:20 +00:00
Aiden Cline
bfb254dac6 ci: auto triage issues 2025-12-15 19:15:40 -06:00
opencode
92fe927785 release: v1.0.162 2025-12-16 00:40:27 +00:00
GitHub Action
2e25fe9d5d chore: format code 2025-12-16 00:36:01 +00:00
Dax Raad
38c5f23f4a tui: update dialog context and server to use new single dialog system 2025-12-15 19:35:19 -05:00
Dax Raad
112c58abf5 tui: refactor dialog system to use single active dialog instead of stack 2025-12-15 19:35:18 -05:00
opencode
0dce5173cc release: v1.0.161 2025-12-16 00:17:15 +00:00
Adam
2c70c0b00f fix: undefined events 2025-12-15 18:13:11 -06:00
opencode-agent[bot]
34024c2504 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-15 18:04:47 -06:00
Luke Parker
27e826eba6 fix(win32): Normalise LSP paths on windows (fixes lua) (#5597) 2025-12-15 18:01:03 -06:00
Aiden Cline
89a4f1c1ae tweak: add .catch for extractZip calls 2025-12-15 17:41:28 -06:00
GitHub Action
c0c61b25ff chore: format code 2025-12-15 23:30:06 +00:00
Luke Parker
0d1c6e0ca9 fix(win32): Missing LSP can now unzip on windows (#5594) 2025-12-15 17:29:30 -06:00
opencode
002db3abf4 release: v1.0.160 2025-12-15 23:26:00 +00:00
Dax Raad
416a919c6d tui: fix dialog replacement to prevent nested dialogs from showing simultaneously 2025-12-15 18:21:54 -05:00
Dax Raad
dbbcf0b8d0 tui: fix model selection dialog to properly replace current dialog instead of creating nested dialogs 2025-12-15 18:14:33 -05:00
Luke Parker
efac8cebb3 fix(win32): correct ElixirLS extension typo (#5590) 2025-12-15 16:47:27 -06:00
GitHub Action
4f2baf1a72 chore: format code 2025-12-15 22:40:18 +00:00
Luke Parker
48b2bde6e5 fix(win32): use path.delimiter for PATH separator in LSP server lookups (#5589) 2025-12-15 16:39:42 -06:00
opencode
88314148e6 release: v1.0.159 2025-12-15 22:19:08 +00:00
Dax Raad
56452d886d fix dialog root complexity 2025-12-15 17:13:20 -05:00
Luke Parker
f3e64cfb19 fix(windows): opencode github install (#5587) 2025-12-15 16:12:54 -06:00
Ariane Emory
8fcc80bc20 fix: restore ability to bind keys for model_cycle_favorite model_cycle_favorite_reverse (resolves #5198) (#5202) 2025-12-15 16:03:50 -06:00
opencode-agent[bot]
0beccc406e 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-15 16:01:35 -06:00
Adam
b82ea693db fix: multiline user input 2025-12-15 15:22:39 -06:00
Adam
4fd9a19fbb fix: keybinds for agent and model selection 2025-12-15 15:19:19 -06:00
Adam
e16487b804 fix: landing page CLS hero jump down 2025-12-15 15:10:48 -06:00
Adam
5388192aac fix: session nav on homepage 2025-12-15 15:10:17 -06:00
Adam
8010448ba1 fix: default steps expanded unless done 2025-12-15 15:03:00 -06:00
Adam
66f3e69867 fix: landing page CLS hero jump down 2025-12-15 14:59:24 -06:00
Aiden Cline
ca599ab8fc tweak: add model flag support for agent create command 2025-12-15 14:55:59 -06:00
Jay V
c3b3b133b0 docs: make project names clickable links in ecosystem documentation 2025-12-15 15:44:46 -05:00
Adam
300ec0e0af fix: missing event type (global) 2025-12-15 14:43:48 -06:00
Ariane Emory
6632987827 fix: record shell mode in history (resolves #5454) (#5551) 2025-12-15 14:42:17 -06:00
GitHub Action
e555e893c4 chore: format code 2025-12-15 20:35:39 +00:00
Aiden Cline
81134cf61e add ability to set topK 2025-12-15 14:34:56 -06:00
opencode
37e4c1e619 release: v1.0.158 2025-12-15 20:28:44 +00:00
Adam
02b5e7d72c fix: modal search 2025-12-15 14:25:19 -06:00
justfortheloveof
7abc2a947e tweak: prioritize fuzzysort results that start with user input (#5571) 2025-12-15 14:22:37 -06:00
Adam
337a7e9646 fix: allow for non-vcs projects in desktop 2025-12-15 14:16:08 -06:00
GitHub Action
62cc532ecc chore: format code 2025-12-15 20:09:08 +00:00
Dax Raad
d5a506d4ae core: fix server response handling to prevent connection timeouts 2025-12-15 15:07:20 -05:00
opencode
9c5f94bd66 release: v1.0.157 2025-12-15 19:46:23 +00:00
Dax Raad
83390314d6 ci: fix tauri updater version mismatch by checking out the release tag 2025-12-15 14:42:34 -05:00
Dax Raad
8b08e9cda5 ci: add app bundle target to fix macOS updater by generating .app.tar.gz files 2025-12-15 14:41:38 -05:00
GitHub Action
b1b1df824c chore: format code 2025-12-15 19:33:56 +00:00
Dax Raad
7d1733c752 core: fix message caching for Anthropic models to improve response consistency 2025-12-15 14:33:14 -05:00
opencode
cf05e6e02b release: v1.0.156 2025-12-15 19:28:02 +00:00
Adam
7e49d0fb15 fix: connect provider on homepage 2025-12-15 13:24:30 -06:00
Adam
c4f63824df fix: terminal in desktop 2025-12-15 13:09:13 -06:00
Adam
4236744fb5 fix: image attachments in desktop 2025-12-15 12:01:11 -06:00
Github Action
284c045795 Update Nix flake.lock and hashes 2025-12-15 16:57:28 +00:00
Dax Raad
2c53abd70c docs: update header navigation to include desktop download 2025-12-15 11:56:53 -05:00
Adam
b7a9cbfc68 fix: share page 2025-12-15 10:56:07 -06:00
Dax Raad
46a35dfc1b docs: restore desktop beta banner to homepage 2025-12-15 11:54:09 -05:00
GitHub Action
b7597c12dd chore: format code 2025-12-15 16:52:45 +00:00
Nalin Singh
6830590183 feat: add F# language server support (#5549) 2025-12-15 10:52:16 -06:00
opencode
b9b4349039 release: v1.0.155 2025-12-15 16:44:08 +00:00
Dax Raad
4107918909 20min 2025-12-15 11:31:38 -05:00
Adam
6347ee9988 chore: update stats 2025-12-15 10:31:11 -06:00
GitHub Action
9daa4e04ea chore: format code 2025-12-15 16:22:49 +00:00
Adam
ed96ae9d45 chore: cleanup 2025-12-15 10:22:07 -06:00
Adam
8ce0966987 wip(desktop): progress 2025-12-15 10:22:06 -06:00
Adam
8cb26b6066 wip(desktop): progress 2025-12-15 10:22:06 -06:00
Adam
5cf6a1343c wip(desktop): progress 2025-12-15 10:22:04 -06:00
Adam
44d6c5780d wip(desktop): progress 2025-12-15 10:20:20 -06:00
Adam
5eaa8e1bf4 chore: cleanup 2025-12-15 10:20:20 -06:00
Adam
df2713a6c2 chore: cleanup 2025-12-15 10:20:19 -06:00
Adam
ff6864a7ca feat(desktop): custom commands 2025-12-15 10:20:19 -06:00
Adam
5e37a902ce wip(desktop): progress 2025-12-15 10:20:19 -06:00
Adam
df2ebfac7d wip(desktop): progress 2025-12-15 10:20:19 -06:00
Adam
5fbcb203f5 wip(desktop): progress 2025-12-15 10:20:19 -06:00
Adam
34db739442 wip(desktop): progress 2025-12-15 10:20:18 -06:00
Adam
ae8c4154aa wip(desktop): progress 2025-12-15 10:20:18 -06:00
Adam
315836c0b7 wip(desktop): progress 2025-12-15 10:20:18 -06:00
Adam
c0d009d5f3 wip(desktop): progress 2025-12-15 10:20:18 -06:00
Adam
c36f3b9dbe wip(desktop): progress 2025-12-15 10:20:17 -06:00
Adam
d31824320e Revert "wip(desktop): session turn state consolidation"
This reverts commit 453f862616dc4d3ac90680581cde279e118b0da1.
2025-12-15 10:20:17 -06:00
Adam
88c0675148 wip(desktop): progress 2025-12-15 10:20:17 -06:00
Adam
82c4755fb0 wip(desktop): progress 2025-12-15 10:20:17 -06:00
Adam
40572eeba4 wip(desktop): progress 2025-12-15 10:20:17 -06:00
Adam
d81d63045a wip(desktop): session turn state consolidation 2025-12-15 10:20:16 -06:00
Adam
ece3bfd93d wip(desktop): progress 2025-12-15 10:20:16 -06:00
Adam
acd91bddf7 wip(desktop): progress 2025-12-15 10:20:16 -06:00
Adam
3a14ca044c wip(desktop): progress 2025-12-15 10:20:16 -06:00
Adam
d66d806700 wip(desktop): progress 2025-12-15 10:20:16 -06:00
Adam
e9b95b2e91 wip(desktop): progress 2025-12-15 10:20:15 -06:00
opencode
56dde2cc83 release: v1.0.154 2025-12-15 16:01:15 +00:00
Dax Raad
d2ce368a3f ci: update publish workflow concurrency to include version inputs and upgrade ubuntu runner to 24.04 2025-12-15 10:14:20 -05:00
GitHub Action
f492122d59 chore: format code 2025-12-15 14:59:05 +00:00
Dax Raad
b0f77da56c core: reorganize agent configuration to separate primary agents (build, plan) from subagents 2025-12-15 09:58:23 -05:00
Dax Raad
274b86b19b 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-15 09:47:19 -05:00
René
2ca118db59 docs: Fix Wakatime repository link in ecosystem.mdx (#5552)
Co-authored-by: Github Action <action@github.com>
2025-12-15 08:32:06 -06:00
David Hill
a0c0e2b5c3 fix: add tooltip to close review tab 2025-12-15 14:22:57 +00:00
David Hill
d43fbec12d fix: prompt input using border shadow 2025-12-15 14:14:10 +00:00
David Hill
bb426112ed fix: replace agents dropdown with shadow border 2025-12-15 14:09:47 +00:00
David Hill
d2217bb825 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-15 14:05:24 +00:00
David Hill
ac495bd351 fix: hide prompt input send tooltip when the send button is disabled 2025-12-15 13:47:50 +00:00
David Hill
b913eb7acc fix: avatar and icon size in sidebar 2025-12-15 13:21:59 +00:00
David Hill
ea65a91b2e fix: remove blue border from prompt input 2025-12-15 13:11:09 +00:00
GitHub Action
ed6d749104 ignore: update download stats 2025-12-15 2025-12-15 12:05:05 +00:00
René
9eefcd1b41 Provider fix, anthropic Errorhandling if empty image file is read (#5521) 2025-12-14 23:56:47 -06:00
Nalin Singh
7c1124199e fix: input lip visibility for transparent themes (#5544) 2025-12-14 23:10:33 -06:00
Spoon
5cf126d489 fix(edit): add per-file lock to prevent read-before-write race (#4388) 2025-12-14 23:01:50 -06:00
Aiden Cline
509f7d9617 ignore: fix debug var in last commit 2025-12-14 22:59:30 -06:00
opencode-agent[bot]
ae1bf92c81 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-14 22:58:26 -06:00
DS
b021b26e77 feat: restore experimental.chat.messages.transform and add experimental.chat.system.transform hooks (#5542) 2025-12-14 22:51:11 -06:00
Aiden Cline
9555d348de ci: switch model 2025-12-14 22:41:54 -06:00
Brendan Allan
220c564047 tauri: use correct sidecar name 2025-12-15 12:40:49 +08:00
Brendan Allan
cf5c0129ac tauri: rename sidecar to opencode-cli 2025-12-15 12:40:14 +08:00
Aiden Cline
543dbe71d2 ci: smart oc 2025-12-14 22:36:46 -06:00
Ravi Kumar
54569b5552 fix(session): fix unshare command not clearing share state (#5523) 2025-12-14 22:05:06 -06:00
GitHub Action
6a09861806 chore: format code 2025-12-15 03:42:56 +00:00
Adam
79a4c65313 fix: test 2025-12-14 21:42:17 -06:00
Adam
654534ac71 fix: update sdk 2025-12-14 21:41:20 -06:00
Adam
ba16bfdf3d wip(desktop): progress 2025-12-14 21:38:59 -06:00
Adam
ad5614bbb9 wip(desktop): progress 2025-12-14 21:38:59 -06:00
Adam
dda579c8ad wip(desktop): progress 2025-12-14 21:38:59 -06:00
Adam
4246cdb069 wip(desktop): progress 2025-12-14 21:38:58 -06:00
Adam
7ade6d386d wip(desktop): progress 2025-12-14 21:38:58 -06:00
Adam
2613f44961 wip(desktop): progress 2025-12-14 21:38:58 -06:00
Adam
62ffeb3987 fix(desktop): auto scroll 2025-12-14 21:38:58 -06:00
Adam
4a8e8f537c wip(desktop): progress 2025-12-14 21:38:58 -06:00
Adam
a68bee7878 fix(desktop): layout fixes 2025-12-14 21:38:57 -06:00
Mark Jaquith
ed33d82535 feat(cli): auto-submit prompt when using --prompt flag (#4510) 2025-12-14 21:06:04 -06:00
Dax Raad
2d63c22d1a fix share link 2025-12-14 22:05:53 -05:00
Dax Raad
e22af25076 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-14 21:23:45 -05:00
Dax Raad
622caae9c9 fix desktop updater 2025-12-14 21:12:38 -05:00
Dax
fed4776451 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-14 21:11:30 -05:00
Ravi Kumar
fdf560c343 fix(tui): --continue selects wrong session (#5513) 2025-12-14 19:50:54 -06:00
GitHub Action
fc3ffb2bf9 chore: format code 2025-12-14 23:14:05 +00:00
Martijn Baay
7368342bab feat: add experimental.continue_loop_on_deny config option (#4729)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-12-14 17:13:32 -06:00
Dax Raad
c8fc910533 ignore: simplify download page to use GitHub latest redirect URLs 2025-12-14 17:10:59 -05:00
GitHub Action
0f9ef84d55 chore: format code 2025-12-14 22:05:28 +00:00
Dax Raad
74b5c285cf disable app image 2025-12-14 17:04:46 -05:00
opencode
a34e67b518 release: v1.0.153 2025-12-14 19:04:01 +00:00
Aiden Cline
0c7f0cfa2e tweak: fallback to provider default for temperature 2025-12-14 11:57:12 -06:00
GitHub Action
10ee6d345b chore: format code 2025-12-14 17:48:34 +00:00
Nalin Singh
48ec68730f fix: ensure input borders are drawn in transparent themes (#5524) 2025-12-14 11:48:02 -06:00
GitHub Action
70e4efe429 chore: format code 2025-12-14 17:46:46 +00:00
Sellers Crisp
92948ed8a4 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-14 11:46:14 -06:00
shekohex
6d412d8872 docs: add opencode-pty and opencode-google-antigravity-auth plugins to the echosystem (#5530) 2025-12-14 11:38:53 -06:00
Lawrence Sarpong
e6a0a005d6 Add Gleam LSP and formatter (#5514) 2025-12-14 10:51:08 -06:00
Aymvn
90d44751e7 docs: Fix Wakatime link in ecosystem documentation (#5528) 2025-12-14 10:49:35 -06:00
GitHub Action
4d062ba1b2 ignore: update download stats 2025-12-14 2025-12-14 12:04:18 +00:00
Aiden Cline
f8bca50f00 rm unnecessary code 2025-12-13 23:45:15 -06:00
Aiden Cline
3d2ef28fa8 add topK function to transform, add temp defaults for glm and minimax 2025-12-13 23:27:11 -06:00
YeonGyu-Kim
210b3e905b fix(ui): guard Node reference for SSR compatibility in isTriggerTitle (#5509) 2025-12-13 22:28:14 -06:00
Brendan Allan
96975ef8d6 tauri: change mainBinaryName to just OpenCode 2025-12-14 12:08:53 +08:00
Brendan Allan
b8b998be56 tauri: bring back appimage 2025-12-14 12:00:14 +08:00
Sachnun
d8ac35f6e5 fix(tui): open parent session instead of subagent on continue flag (#5503) 2025-12-13 21:09:42 -06:00
Zhou Rui
ed1eacfce0 docs: Add opencode-websearch-cited to plugin list (#5501) 2025-12-13 20:28:39 -06:00
Adam
629f475f63 fix: sort models 2025-12-13 20:25:24 -06:00
Adam
43a7c1dd8c fix: use opencode icon 2025-12-13 20:25:24 -06:00
Adam
e288ce0fca chore: cleanup 2025-12-13 20:25:24 -06:00
Adam
67b3fcb31a chore: cleanup 2025-12-13 20:25:24 -06:00
Tommy D. Rossi
aedb5550a8 fix: limit LSP diagnostics to prevent context window waste (#5480)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-13 19:56:26 -06:00
Aiden Cline
1638ffde69 docs: networking 2025-12-13 18:20:44 -06:00
Aiden Cline
d4cfbd8219 chore: reduce duplication of field in transform 2025-12-13 18:07:22 -06:00
Adam
c7bac83212 chore: cleanup 2025-12-13 16:17:32 -06:00
Adam
fc9789d7a7 fix(desktop): archive button 2025-12-13 16:14:31 -06:00
Adam
a8957d8d16 fix(desktop): auto scroll 2025-12-13 15:56:12 -06:00
Adam
0660433921 feat(desktop): show richer status when thinking 2025-12-13 15:47:50 -06:00
Adam
1a6f4f1c0d fix: css scroll jitter 2025-12-13 15:36:28 -06:00
Adam
974a24ba02 fix: don't rotate placeholders in session 2025-12-13 15:25:56 -06:00
Adam
5ebe29de1e fix: don't open shell by default 2025-12-13 15:17:22 -06:00
Github Action
6bdf8b1fe1 Update Nix flake.lock and hashes 2025-12-13 21:13:53 +00:00
Adam
5bcc93851c chore: cleanup 2025-12-13 15:12:41 -06:00
Adam
d0789632b4 fix(desktop): terminal light mode 2025-12-13 15:12:32 -06:00
Adam
a6e297baad feat(desktop): message history 2025-12-13 14:57:24 -06:00
Adam
307af10c8b fix: session turn scroll 2025-12-13 14:57:23 -06:00
Felipe Oduardo Sierra
f254cf76d9 add ARM64 Docker image support (#5483) 2025-12-13 13:01:59 -06:00
Github Action
b4ffaa21ec Update Nix flake.lock and hashes 2025-12-13 19:01:20 +00:00
Aiden Cline
7bf6f264e4 bump bun version & set flags this time 2025-12-13 13:00:03 -06:00
GitHub Action
7434fbba8e chore: format code 2025-12-13 17:34:07 +00:00
Jan-Niklas W.
b7581e01ea docs: fix title for JetBrains ACP config file (#5479) 2025-12-13 11:33:31 -06:00
YeonGyu-Kim
b46d4789fc docs: add oh-my-opencode to plugins list (#5481) 2025-12-13 11:33:10 -06:00
GitHub Action
199bd8a9a2 chore: format code 2025-12-13 17:30:48 +00:00
rari404
decf2452c4 feat: add dockerfile language server (#5252)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-13 11:30:15 -06:00
GitHub Action
d8663a44c2 ignore: update download stats 2025-12-13 2025-12-13 12:04:19 +00:00
rari404
8917a4c609 feat: add texlab language server and latexindent formatter (#5251) 2025-12-12 23:50:09 -06:00
GitHub Action
5d7a52f8b8 chore: format code 2025-12-13 02:09:41 +00:00
Jan-Niklas W.
b7b827c5bd docs: JetBrains IDEs to ACP config docs page (#5465) 2025-12-12 20:09:08 -06:00
Matt Silverlock
613e082358 github: support GITHUB_TOKEN + skip OIDC (#5459) 2025-12-12 19:55:46 -06:00
Charles Cooper
b6856bd593 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-12 19:45:28 -06:00
David Hill
7cb5a77ba6 fix: mute the project path in the sidebar that proceeds the final directory 2025-12-12 23:45:39 +00:00
David Hill
cd9898a565 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-12 23:25:11 +00:00
Dax Raad
a4ffa869cc fix 2025-12-12 18:15:31 -05:00
David Hill
dbc84ff4c3 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-12 22:50:50 +00:00
David Hill
c11ea3fd92 fix: mute the whole prompt area when leader key is active 2025-12-12 22:50:48 +00:00
GitHub Action
3c3a0f8afb chore: format code 2025-12-12 22:48:43 +00:00
Aiden Cline
b93614cb81 docs: add env vars sections 2025-12-12 16:47:50 -06:00
opencode
b84d513bd7 release: v1.0.152 2025-12-12 22:29:21 +00:00
Adam
0554d03162 Revert "fix: archive button"
This reverts commit bc3286de46.
2025-12-12 16:16:52 -06:00
Aiden Cline
15caecdb45 shell tweaks, better handling for windows (#5455)
Co-authored-by: GitHub Action <action@github.com>
2025-12-12 16:11:07 -06:00
Adam
91ab966921 fix: max height on bash tool 2025-12-12 16:10:13 -06:00
Adam
bc3286de46 fix: archive button 2025-12-12 16:03:07 -06:00
Dax Raad
af45444496 desktop: fix build on Linux and Windows by making macOS title bar styling conditional 2025-12-12 16:47:48 -05:00
Sebastian Herrlinger
43202f2820 only exit app when prompt is empty, otherwise fallthrough, fix #5457 2025-12-12 22:45:28 +01:00
GitHub Action
ce37e11bfe chore: format code 2025-12-12 21:44:09 +00:00
Dan Brown
6e9833acce Shell: No -l in fallback, for max compatibility (#5452) 2025-12-12 15:43:35 -06:00
opencode
379c4ecab3 release: v1.0.151 2025-12-12 21:34:32 +00:00
Github Action
f1db4b60c4 Update Nix flake.lock and hashes 2025-12-12 21:28:16 +00:00
Adam
9846b26be7 fix: desktop layout 2025-12-12 15:26:53 -06:00
Adam
d6ba6af6f3 fix: desktop layout 2025-12-12 15:24:43 -06:00
Adam
d463ade028 fix: desktop layout 2025-12-12 15:24:43 -06:00
Adam
6c3495a75a fix: desktop layout 2025-12-12 15:24:43 -06:00
Adam
a16edb4ea0 fix: desktop layout 2025-12-12 15:24:43 -06:00
Adam
9efe09564b fix: desktop layout 2025-12-12 15:24:42 -06:00
Adam
ccdd77032a fix: desktop layout 2025-12-12 15:24:42 -06:00
Adam
41e234c6d0 fix: desktop layout 2025-12-12 15:24:42 -06:00
Adam
3e03646e42 fix: desktop layout 2025-12-12 15:24:42 -06:00
Adam
f7acc34327 fix: desktop layout 2025-12-12 15:24:41 -06:00
Adam
bf420e7df6 chore: cleanup 2025-12-12 15:24:41 -06:00
Adam
78484f545c chore: cleanup 2025-12-12 15:24:41 -06:00
Adam
ad008d2151 wip: desktop timeline changes 2025-12-12 15:24:41 -06:00
Dax Raad
651a10d6db only gen summary if diffs 2025-12-12 16:24:26 -05:00
Aiden Cline
f9674793af tweak: 5.1 -> 5. in transform 2025-12-12 15:17:11 -06:00
GitHub Action
f3a33d41f1 chore: format code 2025-12-12 20:26:02 +00:00
David Hill
642eec3dfd Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-12 20:25:21 +00:00
Dax Raad
73513612d4 reuse existing server query 2025-12-12 15:23:38 -05:00
David Hill
9b77246246 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-12 20:23:06 +00:00
David Hill
cf3bc1e0a6 fix: increase font-size-small to 13px 2025-12-12 20:22:51 +00:00
David Hill
4550ad049e fix: make syntax colors have more contrast 2025-12-12 20:22:08 +00:00
Dax Raad
d51c6ca39f sync 2025-12-12 15:21:29 -05:00
Dax Raad
47c6a2430c sync 2025-12-12 15:18:07 -05:00
Aiden Cline
909013320b tweak: 5.1 -> 5. reasoning effort match 2025-12-12 13:21:57 -06:00
Frank
770a4d87db Zen: add gpt5.2 2025-12-12 13:48:54 -05:00
Luke Parker
2e417c4d8c fix: osascript for clipboard typo (#5430) 2025-12-12 11:37:43 -06:00
Adam
2da527aaa6 fix: desktop layout 2025-12-12 11:11:17 -06:00
Jeremy Osih
0303eb0cc1 Change tooltip text from 'Open file' to 'New Terminal' (#5435) 2025-12-12 10:57:52 -06:00
Adam
3f4a792c8a fix: tweak missing colors 2025-12-12 10:56:30 -06:00
Adam
9629f7464b chore: cleanup 2025-12-12 10:45:34 -06:00
xu0o0
9017d10303 acp: fix internal error on /compact (#5424) 2025-12-12 10:43:18 -06:00
Brendan Allan
59d4041aa4 tauri: add basic custom titlebar (#5438) 2025-12-12 09:37:17 -06:00
Adam
feb8c4f3c6 feat(desktop): archive sessions 2025-12-12 07:07:54 -06:00
Adam
3f5cd2c4a8 fix(desktop): audio stuff 2025-12-12 06:21:31 -06:00
Adam
a160eb76df fix(desktop): homedir aware path on home 2025-12-12 06:21:31 -06:00
GitHub Action
e4b2028f91 ignore: update download stats 2025-12-12 2025-12-12 12:04:34 +00:00
Github Action
ffc48e115b Update Nix flake.lock and hashes 2025-12-12 11:16:22 +00:00
Adam
04b4dacee3 feat(desktop): basic alerting 2025-12-12 05:14:51 -06:00
GitHub Action
c0e30f48c6 chore: format code 2025-12-12 09:44:46 +00:00
David Hill
99158e736b Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-12 09:44:06 +00:00
David Hill
4c02d515a1 wip: desktop sidebar icon updates 2025-12-12 09:44:04 +00:00
David Hill
b803a9732d fix: make the logo on the home screen non-selectable 2025-12-12 09:43:41 +00:00
Brendan Allan
f9d5e18790 tauri: create window with full screen size 2025-12-12 17:04:00 +08:00
Github Action
147111c9c6 Update Nix flake.lock and hashes 2025-12-12 09:00:19 +00:00
GitHub Action
9a70eb538b chore: format code 2025-12-12 08:59:18 +00:00
Brendan Allan
0b1731142e tauri: initialise store and window-state plugins 2025-12-12 16:58:37 +08:00
Viktor Forsman
7ec48dfd15 fix: debug lsp diagnostics cmd for certain lsps (#5420) 2025-12-11 23:20:38 -06:00
Frank
57120e69ed Zen: sync 2025-12-11 23:41:04 -05:00
Rhys Sullivan
11efda3f5c [feat]: show indicator for in progress chats in the sessions list (#5417) 2025-12-11 21:57:03 -05:00
Sachnun
a5cb4e41f5 fix(tui): restore input on timeline revert and show newest first (#5366) 2025-12-11 20:07:31 -06:00
GitHub Action
88b2382b97 chore: format code 2025-12-12 02:06:49 +00:00
Sachnun
237c0253c2 fix(server): make time field optional in session update validator (#5372) 2025-12-11 20:06:15 -06:00
xu0o0
a9f27371cf acp: replay conversation history in session/load (#5385) 2025-12-11 20:02:06 -06:00
opencode-agent[bot]
9c126c5b64 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-11 19:53:39 -06:00
Zeno Jiricek
e241aa21b9 docs: Add opencode-md-table-formatter and plugin template (#5405) 2025-12-11 17:02:49 -06:00
Koichi Nakayamada
1fd005838c fix(tui): ensure fatal error UI is readable in light mode (#5387) 2025-12-11 16:39:27 -06:00
Koichi Nakayamada
61ba844234 fix: add fg color to some TUI text elements for light mode visibility (#5378) 2025-12-11 16:29:34 -06:00
Adán
ffec52a17c fix: Windows LSP URIs using backslashes (Biome initialization failure) (#5317) 2025-12-11 16:28:39 -06:00
Aiden Cline
342595e0f7 tweak: setting chat_template_args in transform 2025-12-11 16:22:10 -06:00
Aiden Cline
b41051b4ee tweak(tui): better error msg rendering 2025-12-11 16:22:10 -06:00
Dax Raad
6f8746ab94 console: fix download route async handling 2025-12-11 16:55:23 -05:00
Dax Raad
9d4ed5b04a console: fix download links to use dynamic GitHub release version 2025-12-11 16:49:15 -05:00
Adam
e149b7c1e2 fix: avatar colors 2025-12-11 15:39:41 -06:00
GitHub Action
55957b2ac7 chore: format code 2025-12-11 21:34:17 +00:00
Jay V
14291bff71 docs: lander copy 2025-12-11 16:26:42 -05:00
Adam
a0472c0312 fix: free model logic 2025-12-11 15:24:32 -06:00
Dax Raad
0400024d02 core: fix missing Flag import in session prompt 2025-12-11 16:18:04 -05:00
Dax Raad
d4dc142cc2 core: add client identification to user agent and request headers for better tracking 2025-12-11 16:18:04 -05:00
Adam
bfdb236581 fix: toast colors 2025-12-11 15:16:44 -06:00
Aiden Cline
4e92f54415 tweak: bash tool, try to prevent the cd spam 2025-12-11 14:58:46 -06:00
Adam
f8dc740c61 fix: remove settings button 2025-12-11 14:55:41 -06:00
Adam
dea5111a5a fix: message nav popover placement 2025-12-11 14:52:06 -06:00
Aiden Cline
4f5abe387d tweak: bash tool deny msg 2025-12-11 14:46:58 -06:00
Adam
7d55aeee0a fix: no loading state in message nav 2025-12-11 14:46:32 -06:00
Adam
b34f434332 fix: message order ascending 2025-12-11 14:46:32 -06:00
Jorgen Henriksen
2a9269c347 add experimental.chat.messages.transform hook (#5207)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-11 14:38:45 -06:00
Sachnun
0efdc3a8a0 feat: add caching for opencode binary in GitHub Actions (#5384) 2025-12-11 14:14:37 -06:00
GitHub Action
fc9dfd054a chore: format code 2025-12-11 20:14:20 +00:00
Dax Raad
013bf079cc ignore: added debug logging for share data loading performance 2025-12-11 15:13:36 -05:00
opencode
5ffcde9dba release: v1.0.150 2025-12-11 20:09:20 +00:00
Dax Raad
e0f885ffc8 ci 2025-12-11 14:57:54 -05:00
Aiden Cline
5b21334fdd fix 2025-12-11 13:55:08 -06:00
Shantur Rathore
755a79cd8e fix: Writing to Bun.stderr causes CPU pinning to 100%. (#5396) 2025-12-11 13:47:59 -06:00
Adam
16b7370d8c wip(desktop): progress 2025-12-11 13:42:47 -06:00
Adam
634fd62b25 wip(desktop): progress 2025-12-11 13:42:47 -06:00
Adam
e845eedbc3 wip(desktop): progress 2025-12-11 13:42:47 -06:00
Adam
4ae7e1b19c wip(desktop): progress 2025-12-11 13:42:46 -06:00
Adam
0ca758e135 wip(desktop): progress 2025-12-11 13:42:45 -06:00
Dax Raad
ea8508ee44 ci 2025-12-11 14:38:45 -05:00
GitHub Action
78d4f32d79 chore: format code 2025-12-11 19:35:36 +00:00
David Hill
afcd547a16 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-11 19:34:34 +00:00
David Hill
521fbb93cd wip: lander updates 2025-12-11 19:33:55 +00:00
Dax Raad
9d73096db0 ci: fix Ubuntu host detection in Tauri build workflow 2025-12-11 13:27:21 -05:00
GitHub Action
c11bb440e6 chore: format code 2025-12-11 18:21:10 +00:00
Sebastian Herrlinger
80e04be84f fix super modifier parsing 2025-12-11 19:20:23 +01:00
Dax Raad
639320b3e1 ci: install libglib2.0-dev on Blacksmith Linux runners to fix missing glib-2.0 system library error 2025-12-11 13:16:44 -05:00
GitHub Action
55ea20de84 chore: format code 2025-12-11 18:12:25 +00:00
Dax Raad
21cf3a7c1b ci 2025-12-11 13:11:36 -05:00
David Hill
4f855072f2 wip: avatar colors 2025-12-11 18:07:14 +00:00
David Hill
11e6a181ad fix: free cta 2025-12-11 18:07:14 +00:00
GitHub Action
4c9208fbf1 chore: format code 2025-12-11 18:05:35 +00:00
Dax Raad
3a9bbe2371 ci: use Blacksmith runners for Linux and Windows Tauri builds to improve build performance 2025-12-11 13:04:55 -05:00
opencode
6382bda7d6 release: v1.0.149 2025-12-11 18:00:30 +00:00
Dax Raad
885a142ae3 temporarily remove undo/redo keybinds 2025-12-11 12:55:08 -05:00
GitHub Action
4387602f9d chore: format code 2025-12-11 17:49:43 +00:00
opencode
71fc8b2115 release: v1.0.148 2025-12-11 17:49:42 +00:00
Dax Raad
137716e0dc ci: fix tauri release asset naming to use opencode-desktop-[platform]-[arch][ext] format 2025-12-11 12:45:48 -05:00
Dax Raad
95526fb9ed fix share image 2025-12-11 12:20:11 -05:00
GitHub Action
a5cc19068b chore: format code 2025-12-11 16:46:43 +00:00
opencode
48c7913431 release: v1.0.147 2025-12-11 16:46:43 +00:00
Dax Raad
89d9856ed2 ci: enable tauri desktop app to be published with GitHub release ID and tag name 2025-12-11 11:41:54 -05:00
Sebastian Herrlinger
c12be73bf7 align default input keybinds 2025-12-11 17:39:29 +01:00
Github Action
de6fb3126c Update Nix flake.lock and hashes 2025-12-11 16:25:22 +00:00
GitHub Action
fad5cbe6c7 chore: format code 2025-12-11 16:24:30 +00:00
Sebastian Herrlinger
e271852bc3 allow custom mappings for all textarea actions via config 2025-12-11 17:23:47 +01:00
David Hill
4e02704f17 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-11 16:21:32 +00:00
David Hill
b17fdc7f4e fix: inverted text colors 2025-12-11 16:21:31 +00:00
Dax Raad
df4d9236a6 fix bug causing windows builds to sometimes fail 2025-12-11 11:01:43 -05:00
GDR!
0ee626ba9f Add ripgrep to Docker image (#5379) 2025-12-11 09:47:56 -06:00
Daniel Polito
dce4585d40 Fix / Improve Github Eyes Reaction Removal (#5383) 2025-12-11 09:42:10 -06:00
David Hill
5ffc1617fe Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-11 14:37:05 +00:00
David Hill
93dd01947d wip: update lander video 2025-12-11 14:36:49 +00:00
Brendan Allan
5e7d908dc9 tauri: always build cli in predev 2025-12-11 22:20:22 +08:00
David Hill
eb6596cb97 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-11 13:10:46 +00:00
David Hill
8fcd31f353 wip: social share image 2025-12-11 13:03:08 +00:00
Adam
dfdd1c9b20 fix: load fonts the right way 2025-12-11 06:51:39 -06:00
Adam
d7b8dce6a7 fix: missing font 2025-12-11 06:49:00 -06:00
Adam
f9f78122d0 wip(desktop): progress 2025-12-11 06:48:59 -06:00
Adam
1980113ee4 wip(desktop): progress 2025-12-11 06:48:59 -06:00
Adam
85c0311d38 wip(desktop): progress 2025-12-11 06:48:59 -06:00
Adam
740aa8e541 wip(desktop): progress 2025-12-11 06:48:59 -06:00
Adam
1b331548ba wip(desktop): progress 2025-12-11 06:48:59 -06:00
Adam
3bb546c94d wip(desktop): progress 2025-12-11 06:48:58 -06:00
David Hill
8e15bcb68e Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-11 12:40:24 +00:00
David Hill
ca08dc87dd wip: add faq to download 2025-12-11 12:38:40 +00:00
David Hill
4287552991 wip: add more items to download 2025-12-11 12:26:47 +00:00
GitHub Action
f1af54ada1 chore: format code 2025-12-11 12:12:13 +00:00
David Hill
577cacb7db Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-11 12:11:36 +00:00
David Hill
8346550d26 fix: change terminal title to OpenCode 2025-12-11 12:10:04 +00:00
David Hill
a8e3caca3f wip: lander updates 2025-12-11 12:09:31 +00:00
GitHub Action
fd83578442 ignore: update download stats 2025-12-11 2025-12-11 12:04:43 +00:00
David Hill
e91d121ef8 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-11 10:47:24 +00:00
David Hill
1b4975ba54 wip: hero and nav updates 2025-12-11 10:47:22 +00:00
David Hill
380d2c466e wip: lander hero updates 2025-12-11 10:36:31 +00:00
Brendan Allan
53d2edc0f2 write typescript artifacts to node_modules/.ts-dist 2025-12-11 18:35:07 +08:00
David Hill
02705e460f wip: downloads 2025-12-11 10:19:41 +00:00
GitHub Action
44cd384e3c chore: format code 2025-12-11 06:33:51 +00:00
Aiden Cline
8ac5204009 ignore: Comment out plugin entry in opencode.jsonc
Comment out the plugin entry in opencode.jsonc
2025-12-11 00:33:17 -06:00
opencode
ddd5464081 release: v1.0.146 2025-12-11 06:23:53 +00:00
opencode
fbad378966 release: v1.0.145 2025-12-11 06:19:42 +00:00
Dax Raad
c211b22a45 update 2025-12-11 01:15:34 -05:00
Dax Raad
5d57d0385c opencode config 2025-12-11 01:13:23 -05:00
Aiden Cline
f7e29a1acf downgrade bun 2025-12-11 00:10:58 -06:00
Aiden Cline
a8f83cdcb5 ignore: comment out item in project cfg 2025-12-11 00:09:43 -06:00
Github Action
9e0a2bc7d0 Update Nix flake.lock and hashes 2025-12-11 06:03:16 +00:00
Dax Raad
2d9c76baae enterprise: add default social card images to HTML head
- Add og:image and twitter:image meta tags to entry-server
- Provide fallback social card image for pages without specific social cards
2025-12-11 01:02:31 -05:00
Dax Raad
85c01e8694 enterprise: add social card meta tags to share pages
- Add og:image and twitter:image meta tags for better social sharing
- Generate dynamic social card URLs with session title, models, and version
- Include description meta tag for search engines
2025-12-11 01:01:32 -05:00
GitHub Action
65a6b3d585 chore: format code 2025-12-11 05:54:58 +00:00
Dax Raad
40f121c3e8 prevent indexing of share page 2025-12-11 00:54:15 -05:00
opencode
6251231e41 release: v1.0.144 2025-12-11 05:48:13 +00:00
Dax Raad
578072bb8e use new share url 2025-12-11 00:43:00 -05:00
Dax Raad
231390cb7b ci 2025-12-11 00:23:06 -05:00
Dax Raad
5955d20539 remove 2025-12-11 00:21:40 -05:00
Dax Raad
4309c078fb domain 2025-12-11 00:11:40 -05:00
Dax Raad
d14462f7a7 fix 2025-12-10 23:19:28 -05:00
Dax Raad
a02223a310 sync 2025-12-10 23:19:28 -05:00
Ayush Walekar
d93c8c7604 docs: update doc sdk.mdx (#5315) 2025-12-10 21:56:23 -06:00
Aiden Cline
7eb509db14 ci: rm bash tool from opencode ci workflow, reduce risks 2025-12-10 21:45:46 -06:00
Dax Raad
f1b8707286 ignore 2025-12-10 22:36:57 -05:00
Dax Raad
9b05217471 ignore 2025-12-10 22:34:16 -05:00
Dax Raad
d88912abf0 global bus 2025-12-10 22:28:56 -05:00
GitHub Action
28c6320cd6 chore: format code 2025-12-11 03:22:51 +00:00
Dax Raad
13a77005f1 global.dispose 2025-12-10 22:22:16 -05:00
Dax Raad
530b75a92a ignore 2025-12-10 22:22:16 -05:00
Aiden Cline
7b4f852f33 ignore: tmp transform exclusion 2025-12-10 21:18:19 -06:00
Aiden Cline
439aebb4e9 tweak: correct thinkingLevel 2025-12-10 21:13:10 -06:00
Brendan Allan
6f5f73a74a desktop: exclude ts-dist from tsconfig sources 2025-12-11 09:51:35 +08:00
Brendan Allan
bd1f5f884e tauri: update icons 2025-12-11 09:49:00 +08:00
Koichi Nakayamada
499ad4f84b fix: light mode visibility for filepath in /undo diff files (#5352) 2025-12-10 19:38:14 -06:00
Jinhyeok Lee
01fd0d8209 docs(bash): clarify description parameter is required (#5353) 2025-12-10 19:37:38 -06:00
Seb Duerr
df55ad89ab Add Cerebras integration header with opencode identifier (#5354) 2025-12-10 19:36:58 -06:00
David Hill
a5a3060208 wip: lander updates 2025-12-11 00:56:05 +00:00
David Hill
a468044c9f Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-12-11 00:46:07 +00:00
David Hill
f0274fd29f wip: lander updates 2025-12-11 00:46:02 +00:00
Dax Raad
fadeed1fa4 desktop: enable zoom hotkeys in Tauri app 2025-12-10 19:27:05 -05:00
David Hill
e9d3e240c2 wip: lander updates 2025-12-11 00:18:22 +00:00
Dax Raad
13611176b0 fix deploy 2025-12-10 19:11:49 -05:00
Jay V
92fa66d76f core: reposition OpenCode as open source multi-platform coding agent
docs: update main intro page to reflect open source positioning and multi-platform availability
2025-12-10 19:05:33 -05:00
David Hill
fba0aad2f8 wip: lander updates 2025-12-10 23:57:11 +00:00
Jay V
1a1874d8b3 docs: desktop 2025-12-10 18:43:19 -05:00
Adam
56540f8312 wip(desktop): progress 2025-12-10 17:31:13 -06:00
Shantur Rathore
89d51ad596 compaction: improve compaction prompt (#5348) 2025-12-10 17:21:38 -06:00
Adam
15b8c14542 fix: tauri 2025-12-10 17:19:50 -06:00
Adam
85cfa226c3 wip(desktop): progress 2025-12-10 17:17:37 -06:00
Christian Stewart
cbb591eb7d fix: more descriptive tool or subtask execution failed error (#5337)
Signed-off-by: Christian Stewart <christian@aperture.us>
2025-12-10 17:12:49 -06:00
Aiden Cline
e36c349222 tweak: oc -> OC 2025-12-10 17:06:16 -06:00
Christian Stewart
b274371dbb feat: use |- for intermediate sub-agent steps (#5336)
Signed-off-by: Christian Stewart <christian@aperture.us>
2025-12-10 16:36:11 -06:00
Hammad Shami
72eb004057 feat: add helicone docs + helicone session tracking (#5265) 2025-12-10 16:23:52 -06:00
Yukai Huang
e46080aa8c fix(auth): add plugin lookup for custom provider in 'Other' flow (#5324) 2025-12-10 16:23:12 -06:00
Aiden Cline
7d82f1769c tweak: small fix 2025-12-10 16:01:10 -06:00
OpeOginni
7435d94f85 fix(cli): obtain directory data from server (#5320) 2025-12-10 15:55:15 -06:00
Github Action
e060f968f5 Update Nix flake.lock and hashes 2025-12-10 21:18:57 +00:00
Dax Raad
86f7cc17ae tui: pass dynamic port to frontend
Frontend now receives the server port via window.__OPENCODE__.port,
allowing it to connect when using a random free port instead of hardcoded 4096
2025-12-10 16:17:36 -05:00
Adam
58e66dd3d1 wip(desktop): progress 2025-12-10 15:17:04 -06:00
Adam
190fa4c87a wip(desktop): progress 2025-12-10 15:17:03 -06:00
Adam
91d743ef9a wip(desktop): progress 2025-12-10 15:17:03 -06:00
Adam
804ad5897f wip(desktop): progress 2025-12-10 15:17:03 -06:00
Adam
f20d6e8555 wip(desktop): progress 2025-12-10 15:17:03 -06:00
Adam
e694d4d880 wip(desktop): progress 2025-12-10 15:17:02 -06:00
Adam
ada40decd1 wip(desktop): progress 2025-12-10 15:17:02 -06:00
Adam
6866a060bc wip(desktop): progress 2025-12-10 15:17:02 -06:00
Adam
a4ec619c74 wip(desktop): progress 2025-12-10 15:17:02 -06:00
Adam
67a95c3cc8 wip(desktop): progress 2025-12-10 15:17:01 -06:00
Dax Raad
8d3eac2347 fix type 2025-12-10 16:14:32 -05:00
Dax Raad
9ad828dcd0 tui: use random free port and enable icon discovery by default
- Tauri app now automatically finds an available port instead of defaulting to 4096
- Icon discovery feature is now enabled by default in the Tauri app
- Prevents port conflicts when multiple OpenCode instances are running
2025-12-10 16:13:11 -05:00
Aiden Cline
59fb3ae606 ignore: add bash tests 2025-12-10 15:07:40 -06:00
GitHub Action
0ab3b88250 chore: format code 2025-12-10 21:01:06 +00:00
Dax Raad
a1175bddcd gen types 2025-12-10 15:59:42 -05:00
Dax Raad
936a6be5d6 stuff adam needs 2025-12-10 15:59:42 -05:00
igordertigor
03c6c3f4cb docs: document accept always behavior (#5340)
Co-authored-by: Ingo Fruend <ingo@oudyo.com>
2025-12-10 14:59:12 -06:00
Aiden Cline
6288a032fd bump bun to 1.3.4 2025-12-10 14:48:52 -06:00
Daniel Polito
31e6ed6806 Add Eyes Reaction instead of Comment Working on Github Action (#5072) 2025-12-10 13:35:55 -06:00
Aiden Cline
da56319af4 ignore: fix test 2025-12-10 13:31:45 -06:00
GitHub Action
2198f9400f chore: format code 2025-12-10 19:31:11 +00:00
Dax Raad
ffc4d53923 add models.dev family 2025-12-10 14:30:28 -05:00
Aiden Cline
18d3c054a3 more interleaved thinking fixes (#5334) 2025-12-10 13:29:26 -06:00
Aiden Cline
59c5da9b6c tweak: set gemini thinkingLevel to high by default 2025-12-10 13:09:27 -06:00
Github Action
15880195a2 Update Nix flake.lock and hashes 2025-12-10 19:04:03 +00:00
Tom Aylott
117de64f39 fix: upgrade OpenRouter sdk pkg (#5302)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-12-10 13:02:26 -06:00
Dax Raad
388156704a fix /provider endpoint to return loaded providers 2025-12-10 13:49:45 -05:00
GitHub Action
faf443132f chore: format code 2025-12-10 18:32:21 +00:00
Nick
36a9be040b docs: add opencode-type-inject to ecosystem plugins (#5331) 2025-12-10 12:31:44 -06:00
GitHub Action
1835d7526f chore: format code 2025-12-10 16:55:19 +00:00
Stanislas
946e4f0a61 docs: add wakatime plugin to ecosystem page (#5326)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-10 10:54:45 -06:00
Matt Silverlock
ae60f41adf themes: update orng theme (#5329) 2025-12-10 10:52:58 -06:00
GitHub Action
6b93d23642 chore: format code 2025-12-10 16:50:49 +00:00
Noè
cfa13df346 docs: Added opencode-antigravity-auth to ecosystem documentation (#5303) 2025-12-10 10:50:12 -06:00
Frank
744a7159e4 zen: sync 2025-12-10 11:44:37 -05:00
arc-source-coder
80d1c62818 tui: only show active MCP server count (#5327) 2025-12-10 10:27:59 -06:00
Connor Adams
83aa42f510 docs: configure mise to use latest version instead of pinned version (#5316) 2025-12-10 10:11:48 -06:00
Aiden Cline
183a1a181c ci: stop zed sync temporarily 2025-12-10 09:57:34 -06:00
Github Action
bc7e7c2c4d Update Nix flake.lock and hashes 2025-12-10 14:04:52 +00:00
GitHub Action
7b5bd89570 chore: format code 2025-12-10 14:04:08 +00:00
Sebastian Herrlinger
ba1c6122b9 bump opentui to v0.1.60, fixing doubled key events on some older terminal emulators and add_buffer leaks for prompt input 2025-12-10 15:03:14 +01:00
Sebastian Herrlinger
baed581a7c remove input_forward_delete special handling 2025-12-10 14:55:56 +01:00
GitHub Action
4a23052778 chore: format code 2025-12-10 13:53:21 +00:00
opencode
ee4190aa41 release: v1.0.143 2025-12-10 13:53:21 +00:00
Dax Raad
de8460cb99 docs: improve bash and grep tool documentation with clearer usage guidelines 2025-12-10 08:48:41 -05:00
opencode
f7b2beaaf1 release: v1.0.142 2025-12-10 13:25:55 +00:00
GitHub Action
9b0933187e ignore: update download stats 2025-12-10 2025-12-10 12:04:47 +00:00
Adam
862141e8b2 fix: exit aliases 2025-12-10 02:49:54 -06:00
Aiden Cline
070ced0b3f fix: revert hook try/catch that surpressed errors 2025-12-10 00:14:24 -06:00
GitHub Action
cc3b699823 chore: format code 2025-12-10 06:02:31 +00:00
spoj
301f1a191b fix: add Windows support for shell mode (! command) (#5311) 2025-12-10 00:01:56 -06:00
Adam
d149c25aab fix: types 2025-12-09 21:44:34 -06:00
Adam
18d24b8f5f wip(desktop): progress 2025-12-09 21:39:13 -06:00
Adam
cf34981e8f wip(desktop): progress 2025-12-09 21:39:13 -06:00
Adam
e2ebe560ea feat: provider icon component 2025-12-09 21:39:12 -06:00
GitHub Action
6db822fd92 chore: format code 2025-12-10 03:32:26 +00:00
Brendan Allan
661122bab8 tauri: don't ask to restart separately in updater 2025-12-10 11:31:50 +08:00
Brendan Allan
4a96836d11 tauri: update macos icon 2025-12-10 11:28:40 +08:00
Brendan Allan
e072f9605c tauri: comment out restart server dialog 2025-12-10 11:18:17 +08:00
Brendan Allan
9986031481 fix: use project references for desktop typecheck 2025-12-10 11:15:12 +08:00
Dax Raad
3d95848607 ci 2025-12-09 22:14:18 -05:00
GitHub Action
221c9028af chore: format code 2025-12-10 03:10:18 +00:00
Timor
b2057791aa feat: add CLI arguments to agent create command for scripting (#5157) 2025-12-09 21:09:45 -06:00
Dax Raad
c1ee6d6c41 ci 2025-12-10 02:57:39 +00:00
opencode
a3fbbece9a release: v1.0.141 2025-12-10 02:57:38 +00:00
Dax Raad
e72c974c4c ci 2025-12-09 21:49:38 -05:00
Dax Raad
a762da7cab ci 2025-12-09 21:49:05 -05:00
Dax Raad
fa6c060324 ci 2025-12-09 21:44:13 -05:00
Dax Raad
8e33ac052b ci: publish with multiple tags instead of using dist-tag
npm dist-tag add command is broken, so publish package multiple times
with different tags directly instead
2025-12-09 21:39:05 -05:00
Dax Raad
0759696ec0 core: enable project discovery for experimental builds 2025-12-09 21:23:55 -05:00
Hosenur Rahaman
59dce63471 docs: Add portal project to ecosystem documentation (#5300)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-09 20:23:14 -06:00
Dax Raad
1ae28090e3 ci 2025-12-09 21:16:36 -05:00
Dax Raad
0decdf6a55 ci 2025-12-09 21:16:23 -05:00
Dax Raad
09b402a274 ci 2025-12-09 21:16:00 -05:00
Dax Raad
150baf3e96 ci 2025-12-09 21:10:58 -05:00
Dax Raad
78c51371af sync 2025-12-09 21:06:57 -05:00
Dax Raad
6dbcacf3ea ci 2025-12-09 21:04:06 -05:00
Dax Raad
4ecebc2c83 ci 2025-12-09 21:03:19 -05:00
Dax Raad
38a79fa449 ci 2025-12-09 21:02:04 -05:00
Dax Raad
bafad6b8a8 ci 2025-12-09 21:01:48 -05:00
Dax Raad
5682dddd45 ci 2025-12-09 21:01:39 -05:00
Dax Raad
a9aacdb94a ci 2025-12-09 20:59:31 -05:00
Dax Raad
e7e32c946b ci 2025-12-09 20:58:01 -05:00
Dax Raad
fc9bc26d86 ci 2025-12-09 20:56:13 -05:00
Dax Raad
ee00b4e0ce ci 2025-12-09 20:54:56 -05:00
Dax Raad
f82156f0b1 ci 2025-12-09 20:51:54 -05:00
Dax Raad
2ed6298584 ci 2025-12-09 20:51:25 -05:00
Dax Raad
52ef8dea3e ci 2025-12-09 20:50:46 -05:00
Adam
ebe6015db0 fix: re-enable tauri typecheck 2025-12-09 19:34:07 -06:00
Dax Raad
56526114e4 ci 2025-12-09 20:26:46 -05:00
Dax Raad
bb1c225027 ci 2025-12-09 20:24:31 -05:00
Dax Raad
e5af0dde08 ci 2025-12-09 20:22:00 -05:00
Dax Raad
3cf17bc24f ci 2025-12-09 20:20:47 -05:00
Dax Raad
4aa1b8de0e ci 2025-12-09 20:19:43 -05:00
Dax Raad
73e9534d08 ci 2025-12-09 20:17:07 -05:00
Dax Raad
cb188f907f ci 2025-12-09 19:53:50 -05:00
Dax Raad
63d9656ad8 ci 2025-12-09 19:46:59 -05:00
Dax Raad
3512d02e9e ci 2025-12-09 19:46:38 -05:00
Dax Raad
1efdceaf10 ci: combine sdk and format workflows into single generate workflow 2025-12-09 19:41:23 -05:00
GitHub Action
632a0fe009 chore: regen sdk 2025-12-10 00:33:05 +00:00
Dax Raad
6fb32cebec ci 2025-12-09 19:32:30 -05:00
GitHub Action
8b8b17d755 chore: format code 2025-12-10 00:28:05 +00:00
GitHub Action
2c27afaaf5 chore: regen sdk 2025-12-10 00:27:29 +00:00
Dax Raad
4bdc7c1426 ci fix 2025-12-09 19:26:54 -05:00
Github Action
3c1e6c2c8f Update Nix flake.lock and hashes 2025-12-09 23:56:13 +00:00
Aiden Cline
b8f5809f95 ignore: rm chalk 2025-12-09 17:54:53 -06:00
Aiden Cline
552ee81455 tweak: add OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS 2025-12-09 17:28:34 -06:00
David Hill
9fdbe193cd fix: add spacer before queued badge 2025-12-09 22:56:44 +00:00
Aiden Cline
df64612d54 better interleaved thinking support (#5298) 2025-12-09 16:32:12 -06:00
Adam
0aa3e6c270 wip(desktop): progress 2025-12-09 16:23:05 -06:00
Adam
44c17c1435 wip(desktop): progress 2025-12-09 16:23:05 -06:00
Dax Raad
132e772c26 core: fix project icon update handling to preserve existing icon properties 2025-12-09 16:55:26 -05:00
Adam
62cbed57cc wip(desktop): progress 2025-12-09 15:55:08 -06:00
Adam
ebab7e176e wip(desktop): progress 2025-12-09 15:53:08 -06:00
Adam
9c93853e22 wip(desktop): progress 2025-12-09 15:46:23 -06:00
Aiden Cline
8a9c7a4ef3 add OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT 2025-12-09 15:44:56 -06:00
Adam
2dad56c9a2 wip(desktop): progress 2025-12-09 15:39:44 -06:00
Adam
41d78c1ecc wip(desktop): progress 2025-12-09 15:32:36 -06:00
ry2009
16c4b02b69 feat: add Biome LSP support (#5290) 2025-12-09 15:31:13 -06:00
Adam
35c04d9283 wip(desktop): progress 2025-12-09 15:24:11 -06:00
Adam
1fbd7a7f9a wip(desktop): progress 2025-12-09 15:21:47 -06:00
Adam
d7563d1694 wip(desktop): progress 2025-12-09 15:21:47 -06:00
Adam
b9fa7d9163 wip(desktop): progress 2025-12-09 15:21:47 -06:00
Adam
f736751ab2 wip(desktop): progress 2025-12-09 15:21:45 -06:00
Aiden Cline
dbcc779f0b ci: fix sdk workflow 2025-12-09 15:08:30 -06:00
Nick
c33a90320c fix: resolve 'latest' to actual version when caching plugins (#5292) 2025-12-09 15:07:59 -06:00
Dax Raad
802b862aae format on write 2025-12-09 16:06:57 -05:00
GitHub Action
b0cd171c1b chore: format code 2025-12-09 21:05:09 +00:00
GitHub Action
13755f4680 chore: regen sdk 2025-12-09 21:04:37 +00:00
Dax Raad
b242659cc3 fix types 2025-12-09 16:03:58 -05:00
Dax Raad
5f6b2fdc6f fix tests 2025-12-09 16:03:33 -05:00
GitHub Action
e34f18991e chore: format code 2025-12-09 20:53:06 +00:00
GitHub Action
209b0a06f7 chore: regen sdk 2025-12-09 20:52:31 +00:00
Dax Raad
a2e460bc4b discover logic 2025-12-09 15:51:55 -05:00
GitHub Action
fc9081afe4 chore: format code 2025-12-09 20:49:25 +00:00
GitHub Action
1a3f7c3d84 chore: regen sdk 2025-12-09 20:48:49 +00:00
Dax Raad
06aa1f49b8 sync 2025-12-09 15:48:22 -05:00
GitHub Action
dfd67cd922 chore: format code 2025-12-09 20:44:09 +00:00
GitHub Action
70f7287ca1 chore: regen sdk 2025-12-09 20:43:35 +00:00
Dax Raad
f1955b4d05 core: fix project event emission to include updated worktree data 2025-12-09 15:41:38 -05:00
Dax Raad
c5e5627cbd test fixes 2025-12-09 15:41:38 -05:00
GitHub Action
93378526b9 chore: format code 2025-12-09 20:26:02 +00:00
GitHub Action
abf176a335 chore: regen sdk 2025-12-09 20:25:21 +00:00
Aiden Cline
84a0868e66 fix: read when file is svg 2025-12-09 14:24:30 -06:00
GitHub Action
75a9c42789 chore: format code 2025-12-09 20:20:03 +00:00
GitHub Action
204fa54625 chore: regen sdk 2025-12-09 20:19:27 +00:00
Dax Raad
365584048f core: fix project creation to include updated timestamp 2025-12-09 15:18:55 -05:00
Dax Raad
edffcc32cf core: make project updated timestamp optional to support legacy project data 2025-12-09 15:18:55 -05:00
GitHub Action
238f441bcb chore: format code 2025-12-09 20:17:37 +00:00
GitHub Action
0571a8302c chore: regen sdk 2025-12-09 20:16:59 +00:00
Aiden Cline
8c07382382 ci: fix sdk gen 2025-12-09 14:16:22 -06:00
GitHub Action
fa32fbd187 chore: format code 2025-12-09 20:12:07 +00:00
GitHub Action
0fd2ecd0ba chore: regen sdk 2025-12-09 20:11:32 +00:00
Dax Raad
7439a40b00 core: fix project icon update to preserve existing icon properties 2025-12-09 15:11:00 -05:00
GitHub Action
2ad99713f3 chore: format code 2025-12-09 20:07:32 +00:00
GitHub Action
19ec970701 chore: regen sdk 2025-12-09 20:06:57 +00:00
Dax Raad
b48caec218 core: add automatic project icon discovery from favicon/logo files 2025-12-09 15:06:24 -05:00
GitHub Action
380c34af53 chore: format code 2025-12-09 19:54:54 +00:00
GitHub Action
553d9013eb chore: regen sdk 2025-12-09 19:54:19 +00:00
Dax Raad
8bff3cdae8 fix ci 2025-12-09 14:53:47 -05:00
Dax Raad
0b40c3d37d rework project loading 2025-12-09 14:41:14 -05:00
Dax Raad
1e3bdcc71c rename bus 2025-12-09 14:32:09 -05:00
GitHub Action
de577e17da chore: format code 2025-12-09 19:31:07 +00:00
GitHub Action
8a9e258ad7 chore: regen sdk 2025-12-09 19:30:32 +00:00
Adam
9a34965432 feat: add color to project 2025-12-09 13:29:59 -06:00
Adam
c944d19c3b wip(desktop): progress 2025-12-09 13:24:37 -06:00
Dax Raad
fb1b6c5e6b add project.name/icon 2025-12-09 13:57:18 -05:00
GitHub Action
ad0c4c5d89 chore: format code 2025-12-09 18:37:14 +00:00
GitHub Action
a54b663a39 chore: regen sdk 2025-12-09 18:36:38 +00:00
Adam
ae4993f39a wip(desktop): progress 2025-12-09 12:36:06 -06:00
GitHub Action
aa638cec48 chore: format code 2025-12-09 18:10:31 +00:00
GitHub Action
4db4a90559 chore: regen sdk 2025-12-09 18:07:43 +00:00
Aiden Cline
e23a81097c core: add test to prevent MCP headers regression when OAuth is enabled
Custom headers configured for remote MCP servers were being silently
dropped when OAuth was enabled (the default). This test ensures headers
are always sent to MCP servers regardless of OAuth configuration.
2025-12-09 12:06:40 -06:00
opencode
76f4803d8d release: v1.0.138 2025-12-09 18:05:21 +00:00
GitHub Action
22e4649318 chore: format code 2025-12-09 17:53:53 +00:00
GitHub Action
0ac70ff261 chore: regen sdk 2025-12-09 17:53:14 +00:00
Adam
1bc1e56da3 wip(desktop): progress 2025-12-09 11:52:43 -06:00
GitHub Action
0d0c20e673 chore: format code 2025-12-09 17:45:35 +00:00
Jay
a964824b22 docs: Modify documentation for SDK ecosystem references
Updated link text to refer to community-built projects.
2025-12-09 12:44:33 -05:00
GitHub Action
2cf0d578fe chore: regen sdk 2025-12-09 17:43:58 +00:00
Jay
13e8fb382f docs: Update community plugins reference in documentation 2025-12-09 12:43:29 -05:00
GitHub Action
4090bc9dea chore: format code 2025-12-09 17:39:25 +00:00
Aiden Cline
cec1caf99e ci: sdk stuff 2025-12-09 11:38:22 -06:00
GitHub Action
c74da97d52 chore: regen sdk 2025-12-09 17:38:01 +00:00
Jay
1f2497ce69 docs: Add submission note for OpenCode projects
Added a note about submitting projects to the list.
2025-12-09 12:37:34 -05:00
GitHub Action
986f14cb15 chore: format code 2025-12-09 17:37:32 +00:00
GitHub Action
34f639d510 chore: regen sdk 2025-12-09 17:34:45 +00:00
Aiden Cline
defe51c825 docs: fix name 2025-12-09 11:34:15 -06:00
GitHub Action
5a16acef8c chore: format code 2025-12-09 17:26:28 +00:00
Aiden Cline
2ce249dbc0 docs: OpenCode ecosystem (#5287)
Co-authored-by: GitHub Action <action@github.com>
2025-12-09 11:25:28 -06:00
GitHub Action
7ba6b18945 chore: format code 2025-12-09 17:10:13 +00:00
GitHub Action
b8c0b393bf chore: regen sdk 2025-12-09 17:09:35 +00:00
Adam
5442adb517 wip(desktop): progress 2025-12-09 11:09:00 -06:00
Adam
6b2ac20abc wip(desktop): progress 2025-12-09 11:09:00 -06:00
GitHub Action
3efc95b157 chore: format code 2025-12-09 16:53:58 +00:00
GitHub Action
cd9db8a81d chore: regen sdk 2025-12-09 16:53:24 +00:00
Dax Raad
036f5d4eef core: add project update timestamps to track when projects were last modified
Projects now track when they were last updated, making it easier for users
to see recent activity and identify stale projects in their workspace.
2025-12-09 11:52:53 -05:00
GitHub Action
c4401290db chore: format code 2025-12-09 15:53:48 +00:00
GitHub Action
4a6deb6420 chore: regen sdk 2025-12-09 15:53:12 +00:00
André Cruz
87a03e1e30 fix(mcp): send custom headers regardless of OAuth settings (#5273)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-09 09:52:39 -06:00
GitHub Action
01dc9d7ec6 chore: format code 2025-12-09 15:47:54 +00:00
GitHub Action
e78e0f9841 chore: regen sdk 2025-12-09 15:47:19 +00:00
Ravi Kumar
8326640670 feat(telemetry): Add userId and sessionId metadata to experimental_telemetry (#5279) 2025-12-09 09:46:48 -06:00
GitHub Action
d079af4be2 chore: format code 2025-12-09 12:16:05 +00:00
GitHub Action
82c9584382 chore: regen sdk 2025-12-09 12:15:30 +00:00
Adam
d3b6de855b chore: cleanup 2025-12-09 06:14:58 -06:00
Adam
5ad000fd99 chore: cleanup 2025-12-09 06:14:22 -06:00
Adam
fe196da430 fix(tui): order 2025-12-09 06:13:36 -06:00
Adam
20662e2101 wip(desktop): progress 2025-12-09 06:12:09 -06:00
Adam
0a357be160 wip(desktop): progress 2025-12-09 06:12:09 -06:00
Adam
d29205e677 fix: diff scroll gutter 2025-12-09 06:12:09 -06:00
GitHub Action
9d0630f094 ignore: update download stats 2025-12-09 2025-12-09 12:04:56 +00:00
Github Action
b6844565e8 Update Nix flake.lock and hashes 2025-12-09 11:15:34 +00:00
GitHub Action
17d1b24def chore: format code 2025-12-09 11:15:02 +00:00
GitHub Action
3d279edf44 chore: regen sdk 2025-12-09 11:14:23 +00:00
Brendan Allan
0a47a3cea0 fix: use ts project references for desktop and tauri 2025-12-09 19:13:51 +08:00
Github Action
306d57fcde Update Nix flake.lock and hashes 2025-12-09 09:17:58 +00:00
GitHub Action
ff6f1abf61 chore: format code 2025-12-09 09:17:36 +00:00
GitHub Action
331278a5be chore: regen sdk 2025-12-09 09:17:00 +00:00
Brendan Allan
78547f3c59 desktop: move updater logic to js 2025-12-09 17:16:24 +08:00
GitHub Action
d32671224f chore: format code 2025-12-09 07:26:55 +00:00
GitHub Action
9ade416ad4 chore: regen sdk 2025-12-09 07:26:16 +00:00
Aiden Cline
f8bd4ff705 core: refactor providerOptions function to accept Provider.Model for cleaner API 2025-12-09 01:25:36 -06:00
GitHub Action
2206e10d92 chore: format code 2025-12-09 06:42:56 +00:00
Aiden Cline
e282d5dc42 ci: run format workflow after sdk workflow completion 2025-12-09 00:41:40 -06:00
GitHub Action
2b4a5aede1 chore: regen sdk 2025-12-09 06:37:35 +00:00
Aiden Cline
654a2cd6a4 core: remove unused fzf dependency to address CVE
- Eliminates fzf binary dependency that was no longer used after file search overhaul
- Removes fzf from Nix package configuration and Arch Linux PKGBUILD dependencies
2025-12-09 00:36:33 -06:00
1717 changed files with 22571 additions and 17056 deletions

View File

@@ -1,63 +0,0 @@
name: Auto-label TUI Issues
on:
issues:
types: [opened]
jobs:
auto-label:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
issues: write
steps:
- name: Auto-label and assign issues
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issue = context.payload.issue;
const title = issue.title;
const description = issue.body || '';
// Check for "opencode web" keyword
const webPattern = /(opencode web)/i;
const isWebRelated = webPattern.test(title) || webPattern.test(description);
// Check for version patterns like v1.0.x or 1.0.x
const versionPattern = /[v]?1\.0\./i;
const isVersionRelated = versionPattern.test(title) || versionPattern.test(description);
// Check for "nix" keyword
const nixPattern = /\bnix\b/i;
const isNixRelated = nixPattern.test(title) || nixPattern.test(description);
const labels = [];
if (isWebRelated) {
labels.push('web');
// Assign to adamdotdevin
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
assignees: ['adamdotdevin']
});
} else if (isVersionRelated) {
// Only add opentui if NOT web-related
labels.push('opentui');
}
if (isNixRelated) {
labels.push('nix');
}
if (labels.length > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: labels
});
}

View File

@@ -16,6 +16,8 @@ jobs:
with:
fetch-depth: 1
- uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash

View File

@@ -1,32 +0,0 @@
name: format
on:
push:
branches-ignore:
- production
pull_request:
branches-ignore:
- production
workflow_dispatch:
jobs:
format:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: run
run: |
./script/format.ts
env:
CI: true
PUSH_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}

55
.github/workflows/generate.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: generate
on:
push:
branches-ignore:
- production
pull_request:
branches-ignore:
- production
workflow_dispatch:
jobs:
generate:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Generate
run: ./script/generate.ts
- name: Commit and push
id: push
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "No changes to commit"
exit 0
fi
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add -A
git commit -m "chore: generate"
git push origin HEAD:${{ github.event.pull_request.head.ref || github.ref_name }} --no-verify
- name: Comment on failure
if: failure()
run: |
MESSAGE=$'Failed to push generated code. Please run locally and push:\n```\n./script/generate.ts\ngit add -A && git commit -m "chore: generate" && git push\n```'
if [ -n "${{ github.event.pull_request.number }}" ]; then
gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --body "$MESSAGE"
else
gh api repos/${{ github.repository }}/commits/${{ github.sha }}/comments -f body="$MESSAGE"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -2,7 +2,7 @@ name: discord
on:
release:
types: [published] # fires only when a release is published
types: [released] # fires when a draft release is published
jobs:
notify:

View File

@@ -29,5 +29,6 @@ jobs:
uses: sst/opencode/github@latest
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
OPENCODE_PERMISSION: '{"bash": "deny"}'
with:
model: opencode/claude-haiku-4-5
model: opencode/claude-opus-4-5

View File

@@ -2,11 +2,15 @@ name: publish
run-name: "${{ format('release {0}', inputs.bump) }}"
on:
push:
branches:
- dev
- snapshot-*
workflow_dispatch:
inputs:
bump:
description: "Bump major, minor, or patch"
required: true
required: false
type: choice
options:
- major
@@ -17,16 +21,17 @@ on:
required: false
type: string
concurrency: ${{ github.workflow }}-${{ github.ref }}
concurrency: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.version || inputs.bump }}
permissions:
id-token: write
contents: write
packages: write
jobs:
publish:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.repository == 'sst/opencode' && github.ref == 'refs/heads/dev'
if: github.repository == 'sst/opencode'
steps:
- uses: actions/checkout@v3
with:
@@ -34,20 +39,13 @@ jobs:
- run: git fetch --force --tags
- uses: actions/setup-go@v5
with:
go-version: ">=1.24.0"
cache: true
cache-dependency-path: go.sum
- uses: ./.github/actions/setup-bun
- name: Install makepkg
- name: Setup SSH for AUR
if: inputs.bump || inputs.version
run: |
sudo apt-get update
sudo apt-get install -y pacman-package-manager
- name: Setup SSH for AUR
run: |
mkdir -p ~/.ssh
echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
@@ -56,11 +54,8 @@ jobs:
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
- name: Install OpenCode
run: curl -fsSL https://opencode.ai/install | bash
- name: Setup npm auth
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
if: inputs.bump || inputs.version
run: bun i -g opencode-ai@1.0.143
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
@@ -69,19 +64,33 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- uses: actions/setup-node@v4
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Publish
run: |
./script/publish.ts
id: publish
run: ./script/publish.ts
env:
OPENCODE_BUMP: ${{ inputs.bump }}
OPENCODE_VERSION: ${{ inputs.version }}
OPENCODE_CHANNEL: latest
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
AUR_KEY: ${{ secrets.AUR_KEY }}
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
NPM_CONFIG_PROVENANCE: false
outputs:
releaseId: ${{ steps.publish.outputs.releaseId }}
tagName: ${{ steps.publish.outputs.tagName }}
publish-tauri:
needs: publish
continue-on-error: true
strategy:
fail-fast: false
@@ -91,15 +100,16 @@ jobs:
target: x86_64-apple-darwin
- host: macos-latest
target: aarch64-apple-darwin
- host: windows-latest
- host: blacksmith-4vcpu-windows-2025
target: x86_64-pc-windows-msvc
- host: ubuntu-24.04
- host: blacksmith-4vcpu-ubuntu-2404
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ needs.publish.outputs.tagName }}
- uses: apple-actions/import-codesign-certs@v2
if: ${{ runner.os == 'macOS' }}
@@ -126,7 +136,7 @@ jobs:
- uses: ./.github/actions/setup-bun
- name: install dependencies (ubuntu only)
if: startsWith(matrix.settings.host, 'ubuntu')
if: contains(matrix.settings.host, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
@@ -155,12 +165,18 @@ jobs:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
RUST_TARGET: ${{ matrix.settings.target }}
GH_TOKEN: ${{ github.token }}
OPENCODE_RELEASE_TAG: ${{ needs.publish.outputs.tagName }}
# Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released
- run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch feat/truly-portable-appimage
if: startsWith(matrix.settings.host, 'ubuntu')
- name: Install tauri-cli from portable appimage branch
if: contains(matrix.settings.host, 'ubuntu')
run: |
cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch feat/truly-portable-appimage --force
echo "Installed tauri-cli version:"
cargo tauri --version
- name: Build and upload artifacts
timeout-minutes: 20
uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -176,7 +192,26 @@ jobs:
with:
projectPath: packages/tauri
uploadWorkflowArtifacts: true
tauriScript: ${{ (startsWith(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
args: --target ${{ matrix.settings.target }}
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
args: --target ${{ matrix.settings.target }} --config src-tauri/tauri.prod.conf.json
updaterJsonPreferNsis: true
# releaseId: TODO
releaseId: ${{ needs.publish.outputs.releaseId }}
tagName: ${{ needs.publish.outputs.tagName }}
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
releaseDraft: true
publish-release:
needs:
- publish
- publish-tauri
if: needs.publish.outputs.tagName
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ needs.publish.outputs.tagName }}
- run: gh release edit ${{ needs.publish.outputs.tagName }} --draft=false
env:
GH_TOKEN: ${{ github.token }}

View File

@@ -0,0 +1,29 @@
name: release-github-action
on:
push:
branches:
- dev
paths:
- "github/**"
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
jobs:
release:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: git fetch --force --tags
- name: Release
run: |
git config --global user.email "opencode@sst.dev"
git config --global user.name "opencode"
./github/script/release

View File

@@ -29,6 +29,8 @@ jobs:
with:
fetch-depth: 1
- uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash

View File

@@ -1,39 +0,0 @@
name: sdk
on:
push:
branches-ignore:
- production
pull_request:
branches-ignore:
- production
workflow_dispatch:
jobs:
format:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: run
run: |
bun ./packages/sdk/js/script/build.ts
(cd packages/opencode && bun dev generate > ../sdk/openapi.json)
if [ -z "$(git status --porcelain)" ]; then
echo "No changes to commit"
exit 0
fi
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add -A
git commit -m "chore: regen sdk"
git push --no-verify
env:
CI: true

View File

@@ -1,38 +0,0 @@
name: snapshot
on:
workflow_dispatch:
push:
branches:
- dev
- test-bedrock
- v0
- otui-diffs
- snapshot-*
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
publish:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: git fetch --force --tags
- uses: actions/setup-go@v5
with:
go-version: ">=1.24.0"
cache: true
cache-dependency-path: go.sum
- uses: ./.github/actions/setup-bun
- name: Publish
run: |
./script/publish.ts
env:
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -2,8 +2,8 @@ name: "sync-zed-extension"
on:
workflow_dispatch:
release:
types: [published]
# release:
# types: [published]
jobs:
zed:

37
.github/workflows/triage.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Issue Triage
on:
issues:
types: [opened]
jobs:
triage:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
- name: Triage issue
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
run: |
opencode run --agent triage "The following issue was just opened, triage it:
Title: $ISSUE_TITLE
$ISSUE_BODY"

2
.gitignore vendored
View File

@@ -9,6 +9,7 @@ node_modules
playground
tmp
dist
ts-dist
.turbo
**/.serena
.serena/
@@ -18,3 +19,4 @@ Session.vim
opencode.json
a.out
target
.scripts

79
.opencode/agent/triage.md Normal file
View File

@@ -0,0 +1,79 @@
---
mode: primary
hidden: true
model: opencode/claude-haiku-4-5
tools:
"*": false
"github-triage": true
---
You are a triage agent responsible for triaging github issues.
Use your github-triage tool to triage issues.
## Labels
### windows
Use for any issue that mentions Windows (the OS). Be sure they are saying that they are on Windows.
- Use if they mention WSL too
#### perf
Performance-related issues:
- Slow performance
- High RAM usage
- High CPU usage
**Only** add if it's likely a RAM or CPU issue. **Do not** add for LLM slowness.
#### desktop
Desktop app issues:
- `opencode web` command
- The desktop app itself
**Only** add if it's specifically about the Desktop application or `opencode web` view. **Do not** add for terminal, TUI, or general opencode issues.
#### nix
**Only** add if the issue explicitly mentions nix.
#### zen
**Only** add if the issue mentions "zen" or "opencode zen". Zen is our gateway for coding models. **Do not** add for other gateways or inference providers.
If the issue doesn't have "zen" in it then don't add zen label
#### docs
Add if the issue requests better documentation or docs updates.
#### opentui
TUI issues potentially caused by our underlying TUI library:
- Keybindings not working
- Scroll speed issues (too fast/slow/laggy)
- Screen flickering
- Crashes with opentui in the log
**Do not** add for general TUI bugs.
---
When assigning to people here are the following rules:
adamdotdev:
ONLY assign adam if the issue will have the "desktop" label.
fwang:
ONLY assign fwang if the issue will have the "zen" label.
jayair:
ONLY assign jayair if the issue will have the "docs" label.
In all other cases use best judgment. Avoid assigning to kommander needlessly, when in doubt assign to rekram1-node.

View File

@@ -1,5 +1,7 @@
---
description: git commit and push
model: opencode/glm-4.6
subtask: true
---
commit and push

4
.opencode/env.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module "*.txt" {
const content: string
export default content
}

View File

@@ -1,28 +1,17 @@
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-openai-codex-auth"],
// "plugin": ["opencode-openai-codex-auth"],
// "enterprise": {
// "url": "https://enterprise.dev.opencode.ai",
// },
"instructions": ["STYLE_GUIDE.md"],
"provider": {
"opencode": {
"options": {
// "baseURL": "http://localhost:8080",
},
"options": {},
},
},
"mcp": {
"exa": {
"type": "remote",
"url": "https://mcp.exa.ai/mcp",
},
"morph": {
"type": "local",
"command": ["bunx", "@morphllm/morphmcp"],
"environment": {
"ENABLED_TOOLS": "warp_grep",
},
},
"mcp": {},
"tools": {
"github-triage": false,
},
}

View File

@@ -0,0 +1,66 @@
/// <reference path="../env.d.ts" />
import { Octokit } from "@octokit/rest"
import { tool } from "@opencode-ai/plugin"
import DESCRIPTION from "./github-triage.txt"
function getIssueNumber(): number {
const issue = parseInt(process.env.ISSUE_NUMBER ?? "", 10)
if (!issue) throw new Error("ISSUE_NUMBER env var not set")
return issue
}
export default tool({
description: DESCRIPTION,
args: {
assignee: tool.schema
.enum(["thdxr", "adamdotdevin", "rekram1-node", "fwang", "jayair", "kommander"])
.describe("The username of the assignee")
.default("rekram1-node"),
labels: tool.schema
.array(tool.schema.enum(["nix", "opentui", "perf", "desktop", "zen", "docs", "windows"]))
.describe("The labels(s) to add to the issue")
.default([]),
},
async execute(args) {
const issue = getIssueNumber()
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
const owner = "sst"
const repo = "opencode"
const results: string[] = []
if (args.assignee === "adamdotdevin" && !args.labels.includes("desktop")) {
throw new Error("Only desktop issues should be assigned to adamdotdevin")
}
if (args.assignee === "fwang" && !args.labels.includes("zen")) {
throw new Error("Only zen issues should be assigned to fwang")
}
if (args.assignee === "kommander" && !args.labels.includes("opentui")) {
throw new Error("Only opentui issues should be assigned to kommander")
}
await octokit.rest.issues.addAssignees({
owner,
repo,
issue_number: issue,
assignees: [args.assignee],
})
results.push(`Assigned @${args.assignee} to issue #${issue}`)
const labels: string[] = args.labels.map((label) => (label === "desktop" ? "web" : label))
if (labels.length > 0) {
await octokit.rest.issues.addLabels({
owner,
repo,
issue_number: issue,
labels,
})
results.push(`Added labels: ${args.labels.join(", ")}`)
}
return results.join("\n")
},
})

View File

@@ -0,0 +1,88 @@
Use this tool to assign and/or label a Github issue.
You can assign the following users:
- thdxr
- adamdotdevin
- fwang
- jayair
- kommander
- rekram1-node
You can use the following labels:
- nix
- opentui
- perf
- web
- zen
- docs
Always try to assign an issue, if in doubt, assign rekram1-node to it.
## Breakdown of responsibilities:
### thdxr
Dax is responsible for managing core parts of the application, for large feature requests, api changes, or things that require significant changes to the codebase assign him.
This relates to OpenCode server primarily but has overlap with just about anything
### adamdotdevin
Adam is responsible for managing the Desktop/Web app. If there is an issue relating to the desktop app or `opencode web` command. Assign him.
### fwang
Frank is responsible for managing Zen, if you see complaints about OpenCode Zen, maybe it's the dashboard, the model quality, billing issues, etc. Assign him to the issue.
### jayair
Jay is responsible for documentation. If there is an issue relating to documentation assign him.
### kommander
Sebastian is responsible for managing an OpenTUI (a library for building terminal user interfaces). OpenCode's TUI is built with OpenTUI. If there are issues about:
- random characters on screen
- keybinds not working on different terminals
- general terminal stuff
Then assign the issue to Him.
### rekram1-node
ALL BUGS SHOULD BE assigned to rekram1-node unless they have the `opentui` label.
Assign Aiden to an issue as a catch all, if you can't assign anyone else. Most of the time this will be bugs/polish things.
If no one else makes sense to assign, assign rekram1-node to it.
Always assign to aiden if the issue mentions "acp", "zed", or model performance issues
## Breakdown of Labels:
### nix
Any issue that mentions nix, or nixos should have a nix label
### opentui
Anything relating to the TUI itself should have an opentui label
### perf
Anything related to slow performance, high ram, high cpu usage, or any other performance related issue should have a perf label
### desktop
Anything related to `opencode web` command or the desktop app should have a desktop label. Never add this label for anything terminal/tui related
### zen
Anything related to OpenCode Zen, billing, or model quality from Zen should have a zen label
### docs
Anything related to the documentation should have a docs label
### windows
Use for any issue that involves the windows OS

View File

@@ -7,7 +7,7 @@
</picture>
</a>
</p>
<p align="center">The AI coding agent built for the terminal.</p>
<p align="center">The open source 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>
@@ -30,13 +30,29 @@ scoop bucket add extras; scoop install extras/opencode # Windows
choco install opencode # Windows
brew install opencode # macOS and Linux
paru -S opencode-bin # Arch Linux
mise use --pin -g ubi:sst/opencode # Any OS
mise use -g ubi:sst/opencode # Any OS
nix run nixpkgs#opencode # or github:sst/opencode for latest dev branch
```
> [!TIP]
> Remove versions older than 0.1.x before installing.
### Desktop App (BETA)
OpenCode is also available as a desktop application. Download directly from the [releases page](https://github.com/sst/opencode/releases) or [opencode.ai/download](https://opencode.ai/download).
| Platform | 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`, or AppImage |
```bash
# macOS (Homebrew)
brew install --cask opencode-desktop
```
#### Installation Directory
The install script respects the following priority order for the installation path:
@@ -78,7 +94,7 @@ If you're interested in contributing to OpenCode, please read our [contributing
### Building on OpenCode
If you are working on a project that's related to OpenCode and is using "opencode" as a part of its name; for example, "opencode-dashboard" or "opencode-mobile", please add a note to your README to clarify that it is not built by the OpenCode team and is not affiliated with us in anyway.
If you are working on a project that's related to OpenCode and is using "opencode" as a part of its name; for example, "opencode-dashboard" or "opencode-mobile", please add a note to your README to clarify that it is not built by the OpenCode team and is not affiliated with us in any way.
### FAQ

338
STATS.md
View File

@@ -1,166 +1,176 @@
# Download Stats
| Date | GitHub Downloads | npm Downloads | Total |
| ---------- | ------------------ | ----------------- | ------------------- |
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
| 2025-10-02 | 440,852 (+7,261) | 348,099 (+6,357) | 788,951 (+13,618) |
| 2025-10-03 | 446,829 (+5,977) | 359,937 (+11,838) | 806,766 (+17,815) |
| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |
| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
| 2025-10-10 | 484,374 (+5,171) | 406,015 (+5,489) | 890,389 (+10,660) |
| 2025-10-11 | 488,427 (+4,053) | 414,699 (+8,684) | 903,126 (+12,737) |
| 2025-10-12 | 492,125 (+3,698) | 418,745 (+4,046) | 910,870 (+7,744) |
| 2025-10-14 | 505,130 (+13,005) | 429,286 (+10,541) | 934,416 (+23,546) |
| 2025-10-15 | 512,717 (+7,587) | 439,290 (+10,004) | 952,007 (+17,591) |
| 2025-10-16 | 517,719 (+5,002) | 447,137 (+7,847) | 964,856 (+12,849) |
| 2025-10-17 | 526,239 (+8,520) | 457,467 (+10,330) | 983,706 (+18,850) |
| 2025-10-18 | 531,564 (+5,325) | 465,272 (+7,805) | 996,836 (+13,130) |
| 2025-10-19 | 536,209 (+4,645) | 469,078 (+3,806) | 1,005,287 (+8,451) |
| 2025-10-20 | 541,264 (+5,055) | 472,952 (+3,874) | 1,014,216 (+8,929) |
| 2025-10-21 | 548,721 (+7,457) | 479,703 (+6,751) | 1,028,424 (+14,208) |
| 2025-10-22 | 557,949 (+9,228) | 491,395 (+11,692) | 1,049,344 (+20,920) |
| 2025-10-23 | 564,716 (+6,767) | 498,736 (+7,341) | 1,063,452 (+14,108) |
| 2025-10-24 | 572,692 (+7,976) | 506,905 (+8,169) | 1,079,597 (+16,145) |
| 2025-10-25 | 578,927 (+6,235) | 516,129 (+9,224) | 1,095,056 (+15,459) |
| 2025-10-26 | 584,409 (+5,482) | 521,179 (+5,050) | 1,105,588 (+10,532) |
| 2025-10-27 | 589,999 (+5,590) | 526,001 (+4,822) | 1,116,000 (+10,412) |
| 2025-10-28 | 595,776 (+5,777) | 532,438 (+6,437) | 1,128,214 (+12,214) |
| 2025-10-29 | 606,259 (+10,483) | 542,064 (+9,626) | 1,148,323 (+20,109) |
| 2025-10-30 | 613,746 (+7,487) | 542,064 (+0) | 1,155,810 (+7,487) |
| 2025-10-30 | 617,846 (+4,100) | 555,026 (+12,962) | 1,172,872 (+17,062) |
| 2025-10-31 | 626,612 (+8,766) | 564,579 (+9,553) | 1,191,191 (+18,319) |
| 2025-11-01 | 636,100 (+9,488) | 581,806 (+17,227) | 1,217,906 (+26,715) |
| 2025-11-02 | 644,067 (+7,967) | 590,004 (+8,198) | 1,234,071 (+16,165) |
| 2025-11-03 | 653,130 (+9,063) | 597,139 (+7,135) | 1,250,269 (+16,198) |
| 2025-11-04 | 663,912 (+10,782) | 608,056 (+10,917) | 1,271,968 (+21,699) |
| 2025-11-05 | 675,074 (+11,162) | 619,690 (+11,634) | 1,294,764 (+22,796) |
| 2025-11-06 | 686,252 (+11,178) | 630,885 (+11,195) | 1,317,137 (+22,373) |
| 2025-11-07 | 696,646 (+10,394) | 642,146 (+11,261) | 1,338,792 (+21,655) |
| 2025-11-08 | 706,035 (+9,389) | 653,489 (+11,343) | 1,359,524 (+20,732) |
| 2025-11-09 | 713,462 (+7,427) | 660,459 (+6,970) | 1,373,921 (+14,397) |
| 2025-11-10 | 722,288 (+8,826) | 668,225 (+7,766) | 1,390,513 (+16,592) |
| 2025-11-11 | 729,769 (+7,481) | 677,501 (+9,276) | 1,407,270 (+16,757) |
| 2025-11-12 | 740,180 (+10,411) | 686,454 (+8,953) | 1,426,634 (+19,364) |
| 2025-11-13 | 749,905 (+9,725) | 696,157 (+9,703) | 1,446,062 (+19,428) |
| 2025-11-14 | 759,928 (+10,023) | 705,237 (+9,080) | 1,465,165 (+19,103) |
| 2025-11-15 | 765,955 (+6,027) | 712,870 (+7,633) | 1,478,825 (+13,660) |
| 2025-11-16 | 771,069 (+5,114) | 716,596 (+3,726) | 1,487,665 (+8,840) |
| 2025-11-17 | 780,161 (+9,092) | 723,339 (+6,743) | 1,503,500 (+15,835) |
| 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) |
| 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) |
| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) |
| 2025-11-21 | 826,309 (+11,689) | 769,307 (+11,400) | 1,595,616 (+23,089) |
| 2025-11-22 | 837,269 (+10,960) | 780,996 (+11,689) | 1,618,265 (+22,649) |
| 2025-11-23 | 846,609 (+9,340) | 795,069 (+14,073) | 1,641,678 (+23,413) |
| 2025-11-24 | 856,733 (+10,124) | 804,033 (+8,964) | 1,660,766 (+19,088) |
| 2025-11-25 | 869,423 (+12,690) | 817,339 (+13,306) | 1,686,762 (+25,996) |
| 2025-11-26 | 881,414 (+11,991) | 832,518 (+15,179) | 1,713,932 (+27,170) |
| 2025-11-27 | 893,960 (+12,546) | 846,180 (+13,662) | 1,740,140 (+26,208) |
| 2025-11-28 | 901,741 (+7,781) | 856,482 (+10,302) | 1,758,223 (+18,083) |
| 2025-11-29 | 908,689 (+6,948) | 863,361 (+6,879) | 1,772,050 (+13,827) |
| 2025-11-30 | 916,116 (+7,427) | 870,194 (+6,833) | 1,786,310 (+14,260) |
| 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
| 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) |
| 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) |
| 2025-12-05 | 977,996 (+12,385) | 930,616 (+14,145) | 1,908,612 (+26,530) |
| 2025-12-06 | 987,884 (+9,888) | 943,773 (+13,157) | 1,931,657 (+23,045) |
| 2025-12-07 | 994,046 (+6,162) | 951,425 (+7,652) | 1,945,471 (+13,814) |
| 2025-12-08 | 1,000,898 (+6,852) | 957,149 (+5,724) | 1,958,047 (+12,576) |
| Date | GitHub Downloads | npm Downloads | Total |
| ---------- | ------------------- | ------------------- | ------------------- |
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
| 2025-10-02 | 440,852 (+7,261) | 348,099 (+6,357) | 788,951 (+13,618) |
| 2025-10-03 | 446,829 (+5,977) | 359,937 (+11,838) | 806,766 (+17,815) |
| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |
| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
| 2025-10-10 | 484,374 (+5,171) | 406,015 (+5,489) | 890,389 (+10,660) |
| 2025-10-11 | 488,427 (+4,053) | 414,699 (+8,684) | 903,126 (+12,737) |
| 2025-10-12 | 492,125 (+3,698) | 418,745 (+4,046) | 910,870 (+7,744) |
| 2025-10-14 | 505,130 (+13,005) | 429,286 (+10,541) | 934,416 (+23,546) |
| 2025-10-15 | 512,717 (+7,587) | 439,290 (+10,004) | 952,007 (+17,591) |
| 2025-10-16 | 517,719 (+5,002) | 447,137 (+7,847) | 964,856 (+12,849) |
| 2025-10-17 | 526,239 (+8,520) | 457,467 (+10,330) | 983,706 (+18,850) |
| 2025-10-18 | 531,564 (+5,325) | 465,272 (+7,805) | 996,836 (+13,130) |
| 2025-10-19 | 536,209 (+4,645) | 469,078 (+3,806) | 1,005,287 (+8,451) |
| 2025-10-20 | 541,264 (+5,055) | 472,952 (+3,874) | 1,014,216 (+8,929) |
| 2025-10-21 | 548,721 (+7,457) | 479,703 (+6,751) | 1,028,424 (+14,208) |
| 2025-10-22 | 557,949 (+9,228) | 491,395 (+11,692) | 1,049,344 (+20,920) |
| 2025-10-23 | 564,716 (+6,767) | 498,736 (+7,341) | 1,063,452 (+14,108) |
| 2025-10-24 | 572,692 (+7,976) | 506,905 (+8,169) | 1,079,597 (+16,145) |
| 2025-10-25 | 578,927 (+6,235) | 516,129 (+9,224) | 1,095,056 (+15,459) |
| 2025-10-26 | 584,409 (+5,482) | 521,179 (+5,050) | 1,105,588 (+10,532) |
| 2025-10-27 | 589,999 (+5,590) | 526,001 (+4,822) | 1,116,000 (+10,412) |
| 2025-10-28 | 595,776 (+5,777) | 532,438 (+6,437) | 1,128,214 (+12,214) |
| 2025-10-29 | 606,259 (+10,483) | 542,064 (+9,626) | 1,148,323 (+20,109) |
| 2025-10-30 | 613,746 (+7,487) | 542,064 (+0) | 1,155,810 (+7,487) |
| 2025-10-30 | 617,846 (+4,100) | 555,026 (+12,962) | 1,172,872 (+17,062) |
| 2025-10-31 | 626,612 (+8,766) | 564,579 (+9,553) | 1,191,191 (+18,319) |
| 2025-11-01 | 636,100 (+9,488) | 581,806 (+17,227) | 1,217,906 (+26,715) |
| 2025-11-02 | 644,067 (+7,967) | 590,004 (+8,198) | 1,234,071 (+16,165) |
| 2025-11-03 | 653,130 (+9,063) | 597,139 (+7,135) | 1,250,269 (+16,198) |
| 2025-11-04 | 663,912 (+10,782) | 608,056 (+10,917) | 1,271,968 (+21,699) |
| 2025-11-05 | 675,074 (+11,162) | 619,690 (+11,634) | 1,294,764 (+22,796) |
| 2025-11-06 | 686,252 (+11,178) | 630,885 (+11,195) | 1,317,137 (+22,373) |
| 2025-11-07 | 696,646 (+10,394) | 642,146 (+11,261) | 1,338,792 (+21,655) |
| 2025-11-08 | 706,035 (+9,389) | 653,489 (+11,343) | 1,359,524 (+20,732) |
| 2025-11-09 | 713,462 (+7,427) | 660,459 (+6,970) | 1,373,921 (+14,397) |
| 2025-11-10 | 722,288 (+8,826) | 668,225 (+7,766) | 1,390,513 (+16,592) |
| 2025-11-11 | 729,769 (+7,481) | 677,501 (+9,276) | 1,407,270 (+16,757) |
| 2025-11-12 | 740,180 (+10,411) | 686,454 (+8,953) | 1,426,634 (+19,364) |
| 2025-11-13 | 749,905 (+9,725) | 696,157 (+9,703) | 1,446,062 (+19,428) |
| 2025-11-14 | 759,928 (+10,023) | 705,237 (+9,080) | 1,465,165 (+19,103) |
| 2025-11-15 | 765,955 (+6,027) | 712,870 (+7,633) | 1,478,825 (+13,660) |
| 2025-11-16 | 771,069 (+5,114) | 716,596 (+3,726) | 1,487,665 (+8,840) |
| 2025-11-17 | 780,161 (+9,092) | 723,339 (+6,743) | 1,503,500 (+15,835) |
| 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) |
| 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) |
| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) |
| 2025-11-21 | 826,309 (+11,689) | 769,307 (+11,400) | 1,595,616 (+23,089) |
| 2025-11-22 | 837,269 (+10,960) | 780,996 (+11,689) | 1,618,265 (+22,649) |
| 2025-11-23 | 846,609 (+9,340) | 795,069 (+14,073) | 1,641,678 (+23,413) |
| 2025-11-24 | 856,733 (+10,124) | 804,033 (+8,964) | 1,660,766 (+19,088) |
| 2025-11-25 | 869,423 (+12,690) | 817,339 (+13,306) | 1,686,762 (+25,996) |
| 2025-11-26 | 881,414 (+11,991) | 832,518 (+15,179) | 1,713,932 (+27,170) |
| 2025-11-27 | 893,960 (+12,546) | 846,180 (+13,662) | 1,740,140 (+26,208) |
| 2025-11-28 | 901,741 (+7,781) | 856,482 (+10,302) | 1,758,223 (+18,083) |
| 2025-11-29 | 908,689 (+6,948) | 863,361 (+6,879) | 1,772,050 (+13,827) |
| 2025-11-30 | 916,116 (+7,427) | 870,194 (+6,833) | 1,786,310 (+14,260) |
| 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
| 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) |
| 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) |
| 2025-12-05 | 977,996 (+12,385) | 930,616 (+14,145) | 1,908,612 (+26,530) |
| 2025-12-06 | 987,884 (+9,888) | 943,773 (+13,157) | 1,931,657 (+23,045) |
| 2025-12-07 | 994,046 (+6,162) | 951,425 (+7,652) | 1,945,471 (+13,814) |
| 2025-12-08 | 1,000,898 (+6,852) | 957,149 (+5,724) | 1,958,047 (+12,576) |
| 2025-12-09 | 1,011,488 (+10,590) | 973,922 (+16,773) | 1,985,410 (+27,363) |
| 2025-12-10 | 1,025,891 (+14,403) | 991,708 (+17,786) | 2,017,599 (+32,189) |
| 2025-12-11 | 1,045,110 (+19,219) | 1,010,559 (+18,851) | 2,055,669 (+38,070) |
| 2025-12-12 | 1,061,340 (+16,230) | 1,030,838 (+20,279) | 2,092,178 (+36,509) |
| 2025-12-13 | 1,073,561 (+12,221) | 1,044,608 (+13,770) | 2,118,169 (+25,991) |
| 2025-12-14 | 1,082,042 (+8,481) | 1,052,425 (+7,817) | 2,134,467 (+16,298) |
| 2025-12-15 | 1,093,632 (+11,590) | 1,059,078 (+6,653) | 2,152,710 (+18,243) |
| 2025-12-16 | 1,120,477 (+26,845) | 1,078,022 (+18,944) | 2,198,499 (+45,789) |
| 2025-12-17 | 1,151,067 (+30,590) | 1,097,661 (+19,639) | 2,248,728 (+50,229) |
| 2025-12-18 | 1,178,658 (+27,591) | 1,113,418 (+15,757) | 2,292,076 (+43,348) |

168
bun.lock
View File

@@ -6,6 +6,7 @@
"name": "opencode",
"dependencies": {
"@aws-sdk/client-s3": "3.933.0",
"@octokit/rest": "22.0.1",
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"typescript": "catalog:",
@@ -20,7 +21,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -48,7 +49,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -75,7 +76,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -99,7 +100,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -123,7 +124,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -131,10 +132,12 @@
"@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": "4.3.3",
"@solid-primitives/storage": "catalog:",
"@solid-primitives/websocket": "1.3.1",
"@solidjs/meta": "catalog:",
"@solidjs/router": "catalog:",
@@ -151,6 +154,7 @@
"solid-list": "catalog:",
"tailwindcss": "catalog:",
"virtua": "catalog:",
"zod": "catalog:",
},
"devDependencies": {
"@happy-dom/global-registrator": "20.0.11",
@@ -168,17 +172,18 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@pierre/precision-diffs": "catalog:",
"@pierre/diffs": "catalog:",
"@solidjs/meta": "catalog:",
"@solidjs/router": "catalog:",
"@solidjs/start": "catalog:",
"aws4fetch": "^1.0.20",
"hono": "catalog:",
"hono-openapi": "catalog:",
"js-base64": "3.7.7",
"luxon": "catalog:",
"nitro": "3.0.1-alpha.1",
"solid-js": "catalog:",
@@ -196,10 +201,10 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
"@octokit/rest": "catalog:",
"hono": "catalog:",
"jose": "6.0.11",
},
@@ -212,7 +217,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.0.137",
"version": "1.0.168",
"bin": {
"opencode": "./bin/opencode",
},
@@ -235,17 +240,17 @@
"@hono/zod-validator": "catalog:",
"@modelcontextprotocol/sdk": "1.15.1",
"@octokit/graphql": "9.0.2",
"@octokit/rest": "22.0.0",
"@octokit/rest": "catalog:",
"@openauthjs/openauth": "catalog:",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.2.8",
"@opentui/core": "0.1.59",
"@opentui/solid": "0.1.59",
"@openrouter/ai-sdk-provider": "1.5.2",
"@opentui/core": "0.1.61",
"@opentui/solid": "0.1.61",
"@parcel/watcher": "2.5.1",
"@pierre/precision-diffs": "catalog:",
"@pierre/diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
"@standard-schema/spec": "1.0.0",
"@zip.js/zip.js": "2.7.62",
@@ -304,7 +309,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -324,7 +329,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.0.137",
"version": "1.0.168",
"devDependencies": {
"@hey-api/openapi-ts": "0.88.1",
"@tsconfig/node22": "catalog:",
@@ -335,7 +340,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -348,33 +353,41 @@
},
"packages/tauri": {
"name": "@opencode-ai/tauri",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@opencode-ai/desktop": "workspace:*",
"@solid-primitives/storage": "catalog:",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "~2",
"@tauri-apps/plugin-opener": "^2",
"@tauri-apps/plugin-os": "~2",
"@tauri-apps/plugin-process": "~2",
"@tauri-apps/plugin-shell": "~2",
"@tauri-apps/plugin-store": "~2",
"@tauri-apps/plugin-updater": "~2",
"@tauri-apps/plugin-window-state": "~2",
"solid-js": "catalog:",
},
"devDependencies": {
"@actions/artifact": "4.0.0",
"@tauri-apps/cli": "^2",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "~5.6.2",
"vite": "catalog:",
},
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@pierre/precision-diffs": "catalog:",
"@pierre/diffs": "catalog:",
"@shikijs/transformers": "3.9.2",
"@solid-primitives/bounds": "0.1.3",
"@solid-primitives/resize-observer": "2.1.3",
"@solidjs/meta": "catalog:",
"@typescript/native-preview": "catalog:",
"fuzzysort": "catalog:",
@@ -391,6 +404,7 @@
"@tailwindcss/vite": "catalog:",
"@tsconfig/node22": "catalog:",
"@types/bun": "catalog:",
"@types/luxon": "catalog:",
"tailwindcss": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
@@ -400,7 +414,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"zod": "catalog:",
},
@@ -411,7 +425,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -459,15 +473,17 @@
"@cloudflare/workers-types": "4.20251008.0",
"@hono/zod-validator": "0.4.2",
"@kobalte/core": "0.13.11",
"@octokit/rest": "22.0.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/precision-diffs": "0.6.0-beta.10",
"@pierre/diffs": "1.0.0-beta.3",
"@solid-primitives/storage": "4.3.3",
"@solidjs/meta": "0.29.4",
"@solidjs/router": "0.15.4",
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
"@tailwindcss/vite": "4.1.11",
"@tsconfig/bun": "1.0.9",
"@tsconfig/node22": "22.0.2",
"@types/bun": "1.3.3",
"@types/bun": "1.3.4",
"@types/luxon": "3.7.1",
"@types/node": "22.13.9",
"@typescript/native-preview": "7.0.0-dev.20251207.1",
@@ -1085,11 +1101,11 @@
"@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="],
"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@13.2.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw=="],
"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@14.0.0", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw=="],
"@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="],
"@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.1.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-VztDkhM0ketQYSh5Im3IcKWFZl7VIrrsCaHbDINkdYeiiAsJzjhS2xRFCSJgfN6VOcsoW4laMtsmf3HcNqIimg=="],
"@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@17.0.0", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw=="],
"@octokit/plugin-retry": ["@octokit/plugin-retry@3.0.9", "", { "dependencies": { "@octokit/types": "^6.0.3", "bottleneck": "^2.15.3" } }, "sha512-r+fArdP5+TG6l1Rv/C9hVoty6tldw6cE2pRHNGmFPdyfrc696R6JjrQ3d7HdVqGwuzfyrcaLAKD7K8TX8aehUQ=="],
@@ -1097,7 +1113,7 @@
"@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="],
"@octokit/rest": ["@octokit/rest@22.0.0", "", { "dependencies": { "@octokit/core": "^7.0.2", "@octokit/plugin-paginate-rest": "^13.0.1", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^16.0.0" } }, "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA=="],
"@octokit/rest": ["@octokit/rest@22.0.1", "", { "dependencies": { "@octokit/core": "^7.0.6", "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^17.0.0" } }, "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw=="],
"@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
@@ -1139,27 +1155,27 @@
"@opencode-ai/web": ["@opencode-ai/web@workspace:packages/web"],
"@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.2.8", "", { "dependencies": { "@openrouter/sdk": "^0.1.8" }, "peerDependencies": { "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" } }, "sha512-pQT8AzZBKg9f4bkt4doF486ZlhK0XjKkevrLkiqYgfh1Jplovieu28nK4Y+xy3sF18/mxjqh9/2y6jh01qzLrA=="],
"@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.5.2", "", { "dependencies": { "@openrouter/sdk": "^0.1.27" }, "peerDependencies": { "@toon-format/toon": "^2.0.0", "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" }, "optionalPeers": ["@toon-format/toon"] }, "sha512-3Th0vmJ9pjnwcPc2H1f59Mb0LFvwaREZAScfOQIpUxAHjZ7ZawVKDP27qgsteZPmMYqccNMy4r4Y3kgUnNcKAg=="],
"@openrouter/sdk": ["@openrouter/sdk@0.1.27", "", { "dependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-RH//L10bSmc81q25zAZudiI4kNkLgxF2E+WU42vghp3N6TEvZ6F0jK7uT3tOxkEn91gzmMw9YVmDENy7SJsajQ=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@opentui/core": ["@opentui/core@0.1.59", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.59", "@opentui/core-darwin-x64": "0.1.59", "@opentui/core-linux-arm64": "0.1.59", "@opentui/core-linux-x64": "0.1.59", "@opentui/core-win32-arm64": "0.1.59", "@opentui/core-win32-x64": "0.1.59", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-vOtEvIulvfCOWJy0EfKAPzAMtDTmC+S0boGYrefjLqIp7tp+bbVJuXVh/8bz6GQTPmbQC6MIk6bv/ij3pdUVkA=="],
"@opentui/core": ["@opentui/core@0.1.61", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.61", "@opentui/core-darwin-x64": "0.1.61", "@opentui/core-linux-arm64": "0.1.61", "@opentui/core-linux-x64": "0.1.61", "@opentui/core-win32-arm64": "0.1.61", "@opentui/core-win32-x64": "0.1.61", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-WrVbdki0tnsgmWCB3Iix6n8eXGXUheTqr/tcnBN7gLA/TqT9udcX+DW3/qRdgtTNJS1sVBVeuwSTYU3eqDSUJQ=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.59", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JQWq7W/wkmTujW/2/Ig0d7S+701rul87LSW5txQ+GM4o6EWchqHrELwo6jcZpczsyOEj4fXxI2O8l4OVYyMa9A=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.61", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zX7EK8PwBJFwsZ2tDnScLFD0GbBfHE7sqpzGDXP2luMnBZJ0OOO95a4Hzu9dQWqxEr4RgfGDT8uIRhgimKNQEg=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.59", "", { "os": "darwin", "cpu": "x64" }, "sha512-GzafWzMP9Lt4AzUwQAk02lxgITgfvvo33OLCN265LtQBO8w23u0eB7Fjs9W+nmtcvzXtB9q6HuA0PvP9a3OioA=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.61", "", { "os": "darwin", "cpu": "x64" }, "sha512-xfvl8EnyN0XwlYpyTskVhHOpbMdgt++ntcuTh7M7IEFYQGzJux19NBwJl17mOxB1McG+KTa7kNx5/zu0VB9eVQ=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.59", "", { "os": "linux", "cpu": "arm64" }, "sha512-QMMFg3dr2v43g3jICgzNFYQyU4YL3zHw733MVJINC+c882+qiQ8l0utTFoVEx/iRYeBzFvMVrKZ4f6G8fFrtrw=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.61", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ghg7j4H6bz7CLxhgDcWx3Ann3AblDIjKFUu4vFrVysuiwfmDHwdKm8awLj8tnmC/0y8juG4ODUQbR0BXBIkE+Q=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.59", "", { "os": "linux", "cpu": "x64" }, "sha512-XSblVjhW/7+Xs+/o+xJHwHn74nw9j69mnPAFiNdH0d8ilP4j09nUYHZOvQ89sHZaMYeSIuJEciHnh/qP0n5QXQ=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.61", "", { "os": "linux", "cpu": "x64" }, "sha512-Xs9czMEOuHtnX4tigC4fNb1MU7+Gaohbk+k4teraulIgYZf19nRHIKNvXissDjOfqvOGygCkxMQIG0zeUFsPEA=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.59", "", { "os": "win32", "cpu": "arm64" }, "sha512-GU5pPUcTpYmeOUYKpQgAPx0VKBMrfz5LNZlK8gm/jlo2CbLrIW7QLMWCoxncVZmNYqYJeG+KUZkmXYe5KLPXCQ=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.61", "", { "os": "win32", "cpu": "arm64" }, "sha512-2CYAEPqArJqE36LkSRAs0csRzWwVJY99S/7EuY7abBm58BIL6RUw5kSw1r75oDo4I3W6v6WwW0u8B5Ik98m0Kg=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.59", "", { "os": "win32", "cpu": "x64" }, "sha512-InIawEI0TOG8MBBpavMq31WBRBjJ6XPuqFcsDnjqDJcXrRbNkguRW3PNXEwlyaU4tXHfYOsdlPpRtsysS8X/bQ=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.61", "", { "os": "win32", "cpu": "x64" }, "sha512-c0OK5YwcKH51Qj6wPmwTZP3X8LHA0I0dKz4fO4mOh4f+OqgU9WOG4hpbf7lv0bVlHoTvgP4zDUsjmtIVA8l6Lg=="],
"@opentui/solid": ["@opentui/solid@0.1.59", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.59", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-O88a/+YHkHlDC4IxbrfWD2ZWlpkpu4oXC2FCLTK8taaUAnLYoybxdrMpv1+o8u8KoWXOoZmEHdntdO9O4abHnQ=="],
"@opentui/solid": ["@opentui/solid@0.1.61", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.61", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-CiZHduIoeABoS0ev+eGeHA/LiRl/SpdL6io4jrwiwFi/rToKtc7YgJ8MWxIgeHScHUbpQnIr1v7jzsGI3DAYvw=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
@@ -1275,7 +1291,7 @@
"@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.6.0-beta.10", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-2rdd1Q1xJbB0Z4oUbm0Ybrr2gLFEdvNetZLadJboZSFL7Q4gFujdQZfXfV3vB9X+esjt++v0nzb3mioW25BOTA=="],
"@pierre/diffs": ["@pierre/diffs@1.0.0-beta.3", "", { "dependencies": { "@shikijs/core": "3.19.0", "@shikijs/engine-javascript": "3.19.0", "@shikijs/transformers": "3.19.0", "diff": "8.0.2", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "3.19.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-W3dFWdFOBZ9OskGSOgN16aci8dsUyAavCxz3ZvbbVLTb2qRzMZ7H90qdfON13/N2l1HTyh84lkrCs1/sDvnRjQ=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
@@ -1543,6 +1559,10 @@
"@solid-primitives/active-element": ["@solid-primitives/active-element@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9t5K4aR2naVDj950XU8OjnLgOg94a8k5wr6JNOPK+N5ESLsJDq42c1ZP8UKpewi1R+wplMMxiM6OPKRzbxJY7A=="],
"@solid-primitives/audio": ["@solid-primitives/audio@1.4.2", "", { "dependencies": { "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-UMD3ORQfI5Ky8yuKPxidDiEazsjv/dsoiKK5yZxLnsgaeNR1Aym3/77h/qT1jBYeXUgj4DX6t7NMpFUSVr14OQ=="],
"@solid-primitives/bounds": ["@solid-primitives/bounds@0.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/resize-observer": "^2.1.3", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-UbiyKMdSPmtijcEDnYLQL3zzaejpwWDAJJ4Gt5P0hgVs6A72piov0GyNw7V2SroH7NZFwxlYS22YmOr8A5xc1Q=="],
"@solid-primitives/event-bus": ["@solid-primitives/event-bus@1.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-l+n10/51neGcMaP3ypYt21bXfoeWh8IaC8k7fYuY3ww2a8S1Zv2N2a7FF5Qn+waTu86l0V8/nRHjkyqVIZBYwA=="],
"@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg=="],
@@ -1655,10 +1675,18 @@
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew=="],
"@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A=="],
"@tauri-apps/plugin-process": ["@tauri-apps/plugin-process@2.3.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA=="],
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Xod+pRcFxmOWFWEnqH5yZcA7qwAMuaaDkMR1Sply+F8VfBj++CGnj2xf5UoialmjZ2Cvd8qrvSCbU+7GgNVsKQ=="],
"@tauri-apps/plugin-store": ["@tauri-apps/plugin-store@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ckGSEzZ5Ii4Hf2D5x25Oqnm2Zf9MfDWAzR+volY0z/OOBz6aucPKEY0F649JvQ0Vupku6UJo7ugpGRDOFOunkA=="],
"@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.9.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg=="],
"@tauri-apps/plugin-window-state": ["@tauri-apps/plugin-window-state@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw=="],
"@thisbeyond/solid-dnd": ["@thisbeyond/solid-dnd@0.7.5", "", { "peerDependencies": { "solid-js": "^1.5" } }, "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw=="],
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
@@ -1681,7 +1709,7 @@
"@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="],
"@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="],
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
@@ -1987,7 +2015,7 @@
"bun-pty": ["bun-pty@0.4.2", "", {}, "sha512-sHImDz6pJDsHAroYpC9ouKVgOyqZ7FP3N+stX5IdMddHve3rf9LIZBDomQcXrACQ7sQDNuwZQHG8BKR7w8krkQ=="],
"bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
"bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="],
@@ -4057,9 +4085,9 @@
"@octokit/oauth-methods/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
"@octokit/plugin-retry/@octokit/types": ["@octokit/types@6.41.0", "", { "dependencies": { "@octokit/openapi-types": "^12.11.0" } }, "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg=="],
@@ -4071,6 +4099,8 @@
"@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
"@opencode-ai/function/@octokit/rest": ["@octokit/rest@22.0.0", "", { "dependencies": { "@octokit/core": "^7.0.2", "@octokit/plugin-paginate-rest": "^13.0.1", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^16.0.0" } }, "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA=="],
"@opencode-ai/tauri/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
"@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.4.2", "", { "dependencies": { "@shikijs/core": "3.4.2", "@shikijs/types": "3.4.2" } }, "sha512-I5baLVi/ynLEOZoWSAMlACHNnG+yw5HDmse0oe+GW6U1u+ULdEB3UHiVWaHoJSSONV7tlcVxuaMy74sREDkSvg=="],
@@ -4089,11 +4119,13 @@
"@parcel/watcher/node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
"@pierre/precision-diffs/@shikijs/core": ["@shikijs/core@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg=="],
"@pierre/diffs/@shikijs/core": ["@shikijs/core@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA=="],
"@pierre/precision-diffs/@shikijs/transformers": ["@shikijs/transformers@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/types": "3.15.0" } }, "sha512-Hmwip5ovvSkg+Kc41JTvSHHVfCYF+C8Cp1omb5AJj4Xvd+y9IXz2rKJwmFRGsuN0vpHxywcXJ1+Y4B9S7EG1/A=="],
"@pierre/diffs/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-ZfWJNm2VMhKkQIKT9qXbs76RRcT0SF/CAvEz0+RkpUDAoDaCx0uFdCGzSRiD9gSlhm6AHkjdieOBJMaO2eC1rQ=="],
"@pierre/precision-diffs/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
"@pierre/diffs/@shikijs/transformers": ["@shikijs/transformers@3.19.0", "", { "dependencies": { "@shikijs/core": "3.19.0", "@shikijs/types": "3.19.0" } }, "sha512-e6vwrsyw+wx4OkcrDbL+FVCxwx8jgKiCoXzakVur++mIWVcgpzIi8vxf4/b4dVTYrV/nUx5RjinMf4tq8YV8Fw=="],
"@pierre/diffs/shiki": ["shiki@3.19.0", "", { "dependencies": { "@shikijs/core": "3.19.0", "@shikijs/engine-javascript": "3.19.0", "@shikijs/engine-oniguruma": "3.19.0", "@shikijs/langs": "3.19.0", "@shikijs/themes": "3.19.0", "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-77VJr3OR/VUZzPiStyRhADmO2jApMM0V2b1qf0RpfWya8Zr1PeZev5AEpPGAAKWdiYUtcZGBE4F5QvJml1PvWA=="],
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
@@ -4261,6 +4293,8 @@
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.27", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bpYruxVLhrTbVH6CCq48zMJNeHu6FmHtEedl9FXckEgcIEAi036idFhJlcRwC1jNCwlacbzb8dPD7OAH1EKJaQ=="],
"opencode/@octokit/rest": ["@octokit/rest@22.0.0", "", { "dependencies": { "@octokit/core": "^7.0.2", "@octokit/plugin-paginate-rest": "^13.0.1", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^16.0.0" } }, "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA=="],
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
"opencontrol/@tsconfig/bun": ["@tsconfig/bun@1.0.7", "", {}, "sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA=="],
@@ -4615,9 +4649,9 @@
"@octokit/oauth-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
"@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
"@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
"@octokit/plugin-retry/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@12.11.0", "", {}, "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="],
@@ -4625,6 +4659,10 @@
"@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
"@opencode-ai/function/@octokit/rest/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@13.2.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw=="],
"@opencode-ai/function/@octokit/rest/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.1.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-VztDkhM0ketQYSh5Im3IcKWFZl7VIrrsCaHbDINkdYeiiAsJzjhS2xRFCSJgfN6VOcsoW4laMtsmf3HcNqIimg=="],
"@opencode-ai/web/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.4.2", "", { "dependencies": { "@shikijs/types": "3.4.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AG8vnSi1W2pbgR2B911EfGqtLE9c4hQBYkv/x7Z+Kt0VxhgQKcW7UNDVYsu9YxwV6u+OJrvdJrMq6DNWoBjihQ=="],
"@opencode-ai/web/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.4.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zHC1l7L+eQlDXLnxvM9R91Efh2V4+rN3oMVS2swCBssbj2U/FBwybD1eeLaq8yl/iwT+zih8iUbTBCgGZOYlVg=="],
@@ -4643,19 +4681,19 @@
"@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@pierre/precision-diffs/@shikijs/core/@shikijs/types": ["@shikijs/types@3.15.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw=="],
"@pierre/diffs/@shikijs/core/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="],
"@pierre/precision-diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.15.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw=="],
"@pierre/diffs/@shikijs/engine-javascript/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="],
"@pierre/precision-diffs/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg=="],
"@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="],
"@pierre/precision-diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA=="],
"@pierre/diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg=="],
"@pierre/precision-diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0" } }, "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A=="],
"@pierre/diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0" } }, "sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg=="],
"@pierre/precision-diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0" } }, "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ=="],
"@pierre/diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0" } }, "sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A=="],
"@pierre/precision-diffs/shiki/@shikijs/types": ["@shikijs/types@3.15.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw=="],
"@pierre/diffs/shiki/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="],
"@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
@@ -4839,6 +4877,10 @@
"opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"opencode/@octokit/rest/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@13.2.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw=="],
"opencode/@octokit/rest/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.1.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-VztDkhM0ketQYSh5Im3IcKWFZl7VIrrsCaHbDINkdYeiiAsJzjhS2xRFCSJgfN6VOcsoW4laMtsmf3HcNqIimg=="],
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
@@ -4979,6 +5021,10 @@
"@modelcontextprotocol/sdk/raw-body/http-errors/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
"@opencode-ai/function/@octokit/rest/@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
"@opencode-ai/function/@octokit/rest/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
"@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
@@ -4999,6 +5045,10 @@
"js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"opencode/@octokit/rest/@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
"opencode/@octokit/rest/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
"opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
@@ -5091,10 +5141,18 @@
"@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"@opencode-ai/function/@octokit/rest/@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
"@opencode-ai/function/@octokit/rest/@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="],
"opencode/@octokit/rest/@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
"opencode/@octokit/rest/@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
"opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"opencontrol/@modelcontextprotocol/sdk/express/body-parser/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],

6
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1764947035,
"narHash": "sha256-EYHSjVM4Ox4lvCXUMiKKs2vETUSL5mx+J2FfutM7T9w=",
"lastModified": 1766025857,
"narHash": "sha256-Lav5jJazCW4mdg1iHcROpuXqmM94BWJvabLFWaJVJp0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a672be65651c80d3f592a89b3945466584a22069",
"rev": "def3da69945bbe338c373fddad5a1bb49cf199ce",
"type": "github"
},
"original": {

View File

@@ -6,7 +6,7 @@ Mention `/opencode` in your comment, and opencode will execute tasks within your
## Features
#### Explain an issues
#### Explain an issue
Leave the following comment on a GitHub issue. `opencode` will read the entire thread, including all comments, and reply with a clear explanation.
@@ -14,7 +14,7 @@ Leave the following comment on a GitHub issue. `opencode` will read the entire t
/opencode explain this issue
```
#### Fix an issues
#### Fix an issue
Leave the following comment on a GitHub issue. opencode will create a new branch, implement the changes, and open a PR with the changes.

View File

@@ -17,13 +17,45 @@ inputs:
description: "Custom prompt to override the default prompt"
required: false
use_github_token:
description: "Use GITHUB_TOKEN directly instead of OpenCode App token exchange. When true, skips OIDC and uses the GITHUB_TOKEN env var."
required: false
default: "false"
mentions:
description: "Comma-separated list of trigger phrases (case-insensitive). Defaults to '/opencode,/oc'"
required: false
oidc_base_url:
description: "Base URL for OIDC token exchange API. Only required when running a custom GitHub App install. Defaults to https://api.opencode.ai"
required: false
runs:
using: "composite"
steps:
- name: Get opencode version
id: version
shell: bash
run: |
VERSION=$(curl -sf https://api.github.com/repos/sst/opencode/releases/latest | grep -o '"tag_name": *"[^"]*"' | cut -d'"' -f4)
echo "version=${VERSION:-latest}" >> $GITHUB_OUTPUT
- name: Cache opencode
id: cache
uses: actions/cache@v4
with:
path: ~/.opencode/bin
key: opencode-${{ runner.os }}-${{ runner.arch }}-${{ steps.version.outputs.version }}
- name: Install opencode
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
run: curl -fsSL https://opencode.ai/install | bash
- name: Add opencode to PATH
shell: bash
run: echo "$HOME/.opencode/bin" >> $GITHUB_PATH
- name: Run opencode
shell: bash
id: run_opencode
@@ -32,3 +64,6 @@ runs:
MODEL: ${{ inputs.model }}
SHARE: ${{ inputs.share }}
PROMPT: ${{ inputs.prompt }}
USE_GITHUB_TOKEN: ${{ inputs.use_github_token }}
MENTIONS: ${{ inputs.mentions }}
OIDC_BASE_URL: ${{ inputs.oidc_base_url }}

View File

@@ -13,7 +13,7 @@
"@actions/core": "1.11.1",
"@actions/github": "6.0.1",
"@octokit/graphql": "9.0.1",
"@octokit/rest": "22.0.0",
"@octokit/rest": "catalog:",
"@opencode-ai/sdk": "workspace:*"
}
}

View File

@@ -102,6 +102,7 @@ const ZEN_MODELS = [
new sst.Secret("ZEN_MODELS2"),
new sst.Secret("ZEN_MODELS3"),
new sst.Secret("ZEN_MODELS4"),
new sst.Secret("ZEN_MODELS5"),
]
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {

View File

@@ -1,10 +1,10 @@
import { SECRET } from "./secret"
import { domain } from "./stage"
import { domain, shortDomain } from "./stage"
const storage = new sst.cloudflare.Bucket("EnterpriseStorage")
const enterprise = new sst.cloudflare.x.SolidStart("Enterprise", {
domain: "enterprise." + domain,
const teams = new sst.cloudflare.x.SolidStart("Teams", {
domain: shortDomain,
path: "packages/enterprise",
buildCommand: "bun run build:cloudflare",
environment: {

View File

@@ -11,3 +11,9 @@ new cloudflare.RegionalHostname("RegionalHostname", {
regionKey: "us",
zoneId: zoneID,
})
export const shortDomain = (() => {
if ($app.stage === "production") return "opncd.ai"
if ($app.stage === "dev") return "dev.opncd.ai"
return `${$app.stage}.dev.opncd.ai`
})()

17
install
View File

@@ -240,22 +240,23 @@ download_with_progress() {
download_and_install() {
print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version"
mkdir -p opencodetmp && cd opencodetmp
local tmp_dir="${TMPDIR:-/tmp}/opencode_install_$$"
mkdir -p "$tmp_dir"
if [[ "$os" == "windows" ]] || ! download_with_progress "$url" "$filename"; then
# Fallback to standard curl on Windows or if custom progress fails
curl -# -L -o "$filename" "$url"
if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
# Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails
curl -# -L -o "$tmp_dir/$filename" "$url"
fi
if [ "$os" = "linux" ]; then
tar -xzf "$filename"
tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
else
unzip -q "$filename"
unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
fi
mv opencode "$INSTALL_DIR"
mv "$tmp_dir/opencode" "$INSTALL_DIR"
chmod 755 "${INSTALL_DIR}/opencode"
cd .. && rm -rf opencodetmp
rm -rf "$tmp_dir"
}
check_version

View File

@@ -1,3 +1,3 @@
{
"nodeModules": "sha256-IzF5XDY09Z1p/8jgYIHhE/jpKPub15KKUpV+a/aKpuc="
"nodeModules": "sha256-U56rAkhKAhZKDEec05Mxj359wjpT03od26tosCjrj9A="
}

View File

@@ -1,4 +1,4 @@
{ lib, stdenvNoCC, bun, fzf, ripgrep, makeBinaryWrapper }:
{ lib, stdenvNoCC, bun, ripgrep, makeBinaryWrapper }:
args:
let
scripts = args.scripts;
@@ -97,7 +97,7 @@ stdenvNoCC.mkDerivation (finalAttrs: {
makeWrapper ${bun}/bin/bun $out/bin/opencode \
--add-flags "run" \
--add-flags "$out/lib/opencode/dist/src/index.js" \
--prefix PATH : ${lib.makeBinPath [ fzf ripgrep ]} \
--prefix PATH : ${lib.makeBinPath [ ripgrep ]} \
--argv0 opencode
runHook postInstall

View File

@@ -4,7 +4,7 @@
"description": "AI-powered development tool",
"private": true,
"type": "module",
"packageManager": "bun@1.3.3",
"packageManager": "bun@1.3.5",
"scripts": {
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
"typecheck": "bun turbo typecheck",
@@ -20,7 +20,8 @@
"packages/slack"
],
"catalog": {
"@types/bun": "1.3.3",
"@types/bun": "1.3.4",
"@octokit/rest": "22.0.0",
"@hono/zod-validator": "0.4.2",
"ulid": "3.0.1",
"@kobalte/core": "0.13.11",
@@ -30,7 +31,8 @@
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/precision-diffs": "0.6.0-beta.10",
"@pierre/diffs": "1.0.0-beta.3",
"@solid-primitives/storage": "4.3.3",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
"ai": "5.0.97",
@@ -62,6 +64,7 @@
},
"dependencies": {
"@aws-sdk/client-s3": "3.933.0",
"@octokit/rest": "22.0.1",
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"typescript": "catalog:"

View File

@@ -49,7 +49,7 @@ use data attributes to represent different states of the component
}
```
this will allow jsx to control the syling
this will allow jsx to control the styling
avoid selectors that just target an element type like `> span` you should assign
it a slot name. it's ok to do this sometimes where it makes sense semantically

View File

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

View File

@@ -3,6 +3,7 @@ import { Router } from "@solidjs/router"
import { FileRoutes } from "@solidjs/start/router"
import { Suspense } from "solid-js"
import { Favicon } from "@opencode-ai/ui/favicon"
import { Font } from "@opencode-ai/ui/font"
import "@ibm/plex/css/ibm-plex.css"
import "./app.css"
@@ -13,8 +14,9 @@ export default function App() {
root={(props) => (
<MetaProvider>
<Title>opencode</Title>
<Meta name="description" content="OpenCode - The AI coding agent built for the terminal." />
<Meta name="description" content="OpenCode - The open source coding agent." />
<Favicon />
<Font />
<Suspense>{props.children}</Suspense>
</MetaProvider>
)}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -25,11 +25,8 @@ export function EmailSignup() {
const submission = useSubmission(emailSignup)
return (
<section data-component="email">
<div data-slot="dock">
<img src={dock} alt="" />
</div>
<div data-slot="section-title">
<h3>OpenCode will be available on desktop soon</h3>
<h3>Be the first to know when we release new products</h3>
<p>Join the waitlist for early access.</p>
</div>
<form data-slot="form" action={emailSignup} method="post">

View File

@@ -34,7 +34,7 @@ const fetchSvgContent = async (svgPath: string): Promise<string> => {
}
}
export function Header(props: { zen?: boolean }) {
export function Header(props: { zen?: boolean; hideGetStarted?: boolean }) {
const navigate = useNavigate()
const githubData = createAsync(() => github())
const starCount = createMemo(() =>
@@ -119,8 +119,8 @@ export function Header(props: { zen?: boolean }) {
<section data-component="top">
<div onContextMenu={handleLogoContextMenu}>
<A href="/">
<img data-slot="logo light" src={logoLight} alt="opencode logo light" />
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
<img data-slot="logo light" src={logoLight} alt="opencode logo light" width="189" height="34" />
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" width="189" height="34" />
</A>
</div>
@@ -169,6 +169,25 @@ export function Header(props: { zen?: boolean }) {
</Match>
</Switch>
</li>
<Show when={!props.hideGetStarted}>
{" "}
<li>
{" "}
<A href="/download" data-slot="cta-button">
{" "}
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
{" "}
<path
d="M12.1875 9.75L9.00001 12.9375L5.8125 9.75M9.00001 2.0625L9 12.375M14.4375 15.9375H3.5625"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="square"
/>{" "}
</svg>{" "}
Free{" "}
</A>{" "}
</li>
</Show>
</ul>
</nav>
<nav data-component="nav-mobile">
@@ -243,6 +262,13 @@ export function Header(props: { zen?: boolean }) {
</Match>
</Switch>
</li>
<Show when={!props.hideGetStarted}>
<li>
<A href="/download" data-slot="cta-button">
Get started for free
</A>
</li>
</Show>
</ul>
</nav>
</div>

View File

@@ -9,6 +9,12 @@ export function Legal() {
<span>
<A href="/brand">Brand</A>
</span>
<span>
<A href="/legal/privacy-policy">Privacy</A>
</span>
<span>
<A href="/legal/terms-of-service">Terms</A>
</span>
</div>
)
}

View File

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

View File

@@ -1,6 +1,8 @@
// @refresh reload
import { createHandler, StartServer } from "@solidjs/start/server"
const criticalCSS = `[data-component="top"]{min-height:80px;display:flex;align-items:center}`
export default createHandler(
() => (
<StartServer
@@ -11,6 +13,7 @@ export default createHandler(
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
<style>{criticalCSS}</style>
{assets}
</head>
<body>

View File

@@ -26,6 +26,7 @@ export const github = query(async () => {
release: {
name: release.name,
url: release.html_url,
tag_name: release.tag_name,
},
contributors: contributorCount,
}

View File

@@ -8,7 +8,8 @@
}
}
[data-page="enterprise"] {
[data-page="enterprise"],
[data-page="legal"] {
--color-background: hsl(0, 20%, 99%);
--color-background-weak: hsl(0, 8%, 97%);
--color-background-weak-hover: hsl(0, 8%, 94%);
@@ -84,7 +85,16 @@
ul {
display: flex;
justify-content: space-between;
align-items: center;
gap: 48px;
@media (max-width: 55rem) {
gap: 32px;
}
@media (max-width: 48rem) {
gap: 24px;
}
li {
display: inline-block;
a {
@@ -98,6 +108,25 @@
text-underline-offset: 2px;
text-decoration-thickness: 1px;
}
[data-slot="cta-button"] {
background: var(--color-background-strong);
color: var(--color-text-inverted);
padding: 8px 16px 8px 10px;
border-radius: 4px;
font-weight: 500;
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
@media (max-width: 55rem) {
display: none;
}
}
[data-slot="cta-button"]:hover {
background: var(--color-background-strong-hover);
text-decoration: none;
}
}
}
@@ -266,7 +295,7 @@
h1 {
font-size: 1.5rem;
font-weight: 500;
font-weight: 700;
color: var(--color-text-strong);
margin-bottom: 1rem;
}

View File

@@ -0,0 +1,37 @@
import { APIEvent } from "@solidjs/start"
import { DownloadPlatform } from "./types"
const assetNames: Record<string, string> = {
"darwin-aarch64-dmg": "opencode-desktop-darwin-aarch64.dmg",
"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-rpm": "opencode-desktop-linux-x86_64.rpm",
} satisfies Record<DownloadPlatform, string>
// Doing this on the server lets us preserve the original name for platforms we don't care to rename for
const downloadNames: Record<string, string> = {
"darwin-aarch64-dmg": "OpenCode Desktop.dmg",
"darwin-x64-dmg": "OpenCode Desktop.dmg",
"windows-x64-nsis": "OpenCode Desktop Installer.exe",
} satisfies { [K in DownloadPlatform]?: string }
export async function GET({ params: { platform } }: APIEvent) {
const assetName = assetNames[platform]
if (!assetName) return new Response("Not Found", { status: 404 })
const resp = await fetch(`https://github.com/sst/opencode/releases/latest/download/${assetName}`, {
cf: {
// in case gh releases has rate limits
cacheTtl: 60 * 60 * 24,
cacheEverything: true,
},
} as any)
const downloadName = downloadNames[platform]
const headers = new Headers(resp.headers)
if (downloadName) headers.set("content-disposition", `attachment; filename="${downloadName}"`)
return new Response(resp.body, { ...resp, headers })
}

View File

@@ -0,0 +1,751 @@
::selection {
background: var(--color-background-interactive);
color: var(--color-text-strong);
@media (prefers-color-scheme: dark) {
background: var(--color-background-interactive);
color: var(--color-text-inverted);
}
}
[data-page="download"] {
--color-background: hsl(0, 20%, 99%);
--color-background-weak: hsl(0, 8%, 97%);
--color-background-weak-hover: hsl(0, 8%, 94%);
--color-background-strong: hsl(0, 5%, 12%);
--color-background-strong-hover: hsl(0, 5%, 18%);
--color-background-interactive: hsl(62, 84%, 88%);
--color-background-interactive-weaker: hsl(64, 74%, 95%);
--color-text: hsl(0, 1%, 39%);
--color-text-weak: hsl(0, 1%, 60%);
--color-text-weaker: hsl(30, 2%, 81%);
--color-text-strong: hsl(0, 5%, 12%);
--color-text-inverted: hsl(0, 20%, 99%);
--color-text-success: hsl(119, 100%, 35%);
--color-border: hsl(30, 2%, 81%);
--color-border-weak: hsl(0, 1%, 85%);
--color-icon: hsl(0, 1%, 55%);
--color-success: hsl(142, 76%, 36%);
background: var(--color-background);
font-family: var(--font-mono);
color: var(--color-text);
padding-bottom: 5rem;
overflow-x: hidden;
@media (prefers-color-scheme: dark) {
--color-background: hsl(0, 9%, 7%);
--color-background-weak: hsl(0, 6%, 10%);
--color-background-weak-hover: hsl(0, 6%, 15%);
--color-background-strong: hsl(0, 15%, 94%);
--color-background-strong-hover: hsl(0, 15%, 97%);
--color-background-interactive: hsl(62, 100%, 90%);
--color-background-interactive-weaker: hsl(60, 20%, 8%);
--color-text: hsl(0, 4%, 71%);
--color-text-weak: hsl(0, 2%, 49%);
--color-text-weaker: hsl(0, 3%, 28%);
--color-text-strong: hsl(0, 15%, 94%);
--color-text-inverted: hsl(0, 9%, 7%);
--color-text-success: hsl(119, 60%, 72%);
--color-border: hsl(0, 3%, 28%);
--color-border-weak: hsl(0, 4%, 23%);
--color-icon: hsl(10, 3%, 43%);
--color-success: hsl(142, 76%, 46%);
}
/* Header and Footer styles - copied from enterprise */
[data-component="top"] {
padding: 24px 5rem;
height: 80px;
position: sticky;
top: 0;
display: flex;
justify-content: space-between;
align-items: center;
background: var(--color-background);
border-bottom: 1px solid var(--color-border-weak);
z-index: 10;
@media (max-width: 60rem) {
padding: 24px 1.5rem;
}
img {
height: 34px;
width: auto;
}
[data-component="nav-desktop"] {
ul {
display: flex;
justify-content: space-between;
align-items: center;
gap: 48px;
@media (max-width: 55rem) {
gap: 32px;
}
@media (max-width: 48rem) {
gap: 24px;
}
li {
display: inline-block;
a {
text-decoration: none;
span {
color: var(--color-text-weak);
}
}
a:hover {
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
}
[data-slot="cta-button"] {
background: var(--color-background-strong);
color: var(--color-text-inverted);
padding: 8px 16px;
border-radius: 4px;
font-weight: 500;
text-decoration: none;
@media (max-width: 55rem) {
display: none;
}
}
[data-slot="cta-button"]:hover {
background: var(--color-background-strong-hover);
text-decoration: none;
}
}
}
@media (max-width: 40rem) {
display: none;
}
}
[data-component="nav-mobile"] {
button > svg {
color: var(--color-icon);
}
}
[data-component="nav-mobile-toggle"] {
border: none;
background: none;
outline: none;
height: 40px;
width: 40px;
cursor: pointer;
margin-right: -8px;
}
[data-component="nav-mobile-toggle"]:hover {
background: var(--color-background-weak);
}
[data-component="nav-mobile"] {
display: none;
@media (max-width: 40rem) {
display: block;
[data-component="nav-mobile-icon"] {
cursor: pointer;
height: 40px;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
}
[data-component="nav-mobile-menu-list"] {
position: fixed;
background: var(--color-background);
top: 80px;
left: 0;
right: 0;
height: 100vh;
ul {
list-style: none;
padding: 20px 0;
li {
a {
text-decoration: none;
padding: 20px;
display: block;
span {
color: var(--color-text-weak);
}
}
a:hover {
background: var(--color-background-weak);
}
}
}
}
}
}
[data-slot="logo dark"] {
display: none;
}
@media (prefers-color-scheme: dark) {
[data-slot="logo light"] {
display: none;
}
[data-slot="logo dark"] {
display: block;
}
}
}
[data-component="footer"] {
border-top: 1px solid var(--color-border-weak);
display: flex;
flex-direction: row;
@media (max-width: 65rem) {
border-bottom: 1px solid var(--color-border-weak);
}
[data-slot="cell"] {
flex: 1;
text-align: center;
a {
text-decoration: none;
padding: 2rem 0;
width: 100%;
display: block;
span {
color: var(--color-text-weak);
@media (max-width: 40rem) {
display: none;
}
}
}
a:hover {
background: var(--color-background-weak);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
}
}
[data-slot="cell"] + [data-slot="cell"] {
border-left: 1px solid var(--color-border-weak);
@media (max-width: 40rem) {
border-left: none;
}
}
@media (max-width: 25rem) {
flex-wrap: wrap;
[data-slot="cell"] {
flex: 1 0 100%;
border-left: none;
border-top: 1px solid var(--color-border-weak);
}
[data-slot="cell"]:nth-child(1) {
border-top: none;
}
}
}
[data-component="container"] {
max-width: 67.5rem;
margin: 0 auto;
border: 1px solid var(--color-border-weak);
border-top: none;
@media (max-width: 65rem) {
border: none;
}
}
[data-component="content"] {
padding: 6rem 5rem;
@media (max-width: 60rem) {
padding: 4rem 1.5rem;
}
}
[data-component="legal"] {
color: var(--color-text-weak);
text-align: center;
padding: 2rem 5rem;
display: flex;
gap: 32px;
justify-content: center;
@media (max-width: 60rem) {
padding: 2rem 1.5rem;
}
a {
color: var(--color-text-weak);
text-decoration: none;
}
a:hover {
color: var(--color-text);
text-decoration: underline;
}
}
/* Download Hero Section */
[data-component="download-hero"] {
display: grid;
grid-template-columns: 260px 1fr;
gap: 4rem;
padding-bottom: 2rem;
margin-bottom: 4rem;
@media (max-width: 50rem) {
grid-template-columns: 1fr;
gap: 1.5rem;
padding-bottom: 2rem;
margin-bottom: 2rem;
}
[data-component="hero-icon"] {
display: flex;
justify-content: flex-end;
align-items: center;
@media (max-width: 40rem) {
display: none;
}
[data-slot="icon-placeholder"] {
width: 120px;
height: 120px;
background: var(--color-background-weak);
border: 1px solid var(--color-border-weak);
border-radius: 24px;
@media (max-width: 50rem) {
width: 80px;
height: 80px;
}
}
img {
width: 120px;
height: 120px;
border-radius: 24px;
box-shadow:
0 1.467px 2.847px 0 rgba(0, 0, 0, 0.42),
0 0.779px 1.512px 0 rgba(0, 0, 0, 0.34),
0 0.324px 0.629px 0 rgba(0, 0, 0, 0.24);
@media (max-width: 50rem) {
width: 80px;
height: 80px;
border-radius: 16px;
}
}
@media (max-width: 50rem) {
justify-content: flex-start;
}
}
[data-component="hero-text"] {
display: flex;
flex-direction: column;
justify-content: center;
h1 {
font-size: 1.5rem;
font-weight: 700;
color: var(--color-text-strong);
margin-bottom: 4px;
@media (max-width: 40rem) {
margin-bottom: 1rem;
}
}
p {
color: var(--color-text);
margin-bottom: 12px;
@media (max-width: 40rem) {
margin-bottom: 2.5rem;
line-height: 1.6;
}
}
[data-component="download-button"] {
padding: 8px 20px 8px 16px;
background: var(--color-background-strong);
color: var(--color-text-inverted);
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 10px;
transition: all 0.2s ease;
text-decoration: none;
width: fit-content;
&:hover:not(:disabled) {
background: var(--color-background-strong-hover);
}
&:active {
transform: scale(0.98);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
}
}
/* Download Sections */
[data-component="download-section"] {
display: grid;
grid-template-columns: 260px 1fr;
gap: 4rem;
margin-bottom: 4rem;
@media (max-width: 50rem) {
grid-template-columns: 1fr;
gap: 1rem;
margin-bottom: 3rem;
}
&:last-child {
margin-bottom: 0;
}
[data-component="section-label"] {
font-weight: 500;
color: var(--color-text-strong);
padding-top: 1rem;
span {
color: var(--color-text-weaker);
}
@media (max-width: 50rem) {
padding-top: 0;
padding-bottom: 0.5rem;
}
}
[data-component="section-content"] {
display: flex;
flex-direction: column;
gap: 0;
}
}
/* CLI Rows */
button[data-component="cli-row"] {
display: flex;
align-items: center;
gap: 12px;
padding: 1rem 0.5rem 1rem 1.5rem;
margin: 0 -0.5rem 0 -1.5rem;
background: none;
border: none;
border-radius: 4px;
width: calc(100% + 2rem);
text-align: left;
cursor: pointer;
transition: background 0.15s ease;
&:hover {
background: var(--color-background-weak);
}
code {
font-family: var(--font-mono);
color: var(--color-text-weak);
strong {
color: var(--color-text-strong);
font-weight: 500;
}
}
[data-component="copy-status"] {
display: flex;
align-items: center;
opacity: 0;
transition: opacity 0.15s ease;
color: var(--color-icon);
svg {
width: 18px;
height: 18px;
}
[data-slot="copy"] {
display: block;
}
[data-slot="check"] {
display: none;
}
}
&:hover [data-component="copy-status"] {
opacity: 1;
}
&[data-copied] [data-component="copy-status"] {
opacity: 1;
[data-slot="copy"] {
display: none;
}
[data-slot="check"] {
display: block;
}
}
}
/* Download Rows */
[data-component="download-row"] {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 0.5rem 0.75rem 1.5rem;
margin: 0 -0.5rem 0 -1.5rem;
border-radius: 4px;
transition: background 0.15s ease;
&:hover {
background: var(--color-background-weak);
}
[data-component="download-info"] {
display: flex;
align-items: center;
gap: 0.75rem;
[data-slot="icon"] {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-icon);
svg {
width: 20px;
height: 20px;
}
img {
width: 20px;
height: 20px;
}
}
span {
color: var(--color-text);
}
}
[data-component="action-button"] {
padding: 6px 16px;
background: var(--color-background);
color: var(--color-text);
border: 1px solid var(--color-border);
border-radius: 4px;
font-weight: 500;
cursor: pointer;
text-decoration: none;
transition: all 0.2s ease;
&:hover {
background: var(--color-background-weak);
border-color: var(--color-border);
text-decoration: none;
}
&:active {
transform: scale(0.98);
}
}
}
a {
color: var(--color-text-strong);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
&:hover {
text-decoration-thickness: 2px;
}
}
/* Narrow screen font sizes */
@media (max-width: 40rem) {
[data-component="download-section"] {
[data-component="section-label"] {
font-size: 14px;
}
}
button[data-component="cli-row"] {
margin: 0;
padding: 1rem 0;
width: 100%;
overflow: hidden;
code {
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
max-width: calc(100vw - 80px);
}
[data-component="copy-status"] {
opacity: 1 !important;
flex-shrink: 0;
}
}
[data-component="download-row"] {
margin: 0;
padding: 0.75rem 0;
[data-component="download-info"] span {
font-size: 14px;
}
[data-component="action-button"] {
font-size: 14px;
padding-left: 8px;
padding-right: 8px;
}
}
}
@media (max-width: 22.5rem) {
[data-slot="hide-narrow"] {
display: none;
}
}
/* FAQ Section */
[data-component="faq"] {
border-top: 1px solid var(--color-border-weak);
padding: 4rem 5rem;
margin-top: 4rem;
@media (max-width: 60rem) {
padding: 3rem 1.5rem;
margin-top: 3rem;
}
[data-slot="section-title"] {
margin-bottom: 24px;
h3 {
font-size: 16px;
font-weight: 700;
color: var(--color-text-strong);
margin-bottom: 12px;
}
}
ul {
padding: 0;
li {
list-style: none;
margin-bottom: 24px;
line-height: 200%;
}
}
[data-slot="faq-question"] {
display: flex;
gap: 16px;
margin-bottom: 8px;
color: var(--color-text-strong);
font-weight: 500;
cursor: pointer;
background: none;
border: none;
padding: 0;
align-items: start;
min-height: 24px;
svg {
margin-top: 2px;
}
[data-slot="faq-icon-plus"] {
flex-shrink: 0;
color: var(--color-text-weak);
margin-top: 2px;
[data-closed] & {
display: block;
}
[data-expanded] & {
display: none;
}
}
[data-slot="faq-icon-minus"] {
flex-shrink: 0;
color: var(--color-text-weak);
margin-top: 2px;
[data-closed] & {
display: none;
}
[data-expanded] & {
display: block;
}
}
[data-slot="faq-question-text"] {
flex-grow: 1;
text-align: left;
}
}
[data-slot="faq-answer"] {
margin-left: 40px;
margin-bottom: 32px;
line-height: 200%;
}
}
}

View File

@@ -0,0 +1,464 @@
import "./index.css"
import { Title, Meta, Link } from "@solidjs/meta"
import { A, createAsync, query } from "@solidjs/router"
import { Header } from "~/component/header"
import { Footer } from "~/component/footer"
import { IconCopy, IconCheck } from "~/component/icon"
import { Faq } from "~/component/faq"
import desktopAppIcon from "../../asset/lander/opencode-desktop-icon.png"
import { Legal } from "~/component/legal"
import { config } from "~/config"
import { createSignal, onMount, Show, JSX } from "solid-js"
import { DownloadPlatform } from "./types"
type OS = "macOS" | "Windows" | "Linux" | null
function detectOS(): OS {
if (typeof navigator === "undefined") return null
const platform = navigator.platform.toLowerCase()
const userAgent = navigator.userAgent.toLowerCase()
if (platform.includes("mac") || userAgent.includes("mac")) return "macOS"
if (platform.includes("win") || userAgent.includes("win")) return "Windows"
if (platform.includes("linux") || userAgent.includes("linux")) return "Linux"
return null
}
function getDownloadPlatform(os: OS): DownloadPlatform {
switch (os) {
case "macOS":
return "darwin-aarch64-dmg"
case "Windows":
return "windows-x64-nsis"
case "Linux":
return "linux-x64-deb"
default:
return "darwin-aarch64-dmg"
}
}
function getDownloadHref(platform: DownloadPlatform) {
return `/download/${platform}`
}
function IconDownload(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="square"
/>
</svg>
)
}
function CopyStatus() {
return (
<span data-component="copy-status">
<IconCopy data-slot="copy" />
<IconCheck data-slot="check" />
</span>
)
}
export default function Download() {
const [detectedOS, setDetectedOS] = createSignal<OS>(null)
onMount(() => {
setDetectedOS(detectOS())
})
const handleCopyClick = (command: string) => (event: Event) => {
const button = event.currentTarget as HTMLButtonElement
navigator.clipboard.writeText(command)
button.setAttribute("data-copied", "")
setTimeout(() => {
button.removeAttribute("data-copied")
}, 1500)
}
return (
<main data-page="download">
<Title>OpenCode | Download</Title>
<Link rel="canonical" href={`${config.baseUrl}/download`} />
<Meta name="description" content="Download OpenCode for macOS, Windows, and Linux" />
<div data-component="container">
<Header hideGetStarted />
<div data-component="content">
<section data-component="download-hero">
<div data-component="hero-icon">
<img src={desktopAppIcon} alt="OpenCode Desktop" />
</div>
<div data-component="hero-text">
<h1>Download OpenCode</h1>
<p>Available in Beta for macOS, Windows, and Linux</p>
<Show when={detectedOS()}>
<a href={getDownloadHref(getDownloadPlatform(detectedOS()))} data-component="download-button">
<IconDownload />
Download for {detectedOS()}
</a>
</Show>
</div>
</section>
<section data-component="download-section">
<div data-component="section-label">
<span>[1]</span> OpenCode Terminal
</div>
<div data-component="section-content">
<button
data-component="cli-row"
onClick={handleCopyClick("curl -fsSL https://opencode.ai/install | bash")}
>
<code>
curl -fsSL https://<strong>opencode.ai/install</strong> | bash
</code>
<CopyStatus />
</button>
<button data-component="cli-row" onClick={handleCopyClick("npm i -g opencode-ai")}>
<code>
npm i -g <strong>opencode-ai</strong>
</code>
<CopyStatus />
</button>
<button data-component="cli-row" onClick={handleCopyClick("bun add -g opencode-ai")}>
<code>
bun add -g <strong>opencode-ai</strong>
</code>
<CopyStatus />
</button>
<button data-component="cli-row" onClick={handleCopyClick("brew install opencode")}>
<code>
brew install <strong>opencode</strong>
</code>
<CopyStatus />
</button>
<button data-component="cli-row" onClick={handleCopyClick("paru -S opencode")}>
<code>
paru -S <strong>opencode</strong>
</code>
<CopyStatus />
</button>
</div>
</section>
<section data-component="download-section">
<div data-component="section-label">
<span>[2]</span> OpenCode Desktop (Beta)
</div>
<div data-component="section-content">
<button data-component="cli-row" onClick={handleCopyClick("brew install --cask opencode-desktop")}>
<code>
brew install --cask <strong>opencode-desktop</strong>
</code>
<CopyStatus />
</button>
<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="M20.0035 7.15814C19.3171 7.5784 18.7485 8.16594 18.351 8.86579C17.9534 9.56563 17.74 10.3549 17.7305 11.1597C17.7332 12.0655 18.0016 12.9506 18.5024 13.7054C19.0032 14.4602 19.7144 15.0515 20.5479 15.4061C20.2193 16.4664 19.7329 17.4712 19.1051 18.3868C18.2069 19.6798 17.2677 20.9727 15.8387 20.9727C14.4096 20.9727 14.0421 20.1425 12.3952 20.1425C10.7892 20.1425 10.2175 21 8.91088 21C7.60426 21 6.69246 19.8022 5.6444 18.3323C4.25999 16.2732 3.49913 13.8583 3.45312 11.3774C3.45312 7.29427 6.10722 5.13028 8.72032 5.13028C10.1086 5.13028 11.2656 6.04208 12.1366 6.04208C12.9669 6.04208 14.2599 5.07572 15.8387 5.07572C16.6504 5.05478 17.4548 5.23375 18.1811 5.59689C18.9074 5.96003 19.5332 6.49619 20.0035 7.15814ZM15.0901 3.34726C15.7861 2.52858 16.18 1.49589 16.2062 0.421702C16.2074 0.280092 16.1937 0.13875 16.1654 0C14.9699 0.116777 13.8644 0.686551 13.0757 1.59245C12.3731 2.37851 11.9643 3.38362 11.9188 4.43697C11.9193 4.56507 11.933 4.69278 11.9597 4.81808C12.0539 4.8359 12.1496 4.84503 12.2455 4.84536C12.7964 4.80152 13.3327 4.64611 13.8217 4.38858C14.3108 4.13104 14.7423 3.77676 15.0901 3.34726Z"
fill="currentColor"
/>
</svg>
</span>
<span>
macOS (<span data-slot="hide-narrow">Apple </span>Silicon)
</span>
</div>
<a href={getDownloadHref("darwin-aarch64-dmg")} data-component="action-button">
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="M20.0035 7.15814C19.3171 7.5784 18.7485 8.16594 18.351 8.86579C17.9534 9.56563 17.74 10.3549 17.7305 11.1597C17.7332 12.0655 18.0016 12.9506 18.5024 13.7054C19.0032 14.4602 19.7144 15.0515 20.5479 15.4061C20.2193 16.4664 19.7329 17.4712 19.1051 18.3868C18.2069 19.6798 17.2677 20.9727 15.8387 20.9727C14.4096 20.9727 14.0421 20.1425 12.3952 20.1425C10.7892 20.1425 10.2175 21 8.91088 21C7.60426 21 6.69246 19.8022 5.6444 18.3323C4.25999 16.2732 3.49913 13.8583 3.45312 11.3774C3.45312 7.29427 6.10722 5.13028 8.72032 5.13028C10.1086 5.13028 11.2656 6.04208 12.1366 6.04208C12.9669 6.04208 14.2599 5.07572 15.8387 5.07572C16.6504 5.05478 17.4548 5.23375 18.1811 5.59689C18.9074 5.96003 19.5332 6.49619 20.0035 7.15814ZM15.0901 3.34726C15.7861 2.52858 16.18 1.49589 16.2062 0.421702C16.2074 0.280092 16.1937 0.13875 16.1654 0C14.9699 0.116777 13.8644 0.686551 13.0757 1.59245C12.3731 2.37851 11.9643 3.38362 11.9188 4.43697C11.9193 4.56507 11.933 4.69278 11.9597 4.81808C12.0539 4.8359 12.1496 4.84503 12.2455 4.84536C12.7964 4.80152 13.3327 4.64611 13.8217 4.38858C14.3108 4.13104 14.7423 3.77676 15.0901 3.34726Z"
fill="currentColor"
/>
</svg>
</span>
<span>macOS (Intel)</span>
</div>
<a href={getDownloadHref("darwin-x64-dmg")} data-component="action-button">
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">
<g clip-path="url(#clip0_2614_159729)">
<path
d="M2 2H11.481V11.4769H2V2ZM12.519 2H22V11.4769H12.519V2ZM2 12.519H11.481V22H2V12.519ZM12.519 12.519H22V22H12.519"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_2614_159729">
<rect width="20" height="20" fill="white" transform="translate(2 2)" />
</clipPath>
</defs>
</svg>
</span>
<span>Windows (x64)</span>
</div>
<a href={getDownloadHref("windows-x64-nsis")} data-component="action-button">
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 (.deb)</span>
</div>
<a href={getDownloadHref("linux-x64-deb")} data-component="action-button">
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 (.rpm)</span>
</div>
<a href={getDownloadHref("linux-x64-rpm")} data-component="action-button">
Download
</a>
</div>
</div>
</section>
<section data-component="download-section">
<div data-component="section-label">
<span>[3]</span> OpenCode Extensions
</div>
<div data-component="section-content">
<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">
<g clip-path="url(#clip0_2614_159777)">
<path
d="M21.7899 4.15451L17.6755 2.17514C17.1968 1.94389 16.6274 2.04139 16.253 2.41576L8.37242 9.60639L4.93805 7.00201C4.6193 6.75764 4.16992 6.77764 3.87367 7.04764L2.77367 8.05014C2.4093 8.37889 2.4093 8.95201 2.77055 9.28076L5.7493 11.9989L2.77055 14.717C2.4093 15.0458 2.4093 15.6189 2.77367 15.9476L3.87367 16.9501C4.17305 17.2201 4.6193 17.2401 4.93805 16.9958L8.37242 14.3883L16.2568 21.582C16.628 21.9564 17.1974 22.0539 17.6762 21.8226L21.7943 19.8401C22.2274 19.632 22.5005 19.1958 22.5005 18.7139V5.27951C22.5005 4.80076 22.2237 4.36139 21.7912 4.15326L21.7899 4.15451ZM17.5024 16.5408L11.5193 11.9995L17.5024 7.45826V16.5408Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_2614_159777">
<rect width="20" height="20" fill="white" transform="translate(2.5 2)" />
</clipPath>
</defs>
</svg>
</span>
<span>VS Code</span>
</div>
<a href="https://opencode.ai/docs/ide/" data-component="action-button">
Install
</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">
<g clip-path="url(#clip0_2614_159762)">
<path
d="M20.1613 6.73388L12.4027 2.11135C12.1535 1.96288 11.8461 1.96288 11.597 2.11135L3.83874 6.73388C3.6293 6.85867 3.5 7.08946 3.5 7.33942V16.6608C3.5 16.9107 3.6293 17.1415 3.83874 17.2663L11.5973 21.8888C11.8465 22.0373 12.1539 22.0373 12.403 21.8888L20.1616 17.2663C20.3711 17.1415 20.5004 16.9107 20.5004 16.6608V7.33942C20.5004 7.08946 20.3711 6.85867 20.1616 6.73388H20.1613ZM19.6739 7.71304L12.1841 21.1002C12.1335 21.1905 11.9998 21.1536 11.9998 21.0491V12.2833C11.9998 12.1082 11.9091 11.9462 11.762 11.8582L4.40586 7.47548C4.31844 7.42324 4.35413 7.28529 4.45539 7.28529H19.435C19.6477 7.28529 19.7806 7.52322 19.6743 7.71341H19.6739V7.71304Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_2614_159762">
<rect width="17" height="20" fill="white" transform="translate(3.5 2)" />
</clipPath>
</defs>
</svg>
</span>
<span>Cursor</span>
</div>
<a href="https://opencode.ai/docs/ide/" data-component="action-button">
Install
</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.375 3.25C4.02982 3.25 3.75 3.52982 3.75 3.875V17.625H2.5V3.875C2.5 2.83947 3.33947 2 4.375 2H21.1206C21.9558 2 22.374 3.00982 21.7835 3.60042L11.4698 13.9141H14.375V12.625H15.625V14.2266C15.625 14.7443 15.2053 15.1641 14.6875 15.1641H10.2198L8.07139 17.3125H17.8125V9.5H19.0625V17.3125C19.0625 18.0029 18.5029 18.5625 17.8125 18.5625H6.82139L4.63389 20.75H20.625C20.9701 20.75 21.25 20.4701 21.25 20.125V6.375H22.5V20.125C22.5 21.1606 21.6606 22 20.625 22H3.87944C3.04422 22 2.62594 20.9901 3.21653 20.3996L13.4911 10.125H10.625V11.375H9.375V9.8125C9.375 9.29474 9.79474 8.875 10.3125 8.875H14.7411L16.9286 6.6875H7.1875V14.5H5.9375V6.6875C5.9375 5.99714 6.49714 5.4375 7.1875 5.4375H18.1786L20.3661 3.25H4.375Z"
fill="currentColor"
/>
</svg>
</span>
<span>Zed</span>
</div>
<a href="https://opencode.ai/docs/ide/" data-component="action-button">
Install
</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="M21.8156 6.00325H21.625C20.6219 6.00162 19.8079 6.8448 19.8079 7.88581V12.0961C19.8079 12.9368 19.1384 13.6179 18.3415 13.6179C17.8681 13.6179 17.3955 13.3706 17.115 12.9555L12.9722 6.814C12.6285 6.30403 12.0691 6 11.4637 6C10.5192 6 9.66922 6.83345 9.66922 7.86232V12.0969C9.66922 12.9376 9.00519 13.6187 8.20289 13.6187C7.72791 13.6187 7.25603 13.3714 6.97557 12.9563L2.33983 6.08351C2.23514 5.92783 2 6.00487 2 6.1946V9.86649C2 10.0522 2.05469 10.2322 2.15702 10.3846L6.71933 17.1471C6.98886 17.5468 7.38651 17.8435 7.84507 17.9514C8.9927 18.2221 10.0489 17.3052 10.0489 16.1369V11.9047C10.0489 11.064 10.7051 10.3829 11.5152 10.3829H11.5176C12.0059 10.3829 12.4636 10.6302 12.7441 11.0453L16.8877 17.186C17.2322 17.6968 17.7627 18 18.3954 18C19.361 18 20.1883 17.1657 20.1883 16.1377V11.9039C20.1883 11.0632 20.8446 10.3821 21.6547 10.3821H21.8164C21.9179 10.3821 22 10.297 22 10.1916V6.19377C22 6.08839 21.9179 6.00325 21.8164 6.00325H21.8156Z"
fill="currentColor"
/>
</svg>
</span>
<span>Windsurf</span>
</div>
<a href="https://opencode.ai/docs/ide/" data-component="action-button">
Install
</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="M11.6179 1.49887C10.99 1.90169 10.8089 2.73615 11.2135 3.36183C13.4375 6.80593 13.9624 9.40369 13.7347 11.6802C12.8142 16.0398 10.8133 16.9242 9.06476 16.9242C7.35756 16.9242 7.81472 14.1145 9.09798 13.2922C9.86402 12.8139 10.8452 12.503 11.5983 12.503C12.3445 12.503 12.9495 11.9 12.9495 11.156C12.9495 10.4117 12.3445 9.80871 11.5983 9.80871C10.7187 9.80871 9.85588 9.99351 9.05046 10.3081C9.21502 9.53173 9.27574 8.69265 9.063 7.80077C8.74004 6.44645 7.81032 5.15285 6.19596 3.89885C5.91326 3.67885 5.55466 3.58007 5.19892 3.62407C4.84318 3.66807 4.51956 3.85111 4.29934 4.13315C3.8413 4.72055 3.94734 5.56711 4.5365 6.02405C5.85166 7.04551 6.28594 7.80165 6.43444 8.42403C6.58294 9.04641 6.46348 9.71411 6.16516 10.6315C5.7839 11.8679 5.34126 12.9716 5.14722 14.0301C5.05174 14.551 5.0436 15.118 5.01896 15.5709C4.07186 14.6478 3.70116 13.429 3.70116 11.6481C3.70094 10.9041 3.09594 10.3008 2.34992 10.3011C1.60434 10.3017 1.00022 10.9045 1 11.6481C1 14.0804 1.71126 16.3948 3.61756 17.9388C5.34324 19.5829 9.73158 18.9752 9.73158 21.6146C9.73158 22.3595 10.8219 22.722 11.5679 22.722C12.3331 22.722 13.296 22.2105 13.296 21.6146C13.296 18.6199 16.4519 16.7999 21.6472 16.8078C22.3935 16.8089 22.9989 16.2063 23 15.4623C23.0013 14.718 22.3976 14.1137 21.6514 14.1123C21.2961 14.1119 20.9498 14.124 20.6084 14.1442C21.1892 12.7783 21.4468 11.2743 21.3936 9.64987C21.3689 8.90605 20.7446 8.32305 19.999 8.34725C19.2525 8.37145 18.6678 8.99471 18.6922 9.73897C18.7626 11.8659 18.6829 13.7652 17.0983 14.7664C16.6477 15.0509 16.1239 15.2977 15.6271 15.2977C16.0128 14.2487 16.3041 13.1415 16.4233 11.948C16.4994 11.1863 16.5076 10.2815 16.4207 9.57859C16.2858 8.48959 16.123 7.25451 16.5364 6.32413C16.9078 5.52289 17.7398 5.18739 18.9615 5.18739C19.707 5.18673 20.3112 4.58371 20.3114 3.84033C20.3118 3.09607 19.7075 2.49239 18.9615 2.49173C17.146 2.49173 15.7699 3.44719 14.9898 4.60153C14.5819 3.73033 14.0852 2.83251 13.485 1.90323C13.2912 1.60293 12.9858 1.39195 12.6358 1.31605C12.4624 1.27843 12.2834 1.27513 12.1087 1.30637C11.934 1.33783 11.7672 1.40317 11.6179 1.49887Z"
fill="currentColor"
/>
</svg>
</span>
<span>VSCodium</span>
</div>
<a href="https://opencode.ai/docs/ide/" data-component="action-button">
Install
</a>
</div>
</div>
</section>
<section data-component="download-section">
<div data-component="section-label">
<span>[4]</span> OpenCode Integrations
</div>
<div data-component="section-content">
<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="M12 1.94922C17.525 1.94922 22 6.42422 22 11.9492C21.9995 14.0445 21.3419 16.0868 20.1198 17.7887C18.8977 19.4907 17.1727 20.7665 15.1875 21.4367C14.6875 21.5367 14.5 21.2242 14.5 20.9617C14.5 20.6242 14.5125 19.5492 14.5125 18.2117C14.5125 17.2742 14.2 16.6742 13.8375 16.3617C16.0625 16.1117 18.4 15.2617 18.4 11.4242C18.4 10.3242 18.0125 9.43672 17.375 8.73672C17.475 8.48672 17.825 7.46172 17.275 6.08672C17.275 6.08672 16.4375 5.81172 14.525 7.11172C13.725 6.88672 12.875 6.77422 12.025 6.77422C11.175 6.77422 10.325 6.88672 9.525 7.11172C7.6125 5.82422 6.775 6.08672 6.775 6.08672C6.225 7.46172 6.575 8.48672 6.675 8.73672C6.0375 9.43672 5.65 10.3367 5.65 11.4242C5.65 15.2492 7.975 16.1117 10.2 16.3617C9.9125 16.6117 9.65 17.0492 9.5625 17.6992C8.9875 17.9617 7.55 18.3867 6.65 16.8742C6.4625 16.5742 5.9 15.8367 5.1125 15.8492C4.275 15.8617 4.775 16.3242 5.125 16.5117C5.55 16.7492 6.0375 17.6367 6.15 17.9242C6.35 18.4867 7 19.5617 9.5125 19.0992C9.5125 19.9367 9.525 20.7242 9.525 20.9617C9.525 21.2242 9.3375 21.5242 8.8375 21.4367C6.8458 20.7738 5.11342 19.5005 3.88611 17.7975C2.65881 16.0945 1.9989 14.0484 2 11.9492C2 6.42422 6.475 1.94922 12 1.94922Z"
fill="currentColor"
/>
</svg>
</span>
<span>GitHub</span>
</div>
<a href="https://opencode.ai/docs/github/" data-component="action-button">
Install
</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="M20.7011 10.1255L20.6758 10.0583L18.2257 3.41877C18.1759 3.28864 18.0876 3.17824 17.9736 3.10343C17.8595 3.02989 17.7264 2.99447 17.5924 3.00196C17.4583 3.00944 17.3296 3.05947 17.2238 3.14528C17.1191 3.23356 17.0432 3.35318 17.0063 3.48787L15.352 8.74347H8.65334L6.99905 3.48787C6.96317 3.35245 6.88708 3.23223 6.7816 3.14431C6.67576 3.05849 6.54711 3.00847 6.41303 3.00098C6.27894 2.9935 6.14587 3.02892 6.03178 3.10246C5.91802 3.17757 5.82983 3.28787 5.77965 3.4178L3.32493 10.0545L3.30056 10.1216C2.94787 11.0785 2.90433 12.1286 3.17652 13.1134C3.44871 14.0983 4.02187 14.9645 4.80957 15.5816L4.81801 15.5884L4.8405 15.605L8.57273 18.5072L10.4192 19.9584L11.5439 20.8401C11.6755 20.9438 11.8361 21 12.0013 21C12.1665 21 12.3271 20.9438 12.4587 20.8401L13.5834 19.9584L15.4298 18.5072L19.1846 15.5874L19.1939 15.5797C19.9799 14.9625 20.5517 14.0971 20.8235 13.1136C21.0952 12.1301 21.0523 11.0815 20.7011 10.1255Z"
fill="currentColor"
/>
</svg>
</span>
<span>GitLab</span>
</div>
<a href="https://opencode.ai/docs/gitlab/" data-component="action-button">
Install
</a>
</div>
</div>
</section>
</div>
<section data-component="faq">
<div data-slot="section-title">
<h3>FAQ</h3>
</div>
<ul>
<li>
<Faq question="What is OpenCode?">
OpenCode is an open source agent that helps you write and run code with any AI model. It's available as
a terminal-based interface, desktop app, or IDE extension.
</Faq>
</li>
<li>
<Faq question="How do I use OpenCode?">
The easiest way to get started is to read the <a href="/docs">intro</a>.
</Faq>
</li>
<li>
<Faq question="Do I need extra AI subscriptions to use OpenCode?">
Not necessarily, but probably. You'll need an AI subscription if you want to connect OpenCode to a paid
provider, although you can work with{" "}
<a href="/docs/providers/#lm-studio" target="_blank">
local models
</a>{" "}
for free. While we encourage users to use <A href="/zen">Zen</A>, OpenCode works with all popular
providers such as OpenAI, Anthropic, xAI etc.
</Faq>
</li>
<li>
<Faq question="Can I only use OpenCode in the terminal?">
Not anymore! OpenCode is now available as an app for your desktop.
</Faq>
</li>
<li>
<Faq question="How much does OpenCode cost?">
OpenCode is 100% free to use. Any additional costs will come from your subscription to a model provider.
While OpenCode works with any model provider, we recommend using <A href="/zen">Zen</A>.
</Faq>
</li>
<li>
<Faq question="What about data and privacy?">
Your data and information is only stored when you create sharable links in OpenCode. Learn more about{" "}
<a href="/docs/share/#privacy">share pages</a>.
</Faq>
</li>
<li>
<Faq question="Is OpenCode open source?">
Yes, OpenCode is fully open source. The source code is public on{" "}
<a href={config.github.repoUrl} target="_blank">
GitHub
</a>{" "}
under the{" "}
<a href={`${config.github.repoUrl}?tab=MIT-1-ov-file#readme`} target="_blank">
MIT License
</a>
, meaning anyone can use, modify, or contribute to its development. Anyone from the community can file
issues, submit pull requests, and extend functionality.
</Faq>
</li>
</ul>
</section>
<Footer />
</div>
<Legal />
</main>
)
}

View File

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

View File

@@ -84,7 +84,16 @@
ul {
display: flex;
justify-content: space-between;
align-items: center;
gap: 48px;
@media (max-width: 55rem) {
gap: 32px;
}
@media (max-width: 48rem) {
gap: 24px;
}
li {
display: inline-block;
a {
@@ -98,6 +107,25 @@
text-underline-offset: 2px;
text-decoration-thickness: 1px;
}
[data-slot="cta-button"] {
background: var(--color-background-strong);
color: var(--color-text-inverted);
padding: 8px 16px 8px 10px;
border-radius: 4px;
font-weight: 500;
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
@media (max-width: 55rem) {
display: none;
}
}
[data-slot="cta-button"]:hover {
background: var(--color-background-strong-hover);
text-decoration: none;
}
}
}
@@ -289,7 +317,7 @@
[data-component="enterprise-column-1"] {
h1 {
font-size: 1.5rem;
font-weight: 500;
font-weight: 700;
color: var(--color-text-strong);
margin-bottom: 1rem;
}
@@ -441,7 +469,7 @@
h3 {
font-size: 16px;
font-weight: 500;
font-weight: 700;
color: var(--color-text-strong);
margin-bottom: 12px;
}

View File

@@ -16,6 +16,8 @@
--color-background-strong-hover: hsl(0, 5%, 18%);
--color-background-interactive: hsl(62, 84%, 88%);
--color-background-interactive-weaker: hsl(64, 74%, 95%);
--color-surface-raised-base: hsla(0, 100%, 3%, 0.01);
--color-surface-raised-base-active: hsla(0, 100%, 17%, 0.06);
--color-text: hsl(0, 1%, 39%);
--color-text-weak: hsl(0, 1%, 60%);
@@ -24,7 +26,7 @@
--color-text-inverted: hsl(0, 20%, 99%);
--color-border: hsl(30, 2%, 81%);
--color-border-weak: hsl(0, 1%, 85%);
--color-border-weak: hsla(0, 100%, 3%, 0.12);
--color-icon: hsl(0, 1%, 55%);
}
@@ -62,6 +64,14 @@ body {
}
}
[data-slot="br"] {
display: block;
@media (max-width: 60rem) {
display: none;
}
}
[data-page="opencode"] {
background: var(--color-background);
--padding: 5rem;
@@ -196,6 +206,7 @@ body {
[data-component="top"] {
padding: 24px var(--padding);
height: 80px;
min-height: 80px;
position: sticky;
top: 0;
display: flex;
@@ -215,7 +226,16 @@ body {
ul {
display: flex;
justify-content: space-between;
align-items: center;
gap: 48px;
@media (max-width: 55rem) {
gap: 32px;
}
@media (max-width: 48rem) {
gap: 24px;
}
li {
display: inline-block;
a {
@@ -229,6 +249,25 @@ body {
text-underline-offset: var(--space-1);
text-decoration-thickness: 1px;
}
[data-slot="cta-button"] {
background: var(--color-background-strong);
color: var(--color-text-inverted);
padding: 8px 16px 8px 10px;
border-radius: 4px;
font-weight: 500;
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
@media (max-width: 55rem) {
display: none;
}
}
[data-slot="cta-button"]:hover {
background: var(--color-background-strong-hover);
text-decoration: none;
}
}
}
@@ -322,7 +361,7 @@ body {
display: flex;
flex-direction: column;
max-width: 100%;
padding: calc(var(--vertical-padding) * 2) var(--padding);
padding: calc(var(--vertical-padding) * 1.5) var(--padding);
@media (max-width: 30rem) {
padding: var(--vertical-padding) var(--padding);
@@ -426,7 +465,7 @@ body {
cursor: pointer;
align-items: center;
color: var(--color-text);
gap: var(--space-1);
gap: 16px;
color: var(--color-text);
padding: 8px 16px 8px 8px;
border-radius: 4px;
@@ -465,6 +504,77 @@ body {
}
}
[data-component="desktop-app-banner"] {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 32px;
[data-slot="badge"] {
background: var(--color-background-strong);
color: var(--color-text-inverted);
font-weight: 500;
padding: 4px 8px;
line-height: 1;
flex-shrink: 0;
}
[data-slot="content"] {
display: flex;
align-items: center;
gap: 4px;
}
[data-slot="text"] {
color: var(--color-text-strong);
line-height: 1.4;
@media (max-width: 30.625rem) {
display: none;
}
}
[data-slot="platforms"] {
@media (max-width: 49.125rem) {
display: none;
}
}
[data-slot="link"] {
color: var(--color-text-weak);
white-space: nowrap;
text-decoration: none;
@media (max-width: 30.625rem) {
display: none;
}
}
[data-slot="link"]:hover {
color: var(--color-text);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
}
[data-slot="link-mobile"] {
display: none;
color: var(--color-text-strong);
white-space: nowrap;
text-decoration: none;
@media (max-width: 30.625rem) {
display: inline;
}
}
[data-slot="link-mobile"]:hover {
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
}
}
[data-slot="hero-copy"] {
[data-slot="releases"] {
background: none;
@@ -492,7 +602,7 @@ body {
h1 {
font-size: 38px;
color: var(--color-text-strong);
font-weight: 500;
font-weight: 700;
margin-bottom: 8px;
@media (max-width: 60rem) {
@@ -502,7 +612,7 @@ body {
p {
color: var(--color-text);
margin-bottom: 40px;
margin-bottom: 32px;
max-width: 82%;
@media (max-width: 50rem) {
@@ -518,7 +628,6 @@ body {
border-radius: 4px;
font-weight: 500;
cursor: pointer;
margin-bottom: 80px;
display: flex;
width: fit-content;
gap: 12px;
@@ -596,7 +705,7 @@ body {
h3 {
font-size: 16px;
font-weight: 500;
font-weight: 700;
color: var(--color-text-strong);
margin-bottom: 12px;
}
@@ -701,7 +810,7 @@ body {
[data-slot="privacy-title"] {
h3 {
font-size: 16px;
font-weight: 500;
font-weight: 700;
color: var(--color-text-strong);
margin-bottom: 12px;
}
@@ -727,7 +836,7 @@ body {
[data-slot="zen-cta-copy"] {
strong {
color: var(--color-text-strong);
font-weight: 500;
font-weight: 700;
margin-bottom: 16px;
display: block;
}

View File

@@ -52,17 +52,33 @@ export default function Home() {
<div data-component="content">
<section data-component="hero">
<div data-component="desktop-app-banner">
<span data-slot="badge">New</span>
<div data-slot="content">
<span data-slot="text">
Desktop app available in beta<span data-slot="platforms"> on macOS, Windows, and Linux</span>.
</span>
<a href="/download" data-slot="link">
Download now
</a>
<a href="/download" data-slot="link-mobile">
Download the desktop beta now
</a>
</div>
</div>
<div data-slot="hero-copy">
<a data-slot="releases" href={release()?.url ?? `${config.github.repoUrl}/releases`} target="_blank">
Whats new in {release()?.name ?? "the latest release"}
</a>
<h1>The open source coding agent</h1>
{/*<a data-slot="releases"*/}
{/* href={release()?.url ?? `${config.github.repoUrl}/releases`}*/}
{/* target="_blank">*/}
{/* Whats new in {release()?.name ?? "the latest release"}*/}
{/*</a>*/}
<h1>The open source AI coding agent</h1>
<p>
OpenCode includes free models or connect from any provider to <br />
use other models, including Claude, GPT, Gemini and more.
Free models included or connect any model from any provider, <span data-slot="br"></span>including
Claude, GPT, Gemini and more.
</p>
</div>
<p data-slot="installation-instructions">Install and use. No account, no email, and no credit card.</p>
<div data-slot="installation">
<Tabs
as="section"
@@ -141,11 +157,6 @@ export default function Home() {
</div>
</Tabs>
</div>
<p data-slot="installation-options">
Available in terminal, web, and desktop (coming soon).
<br />
Extensions for VS Code, Cursor, Windsurf, and more.
</p>
</section>
<section data-component="video">
@@ -157,15 +168,9 @@ export default function Home() {
<section data-component="what">
<div data-slot="section-title">
<h3>What is OpenCode?</h3>
<p>OpenCode is an open source agent that helps you write and run code directly from the terminal.</p>
<p>OpenCode is an open source agent that helps you write code in your terminal, IDE, or desktop.</p>
</div>
<ul>
<li>
<span>[*]</span>
<div>
<strong>Native TUI</strong> A responsive, native, themeable terminal UI
</div>
</li>
<li>
<span>[*]</span>
<div>
@@ -199,7 +204,7 @@ export default function Home() {
<li>
<span>[*]</span>
<div>
<strong>Any editor</strong> OpenCode runs in your terminal, pair it with any IDE
<strong>Any editor</strong> Available as a terminal interface, desktop app, and IDE extension
</div>
</li>
</ul>
@@ -223,7 +228,7 @@ export default function Home() {
<span>[*]</span>
<p>
With over <strong>{config.github.starsFormatted.full}</strong> GitHub stars,{" "}
<strong>{config.stats.contributors}</strong> contributors, and almost{" "}
<strong>{config.stats.contributors}</strong> contributors, and over{" "}
<strong>{config.stats.commits}</strong> commits, OpenCode is used and trusted by over{" "}
<strong>{config.stats.monthlyUsers}</strong> developers every month.
</p>
@@ -651,9 +656,8 @@ export default function Home() {
<ul>
<li>
<Faq question="What is OpenCode?">
OpenCode is an open source agent that helps you write and run code directly from the terminal. You can
pair OpenCode with any AI model, and because its terminal-based you can pair it with your preferred
code editor.
OpenCode is an open source agent that helps you write and run code with any AI model. It's available
as a terminal-based interface, desktop app, or IDE extension.
</Faq>
</li>
<li>
@@ -663,29 +667,38 @@ export default function Home() {
</li>
<li>
<Faq question="Do I need extra AI subscriptions to use OpenCode?">
Not necessarily, but probably. Youll need an AI subscription if you want to connect OpenCode to a
paid provider, although you can work with{" "}
Not necessarily, OpenCode comes with a set of free models that you can use without creating an
account. Aside from these, you can use any of the popular coding models by creating a{" "}
<A href="/zen">Zen</A> account. While we encourage users to use Zen, OpenCode also works with all
popular providers such as OpenAI, Anthropic, xAI etc. You can even connect your{" "}
<a href="/docs/providers/#lm-studio" target="_blank">
local models
</a>{" "}
for free. While we encourage users to use <A href="/zen">Zen</A>, OpenCode works with all popular
providers such as OpenAI, Anthropic, xAI etc.
</a>
.
</Faq>
</li>
<li>
<Faq question="Can I use my existing AI subscriptions with OpenCode?">
Yes, OpenCode supports subscription plans from all major providers. You can use your Claude Pro/Max,
ChatGPT Plus/Pro, or GitHub Copilot subscriptions. <a href="/docs/providers/#directory">Learn more</a>
.
</Faq>
</li>
<li>
<Faq question="Can I only use OpenCode in the terminal?">
Yes, for now. We are actively working on a desktop app. Join the waitlist for early access.
Not anymore! OpenCode is now available as an app for your desktop.
</Faq>
</li>
<li>
<Faq question="How much does OpenCode cost?">
OpenCode is 100% free to use. Any additional costs will come from your subscription to a model
provider. While OpenCode works with any model provider, we recommend using <A href="/zen">Zen</A>.
OpenCode is 100% free to use. It also comes with a set of free models. There might be additional costs
if you connect any other provider.
</Faq>
</li>
<li>
<Faq question="What about data and privacy?">
Your data and information is only stored when you create sharable links in OpenCode. Learn more about{" "}
Your data and information is only stored when you use our free models or create sharable links. Learn
more about <a href="/docs/zen/#privacy">our models</a> and{" "}
<a href="/docs/share/#privacy">share pages</a>.
</Faq>
</li>
@@ -745,6 +758,17 @@ export default function Home() {
/>
</svg>
</div>
<div>
<svg
width="24"
height="24"
viewBox="0 0 50 50"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M49.04,24.001l-1.082-0.043h-0.001C36.134,23.492,26.508,13.866,26.042,2.043L25.999,0.96C25.978,0.424,25.537,0,25,0 s-0.978,0.424-0.999,0.96l-0.043,1.083C23.492,13.866,13.866,23.492,2.042,23.958L0.96,24.001C0.424,24.022,0,24.463,0,25 c0,0.537,0.424,0.978,0.961,0.999l1.082,0.042c11.823,0.467,21.449,10.093,21.915,21.916l0.043,1.083C24.022,49.576,24.463,50,25,50 s0.978-0.424,0.999-0.96l0.043-1.083c0.466-11.823,10.092-21.449,21.915-21.916l1.082-0.042C49.576,25.978,50,25.537,50,25 C50,24.463,49.576,24.022,49.04,24.001z"></path>
</svg>
</div>
<div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@@ -775,6 +799,14 @@ export default function Home() {
/>
</svg>
</div>
<div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.0962 3L10.0998 5.6577H1.59858L3.59417 3H12.0972H12.0962ZM22.3162 18.3432L20.3215 21H11.8497L13.8425 18.3432H22.3162ZM23 3L9.492 21H1L14.508 3H23Z"
fill="black"
/>
</svg>
</div>
</div>
<A href="/zen">
<span>Learn about Zen </span>

View File

@@ -0,0 +1,343 @@
[data-component="privacy-policy"] {
max-width: 800px;
margin: 0 auto;
line-height: 1.7;
}
[data-component="privacy-policy"] h1 {
font-size: 2rem;
font-weight: 700;
color: var(--color-text-strong);
margin-bottom: 0.5rem;
margin-top: 0;
}
[data-component="privacy-policy"] .effective-date {
font-size: 0.95rem;
color: var(--color-text-weak);
margin-bottom: 2rem;
}
[data-component="privacy-policy"] h2 {
font-size: 1.5rem;
font-weight: 600;
color: var(--color-text-strong);
margin-top: 3rem;
margin-bottom: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--color-border-weak);
}
[data-component="privacy-policy"] h2:first-of-type {
margin-top: 2rem;
}
[data-component="privacy-policy"] h3 {
font-size: 1.25rem;
font-weight: 600;
color: var(--color-text-strong);
margin-top: 2rem;
margin-bottom: 1rem;
}
[data-component="privacy-policy"] h4 {
font-size: 1.1rem;
font-weight: 600;
color: var(--color-text-strong);
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
[data-component="privacy-policy"] p {
margin-bottom: 1rem;
color: var(--color-text);
}
[data-component="privacy-policy"] ul,
[data-component="privacy-policy"] ol {
margin-bottom: 1rem;
padding-left: 1.5rem;
color: var(--color-text);
}
[data-component="privacy-policy"] li {
margin-bottom: 0.5rem;
line-height: 1.7;
}
[data-component="privacy-policy"] ul ul,
[data-component="privacy-policy"] ul ol,
[data-component="privacy-policy"] ol ul,
[data-component="privacy-policy"] ol ol {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
[data-component="privacy-policy"] a {
color: var(--color-text-strong);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
word-break: break-word;
}
[data-component="privacy-policy"] a:hover {
text-decoration-thickness: 2px;
}
[data-component="privacy-policy"] strong {
font-weight: 600;
color: var(--color-text-strong);
}
[data-component="privacy-policy"] .table-wrapper {
overflow-x: auto;
margin: 1.5rem 0;
}
[data-component="privacy-policy"] table {
width: 100%;
border-collapse: collapse;
border: 1px solid var(--color-border);
}
[data-component="privacy-policy"] th,
[data-component="privacy-policy"] td {
padding: 0.75rem 1rem;
text-align: left;
border: 1px solid var(--color-border);
vertical-align: top;
}
[data-component="privacy-policy"] th {
background: var(--color-background-weak);
font-weight: 600;
color: var(--color-text-strong);
}
[data-component="privacy-policy"] td {
color: var(--color-text);
}
[data-component="privacy-policy"] td ul {
margin: 0;
padding-left: 1.25rem;
}
[data-component="privacy-policy"] td li {
margin-bottom: 0.25rem;
}
/* Mobile responsiveness */
@media (max-width: 60rem) {
[data-component="privacy-policy"] {
padding: 0;
}
[data-component="privacy-policy"] h1 {
font-size: 1.75rem;
}
[data-component="privacy-policy"] h2 {
font-size: 1.35rem;
margin-top: 2.5rem;
}
[data-component="privacy-policy"] h3 {
font-size: 1.15rem;
}
[data-component="privacy-policy"] h4 {
font-size: 1rem;
}
[data-component="privacy-policy"] table {
font-size: 0.9rem;
}
[data-component="privacy-policy"] th,
[data-component="privacy-policy"] td {
padding: 0.5rem 0.75rem;
}
}
html {
scroll-behavior: smooth;
}
[data-component="privacy-policy"] [id] {
scroll-margin-top: 100px;
}
@media print {
@page {
margin: 2cm;
size: letter;
}
[data-component="top"],
[data-component="footer"],
[data-component="legal"] {
display: none !important;
}
[data-page="legal"] {
background: white !important;
padding: 0 !important;
}
[data-component="container"] {
max-width: none !important;
border: none !important;
margin: 0 !important;
}
[data-component="content"],
[data-component="brand-content"] {
padding: 0 !important;
margin: 0 !important;
}
[data-component="privacy-policy"] {
max-width: none !important;
margin: 0 !important;
padding: 0 !important;
}
[data-component="privacy-policy"] * {
color: black !important;
background: transparent !important;
}
[data-component="privacy-policy"] h1 {
font-size: 24pt;
margin-top: 0;
margin-bottom: 12pt;
page-break-after: avoid;
}
[data-component="privacy-policy"] h2 {
font-size: 18pt;
border-top: 2pt solid black !important;
padding-top: 12pt;
margin-top: 24pt;
margin-bottom: 8pt;
page-break-after: avoid;
page-break-before: auto;
}
[data-component="privacy-policy"] h2:first-of-type {
margin-top: 16pt;
}
[data-component="privacy-policy"] h3 {
font-size: 14pt;
margin-top: 16pt;
margin-bottom: 8pt;
page-break-after: avoid;
}
[data-component="privacy-policy"] h4 {
font-size: 12pt;
margin-top: 12pt;
margin-bottom: 6pt;
page-break-after: avoid;
}
[data-component="privacy-policy"] p {
font-size: 11pt;
line-height: 1.5;
margin-bottom: 8pt;
orphans: 3;
widows: 3;
}
[data-component="privacy-policy"] .effective-date {
font-size: 10pt;
margin-bottom: 16pt;
}
[data-component="privacy-policy"] ul,
[data-component="privacy-policy"] ol {
margin-bottom: 8pt;
page-break-inside: auto;
}
[data-component="privacy-policy"] li {
font-size: 11pt;
line-height: 1.5;
margin-bottom: 4pt;
page-break-inside: avoid;
}
[data-component="privacy-policy"] a {
color: black !important;
text-decoration: underline;
}
[data-component="privacy-policy"] .table-wrapper {
overflow: visible !important;
margin: 12pt 0;
}
[data-component="privacy-policy"] table {
border: 2pt solid black !important;
page-break-inside: avoid;
width: 100% !important;
font-size: 10pt;
}
[data-component="privacy-policy"] th,
[data-component="privacy-policy"] td {
border: 1pt solid black !important;
padding: 6pt 8pt !important;
background: white !important;
}
[data-component="privacy-policy"] th {
background: #f0f0f0 !important;
font-weight: bold;
page-break-after: avoid;
}
[data-component="privacy-policy"] tr {
page-break-inside: avoid;
}
[data-component="privacy-policy"] td ul {
margin: 2pt 0;
padding-left: 12pt;
}
[data-component="privacy-policy"] td li {
margin-bottom: 2pt;
font-size: 9pt;
}
[data-component="privacy-policy"] strong {
font-weight: bold;
color: black !important;
}
[data-component="privacy-policy"] h1,
[data-component="privacy-policy"] h2,
[data-component="privacy-policy"] h3,
[data-component="privacy-policy"] h4 {
page-break-inside: avoid;
page-break-after: avoid;
}
[data-component="privacy-policy"] h2 + p,
[data-component="privacy-policy"] h3 + p,
[data-component="privacy-policy"] h4 + p,
[data-component="privacy-policy"] h2 + ul,
[data-component="privacy-policy"] h3 + ul,
[data-component="privacy-policy"] h4 + ul {
page-break-before: avoid;
}
[data-component="privacy-policy"] table,
[data-component="privacy-policy"] .table-wrapper {
page-break-inside: avoid;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,254 @@
[data-component="terms-of-service"] {
max-width: 800px;
margin: 0 auto;
line-height: 1.7;
}
[data-component="terms-of-service"] h1 {
font-size: 2rem;
font-weight: 700;
color: var(--color-text-strong);
margin-bottom: 0.5rem;
margin-top: 0;
}
[data-component="terms-of-service"] .effective-date {
font-size: 0.95rem;
color: var(--color-text-weak);
margin-bottom: 2rem;
}
[data-component="terms-of-service"] h2 {
font-size: 1.5rem;
font-weight: 600;
color: var(--color-text-strong);
margin-top: 3rem;
margin-bottom: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--color-border-weak);
}
[data-component="terms-of-service"] h2:first-of-type {
margin-top: 2rem;
}
[data-component="terms-of-service"] h3 {
font-size: 1.25rem;
font-weight: 600;
color: var(--color-text-strong);
margin-top: 2rem;
margin-bottom: 1rem;
}
[data-component="terms-of-service"] h4 {
font-size: 1.1rem;
font-weight: 600;
color: var(--color-text-strong);
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
[data-component="terms-of-service"] p {
margin-bottom: 1rem;
color: var(--color-text);
}
[data-component="terms-of-service"] ul,
[data-component="terms-of-service"] ol {
margin-bottom: 1rem;
padding-left: 1.5rem;
color: var(--color-text);
}
[data-component="terms-of-service"] li {
margin-bottom: 0.5rem;
line-height: 1.7;
}
[data-component="terms-of-service"] ul ul,
[data-component="terms-of-service"] ul ol,
[data-component="terms-of-service"] ol ul,
[data-component="terms-of-service"] ol ol {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
[data-component="terms-of-service"] a {
color: var(--color-text-strong);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
word-break: break-word;
}
[data-component="terms-of-service"] a:hover {
text-decoration-thickness: 2px;
}
[data-component="terms-of-service"] strong {
font-weight: 600;
color: var(--color-text-strong);
}
@media (max-width: 60rem) {
[data-component="terms-of-service"] {
padding: 0;
}
[data-component="terms-of-service"] h1 {
font-size: 1.75rem;
}
[data-component="terms-of-service"] h2 {
font-size: 1.35rem;
margin-top: 2.5rem;
}
[data-component="terms-of-service"] h3 {
font-size: 1.15rem;
}
[data-component="terms-of-service"] h4 {
font-size: 1rem;
}
}
html {
scroll-behavior: smooth;
}
[data-component="terms-of-service"] [id] {
scroll-margin-top: 100px;
}
@media print {
@page {
margin: 2cm;
size: letter;
}
[data-component="top"],
[data-component="footer"],
[data-component="legal"] {
display: none !important;
}
[data-page="legal"] {
background: white !important;
padding: 0 !important;
}
[data-component="container"] {
max-width: none !important;
border: none !important;
margin: 0 !important;
}
[data-component="content"],
[data-component="brand-content"] {
padding: 0 !important;
margin: 0 !important;
}
[data-component="terms-of-service"] {
max-width: none !important;
margin: 0 !important;
padding: 0 !important;
}
[data-component="terms-of-service"] * {
color: black !important;
background: transparent !important;
}
[data-component="terms-of-service"] h1 {
font-size: 24pt;
margin-top: 0;
margin-bottom: 12pt;
page-break-after: avoid;
}
[data-component="terms-of-service"] h2 {
font-size: 18pt;
border-top: 2pt solid black !important;
padding-top: 12pt;
margin-top: 24pt;
margin-bottom: 8pt;
page-break-after: avoid;
page-break-before: auto;
}
[data-component="terms-of-service"] h2:first-of-type {
margin-top: 16pt;
}
[data-component="terms-of-service"] h3 {
font-size: 14pt;
margin-top: 16pt;
margin-bottom: 8pt;
page-break-after: avoid;
}
[data-component="terms-of-service"] h4 {
font-size: 12pt;
margin-top: 12pt;
margin-bottom: 6pt;
page-break-after: avoid;
}
[data-component="terms-of-service"] p {
font-size: 11pt;
line-height: 1.5;
margin-bottom: 8pt;
orphans: 3;
widows: 3;
}
[data-component="terms-of-service"] .effective-date {
font-size: 10pt;
margin-bottom: 16pt;
}
[data-component="terms-of-service"] ul,
[data-component="terms-of-service"] ol {
margin-bottom: 8pt;
page-break-inside: auto;
}
[data-component="terms-of-service"] li {
font-size: 11pt;
line-height: 1.5;
margin-bottom: 4pt;
page-break-inside: avoid;
}
[data-component="terms-of-service"] a {
color: black !important;
text-decoration: underline;
}
[data-component="terms-of-service"] strong {
font-weight: bold;
color: black !important;
}
[data-component="terms-of-service"] h1,
[data-component="terms-of-service"] h2,
[data-component="terms-of-service"] h3,
[data-component="terms-of-service"] h4 {
page-break-inside: avoid;
page-break-after: avoid;
}
[data-component="terms-of-service"] h2 + p,
[data-component="terms-of-service"] h3 + p,
[data-component="terms-of-service"] h4 + p,
[data-component="terms-of-service"] h2 + ul,
[data-component="terms-of-service"] h3 + ul,
[data-component="terms-of-service"] h4 + ul,
[data-component="terms-of-service"] h2 + ol,
[data-component="terms-of-service"] h3 + ol,
[data-component="terms-of-service"] h4 + ol {
page-break-before: avoid;
}
}

View File

@@ -0,0 +1,512 @@
import "../../brand/index.css"
import "./index.css"
import { Title, Meta, Link } from "@solidjs/meta"
import { Header } from "~/component/header"
import { config } from "~/config"
import { Footer } from "~/component/footer"
import { Legal } from "~/component/legal"
export default function TermsOfService() {
return (
<main data-page="legal">
<Title>OpenCode | Terms of Service</Title>
<Link rel="canonical" href={`${config.baseUrl}/legal/terms-of-service`} />
<Meta name="description" content="OpenCode terms of service" />
<div data-component="container">
<Header />
<div data-component="content">
<section data-component="brand-content">
<article data-component="terms-of-service">
<h1>Terms of Use</h1>
<p class="effective-date">Effective date: Dec 16, 2025</p>
<p>
Welcome to OpenCode. Please read on to learn the rules and restrictions that govern your use of OpenCode
(the "Services"). If you have any questions, comments, or concerns regarding these terms or the
Services, please contact us at:
</p>
<p>
Email: <a href="mailto:contact@anoma.ly">contact@anoma.ly</a>
</p>
<p>
These Terms of Use (the "Terms") are a binding contract between you and{" "}
<strong>ANOMALY INNOVATIONS, INC.</strong> ("OpenCode," "we" and "us"). Your use of the Services in any
way means that you agree to all of these Terms, and these Terms will remain in effect while you use the
Services. These Terms include the provisions in this document as well as those in the Privacy Policy{" "}
<a href="/legal/privacy-policy">https://opencode.ai/legal/privacy-policy</a>.{" "}
<strong>
Your use of or participation in certain Services may also be subject to additional policies, rules
and/or conditions ("Additional Terms"), which are incorporated herein by reference, and you understand
and agree that by using or participating in any such Services, you agree to also comply with these
Additional Terms.
</strong>
</p>
<p>
Please read these Terms carefully. They cover important information about Services provided to you and
any charges, taxes, and fees we bill you. These Terms include information about{" "}
<a href="#will-these-terms-ever-change">future changes to these Terms</a>,{" "}
<a href="#recurring-billing">automatic renewals</a>,{" "}
<a href="#limitation-of-liability">limitations of liability</a>,{" "}
<a href="#waiver-of-class">a class action waiver</a> and{" "}
<a href="#arbitration-agreement">resolution of disputes by arbitration instead of in court</a>.{" "}
<strong>
PLEASE NOTE THAT YOUR USE OF AND ACCESS TO OUR SERVICES ARE SUBJECT TO THE FOLLOWING TERMS; IF YOU DO
NOT AGREE TO ALL OF THE FOLLOWING, YOU MAY NOT USE OR ACCESS THE SERVICES IN ANY MANNER.
</strong>
</p>
<p>
<strong>ARBITRATION NOTICE AND CLASS ACTION WAIVER:</strong> EXCEPT FOR CERTAIN TYPES OF DISPUTES
DESCRIBED IN THE <a href="#arbitration-agreement">ARBITRATION AGREEMENT SECTION BELOW</a>, YOU AGREE
THAT DISPUTES BETWEEN YOU AND US WILL BE RESOLVED BY BINDING, INDIVIDUAL ARBITRATION AND YOU WAIVE YOUR
RIGHT TO PARTICIPATE IN A CLASS ACTION LAWSUIT OR CLASS-WIDE ARBITRATION.
</p>
<h2 id="what-is-opencode">What is OpenCode?</h2>
<p>
OpenCode is an AI-powered coding agent that helps you write, understand, and modify code using large
language models. Certain of these large language models are provided by third parties ("Third Party
Models") and certain of these models are provided directly by us if you use the OpenCode Zen paid
offering ("Zen"). Regardless of whether you use Third Party Models or Zen, OpenCode enables you to
access the functionality of models through a coding agent running within your terminal.
</p>
<h2 id="will-these-terms-ever-change">Will these Terms ever change?</h2>
<p>
We are constantly trying to improve our Services, so these Terms may need to change along with our
Services. We reserve the right to change the Terms at any time, but if we do, we will place a notice on
our site located at opencode.ai, send you an email, and/or notify you by some other means.
</p>
<p>
If you don't agree with the new Terms, you are free to reject them; unfortunately, that means you will
no longer be able to use the Services. If you use the Services in any way after a change to the Terms is
effective, that means you agree to all of the changes.
</p>
<p>
Except for changes by us as described here, no other amendment or modification of these Terms will be
effective unless in writing and signed by both you and us.
</p>
<h2 id="what-about-my-privacy">What about my privacy?</h2>
<p>
OpenCode takes the privacy of its users very seriously. For the current OpenCode Privacy Policy, please
click here{" "}
<a href="https://opencode.ai/legal/privacy-policy">https://opencode.ai/legal/privacy-policy</a>.
</p>
<h3>Children's Online Privacy Protection Act</h3>
<p>
The Children's Online Privacy Protection Act ("COPPA") requires that online service providers obtain
parental consent before they knowingly collect personally identifiable information online from children
who are under 13 years of age. We do not knowingly collect or solicit personally identifiable
information from children under 13 years of age; if you are a child under 13 years of age, please do not
attempt to register for or otherwise use the Services or send us any personal information. If we learn
we have collected personal information from a child under 13 years of age, we will delete that
information as quickly as possible. If you believe that a child under 13 years of age may have provided
us personal information, please contact us at <a href="mailto:contact@anoma.ly">contact@anoma.ly</a>.
</p>
<h2 id="what-are-the-basics">What are the basics of using OpenCode?</h2>
<p>
You represent and warrant that you are an individual of legal age to form a binding contract (or if not,
you've received your parent's or guardian's permission to use the Services and have gotten your parent
or guardian to agree to these Terms on your behalf). If you're agreeing to these Terms on behalf of an
organization or entity, you represent and warrant that you are authorized to agree to these Terms on
that organization's or entity's behalf and bind them to these Terms (in which case, the references to
"you" and "your" in these Terms, except for in this sentence, refer to that organization or entity).
</p>
<p>
You will only use the Services for your own internal use, and not on behalf of or for the benefit of any
third party, and only in a manner that complies with all laws that apply to you. If your use of the
Services is prohibited by applicable laws, then you aren't authorized to use the Services. We can't and
won't be responsible for your using the Services in a way that breaks the law.
</p>
<h2 id="are-there-restrictions">Are there restrictions in how I can use the Services?</h2>
<p>
You represent, warrant, and agree that you will not provide or contribute anything, including any
Content (as that term is defined below), to the Services, or otherwise use or interact with the
Services, in a manner that:
</p>
<ol style="list-style-type: lower-alpha;">
<li>
infringes or violates the intellectual property rights or any other rights of anyone else (including
OpenCode);
</li>
<li>
violates any law or regulation, including, without limitation, any applicable export control laws,
privacy laws or any other purpose not reasonably intended by OpenCode;
</li>
<li>
is dangerous, harmful, fraudulent, deceptive, threatening, harassing, defamatory, obscene, or
otherwise objectionable;
</li>
<li>automatically or programmatically extracts data or Output (defined below);</li>
<li>Represent that the Output was human-generated when it was not;</li>
<li>
uses Output to develop artificial intelligence models that compete with the Services or any Third
Party Models;
</li>
<li>
attempts, in any manner, to obtain the password, account, or other security information from any other
user;
</li>
<li>
violates the security of any computer network, or cracks any passwords or security encryption codes;
</li>
<li>
runs Maillist, Listserv, any form of auto-responder or "spam" on the Services, or any processes that
run or are activated while you are not logged into the Services, or that otherwise interfere with the
proper working of the Services (including by placing an unreasonable load on the Services'
infrastructure);
</li>
<li>
"crawls," "scrapes," or "spiders" any page, data, or portion of or relating to the Services or Content
(through use of manual or automated means);
</li>
<li>copies or stores any significant portion of the Content; or</li>
<li>
decompiles, reverse engineers, or otherwise attempts to obtain the source code or underlying ideas or
information of or relating to the Services.
</li>
</ol>
<p>
A violation of any of the foregoing is grounds for termination of your right to use or access the
Services.
</p>
<h2 id="who-owns-the-services-and-content">Who Owns the Services and Content?</h2>
<h3>Our IP</h3>
<p>
We retain all right, title and interest in and to the Services. Except as expressly set forth herein, no
rights to the Services or Third Party Models are granted to you.
</p>
<h3>Your IP</h3>
<p>
You may provide input to the Services ("Input"), and receive output from the Services based on the Input
("Output"). Input and Output are collectively "Content." You are responsible for Content, including
ensuring that it does not violate any applicable law or these Terms. You represent and warrant that you
have all rights, licenses, and permissions needed to provide Input to our Services.
</p>
<p>
As between you and us, and to the extent permitted by applicable law, you (a) retain your ownership
rights in Input and (b) own the Output. We hereby assign to you all our right, title, and interest, if
any, in and to Output.
</p>
<p>
Due to the nature of our Services and artificial intelligence generally, output may not be unique and
other users may receive similar output from our Services. Our assignment above does not extend to other
users' output.
</p>
<p>
We use Content to provide our Services, comply with applicable law, enforce our terms and policies, and
keep our Services safe. In addition, if you are using the Services through an unpaid account, we may use
Content to further develop and improve our Services.
</p>
<p>
If you use OpenCode with Third Party Models, then your Content will be subject to the data retention
policies of the providers of such Third Party Models. Although we will not retain your Content, we
cannot and do not control the retention practices of Third Party Model providers. You should review the
terms and conditions applicable to any Third Party Model for more information about the data use and
retention policies applicable to such Third Party Models.
</p>
<h2 id="what-about-third-party-models">What about Third Party Models?</h2>
<p>
The Services enable you to access and use Third Party Models, which are not owned or controlled by
OpenCode. Your ability to access Third Party Models is contingent on you having API keys or otherwise
having the right to access such Third Party Models.
</p>
<p>
OpenCode has no control over, and assumes no responsibility for, the content, accuracy, privacy
policies, or practices of any providers of Third Party Models. We encourage you to read the terms and
conditions and privacy policy of each provider of a Third Party Model that you choose to utilize. By
using the Services, you release and hold us harmless from any and all liability arising from your use of
any Third Party Model.
</p>
<h2 id="will-opencode-ever-change-the-services">Will OpenCode ever change the Services?</h2>
<p>
We're always trying to improve our Services, so they may change over time. We may suspend or discontinue
any part of the Services, or we may introduce new features or impose limits on certain features or
restrict access to parts or all of the Services.
</p>
<h2 id="do-the-services-cost-anything">Do the Services cost anything?</h2>
<p>
The Services may be free or we may charge a fee for using the Services. If you are using a free version
of the Services, we will notify you before any Services you are then using begin carrying a fee, and if
you wish to continue using such Services, you must pay all applicable fees for such Services. Any and
all such charges, fees or costs are your sole responsibility. You should consult with your
</p>
<h3>Paid Services</h3>
<p>
Certain of our Services, including Zen, may be subject to payments now or in the future (the "Paid
Services"). Please see our Paid Services page <a href="/zen">https://opencode.ai/zen</a> for a
description of the current Paid Services. Please note that any payment terms presented to you in the
process of using or signing up for a Paid Service are deemed part of these Terms.
</p>
<h3>Billing</h3>
<p>
We use a third-party payment processor (the "Payment Processor") to bill you through a payment account
linked to your account on the Services (your "Billing Account") for use of the Paid Services. The
processing of payments will be subject to the terms, conditions and privacy policies of the Payment
Processor in addition to these Terms. Currently, we use Stripe, Inc. as our Payment Processor. You can
access Stripe's Terms of Service at{" "}
<a href="https://stripe.com/us/checkout/legal">https://stripe.com/us/checkout/legal</a> and their
Privacy Policy at <a href="https://stripe.com/us/privacy">https://stripe.com/us/privacy</a>. We are not
responsible for any error by, or other acts or omissions of, the Payment Processor. By choosing to use
Paid Services, you agree to pay us, through the Payment Processor, all charges at the prices then in
effect for any use of such Paid Services in accordance with the applicable payment terms, and you
authorize us, through the Payment Processor, to charge your chosen payment provider (your "Payment
Method"). You agree to make payment using that selected Payment Method. We reserve the right to correct
any errors or mistakes that the Payment Processor makes even if it has already requested or received
payment.
</p>
<h3>Payment Method</h3>
<p>
The terms of your payment will be based on your Payment Method and may be determined by agreements
between you and the financial institution, credit card issuer or other provider of your chosen Payment
Method. If we, through the Payment Processor, do not receive payment from you, you agree to pay all
amounts due on your Billing Account upon demand.
</p>
<h3 id="recurring-billing">Recurring Billing</h3>
<p>
Some of the Paid Services may consist of an initial period, for which there is a one-time charge,
followed by recurring period charges as agreed to by you. By choosing a recurring payment plan, you
acknowledge that such Services have an initial and recurring payment feature and you accept
responsibility for all recurring charges prior to cancellation. WE MAY SUBMIT PERIODIC CHARGES (E.G.,
MONTHLY) WITHOUT FURTHER AUTHORIZATION FROM YOU, UNTIL YOU PROVIDE PRIOR NOTICE (RECEIPT OF WHICH IS
CONFIRMED BY US) THAT YOU HAVE TERMINATED THIS AUTHORIZATION OR WISH TO CHANGE YOUR PAYMENT METHOD. SUCH
NOTICE WILL NOT AFFECT CHARGES SUBMITTED BEFORE WE REASONABLY COULD ACT. TO TERMINATE YOUR AUTHORIZATION
OR CHANGE YOUR PAYMENT METHOD, GO TO ACCOUNT SETTINGS{" "}
<a href="https://opencode.ai/auth">https://opencode.ai/auth</a>.
</p>
<h3>Free Trials and Other Promotions</h3>
<p>
Any free trial or other promotion that provides access to a Paid Service must be used within the
specified time of the trial. You must stop using a Paid Service before the end of the trial period in
order to avoid being charged for that Paid Service. If you cancel prior to the end of the trial period
and are inadvertently charged for a Paid Service, please contact us at{" "}
<a href="mailto:contact@anoma.ly">contact@anoma.ly</a>.
</p>
<h2 id="what-if-i-want-to-stop">What if I want to stop using the Services?</h2>
<p>
You're free to do that at any time; please refer to our Privacy Policy{" "}
<a href="/legal/privacy-policy">https://opencode.ai/legal/privacy-policy</a>, as well as the licenses
above, to understand how we treat information you provide to us after you have stopped using our
Services.
</p>
<p>
OpenCode is also free to terminate (or suspend access to) your use of the Services for any reason in our
discretion, including your breach of these Terms. OpenCode has the sole right to decide whether you are
in violation of any of the restrictions set forth in these Terms.
</p>
<p>
Provisions that, by their nature, should survive termination of these Terms shall survive termination.
By way of example, all of the following will survive termination: any obligation you have to pay us or
indemnify us, any limitations on our liability, any terms regarding ownership or intellectual property
rights, and terms regarding disputes between us, including without limitation the arbitration agreement.
</p>
<h2 id="what-else-do-i-need-to-know">What else do I need to know?</h2>
<h3>Warranty Disclaimer</h3>
<p>
OpenCode and its licensors, suppliers, partners, parent, subsidiaries or affiliated entities, and each
of their respective officers, directors, members, employees, consultants, contract employees,
representatives and agents, and each of their respective successors and assigns (OpenCode and all such
parties together, the "OpenCode Parties") make no representations or warranties concerning the Services,
including without limitation regarding any Content contained in or accessed through the Services, and
the OpenCode Parties will not be responsible or liable for the accuracy, copyright compliance, legality,
or decency of material contained in or accessed through the Services or any claims, actions, suits
procedures, costs, expenses, damages or liabilities arising out of use of, or in any way related to your
participation in, the Services. The OpenCode Parties make no representations or warranties regarding
suggestions or recommendations of services or products offered or purchased through or in connection
with the Services. THE SERVICES AND CONTENT ARE PROVIDED BY OPENCODE (AND ITS LICENSORS AND SUPPLIERS)
ON AN "AS-IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT
LIMITATION, IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT,
OR THAT USE OF THE SERVICES WILL BE UNINTERRUPTED OR ERROR-FREE. SOME STATES DO NOT ALLOW LIMITATIONS ON
HOW LONG AN IMPLIED WARRANTY LASTS, SO THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.
</p>
<h3 id="limitation-of-liability">Limitation of Liability</h3>
<p>
TO THE FULLEST EXTENT ALLOWED BY APPLICABLE LAW, UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY
(INCLUDING, WITHOUT LIMITATION, TORT, CONTRACT, STRICT LIABILITY, OR OTHERWISE) SHALL ANY OF THE
OPENCODE PARTIES BE LIABLE TO YOU OR TO ANY OTHER PERSON FOR (A) ANY INDIRECT, SPECIAL, INCIDENTAL,
PUNITIVE OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING DAMAGES FOR LOST PROFITS, BUSINESS
INTERRUPTION, LOSS OF DATA, LOSS OF GOODWILL, WORK STOPPAGE, ACCURACY OF RESULTS, OR COMPUTER FAILURE OR
MALFUNCTION, (B) ANY SUBSTITUTE GOODS, SERVICES OR TECHNOLOGY, (C) ANY AMOUNT, IN THE AGGREGATE, IN
EXCESS OF THE GREATER OF (I) ONE-HUNDRED ($100) DOLLARS OR (II) THE AMOUNTS PAID AND/OR PAYABLE BY YOU
TO OPENCODE IN CONNECTION WITH THE SERVICES IN THE TWELVE (12) MONTH PERIOD PRECEDING THIS APPLICABLE
CLAIM OR (D) ANY MATTER BEYOND OUR REASONABLE CONTROL. SOME STATES DO NOT ALLOW THE EXCLUSION OR
LIMITATION OF INCIDENTAL OR CONSEQUENTIAL OR CERTAIN OTHER DAMAGES, SO THE ABOVE LIMITATION AND
EXCLUSIONS MAY NOT APPLY TO YOU.
</p>
<h3>Indemnity</h3>
<p>
You agree to indemnify and hold the OpenCode Parties harmless from and against any and all claims,
liabilities, damages (actual and consequential), losses and expenses (including attorneys' fees) arising
from or in any way related to any claims relating to (a) your use of the Services, and (b) your
violation of these Terms. In the event of such a claim, suit, or action ("Claim"), we will attempt to
provide notice of the Claim to the contact information we have for your account (provided that failure
to deliver such notice shall not eliminate or reduce your indemnification obligations hereunder).
</p>
<h3>Assignment</h3>
<p>
You may not assign, delegate or transfer these Terms or your rights or obligations hereunder, or your
Services account, in any way (by operation of law or otherwise) without OpenCode's prior written
consent. We may transfer, assign, or delegate these Terms and our rights and obligations without
consent.
</p>
<h3>Choice of Law</h3>
<p>
These Terms are governed by and will be construed under the Federal Arbitration Act, applicable federal
law, and the laws of the State of Delaware, without regard to the conflicts of laws provisions thereof.
</p>
<h3 id="arbitration-agreement">Arbitration Agreement</h3>
<p>
Please read the following ARBITRATION AGREEMENT carefully because it requires you to arbitrate certain
disputes and claims with OpenCode and limits the manner in which you can seek relief from OpenCode. Both
you and OpenCode acknowledge and agree that for the purposes of any dispute arising out of or relating
to the subject matter of these Terms, OpenCode's officers, directors, employees and independent
contractors ("Personnel") are third-party beneficiaries of these Terms, and that upon your acceptance of
these Terms, Personnel will have the right (and will be deemed to have accepted the right) to enforce
these Terms against you as the third-party beneficiary hereof.
</p>
<h4>Arbitration Rules; Applicability of Arbitration Agreement</h4>
<p>
The parties shall use their best efforts to settle any dispute, claim, question, or disagreement arising
out of or relating to the subject matter of these Terms directly through good-faith negotiations, which
shall be a precondition to either party initiating arbitration. If such negotiations do not resolve the
dispute, it shall be finally settled by binding arbitration in New Castle County, Delaware. The
arbitration will proceed in the English language, in accordance with the JAMS Streamlined Arbitration
Rules and Procedures (the "Rules") then in effect, by one commercial arbitrator with substantial
experience in resolving intellectual property and commercial contract disputes. The arbitrator shall be
selected from the appropriate list of JAMS arbitrators in accordance with such Rules. Judgment upon the
award rendered by such arbitrator may be entered in any court of competent jurisdiction.
</p>
<h4>Costs of Arbitration</h4>
<p>
The Rules will govern payment of all arbitration fees. OpenCode will pay all arbitration fees for claims
less than seventy-five thousand ($75,000) dollars. OpenCode will not seek its attorneys' fees and costs
in arbitration unless the arbitrator determines that your claim is frivolous.
</p>
<h4>Small Claims Court; Infringement</h4>
<p>
Either you or OpenCode may assert claims, if they qualify, in small claims court in New Castle County,
Delaware or any United States county where you live or work. Furthermore, notwithstanding the foregoing
obligation to arbitrate disputes, each party shall have the right to pursue injunctive or other
equitable relief at any time, from any court of competent jurisdiction, to prevent the actual or
threatened infringement, misappropriation or violation of a party's copyrights, trademarks, trade
secrets, patents or other intellectual property rights.
</p>
<h4>Waiver of Jury Trial</h4>
<p>
YOU AND OPENCODE WAIVE ANY CONSTITUTIONAL AND STATUTORY RIGHTS TO GO TO COURT AND HAVE A TRIAL IN FRONT
OF A JUDGE OR JURY. You and OpenCode are instead choosing to have claims and disputes resolved by
arbitration. Arbitration procedures are typically more limited, more efficient, and less costly than
rules applicable in court and are subject to very limited review by a court. In any litigation between
you and OpenCode over whether to vacate or enforce an arbitration award, YOU AND OPENCODE WAIVE ALL
RIGHTS TO A JURY TRIAL, and elect instead to have the dispute be resolved by a judge.
</p>
<h4 id="waiver-of-class">Waiver of Class or Consolidated Actions</h4>
<p>
ALL CLAIMS AND DISPUTES WITHIN THE SCOPE OF THIS ARBITRATION AGREEMENT MUST BE ARBITRATED OR LITIGATED
ON AN INDIVIDUAL BASIS AND NOT ON A CLASS BASIS. CLAIMS OF MORE THAN ONE CUSTOMER OR USER CANNOT BE
ARBITRATED OR LITIGATED JOINTLY OR CONSOLIDATED WITH THOSE OF ANY OTHER CUSTOMER OR USER. If however,
this waiver of class or consolidated actions is deemed invalid or unenforceable, neither you nor
OpenCode is entitled to arbitration; instead all claims and disputes will be resolved in a court as set
forth in (g) below.
</p>
<h4>Opt-out</h4>
<p>
You have the right to opt out of the provisions of this Section by sending written notice of your
decision to opt out to the following address: [ADDRESS], [CITY], Canada [ZIP CODE] postmarked within
thirty (30) days of first accepting these Terms. You must include (i) your name and residence address,
(ii) the email address and/or telephone number associated with your account, and (iii) a clear statement
that you want to opt out of these Terms' arbitration agreement.
</p>
<h4>Exclusive Venue</h4>
<p>
If you send the opt-out notice in (f), and/or in any circumstances where the foregoing arbitration
agreement permits either you or OpenCode to litigate any dispute arising out of or relating to the
subject matter of these Terms in court, then the foregoing arbitration agreement will not apply to
either party, and both you and OpenCode agree that any judicial proceeding (other than small claims
actions) will be brought in the state or federal courts located in, respectively, New Castle County,
Delaware, or the federal district in which that county falls.
</p>
<h4>Severability</h4>
<p>
If the prohibition against class actions and other claims brought on behalf of third parties contained
above is found to be unenforceable, then all of the preceding language in this Arbitration Agreement
section will be null and void. This arbitration agreement will survive the termination of your
relationship with OpenCode.
</p>
<h3>Miscellaneous</h3>
<p>
You will be responsible for paying, withholding, filing, and reporting all taxes, duties, and other
governmental assessments associated with your activity in connection with the Services, provided that
the OpenCode may, in its sole discretion, do any of the foregoing on your behalf or for itself as it
sees fit. The failure of either you or us to exercise, in any way, any right herein shall not be deemed
a waiver of any further rights hereunder. If any provision of these Terms are found to be unenforceable
or invalid, that provision will be limited or eliminated, to the minimum extent necessary, so that these
Terms shall otherwise remain in full force and effect and enforceable. You and OpenCode agree that these
Terms are the complete and exclusive statement of the mutual understanding between you and OpenCode, and
that these Terms supersede and cancel all previous written and oral agreements, communications and other
understandings relating to the subject matter of these Terms. You hereby acknowledge and agree that you
are not an employee, agent, partner, or joint venture of OpenCode, and you do not have any authority of
any kind to bind OpenCode in any respect whatsoever.
</p>
<p>
Except as expressly set forth in the section above regarding the arbitration agreement, you and OpenCode
agree there are no third-party beneficiaries intended under these Terms.
</p>
</article>
</section>
</div>
<Footer />
</div>
<Legal />
</main>
)
}

View File

@@ -0,0 +1,20 @@
import type { APIEvent } from "@solidjs/start/server"
async function handler(evt: APIEvent) {
const req = evt.request.clone()
const url = new URL(req.url)
const targetUrl = `https://enterprise.opencode.ai/${url.pathname}${url.search}`
const response = await fetch(targetUrl, {
method: req.method,
headers: req.headers,
body: req.body,
})
return response
}
export const GET = handler
export const POST = handler
export const PUT = handler
export const DELETE = handler
export const OPTIONS = handler
export const PATCH = handler

View File

@@ -43,9 +43,12 @@ const getModelsInfo = query(async (workspaceID: string) => {
const pA = getPriority(idA)
const pB = getPriority(idB)
if (pA !== pB) return pA - pB
return modelA.name.localeCompare(modelB.name)
const modelAName = Array.isArray(modelA) ? modelA[0].name : modelA.name
const modelBName = Array.isArray(modelB) ? modelB[0].name : modelB.name
return modelAName.localeCompare(modelBName)
})
.map(([id, model]) => ({ id, name: model.name })),
.map(([id, model]) => ({ id, name: Array.isArray(model) ? model[0].name : model.name })),
disabled: await Model.listDisabled(),
}
}, workspaceID)

View File

@@ -147,7 +147,16 @@ body {
ul {
display: flex;
justify-content: space-between;
align-items: center;
gap: 48px;
@media (max-width: 55rem) {
gap: 32px;
}
@media (max-width: 48rem) {
gap: 24px;
}
li {
display: inline-block;
a {
@@ -161,6 +170,22 @@ body {
text-underline-offset: var(--space-1);
text-decoration-thickness: 1px;
}
[data-slot="cta-button"] {
background: var(--color-background-strong);
color: var(--color-text-inverted);
padding: 8px 16px;
border-radius: 4px;
font-weight: 500;
text-decoration: none;
@media (max-width: 55rem) {
display: none;
}
}
[data-slot="cta-button"]:hover {
background: var(--color-background-strong-hover);
text-decoration: none;
}
}
}
@@ -280,7 +305,7 @@ body {
h1 {
font-size: 28px;
color: var(--color-text-strong);
font-weight: 500;
font-weight: 700;
margin-bottom: 16px;
display: block;
@@ -369,7 +394,7 @@ body {
h3 {
font-size: 16px;
font-weight: 500;
font-weight: 700;
color: var(--color-text-strong);
margin-bottom: 12px;
}
@@ -442,7 +467,7 @@ body {
[data-slot="privacy-title"] {
h3 {
font-size: 16px;
font-weight: 500;
font-weight: 700;
color: var(--color-text);
margin-bottom: 12px;
}

View File

@@ -38,7 +38,7 @@ export default function Home() {
<Meta name="opencode:auth" content={loggedin() ? "true" : "false"} />
<div data-component="container">
<Header zen />
<Header zen hideGetStarted />
<div data-component="content">
<section data-component="hero">

View File

@@ -57,15 +57,17 @@ export async function handler(
const sessionId = input.request.headers.get("x-opencode-session") ?? ""
const requestId = input.request.headers.get("x-opencode-request") ?? ""
const projectId = input.request.headers.get("x-opencode-project") ?? ""
const ocClient = input.request.headers.get("x-opencode-client") ?? ""
logger.metric({
is_tream: isStream,
session: sessionId,
request: requestId,
client: ocClient,
})
const zenData = ZenData.list()
const modelInfo = validateModel(zenData, model)
const dataDumper = createDataDumper(sessionId, requestId, projectId)
const trialLimiter = createTrialLimiter(modelInfo.trial?.limit, ip)
const trialLimiter = createTrialLimiter(modelInfo.trial, ip, ocClient)
const isTrial = await trialLimiter?.isTrial()
const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip)
await rateLimiter?.check()
@@ -110,13 +112,21 @@ export async function handler(
headers.delete("content-length")
headers.delete("x-opencode-request")
headers.delete("x-opencode-session")
headers.delete("x-opencode-project")
headers.delete("x-opencode-client")
return headers
})(),
body: reqBody,
})
// Try another provider => stop retrying if using fallback provider
if (res.status !== 200 && modelInfo.fallbackProvider && providerInfo.id !== modelInfo.fallbackProvider) {
if (
res.status !== 200 &&
// ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found.
res.status !== 404 &&
modelInfo.fallbackProvider &&
providerInfo.id !== modelInfo.fallbackProvider
) {
return retriableRequest({
excludeProviders: [...retry.excludeProviders, providerInfo.id],
retryCount: retry.retryCount + 1,
@@ -135,6 +145,9 @@ export async function handler(
// Store sticky provider
await stickyTracker?.set(providerInfo.id)
// Temporarily change 404 to 400 status code b/c solid start automatically override 404 response
const resStatus = res.status === 404 ? 400 : res.status
// Scrub response headers
const resHeaders = new Headers()
const keepHeaders = ["content-type", "cache-control"]
@@ -160,7 +173,7 @@ export async function handler(
await trackUsage(authInfo, modelInfo, providerInfo, tokensInfo)
await reload(authInfo)
return new Response(body, {
status: res.status,
status: resStatus,
statusText: res.statusText,
headers: resHeaders,
})
@@ -238,7 +251,7 @@ export async function handler(
})
return new Response(stream, {
status: res.status,
status: resStatus,
statusText: res.statusText,
headers: resHeaders,
})
@@ -286,11 +299,14 @@ export async function handler(
}
function validateModel(zenData: ZenData, reqModel: string) {
if (!(reqModel in zenData.models)) {
throw new ModelError(`Model ${reqModel} not supported`)
}
if (!(reqModel in zenData.models)) throw new ModelError(`Model ${reqModel} not supported`)
const modelId = reqModel as keyof typeof zenData.models
const modelData = zenData.models[modelId]
const modelData = Array.isArray(zenData.models[modelId])
? zenData.models[modelId].find((model) => opts.format === model.formatFilter)
: zenData.models[modelId]
if (!modelData) throw new ModelError(`Model ${reqModel} not supported for format ${opts.format}`)
logger.metric({ model: modelId })
@@ -588,7 +604,7 @@ export async function handler(
tx
.update(KeyTable)
.set({ timeUsed: sql`now()` })
.where(eq(KeyTable.id, authInfo.apiKeyId)),
.where(and(eq(KeyTable.workspaceID, authInfo.workspaceID), eq(KeyTable.id, authInfo.apiKeyId))),
)
}

View File

@@ -1,12 +1,18 @@
import { Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js"
import { IpTable } from "@opencode-ai/console-core/schema/ip.sql.js"
import { UsageInfo } from "./provider/provider"
import { ZenData } from "@opencode-ai/console-core/model.js"
export function createTrialLimiter(limit: number | undefined, ip: string) {
if (!limit) return
export function createTrialLimiter(trial: ZenData.Trial | undefined, ip: string, client: string) {
if (!trial) return
if (!ip) return
let trial: boolean
const limit =
trial.limits.find((limit) => limit.client === client)?.limit ??
trial.limits.find((limit) => limit.client === undefined)?.limit
if (!limit) return
let _isTrial: boolean
return {
isTrial: async () => {
@@ -20,11 +26,11 @@ export function createTrialLimiter(limit: number | undefined, ip: string) {
.then((rows) => rows[0]),
)
trial = (data?.usage ?? 0) < limit
return trial
_isTrial = (data?.usage ?? 0) < limit
return _isTrial
},
track: async (usageInfo: UsageInfo) => {
if (!trial) return
if (!_isTrial) return
const usage =
usageInfo.inputTokens +
usageInfo.outputTokens +

View File

@@ -15,6 +15,7 @@ body {
--font-size-9xl: 8rem;
--font-mono:
"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
"Berkeley Mono", "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
--font-sans: var(--font-mono);
}

View File

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

View File

@@ -16,16 +16,19 @@ const value1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[
const value2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1]
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]
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")
// validate value
ZenData.validate(JSON.parse(value1 + value2 + value3 + value4))
ZenData.validate(JSON.parse(value1 + value2 + value3 + value4 + value5))
// update the secret
await $`bun sst secret set ZEN_MODELS1 ${value1} --stage ${stage}`
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}`

View File

@@ -16,16 +16,19 @@ const value1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[
const value2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1]
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]
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")
// validate value
ZenData.validate(JSON.parse(value1 + value2 + value3 + value4))
ZenData.validate(JSON.parse(value1 + value2 + value3 + value4 + value5))
// update the secret
await $`bun sst secret set ZEN_MODELS1 ${value1}`
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}`

View File

@@ -14,15 +14,17 @@ const oldValue1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=
const oldValue2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1]
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]
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")
// 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), null, 2))
await tempFile.write(JSON.stringify(JSON.parse(oldValue1 + oldValue2 + oldValue3 + oldValue4 + oldValue5), null, 2))
console.log("tempFile", tempFile.name)
// open temp file in vim and read the file on close
@@ -31,12 +33,15 @@ const newValue = JSON.stringify(JSON.parse(await tempFile.text()))
ZenData.validate(JSON.parse(newValue))
// update the secret
const chunk = Math.ceil(newValue.length / 4)
const chunk = Math.ceil(newValue.length / 5)
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)
const newValue4 = newValue.slice(chunk * 3, chunk * 4)
const newValue5 = newValue.slice(chunk * 4)
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}`

View File

@@ -9,7 +9,17 @@ import { Resource } from "@opencode-ai/console-resource"
export namespace ZenData {
const FormatSchema = z.enum(["anthropic", "google", "openai", "oa-compat"])
const TrialSchema = z.object({
provider: z.string(),
limits: z.array(
z.object({
limit: z.number(),
client: z.enum(["cli", "desktop"]).optional(),
}),
),
})
export type Format = z.infer<typeof FormatSchema>
export type Trial = z.infer<typeof TrialSchema>
const ModelCostSchema = z.object({
input: z.number(),
@@ -26,12 +36,7 @@ export namespace ZenData {
allowAnonymous: z.boolean().optional(),
byokProvider: z.enum(["openai", "anthropic", "google"]).optional(),
stickyProvider: z.boolean().optional(),
trial: z
.object({
limit: z.number(),
provider: z.string(),
})
.optional(),
trial: TrialSchema.optional(),
rateLimit: z.number().optional(),
fallbackProvider: z.string().optional(),
providers: z.array(
@@ -53,7 +58,7 @@ export namespace ZenData {
})
const ModelsSchema = z.object({
models: z.record(z.string(), ModelSchema),
models: z.record(z.string(), z.union([ModelSchema, z.array(ModelSchema.extend({ formatFilter: FormatSchema }))])),
providers: z.record(z.string(), ProviderSchema),
})
@@ -63,7 +68,11 @@ export namespace ZenData {
export const list = fn(z.void(), () => {
const json = JSON.parse(
Resource.ZEN_MODELS1.value + Resource.ZEN_MODELS2.value + Resource.ZEN_MODELS3.value + Resource.ZEN_MODELS4.value,
Resource.ZEN_MODELS1.value +
Resource.ZEN_MODELS2.value +
Resource.ZEN_MODELS3.value +
Resource.ZEN_MODELS4.value +
Resource.ZEN_MODELS5.value,
)
return ModelsSchema.parse(json)
})

View File

@@ -50,10 +50,6 @@ declare module "sst" {
"type": "sst.sst.Secret"
"value": string
}
"Enterprise": {
"type": "sst.cloudflare.SolidStart"
"url": string
}
"GITHUB_APP_ID": {
"type": "sst.sst.Secret"
"value": string
@@ -94,6 +90,10 @@ declare module "sst" {
"type": "sst.sst.Linkable"
"value": string
}
"Teams": {
"type": "sst.cloudflare.SolidStart"
"url": string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
@@ -114,6 +114,10 @@ declare module "sst" {
"type": "sst.sst.Secret"
"value": string
}
"ZEN_MODELS5": {
"type": "sst.sst.Secret"
"value": string
}
}
}
// cloudflare

View File

@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.0.137",
"version": "1.0.168",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",

View File

@@ -50,10 +50,6 @@ declare module "sst" {
"type": "sst.sst.Secret"
"value": string
}
"Enterprise": {
"type": "sst.cloudflare.SolidStart"
"url": string
}
"GITHUB_APP_ID": {
"type": "sst.sst.Secret"
"value": string
@@ -94,6 +90,10 @@ declare module "sst" {
"type": "sst.sst.Linkable"
"value": string
}
"Teams": {
"type": "sst.cloudflare.SolidStart"
"url": string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
@@ -114,6 +114,10 @@ declare module "sst" {
"type": "sst.sst.Secret"
"value": string
}
"ZEN_MODELS5": {
"type": "sst.sst.Secret"
"value": string
}
}
}
// cloudflare

View File

@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.0.137",
"version": "1.0.168",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",

View File

@@ -50,10 +50,6 @@ declare module "sst" {
"type": "sst.sst.Secret"
"value": string
}
"Enterprise": {
"type": "sst.cloudflare.SolidStart"
"url": string
}
"GITHUB_APP_ID": {
"type": "sst.sst.Secret"
"value": string
@@ -94,6 +90,10 @@ declare module "sst" {
"type": "sst.sst.Linkable"
"value": string
}
"Teams": {
"type": "sst.cloudflare.SolidStart"
"url": string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
@@ -114,6 +114,10 @@ declare module "sst" {
"type": "sst.sst.Secret"
"value": string
}
"ZEN_MODELS5": {
"type": "sst.sst.Secret"
"value": string
}
}
}
// cloudflare

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">
<body class="antialiased overscroll-none select-none text-12-regular overflow-hidden">
<script>
;(function () {
const savedTheme = localStorage.getItem("theme") || "oc-1"
@@ -22,7 +22,7 @@
})()
</script>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<div id="root" class="flex flex-col h-screen"></div>
<script src="/src/entry.tsx" type="module"></script>
</body>
</html>

View File

@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/desktop",
"version": "1.0.137",
"version": "1.0.168",
"description": "",
"type": "module",
"exports": {
@@ -8,7 +8,7 @@
"./vite": "./vite.js"
},
"scripts": {
"typecheck": "tsgo --noEmit",
"typecheck": "tsgo -b",
"start": "vite",
"dev": "vite",
"build": "vite build",
@@ -35,10 +35,12 @@
"@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": "4.3.3",
"@solid-primitives/storage": "catalog:",
"@solid-primitives/websocket": "1.3.1",
"@solidjs/meta": "catalog:",
"@solidjs/router": "catalog:",
@@ -54,6 +56,7 @@
"solid-js": "catalog:",
"solid-list": "catalog:",
"tailwindcss": "catalog:",
"virtua": "catalog:"
"virtua": "catalog:",
"zod": "catalog:"
}
}

View File

@@ -1,67 +0,0 @@
import "@/index.css"
import { Router, Route, Navigate } from "@solidjs/router"
import { MetaProvider } from "@solidjs/meta"
import { Font } from "@opencode-ai/ui/font"
import { Favicon } from "@opencode-ai/ui/favicon"
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
import { Diff } from "@opencode-ai/ui/diff"
import { GlobalSyncProvider, useGlobalSync } from "./context/global-sync"
import Layout from "@/pages/layout"
import DirectoryLayout from "@/pages/directory-layout"
import Session from "@/pages/session"
import { LayoutProvider } from "./context/layout"
import { GlobalSDKProvider } from "./context/global-sdk"
import { SessionProvider } from "./context/session"
import { base64Encode } from "@opencode-ai/util/encode"
import { createMemo, Show } from "solid-js"
const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
const url =
new URLSearchParams(document.location.search).get("url") ||
(location.hostname.includes("opencode.ai") || location.hostname.includes("localhost")
? `http://${host}:${port}`
: "/")
export function DesktopInterface() {
return (
<MarkedProvider>
<DiffComponentProvider component={Diff}>
<GlobalSDKProvider url={url}>
<GlobalSyncProvider>
<LayoutProvider>
<MetaProvider>
<Font />
<Router root={Layout}>
<Route
path="/"
component={() => {
const globalSync = useGlobalSync()
const slug = createMemo(() => base64Encode(globalSync.data.defaultProject!.worktree))
return <Navigate href={`${slug()}/session`} />
}}
/>
<Route path="/:dir" component={DirectoryLayout}>
<Route path="/" component={() => <Navigate href="session" />} />
<Route
path="/session/:id?"
component={(p) => (
<Show when={p.params.id || true} keyed>
<SessionProvider>
<Session />
</SessionProvider>
</Show>
)}
/>
</Route>
</Router>
</MetaProvider>
</LayoutProvider>
</GlobalSyncProvider>
</GlobalSDKProvider>
</DiffComponentProvider>
</MarkedProvider>
)
}

View File

@@ -1,14 +0,0 @@
import { createContext } from "solid-js"
import { useContext } from "solid-js"
export interface Platform {}
const PlatformContext = createContext<Platform>()
export const PlatformProvider = PlatformContext.Provider
export function usePlatform() {
const ctx = useContext(PlatformContext)
if (!ctx) throw new Error("usePlatform must be used within a PlatformProvider")
return ctx
}

View File

@@ -0,0 +1,88 @@
import "@/index.css"
import { ErrorBoundary, Show } from "solid-js"
import { Router, Route, Navigate } from "@solidjs/router"
import { MetaProvider } from "@solidjs/meta"
import { Font } from "@opencode-ai/ui/font"
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
import { CodeComponentProvider } from "@opencode-ai/ui/context/code"
import { Diff } from "@opencode-ai/ui/diff"
import { Code } from "@opencode-ai/ui/code"
import { GlobalSyncProvider } from "@/context/global-sync"
import { LayoutProvider } from "@/context/layout"
import { GlobalSDKProvider } from "@/context/global-sdk"
import { TerminalProvider } from "@/context/terminal"
import { PromptProvider } from "@/context/prompt"
import { NotificationProvider } from "@/context/notification"
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
import { CommandProvider } from "@/context/command"
import Layout from "@/pages/layout"
import Home from "@/pages/home"
import DirectoryLayout from "@/pages/directory-layout"
import Session from "@/pages/session"
import { ErrorPage } from "./pages/error"
declare global {
interface Window {
__OPENCODE__?: { updaterEnabled?: boolean; port?: number }
}
}
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 =
new URLSearchParams(document.location.search).get("url") ||
(location.hostname.includes("opencode.ai") || location.hostname.includes("localhost")
? `http://${host}:${port}`
: "/")
export function App() {
return (
<MetaProvider>
<Font />
<ErrorBoundary fallback={ErrorPage}>
<DialogProvider>
<MarkedProvider>
<DiffComponentProvider component={Diff}>
<CodeComponentProvider component={Code}>
<GlobalSDKProvider url={url}>
<GlobalSyncProvider>
<LayoutProvider>
<NotificationProvider>
<Router
root={(props) => (
<CommandProvider>
<Layout>{props.children}</Layout>
</CommandProvider>
)}
>
<Route path="/" component={Home} />
<Route path="/:dir" component={DirectoryLayout}>
<Route path="/" component={() => <Navigate href="session" />} />
<Route
path="/session/:id?"
component={(p) => (
<Show when={p.params.id || true} keyed>
<TerminalProvider>
<PromptProvider>
<Session />
</PromptProvider>
</TerminalProvider>
</Show>
)}
/>
</Route>
</Router>
</NotificationProvider>
</LayoutProvider>
</GlobalSyncProvider>
</GlobalSDKProvider>
</CodeComponentProvider>
</DiffComponentProvider>
</MarkedProvider>
</DialogProvider>
</ErrorBoundary>
</MetaProvider>
)
}

View File

@@ -0,0 +1,381 @@
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 { DialogSelectModel } from "./dialog-select-model"
export function DialogConnectProvider(props: { provider: string }) {
const dialog = useDialog()
const globalSync = useGlobalSync()
const globalSDK = useGlobalSDK()
const platform = usePlatform()
const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === props.provider)!)
const methods = createMemo(
() =>
globalSync.data.provider_auth[props.provider] ?? [
{
type: "api",
label: "API key",
},
],
)
const [store, setStore] = createStore({
methodIndex: undefined as undefined | number,
authorization: undefined as undefined | ProviderAuthAuthorization,
state: "pending" as undefined | "pending" | "complete" | "error",
error: undefined as string | undefined,
})
const method = createMemo(() => (store.methodIndex !== undefined ? methods().at(store.methodIndex!) : undefined))
async function selectMethod(index: number) {
const method = methods()[index]
setStore(
produce((draft) => {
draft.methodIndex = index
draft.authorization = undefined
draft.state = undefined
draft.error = undefined
}),
)
if (method.type === "oauth") {
setStore("state", "pending")
const start = Date.now()
await globalSDK.client.provider.oauth
.authorize(
{
providerID: props.provider,
method: index,
},
{ throwOnError: true },
)
.then((x) => {
const elapsed = Date.now() - start
const delay = 1000 - elapsed
if (delay > 0) {
setTimeout(() => {
setStore("state", "complete")
setStore("authorization", x.data!)
}, delay)
return
}
setStore("state", "complete")
setStore("authorization", x.data!)
})
.catch((e) => {
setStore("state", "error")
setStore("error", String(e))
})
}
}
let listRef: ListRef | undefined
function handleKey(e: KeyboardEvent) {
if (e.key === "Enter" && e.target instanceof HTMLInputElement) {
return
}
if (e.key === "Escape") return
listRef?.onKeyDown(e)
}
onMount(() => {
if (methods().length === 1) {
selectMethod(0)
}
document.addEventListener("keydown", handleKey)
onCleanup(() => {
document.removeEventListener("keydown", handleKey)
})
})
async function complete() {
await globalSDK.client.global.dispose()
dialog.close()
showToast({
variant: "success",
icon: "circle-check",
title: `${provider().name} connected`,
description: `${provider().name} models are now available to use.`,
})
}
function goBack() {
if (methods().length === 1) {
dialog.show(() => <DialogSelectProvider />)
return
}
if (store.authorization) {
setStore("authorization", undefined)
setStore("methodIndex", undefined)
return
}
if (store.methodIndex) {
setStore("methodIndex", undefined)
return
}
dialog.show(() => <DialogSelectProvider />)
}
return (
<Dialog title={<IconButton tabIndex={-1} icon="arrow-left" variant="ghost" onClick={goBack} />}>
<div class="flex flex-col gap-6 px-2.5 pb-3">
<div class="px-2.5 flex gap-4 items-center">
<ProviderIcon id={props.provider as IconName} class="size-5 shrink-0 icon-strong-base" />
<div class="text-16-medium text-text-strong">
<Switch>
<Match when={props.provider === "anthropic" && method()?.label?.toLowerCase().includes("max")}>
Login with Claude Pro/Max
</Match>
<Match when={true}>Connect {provider().name}</Match>
</Switch>
</div>
</div>
<div class="px-2.5 pb-10 flex flex-col gap-6">
<Switch>
<Match when={store.methodIndex === undefined}>
<div class="text-14-regular text-text-base">Select login method for {provider().name}.</div>
<div class="">
<List
ref={(ref) => (listRef = ref)}
items={methods}
key={(m) => m?.label}
onSelect={async (method, index) => {
if (!method) return
selectMethod(index)
}}
>
{(i) => (
<div class="w-full flex items-center gap-x-4">
<div class="w-4 h-2 rounded-[1px] bg-input-base shadow-xs-border-base flex items-center justify-center">
<div class="w-2.5 h-0.5 bg-icon-strong-base hidden" data-slot="list-item-extra-icon" />
</div>
<span>{i.label}</span>
</div>
)}
</List>
</div>
</Match>
<Match when={store.state === "pending"}>
<div class="text-14-regular text-text-base">
<div class="flex items-center gap-x-4">
<Spinner />
<span>Authorization in progress...</span>
</div>
</div>
</Match>
<Match when={store.state === "error"}>
<div class="text-14-regular text-text-base">
<div class="flex items-center gap-x-4">
<Icon name="circle-ban-sign" class="text-icon-critical-base" />
<span>Authorization failed: {store.error}</span>
</div>
</div>
</Match>
<Match when={method()?.type === "api"}>
{iife(() => {
const [formStore, setFormStore] = createStore({
value: "",
error: undefined as string | undefined,
})
async function handleSubmit(e: SubmitEvent) {
e.preventDefault()
const form = e.currentTarget as HTMLFormElement
const formData = new FormData(form)
const apiKey = formData.get("apiKey") as string
if (!apiKey?.trim()) {
setFormStore("error", "API key is required")
return
}
setFormStore("error", undefined)
await globalSDK.client.auth.set({
providerID: props.provider,
auth: {
type: "api",
key: apiKey,
},
})
await complete()
}
return (
<div class="flex flex-col gap-6">
<Switch>
<Match when={provider().id === "opencode"}>
<div class="flex flex-col gap-4">
<div class="text-14-regular text-text-base">
OpenCode Zen gives you access to a curated set of reliable optimized models for coding
agents.
</div>
<div class="text-14-regular text-text-base">
With a single API key you'll get access to models such as Claude, GPT, Gemini, GLM and more.
</div>
<div class="text-14-regular text-text-base">
Visit{" "}
<Link href="https://opencode.ai/zen" tabIndex={-1}>
opencode.ai/zen
</Link>{" "}
to collect your API key.
</div>
</div>
</Match>
<Match when={true}>
<div class="text-14-regular text-text-base">
Enter your {provider().name} API key to connect your account and use {provider().name} models
in OpenCode.
</div>
</Match>
</Switch>
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
<TextField
autofocus
type="text"
label={`${provider().name} API key`}
placeholder="API key"
name="apiKey"
value={formStore.value}
onChange={setFormStore.bind(null, "value")}
validationState={formStore.error ? "invalid" : undefined}
error={formStore.error}
/>
<Button class="w-auto" type="submit" size="large" variant="primary">
Submit
</Button>
</form>
</div>
)
})}
</Match>
<Match when={method()?.type === "oauth"}>
<Switch>
<Match when={store.authorization?.method === "code"}>
{iife(() => {
const [formStore, setFormStore] = createStore({
value: "",
error: undefined as string | undefined,
})
onMount(() => {
if (store.authorization?.method === "code" && store.authorization?.url) {
platform.openLink(store.authorization.url)
}
})
async function handleSubmit(e: SubmitEvent) {
e.preventDefault()
const form = e.currentTarget as HTMLFormElement
const formData = new FormData(form)
const code = formData.get("code") as string
if (!code?.trim()) {
setFormStore("error", "Authorization code is required")
return
}
setFormStore("error", undefined)
const { error } = await globalSDK.client.provider.oauth.callback({
providerID: props.provider,
method: store.methodIndex,
code,
})
if (!error) {
await complete()
return
}
setFormStore("error", "Invalid authorization code")
}
return (
<div class="flex flex-col gap-6">
<div class="text-14-regular text-text-base">
Visit <Link href={store.authorization!.url}>this link</Link> to collect your authorization
code to connect your account and use {provider().name} models in OpenCode.
</div>
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
<TextField
autofocus
type="text"
label={`${method()?.label} authorization code`}
placeholder="Authorization code"
name="code"
value={formStore.value}
onChange={setFormStore.bind(null, "value")}
validationState={formStore.error ? "invalid" : undefined}
error={formStore.error}
/>
<Button class="w-auto" type="submit" size="large" variant="primary">
Submit
</Button>
</form>
</div>
)
})}
</Match>
<Match when={store.authorization?.method === "auto"}>
{iife(() => {
const code = createMemo(() => {
const instructions = store.authorization?.instructions
if (instructions?.includes(":")) {
return instructions?.split(":")[1]?.trim()
}
return instructions
})
onMount(async () => {
const result = await globalSDK.client.provider.oauth.callback({
providerID: props.provider,
method: store.methodIndex,
})
if (result.error) {
// TODO: show error
dialog.close()
return
}
await complete()
})
return (
<div class="flex flex-col gap-6">
<div class="text-14-regular text-text-base">
Visit <Link href={store.authorization!.url}>this link</Link> and enter the code below to
connect your account and use {provider().name} models in OpenCode.
</div>
<TextField label="Confirmation code" class="font-mono" value={code()} readOnly copyable />
<div class="text-14-regular text-text-base flex items-center gap-4">
<Spinner />
<span>Waiting for authorization...</span>
</div>
</div>
)
})}
</Match>
</Switch>
</Match>
</Switch>
</div>
</div>
</Dialog>
)
}

View File

@@ -0,0 +1,50 @@
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"
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}`}
items={local.model.list()}
filterKeys={["provider.name", "name", "id"]}
sortBy={(a, b) => a.name.localeCompare(b.name)}
groupBy={(x) => x.provider.name}
sortGroupsBy={(a, b) => {
const aProvider = a.items[0].provider.id
const bProvider = b.items[0].provider.id
if (popularProviders.includes(aProvider) && !popularProviders.includes(bProvider)) return -1
if (!popularProviders.includes(aProvider) && popularProviders.includes(bProvider)) return 1
return popularProviders.indexOf(aProvider) - popularProviders.indexOf(bProvider)
}}
onSelect={(x) => {
if (!x) return
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">
<span>{i.name}</span>
<div onClick={(e) => e.stopPropagation()}>
<Switch
checked={!!local.model.visible({ modelID: i.id, providerID: i.provider.id })}
onChange={(checked) => {
local.model.setVisibility({ modelID: i.id, providerID: i.provider.id }, checked)
}}
/>
</div>
</div>
)}
</List>
</Dialog>
)
}

View File

@@ -0,0 +1,49 @@
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 { useParams } from "@solidjs/router"
import { createMemo } from "solid-js"
export function DialogSelectFile() {
const layout = useLayout()
const local = useLocal()
const dialog = useDialog()
const params = useParams()
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const tabs = createMemo(() => layout.tabs(sessionKey()))
return (
<Dialog title="Select file">
<List
class="px-2.5"
search={{ placeholder: "Search files", autofocus: true }}
emptyMessage="No files found"
items={local.file.searchFiles}
key={(x) => x}
onSelect={(path) => {
if (path) {
tabs().open("file://" + path)
}
dialog.close()
}}
>
{(i) => (
<div class="w-full flex items-center justify-between rounded-md">
<div class="flex items-center gap-x-2 grow min-w-0">
<FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
<div class="flex items-center text-14-regular">
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
{getDirectory(i)}
</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
</div>
</div>
</div>
)}
</List>
</Dialog>
)
}

View File

@@ -0,0 +1,119 @@
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 { Dialog } from "@opencode-ai/ui/dialog"
import { List, 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 { DialogConnectProvider } from "./dialog-connect-provider"
export const DialogSelectModelUnpaid: Component = () => {
const local = useLocal()
const dialog = useDialog()
const providers = useProviders()
let listRef: ListRef | undefined
const handleKey = (e: KeyboardEvent) => {
if (e.key === "Escape") return
listRef?.onKeyDown(e)
}
onMount(() => {
document.addEventListener("keydown", handleKey)
onCleanup(() => {
document.removeEventListener("keydown", handleKey)
})
})
return (
<Dialog title="Select model">
<div class="flex flex-col gap-3 px-2.5">
<div class="text-14-medium text-text-base px-2.5">Free models provided by OpenCode</div>
<List
ref={(ref) => (listRef = ref)}
items={local.model.list}
current={local.model.current()}
key={(x) => `${x.provider.id}:${x.id}`}
onSelect={(x) => {
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
recent: true,
})
dialog.close()
}}
>
{(i) => (
<div class="w-full flex items-center gap-x-2.5">
<span>{i.name}</span>
<Tag>Free</Tag>
<Show when={i.latest}>
<Tag>Latest</Tag>
</Show>
</div>
)}
</List>
<div />
<div />
</div>
<div class="px-1.5 pb-1.5">
<div class="w-full rounded-sm border border-border-weak-base bg-surface-raised-base">
<div class="w-full flex flex-col items-start gap-4 px-1.5 pt-4 pb-4">
<div class="px-2 text-14-medium text-text-base">Add more models from popular providers</div>
<div class="w-full">
<List
class="w-full"
key={(x) => x?.id}
items={providers.popular}
activeIcon="plus-small"
sortBy={(a, b) => {
if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
return a.name.localeCompare(b.name)
}}
onSelect={(x) => {
if (!x) return
dialog.show(() => <DialogConnectProvider provider={x.id} />)
}}
>
{(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",
}}
/>
<span>{i.name}</span>
<Show when={i.id === "opencode"}>
<Tag>Recommended</Tag>
</Show>
<Show when={i.id === "anthropic"}>
<div class="text-14-regular text-text-weak">Connect with Claude Pro/Max or API key</div>
</Show>
</div>
)}
</List>
<Button
variant="ghost"
class="w-full justify-start px-[11px] py-3.5 gap-4.5 text-14-medium"
icon="dot-grid"
onClick={() => {
dialog.show(() => <DialogSelectProvider />)
}}
>
View all providers
</Button>
</div>
</div>
</div>
</div>
</Dialog>
)
}

View File

@@ -0,0 +1,84 @@
import { Component, createMemo, Show } from "solid-js"
import { useLocal } from "@/context/local"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { popularProviders } from "@/hooks/use-providers"
import { Button } from "@opencode-ai/ui/button"
import { Tag } from "@opencode-ai/ui/tag"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { DialogSelectProvider } from "./dialog-select-provider"
import { DialogManageModels } from "./dialog-manage-models"
export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
const local = useLocal()
const dialog = useDialog()
const models = createMemo(() =>
local.model
.list()
.filter((m) => local.model.visible({ modelID: m.id, providerID: m.provider.id }))
.filter((m) => (props.provider ? m.provider.id === props.provider : true)),
)
return (
<Dialog
title="Select model"
action={
<Button
class="h-7 -my-1 text-14-medium"
icon="plus-small"
tabIndex={-1}
onClick={() => dialog.show(() => <DialogSelectProvider />)}
>
Connect provider
</Button>
}
>
<List
class="px-2.5"
search={{ placeholder: "Search models", autofocus: true }}
emptyMessage="No model results"
key={(x) => `${x.provider.id}:${x.id}`}
items={models}
current={local.model.current()}
filterKeys={["provider.name", "name", "id"]}
sortBy={(a, b) => a.name.localeCompare(b.name)}
groupBy={(x) => x.provider.name}
sortGroupsBy={(a, b) => {
if (a.category === "Recent" && b.category !== "Recent") return -1
if (b.category === "Recent" && a.category !== "Recent") return 1
const aProvider = a.items[0].provider.id
const bProvider = b.items[0].provider.id
if (popularProviders.includes(aProvider) && !popularProviders.includes(bProvider)) return -1
if (!popularProviders.includes(aProvider) && popularProviders.includes(bProvider)) return 1
return popularProviders.indexOf(aProvider) - popularProviders.indexOf(bProvider)
}}
onSelect={(x) => {
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
recent: true,
})
dialog.close()
}}
>
{(i) => (
<div class="w-full flex items-center gap-x-2.5">
<span>{i.name}</span>
<Show when={i.provider.id === "opencode" && (!i.cost || i.cost?.input === 0)}>
<Tag>Free</Tag>
</Show>
<Show when={i.latest}>
<Tag>Latest</Tag>
</Show>
</div>
)}
</List>
<Button
variant="ghost"
class="ml-3 mt-5 mb-6 text-text-base self-start"
onClick={() => dialog.show(() => <DialogManageModels />)}
>
Manage models
</Button>
</Dialog>
)
}

View File

@@ -0,0 +1,64 @@
import { Component, Show } from "solid-js"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { popularProviders, useProviders } from "@/hooks/use-providers"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { Tag } from "@opencode-ai/ui/tag"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { IconName } from "@opencode-ai/ui/icons/provider"
import { DialogConnectProvider } from "./dialog-connect-provider"
export const DialogSelectProvider: Component = () => {
const dialog = useDialog()
const providers = useProviders()
return (
<Dialog title="Connect provider">
<List
class="px-2.5"
search={{ placeholder: "Search providers", autofocus: true }}
activeIcon="plus-small"
key={(x) => x?.id}
items={providers.all}
filterKeys={["id", "name"]}
groupBy={(x) => (popularProviders.includes(x.id) ? "Popular" : "Other")}
sortBy={(a, b) => {
if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
return a.name.localeCompare(b.name)
}}
sortGroupsBy={(a, b) => {
if (a.category === "Popular" && b.category !== "Popular") return -1
if (b.category === "Popular" && a.category !== "Popular") return 1
return 0
}}
onSelect={(x) => {
if (!x) return
dialog.show(() => <DialogConnectProvider provider={x.id} />)
}}
>
{(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",
}}
/>
<span>{i.name}</span>
<Show when={i.id === "opencode"}>
<Tag>Recommended</Tag>
</Show>
<Show when={i.id === "anthropic"}>
<div class="text-14-regular text-text-weak">Connect with Claude Pro/Max or API key</div>
</Show>
</div>
)}
</List>
</Dialog>
)
}

View File

@@ -0,0 +1,147 @@
import { useGlobalSync } from "@/context/global-sync"
import { useGlobalSDK } from "@/context/global-sdk"
import { useLayout } from "@/context/layout"
import { Session } from "@opencode-ai/sdk/v2/client"
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import { Mark } from "@opencode-ai/ui/logo"
import { Popover } from "@opencode-ai/ui/popover"
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 { getFilename } from "@opencode-ai/util/path"
import { A, useParams } from "@solidjs/router"
import { createMemo, createResource, Show } from "solid-js"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { iife } from "@opencode-ai/util/iife"
export function Header(props: {
navigateToProject: (directory: string) => void
navigateToSession: (session: Session | undefined) => void
}) {
const globalSync = useGlobalSync()
const globalSDK = useGlobalSDK()
const layout = useLayout()
const params = useParams()
const currentDirectory = createMemo(() => base64Decode(params.dir ?? ""))
const store = createMemo(() => globalSync.child(currentDirectory())[0])
const sessions = createMemo(() => store().session ?? [])
const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
const shareEnabled = createMemo(() => store().config.share !== "disabled")
return (
<header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex" data-tauri-drag-region>
<A
href="/"
classList={{
"w-12 shrink-0 px-4 py-3.5": true,
"flex items-center justify-start self-stretch": true,
"border-r border-border-weak-base": true,
}}
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
data-tauri-drag-region
>
<Mark class="shrink-0" />
</A>
<div class="pl-4 px-6 flex items-center justify-between gap-4 w-full">
<Show when={params.dir && layout.projects.list().length > 0}>
<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>
<Select
options={sessions()}
current={currentSession()}
placeholder="New session"
label={(x) => x.title}
value={(x) => x.id}
onSelect={props.navigateToSession}
class="text-14-regular text-text-base max-w-md"
variant="ghost"
/>
</div>
<Show when={currentSession()}>
<Button as={A} href={`/${params.dir}/session`} icon="plus-small">
New session
</Button>
</Show>
</div>
<div class="flex items-center gap-4">
<Tooltip
class="shrink-0"
value={
<div class="flex items-center gap-2">
<span>Toggle terminal</span>
<span class="text-icon-base text-12-medium">Ctrl `</span>
</div>
}
>
<Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
size="small"
name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
class="group-hover/terminal-toggle:hidden"
/>
<Icon
size="small"
name="layout-bottom-partial"
class="hidden group-hover/terminal-toggle:inline-block"
/>
<Icon
size="small"
name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
class="hidden group-active/terminal-toggle:inline-block"
/>
</div>
</Button>
</Tooltip>
<Show when={shareEnabled() && currentSession()}>
<Popover
title="Share session"
trigger={
<Tooltip class="shrink-0" value="Share session">
<IconButton icon="share" variant="ghost" class="" />
</Tooltip>
}
>
{iife(() => {
const [url] = createResource(
() => currentSession(),
async (session) => {
if (!session) return
let shareURL = session.share?.url
if (!shareURL) {
shareURL = await globalSDK.client.session
.share({ sessionID: session.id, directory: currentDirectory() })
.then((r) => r.data?.share?.url)
}
return shareURL
},
)
return <Show when={url()}>{(url) => <TextField value={url()} readOnly copyable class="w-72" />}</Show>
})}
</Popover>
</Show>
</div>
</Show>
</div>
</header>
)
}

View File

@@ -0,0 +1,17 @@
import { ComponentProps, splitProps } from "solid-js"
import { usePlatform } from "@/context/platform"
export interface LinkProps extends ComponentProps<"button"> {
href: string
}
export function Link(props: LinkProps) {
const platform = usePlatform()
const [local, rest] = splitProps(props, ["href", "children"])
return (
<button class="text-text-strong underline" onClick={() => platform.openLink(local.href)} {...rest}>
{local.children}
</button>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,8 @@ import { Ghostty, Terminal as Term, FitAddon } from "ghostty-web"
import { ComponentProps, onCleanup, onMount, splitProps } from "solid-js"
import { useSDK } from "@/context/sdk"
import { SerializeAddon } from "@/addons/serialize"
import { LocalPTY } from "@/context/session"
import { LocalPTY } from "@/context/terminal"
import { usePrefersDark } from "@solid-primitives/media"
export interface TerminalProps extends ComponentProps<"div"> {
pty: LocalPTY
@@ -21,6 +22,7 @@ export const Terminal = (props: TerminalProps) => {
let serializeAddon: SerializeAddon
let fitAddon: FitAddon
let handleResize: () => void
const prefersDark = usePrefersDark()
onMount(async () => {
ghostty = await Ghostty.load()
@@ -29,12 +31,19 @@ export const Terminal = (props: TerminalProps) => {
term = new Term({
cursorBlink: true,
fontSize: 14,
fontFamily: "TX-02, monospace",
fontFamily: "IBM Plex Mono, monospace",
allowTransparency: true,
theme: {
background: "#191515",
foreground: "#d4d4d4",
},
theme: prefersDark()
? {
background: "#191515",
foreground: "#d4d4d4",
cursor: "#d4d4d4",
}
: {
background: "#fcfcfc",
foreground: "#211e1e",
cursor: "#211e1e",
},
scrollback: 10_000,
ghostty,
})
@@ -139,6 +148,7 @@ export const Terminal = (props: TerminalProps) => {
<div
ref={container}
data-component="terminal"
data-prevent-autofocus
classList={{
...(local.classList ?? {}),
"size-full px-6 py-3 font-mono": true,

View File

@@ -0,0 +1,239 @@
import { createMemo, createSignal, onCleanup, onMount, Show, type Accessor } from "solid-js"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
export type KeybindConfig = string
export interface Keybind {
key: string
ctrl: boolean
meta: boolean
shift: boolean
alt: boolean
}
export interface CommandOption {
id: string
title: string
description?: string
category?: string
keybind?: KeybindConfig
slash?: string
suggested?: boolean
disabled?: boolean
onSelect?: (source?: "palette" | "keybind" | "slash") => void
}
export function parseKeybind(config: string): Keybind[] {
if (!config || config === "none") return []
return config.split(",").map((combo) => {
const parts = combo.trim().toLowerCase().split("+")
const keybind: Keybind = {
key: "",
ctrl: false,
meta: false,
shift: false,
alt: false,
}
for (const part of parts) {
switch (part) {
case "ctrl":
case "control":
keybind.ctrl = true
break
case "meta":
case "cmd":
case "command":
keybind.meta = true
break
case "mod":
if (IS_MAC) keybind.meta = true
else keybind.ctrl = true
break
case "alt":
case "option":
keybind.alt = true
break
case "shift":
keybind.shift = true
break
default:
keybind.key = part
break
}
}
return keybind
})
}
export function matchKeybind(keybinds: Keybind[], event: KeyboardEvent): boolean {
const eventKey = event.key.toLowerCase()
for (const kb of keybinds) {
const keyMatch = kb.key === eventKey
const ctrlMatch = kb.ctrl === (event.ctrlKey || false)
const metaMatch = kb.meta === (event.metaKey || false)
const shiftMatch = kb.shift === (event.shiftKey || false)
const altMatch = kb.alt === (event.altKey || false)
if (keyMatch && ctrlMatch && metaMatch && shiftMatch && altMatch) {
return true
}
}
return false
}
export function formatKeybind(config: string): string {
if (!config || config === "none") return ""
const keybinds = parseKeybind(config)
if (keybinds.length === 0) return ""
const kb = keybinds[0]
const parts: string[] = []
if (kb.ctrl) parts.push(IS_MAC ? "⌃" : "Ctrl")
if (kb.alt) parts.push(IS_MAC ? "⌥" : "Alt")
if (kb.shift) parts.push(IS_MAC ? "⇧" : "Shift")
if (kb.meta) parts.push(IS_MAC ? "⌘" : "Meta")
if (kb.key) {
const displayKey = kb.key.length === 1 ? kb.key.toUpperCase() : kb.key.charAt(0).toUpperCase() + kb.key.slice(1)
parts.push(displayKey)
}
return IS_MAC ? parts.join("") : parts.join("+")
}
function DialogCommand(props: { options: CommandOption[] }) {
const dialog = useDialog()
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)}
key={(x) => x?.id}
filterKeys={["title", "description", "category"]}
groupBy={(x) => x.category ?? ""}
onSelect={(option) => {
if (option) {
dialog.close()
option.onSelect?.("palette")
}
}}
>
{(option) => (
<div class="w-full flex items-center justify-between gap-4">
<div class="flex items-center gap-2 min-w-0">
<span class="text-14-regular text-text-strong whitespace-nowrap">{option.title}</span>
<Show when={option.description}>
<span class="text-14-regular text-text-weak truncate">{option.description}</span>
</Show>
</div>
<Show when={option.keybind}>
<span class="text-12-regular text-text-subtle shrink-0">{formatKeybind(option.keybind!)}</span>
</Show>
</div>
)}
</List>
</Dialog>
)
}
export const { use: useCommand, provider: CommandProvider } = createSimpleContext({
name: "Command",
init: () => {
const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
const [suspendCount, setSuspendCount] = createSignal(0)
const dialog = useDialog()
const options = createMemo(() => {
const all = registrations().flatMap((x) => x())
const suggested = all.filter((x) => x.suggested && !x.disabled)
return [
...suggested.map((x) => ({
...x,
id: "suggested." + x.id,
category: "Suggested",
})),
...all,
]
})
const suspended = () => suspendCount() > 0
const showPalette = () => {
if (!dialog.active) {
dialog.show(() => <DialogCommand options={options().filter((x) => !x.disabled)} />)
}
}
const handleKeyDown = (event: KeyboardEvent) => {
if (suspended()) return
const paletteKeybinds = parseKeybind("mod+shift+p")
if (matchKeybind(paletteKeybinds, event)) {
event.preventDefault()
showPalette()
return
}
for (const option of options()) {
if (option.disabled) continue
if (!option.keybind) continue
const keybinds = parseKeybind(option.keybind)
if (matchKeybind(keybinds, event)) {
event.preventDefault()
option.onSelect?.("keybind")
return
}
}
}
onMount(() => {
document.addEventListener("keydown", handleKeyDown)
})
onCleanup(() => {
document.removeEventListener("keydown", handleKeyDown)
})
return {
register(cb: () => CommandOption[]) {
const results = createMemo(cb)
setRegistrations((arr) => [results, ...arr])
onCleanup(() => {
setRegistrations((arr) => arr.filter((x) => x !== results))
})
},
trigger(id: string, source?: "palette" | "keybind" | "slash") {
for (const option of options()) {
if (option.id === id || option.id === "suggested." + id) {
option.onSelect?.(source)
return
}
}
},
show: showPalette,
keybinds(enabled: boolean) {
setSuspendCount((count) => count + (enabled ? -1 : 1))
},
suspended,
get options() {
return options()
},
}
},
})

View File

@@ -10,6 +10,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
const sdk = createOpencodeClient({
baseUrl: props.url,
signal: abort.signal,
throwOnError: true,
})
const emitter = createGlobalEmitter<{
@@ -19,7 +20,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
sdk.global.event().then(async (events) => {
for await (const event of events.stream) {
// console.log("event", event)
emitter.emit(event.directory, event.payload)
emitter.emit(event.directory ?? "global", event.payload)
}
})

View File

@@ -1,28 +1,33 @@
import type {
Message,
Agent,
Provider,
Session,
Part,
Config,
Path,
File,
FileNode,
Project,
FileDiff,
Todo,
SessionStatus,
} from "@opencode-ai/sdk/v2"
import {
type Message,
type Agent,
type Session,
type Part,
type Config,
type Path,
type File,
type FileNode,
type Project,
type FileDiff,
type Todo,
type SessionStatus,
type ProviderListResponse,
type ProviderAuthResponse,
type Command,
createOpencodeClient,
} from "@opencode-ai/sdk/v2/client"
import { createStore, produce, reconcile } from "solid-js/store"
import { Binary } from "@opencode-ai/util/binary"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useGlobalSDK } from "./global-sdk"
import { ErrorPage, type InitError } from "../pages/error"
import { createContext, useContext, onMount, type ParentProps, Switch, Match } from "solid-js"
type State = {
ready: boolean
provider: Provider[]
agent: Agent[]
project: Project
command: Command[]
project: string
provider: ProviderListResponse
config: Config
path: Path
session: Session[]
@@ -46,138 +51,303 @@ type State = {
changes: File[]
}
export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimpleContext({
name: "GlobalSync",
init: () => {
const [globalStore, setGlobalStore] = createStore<{
ready: boolean
defaultProject?: Project // TODO: remove this when we can select projects
projects: Project[]
children: Record<string, State>
}>({
ready: false,
projects: [],
children: {},
})
function createGlobalSync() {
const globalSDK = useGlobalSDK()
const [globalStore, setGlobalStore] = createStore<{
ready: boolean
error?: InitError
path: Path
project: Project[]
provider: ProviderListResponse
provider_auth: ProviderAuthResponse
children: Record<string, State>
}>({
ready: false,
path: { state: "", config: "", worktree: "", directory: "", home: "" },
project: [],
provider: { all: [], connected: [], default: {} },
provider_auth: {},
children: {},
})
const children: Record<string, ReturnType<typeof createStore<State>>> = {}
function child(directory: string) {
if (!children[directory]) {
setGlobalStore("children", directory, {
project: { id: "", worktree: "", time: { created: 0, initialized: 0 } },
config: {},
path: { state: "", config: "", worktree: "", directory: "" },
ready: false,
agent: [],
provider: [],
session: [],
session_status: {},
session_diff: {},
todo: {},
limit: 10,
message: {},
part: {},
node: [],
changes: [],
})
children[directory] = createStore(globalStore.children[directory])
}
return children[directory]
const children: Record<string, ReturnType<typeof createStore<State>>> = {}
function child(directory: string) {
if (!children[directory]) {
setGlobalStore("children", directory, {
project: "",
provider: { all: [], connected: [], default: {} },
config: {},
path: { state: "", config: "", worktree: "", directory: "", home: "" },
ready: false,
agent: [],
command: [],
session: [],
session_status: {},
session_diff: {},
todo: {},
limit: 5,
message: {},
part: {},
node: [],
changes: [],
})
children[directory] = createStore(globalStore.children[directory])
bootstrapInstance(directory)
}
return children[directory]
}
const sdk = useGlobalSDK()
sdk.event.listen((e) => {
const directory = e.name
const [store, setStore] = child(directory)
async function loadSessions(directory: string) {
const [store, setStore] = child(directory)
globalSDK.client.session
.list({ directory })
.then((x) => {
const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
const nonArchived = (x.data ?? [])
.slice()
.filter((s) => !s.time.archived)
.sort((a, b) => a.id.localeCompare(b.id))
// Include up to the limit, plus any updated in the last 4 hours
const sessions = nonArchived.filter((s, i) => {
if (i < store.limit) return true
const updated = new Date(s.time.updated).getTime()
return updated > fourHoursAgo
})
setStore("session", sessions)
})
.catch((err) => {
console.error("Failed to load sessions", err)
setGlobalStore("error", err)
})
}
const event = e.details
switch (event.type) {
case "session.updated": {
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
async function bootstrapInstance(directory: string) {
const [, setStore] = child(directory)
const sdk = createOpencodeClient({
baseUrl: globalSDK.url,
directory,
throwOnError: true,
})
const load = {
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
provider: () => sdk.provider.list().then((x) => setStore("provider", x.data!)),
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!)),
}
await Promise.all(Object.values(load).map((p) => p().catch((e) => setGlobalStore("error", e))))
.then(() => setStore("ready", true))
.catch((e) => setGlobalStore("error", e))
}
globalSDK.event.listen((e) => {
const directory = e.name
const event = e.details
if (directory === "global") {
switch (event?.type) {
case "global.disposed": {
bootstrap()
break
}
case "project.updated": {
const result = Binary.search(globalStore.project, event.properties.id, (s) => s.id)
if (result.found) {
setStore("session", result.index, reconcile(event.properties.info))
break
setGlobalStore("project", result.index, reconcile(event.properties))
return
}
setStore(
"session",
setGlobalStore(
"project",
produce((draft) => {
draft.splice(result.index, 0, event.properties.info)
draft.splice(result.index, 0, event.properties)
}),
)
break
}
case "session.diff":
setStore("session_diff", event.properties.sessionID, event.properties.diff)
break
case "todo.updated":
setStore("todo", event.properties.sessionID, event.properties.todos)
break
case "session.status": {
setStore("session_status", event.properties.sessionID, event.properties.status)
}
return
}
const [store, setStore] = child(directory)
switch (event.type) {
case "server.instance.disposed": {
bootstrapInstance(directory)
break
}
case "session.updated": {
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
if (event.properties.info.time.archived) {
if (result.found) {
setStore(
"session",
produce((draft) => {
draft.splice(result.index, 1)
}),
)
}
break
}
case "message.updated": {
const messages = store.message[event.properties.info.sessionID]
if (!messages) {
setStore("message", event.properties.info.sessionID, [event.properties.info])
break
}
const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
if (result.found) {
setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
break
}
if (result.found) {
setStore("session", result.index, reconcile(event.properties.info))
break
}
setStore(
"session",
produce((draft) => {
draft.splice(result.index, 0, event.properties.info)
}),
)
break
}
case "session.diff":
setStore("session_diff", event.properties.sessionID, event.properties.diff)
break
case "todo.updated":
setStore("todo", event.properties.sessionID, event.properties.todos)
break
case "session.status": {
setStore("session_status", event.properties.sessionID, event.properties.status)
break
}
case "message.updated": {
const messages = store.message[event.properties.info.sessionID]
if (!messages) {
setStore("message", event.properties.info.sessionID, [event.properties.info])
break
}
const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
if (result.found) {
setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
break
}
setStore(
"message",
event.properties.info.sessionID,
produce((draft) => {
draft.splice(result.index, 0, event.properties.info)
}),
)
break
}
case "message.removed": {
const messages = store.message[event.properties.sessionID]
if (!messages) break
const result = Binary.search(messages, event.properties.messageID, (m) => m.id)
if (result.found) {
setStore(
"message",
event.properties.info.sessionID,
event.properties.sessionID,
produce((draft) => {
draft.splice(result.index, 0, event.properties.info)
draft.splice(result.index, 1)
}),
)
}
break
}
case "message.part.updated": {
const part = event.properties.part
const parts = store.part[part.messageID]
if (!parts) {
setStore("part", part.messageID, [part])
break
}
case "message.part.updated": {
const part = event.properties.part
const parts = store.part[part.messageID]
if (!parts) {
setStore("part", part.messageID, [part])
break
}
const result = Binary.search(parts, part.id, (p) => p.id)
if (result.found) {
setStore("part", part.messageID, result.index, reconcile(part))
break
}
const result = Binary.search(parts, part.id, (p) => p.id)
if (result.found) {
setStore("part", part.messageID, result.index, reconcile(part))
break
}
setStore(
"part",
part.messageID,
produce((draft) => {
draft.splice(result.index, 0, part)
}),
)
break
}
case "message.part.removed": {
const parts = store.part[event.properties.messageID]
if (!parts) break
const result = Binary.search(parts, event.properties.partID, (p) => p.id)
if (result.found) {
setStore(
"part",
part.messageID,
event.properties.messageID,
produce((draft) => {
draft.splice(result.index, 0, part)
draft.splice(result.index, 1)
}),
)
break
}
break
}
})
Promise.all([
sdk.client.project.list().then((x) =>
setGlobalStore(
"projects",
x.data!.filter((x) => !x.worktree.includes("opencode-test")),
),
),
// TODO: remove this when we can select projects
sdk.client.project.current().then((x) => setGlobalStore("defaultProject", x.data)),
]).then(() => setGlobalStore("ready", true))
return {
data: globalStore,
get ready() {
return globalStore.ready
},
child,
}
},
})
})
async function bootstrap() {
return Promise.all([
globalSDK.client.path.get().then((x) => {
setGlobalStore("path", x.data!)
}),
globalSDK.client.project.list().then(async (x) => {
setGlobalStore(
"project",
x.data!.filter((p) => !p.worktree.includes("opencode-test")).sort((a, b) => a.id.localeCompare(b.id)),
)
}),
globalSDK.client.provider.list().then((x) => {
setGlobalStore("provider", x.data ?? {})
}),
globalSDK.client.provider.auth().then((x) => {
setGlobalStore("provider_auth", x.data ?? {})
}),
])
.then(() => setGlobalStore("ready", true))
.catch((e) => setGlobalStore("error", e))
}
onMount(() => {
bootstrap()
})
return {
data: globalStore,
get ready() {
return globalStore.ready
},
get error() {
return globalStore.error
},
child,
bootstrap,
project: {
loadSessions,
},
}
}
const GlobalSyncContext = createContext<ReturnType<typeof createGlobalSync>>()
export function GlobalSyncProvider(props: ParentProps) {
const value = createGlobalSync()
return (
<Switch>
<Match when={value.error}>
<ErrorPage error={value.error} />
</Match>
<Match when={value.ready}>
<GlobalSyncContext.Provider value={value}>{props.children}</GlobalSyncContext.Provider>
</Match>
</Switch>
)
}
export function useGlobalSync() {
const context = useContext(GlobalSyncContext)
if (!context) throw new Error("useGlobalSync must be used within GlobalSyncProvider")
return context
}

View File

@@ -1,18 +1,43 @@
import { createStore } from "solid-js/store"
import { createMemo } from "solid-js"
import { createStore, produce } from "solid-js/store"
import { batch, createMemo, onMount } from "solid-js"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { makePersisted } from "@solid-primitives/storage"
import { useGlobalSync } from "./global-sync"
import { useGlobalSDK } from "./global-sdk"
import { Project } from "@opencode-ai/sdk/v2"
import { persisted } from "@/utils/persist"
const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
export type AvatarColorKey = (typeof AVATAR_COLOR_KEYS)[number]
export function getAvatarColors(key?: string) {
if (key && AVATAR_COLOR_KEYS.includes(key as AvatarColorKey)) {
return {
background: `var(--avatar-background-${key})`,
foreground: `var(--avatar-text-${key})`,
}
}
return {
background: "var(--surface-info-base)",
foreground: "var(--text-base)",
}
}
type SessionTabs = {
active?: string
all: string[]
}
export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
name: "Layout",
init: () => {
const globalSdk = useGlobalSDK()
const globalSync = useGlobalSync()
const [store, setStore] = makePersisted(
const [store, setStore, _, ready] = persisted(
"layout.v3",
createStore({
projects: [] as { directory: string; expanded: boolean }[],
projects: [] as { worktree: string; expanded: boolean }[],
sidebar: {
opened: true,
opened: false,
width: 280,
},
terminal: {
@@ -22,31 +47,76 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
review: {
state: "pane" as "pane" | "tab",
},
sessionTabs: {} as Record<string, SessionTabs>,
}),
{
name: "____default-layout",
},
)
const usedColors = new Set<AvatarColorKey>()
function pickAvailableColor(): AvatarColorKey {
const available = AVATAR_COLOR_KEYS.filter((c) => !usedColors.has(c))
if (available.length === 0) return AVATAR_COLOR_KEYS[Math.floor(Math.random() * AVATAR_COLOR_KEYS.length)]
return available[Math.floor(Math.random() * available.length)]
}
function enrich(project: { worktree: string; expanded: boolean }) {
const metadata = globalSync.data.project.find((x) => x.worktree === project.worktree)
if (!metadata) return []
return [
{
...project,
...metadata,
},
]
}
function colorize(project: Project & { expanded: boolean }) {
if (project.icon?.color) return project
const color = pickAvailableColor()
usedColors.add(color)
project.icon = { ...project.icon, color }
globalSdk.client.project.update({ projectID: project.id, icon: { color } })
return project
}
const enriched = createMemo(() => store.projects.flatMap(enrich))
const list = createMemo(() => enriched().flatMap(colorize))
onMount(() => {
Promise.all(
store.projects.map((project) => {
return globalSync.project.loadSessions(project.worktree)
}),
)
})
return {
ready,
projects: {
list: createMemo(() =>
globalSync.data.defaultProject
? [{ directory: globalSync.data.defaultProject!.worktree, expanded: true }, ...store.projects]
: store.projects,
),
list,
open(directory: string) {
if (store.projects.find((x) => x.directory === directory)) return
setStore("projects", (x) => [...x, { directory, expanded: true }])
if (store.projects.find((x) => x.worktree === directory)) return
globalSync.project.loadSessions(directory)
setStore("projects", (x) => [{ worktree: directory, expanded: true }, ...x])
},
close(directory: string) {
setStore("projects", (x) => x.filter((x) => x.directory !== directory))
setStore("projects", (x) => x.filter((x) => x.worktree !== directory))
},
expand(directory: string) {
setStore("projects", (x) => x.map((x) => (x.directory === directory ? { ...x, expanded: true } : x)))
setStore("projects", (x) => x.map((x) => (x.worktree === directory ? { ...x, expanded: true } : x)))
},
collapse(directory: string) {
setStore("projects", (x) => x.map((x) => (x.directory === directory ? { ...x, expanded: false } : x)))
setStore("projects", (x) => x.map((x) => (x.worktree === directory ? { ...x, expanded: false } : x)))
},
move(directory: string, toIndex: number) {
setStore("projects", (projects) => {
const fromIndex = projects.findIndex((x) => x.worktree === directory)
if (fromIndex === -1 || fromIndex === toIndex) return projects
const result = [...projects]
const [item] = result.splice(fromIndex, 1)
result.splice(toIndex, 0, item)
return result
})
},
},
sidebar: {
@@ -90,6 +160,86 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setStore("review", "state", "tab")
},
},
tabs(sessionKey: string) {
const tabs = createMemo(() => store.sessionTabs[sessionKey] ?? { all: [] })
return {
tabs,
active: createMemo(() => tabs().active),
all: createMemo(() => tabs().all),
setActive(tab: string | undefined) {
if (!store.sessionTabs[sessionKey]) {
setStore("sessionTabs", sessionKey, { all: [], active: tab })
} else {
setStore("sessionTabs", sessionKey, "active", tab)
}
},
setAll(all: string[]) {
if (!store.sessionTabs[sessionKey]) {
setStore("sessionTabs", sessionKey, { all, active: undefined })
} else {
setStore("sessionTabs", sessionKey, "all", all)
}
},
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)) {
if (!store.sessionTabs[sessionKey]) {
setStore("sessionTabs", sessionKey, { all: [tab], active: tab })
} else {
setStore("sessionTabs", sessionKey, "all", [...current.all, tab])
setStore("sessionTabs", sessionKey, "active", tab)
}
return
}
}
if (!store.sessionTabs[sessionKey]) {
setStore("sessionTabs", sessionKey, { all: [], active: tab })
} else {
setStore("sessionTabs", sessionKey, "active", tab)
}
},
close(tab: string) {
const current = store.sessionTabs[sessionKey]
if (!current) return
batch(() => {
setStore(
"sessionTabs",
sessionKey,
"all",
current.all.filter((x) => x !== tab),
)
if (current.active === tab) {
const index = current.all.findIndex((f) => f === tab)
const previous = current.all[Math.max(0, index - 1)]
setStore("sessionTabs", sessionKey, "active", previous)
}
})
},
move(tab: string, to: number) {
const current = store.sessionTabs[sessionKey]
if (!current) return
const index = current.all.findIndex((f) => f === tab)
if (index === -1) return
setStore(
"sessionTabs",
sessionKey,
"all",
produce((opened) => {
opened.splice(to, 0, opened.splice(index, 1)[0])
}),
)
},
}
},
}
},
})

View File

@@ -1,11 +1,14 @@
import { createStore, produce, reconcile } from "solid-js/store"
import { batch, createEffect, createMemo } from "solid-js"
import { uniqueBy } from "remeda"
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"
import { useSDK } from "./sdk"
import { useSync } from "./sync"
import { base64Encode } from "@opencode-ai/util/encode"
import { useProviders } from "@/hooks/use-providers"
import { DateTime } from "luxon"
import { persisted } from "@/utils/persist"
export type LocalFile = FileNode &
Partial<{
@@ -25,6 +28,7 @@ export type View = LocalFile["view"]
export type LocalModel = Omit<Model, "provider"> & {
provider: Provider
latest?: boolean
}
export type ModelKey = { providerID: string; modelID: string }
@@ -36,10 +40,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
init: () => {
const sdk = useSDK()
const sync = useSync()
const providers = useProviders()
function isModelValid(model: ModelKey) {
const provider = sync.data.provider.find((x) => x.id === model.providerID)
return !!provider?.models[model.modelID]
const provider = providers.all().find((x) => x.id === model.providerID)
return (
!!provider?.models[model.modelID] &&
providers
.connected()
.map((p) => p.id)
.includes(model.providerID)
)
}
function getFirstValidModel(...modelFns: (() => ModelKey | undefined)[]) {
@@ -69,7 +80,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
})
const agent = (() => {
const list = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent"))
const list = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent" && !x.hidden))
const [store, setStore] = createStore<{
current: string
}>({
@@ -99,23 +110,62 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
})()
const model = (() => {
const [store, setStore] = createStore<{
const [store, setStore, _, modelReady] = persisted(
"model.v1",
createStore<{
user: (ModelKey & { visibility: "show" | "hide"; favorite?: boolean })[]
recent: ModelKey[]
}>({
user: [],
recent: [],
}),
)
const [ephemeral, setEphemeral] = createStore<{
model: Record<string, ModelKey>
recent: ModelKey[]
}>({
model: {},
recent: [],
})
const value = localStorage.getItem("model")
setStore("recent", JSON.parse(value ?? "[]"))
createEffect(() => {
localStorage.setItem("model", JSON.stringify(store.recent))
})
const available = createMemo(() =>
providers.connected().flatMap((p) =>
Object.values(p.models).map((m) => ({
...m,
provider: p,
})),
),
)
const latest = createMemo(() =>
pipe(
available(),
filter((x) => Math.abs(DateTime.fromISO(x.release_date).diffNow().as("months")) < 6),
groupBy((x) => x.provider.id),
mapValues((models) =>
pipe(
models,
groupBy((x) => x.family),
values(),
(groups) =>
groups.flatMap((g) => {
const first = firstBy(g, [(x) => x.release_date, "desc"])
return first ? [{ modelID: first.id, providerID: first.provider.id }] : []
}),
),
),
values(),
flat(),
),
)
const list = createMemo(() =>
sync.data.provider.flatMap((p) => Object.values(p.models).map((m) => ({ ...m, provider: p }) as LocalModel)),
available().map((m) => ({
...m,
name: m.name.replace("(latest)", "").trim(),
latest: m.name.includes("(latest)"),
})),
)
const find = (key: ModelKey) => list().find((m) => m.id === key?.modelID && m.provider.id === key.providerID)
const fallbackModel = createMemo(() => {
@@ -134,18 +184,23 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
return item
}
}
const provider = sync.data.provider[0]
const model = Object.values(provider.models)[0]
return {
providerID: provider.id,
modelID: model.id,
for (const p of providers.connected()) {
if (p.id in providers.default()) {
return {
providerID: p.id,
modelID: providers.default()[p.id],
}
}
}
throw new Error("No default model found")
})
const currentModel = createMemo(() => {
const current = createMemo(() => {
const a = agent.current()
const key = getFirstValidModel(
() => store.model[a.name],
() => ephemeral.model[a.name],
() => a.model,
fallbackModel,
)!
@@ -156,10 +211,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const cycle = (direction: 1 | -1) => {
const recentList = recent()
const current = currentModel()
if (!current) return
const currentModel = current()
if (!currentModel) return
const index = recentList.findIndex((x) => x?.provider.id === current.provider.id && x?.id === current.id)
const index = recentList.findIndex(
(x) => x?.provider.id === currentModel.provider.id && x?.id === currentModel.id,
)
if (index === -1) return
let next = index + direction
@@ -175,14 +232,25 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
})
}
function updateVisibility(model: ModelKey, visibility: "show" | "hide") {
const index = store.user.findIndex((x) => x.modelID === model.modelID && x.providerID === model.providerID)
if (index >= 0) {
setStore("user", index, { visibility })
} else {
setStore("user", store.user.length, { ...model, visibility })
}
}
return {
current: currentModel,
ready: modelReady,
current,
recent,
list,
cycle,
set(model: ModelKey | undefined, options?: { recent?: boolean }) {
batch(() => {
setStore("model", agent.current().name, model ?? fallbackModel())
setEphemeral("model", agent.current().name, model ?? fallbackModel())
if (model) updateVisibility(model, "show")
if (options?.recent && model) {
const uniq = uniqueBy([model, ...store.recent], (x) => x.providerID + x.modelID)
if (uniq.length > 5) uniq.pop()
@@ -190,6 +258,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}
})
},
visible(model: ModelKey) {
const user = store.user.find((x) => x.modelID === model.modelID && x.providerID === model.providerID)
return (
user?.visibility !== "hide" &&
(latest().find((x) => x.modelID === model.modelID && x.providerID === model.providerID) ||
user?.visibility === "show")
)
},
setVisibility(model: ModelKey, visible: boolean) {
updateVisibility(model, visible ? "show" : "hide")
},
}
})()
@@ -257,7 +336,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const load = async (path: string) => {
const relativePath = relative(path)
sdk.client.file.read({ path: relativePath }).then((x) => {
await sdk.client.file.read({ path: relativePath }).then((x) => {
setStore(
"node",
relativePath,
@@ -328,14 +407,14 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
case "file.watcher.updated":
const relativePath = relative(event.properties.file)
if (relativePath.startsWith(".git/")) return
load(relativePath)
if (store.node[relativePath]) load(relativePath)
break
}
})
return {
node: async (path: string) => {
if (!store.node[path]) {
if (!store.node[path] || !store.node[path].loaded) {
await init(path)
}
return store.node[path]

View File

@@ -0,0 +1,127 @@
import { createStore } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useGlobalSDK } from "./global-sdk"
import { useGlobalSync } from "./global-sync"
import { Binary } from "@opencode-ai/util/binary"
import { EventSessionError } from "@opencode-ai/sdk/v2"
import { makeAudioPlayer } from "@solid-primitives/audio"
import idleSound from "@opencode-ai/ui/audio/staplebops-01.aac"
import errorSound from "@opencode-ai/ui/audio/nope-03.aac"
import { persisted } from "@/utils/persist"
type NotificationBase = {
directory?: string
session?: string
metadata?: any
time: number
viewed: boolean
}
type TurnCompleteNotification = NotificationBase & {
type: "turn-complete"
}
type ErrorNotification = NotificationBase & {
type: "error"
error: EventSessionError["properties"]["error"]
}
export type Notification = TurnCompleteNotification | ErrorNotification
export const { use: useNotification, provider: NotificationProvider } = createSimpleContext({
name: "Notification",
init: () => {
let idlePlayer: ReturnType<typeof makeAudioPlayer> | undefined
let errorPlayer: ReturnType<typeof makeAudioPlayer> | undefined
try {
idlePlayer = makeAudioPlayer(idleSound)
errorPlayer = makeAudioPlayer(errorSound)
} catch (err) {
console.log("Failed to load audio", err)
}
const globalSDK = useGlobalSDK()
const globalSync = useGlobalSync()
const [store, setStore, _, ready] = persisted(
"notification.v1",
createStore({
list: [] as Notification[],
}),
)
globalSDK.event.listen((e) => {
const directory = e.name
const event = e.details
const base = {
directory,
time: Date.now(),
viewed: false,
}
switch (event.type) {
case "session.idle": {
const sessionID = event.properties.sessionID
const [syncStore] = globalSync.child(directory)
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
const isChild = match.found && syncStore.session[match.index].parentID
if (isChild) break
try {
idlePlayer?.play()
} catch {}
setStore("list", store.list.length, {
...base,
type: "turn-complete",
session: sessionID,
})
break
}
case "session.error": {
const sessionID = event.properties.sessionID
if (sessionID) {
const [syncStore] = globalSync.child(directory)
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
const isChild = match.found && syncStore.session[match.index].parentID
if (isChild) break
}
try {
errorPlayer?.play()
} catch {}
setStore("list", store.list.length, {
...base,
type: "error",
session: sessionID ?? "global",
error: "error" in event.properties ? event.properties.error : undefined,
})
break
}
}
})
return {
ready,
session: {
all(session: string) {
return store.list.filter((n) => n.session === session)
},
unseen(session: string) {
return store.list.filter((n) => n.session === session && !n.viewed)
},
markViewed(session: string) {
setStore("list", (n) => n.session === session, "viewed", true)
},
},
project: {
all(directory: string) {
return store.list.filter((n) => n.directory === directory)
},
unseen(directory: string) {
return store.list.filter((n) => n.directory === directory && !n.viewed)
},
markViewed(directory: string) {
setStore("list", (n) => n.directory === directory, "viewed", true)
},
},
}
},
})

View File

@@ -0,0 +1,35 @@
import { createSimpleContext } from "@opencode-ai/ui/context"
import { AsyncStorage, SyncStorage } from "@solid-primitives/storage"
export type Platform = {
/** Platform discriminator */
platform: "web" | "tauri"
/** Open native directory picker dialog (Tauri only) */
openDirectoryPickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise<string | string[] | null>
/** Open native file picker dialog (Tauri only) */
openFilePickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise<string | string[] | null>
/** Save file picker dialog (Tauri only) */
saveFilePickerDialog?(opts?: { title?: string; defaultPath?: string }): Promise<string | null>
/** Open a URL in the default browser */
openLink(url: string): void
/** Storage mechanism, defaults to localStorage */
storage?: (name?: string) => SyncStorage | AsyncStorage
/** Check for updates (Tauri only) */
checkUpdate?(): Promise<{ updateAvailable: boolean; version?: string }>
/** Install updates (Tauri only) */
update?(): Promise<void>
}
export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({
name: "Platform",
init: (props: { value: Platform }) => {
return props.value
},
})

View File

@@ -0,0 +1,111 @@
import { createStore } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { batch, createMemo } from "solid-js"
import { useParams } from "@solidjs/router"
import { TextSelection } from "./local"
import { persisted } from "@/utils/persist"
interface PartBase {
content: string
start: number
end: number
}
export interface TextPart extends PartBase {
type: "text"
}
export interface FileAttachmentPart extends PartBase {
type: "file"
path: string
selection?: TextSelection
}
export interface ImageAttachmentPart {
type: "image"
id: string
filename: string
mime: string
dataUrl: string
}
export type ContentPart = TextPart | FileAttachmentPart | ImageAttachmentPart
export type Prompt = ContentPart[]
export const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
export function isPromptEqual(promptA: Prompt, promptB: Prompt): boolean {
if (promptA.length !== promptB.length) return false
for (let i = 0; i < promptA.length; i++) {
const partA = promptA[i]
const partB = promptB[i]
if (partA.type !== partB.type) return false
if (partA.type === "text" && partA.content !== (partB as TextPart).content) {
return false
}
if (partA.type === "file" && partA.path !== (partB as FileAttachmentPart).path) {
return false
}
if (partA.type === "image" && partA.id !== (partB as ImageAttachmentPart).id) {
return false
}
}
return true
}
function cloneSelection(selection?: TextSelection) {
if (!selection) return undefined
return { ...selection }
}
function clonePart(part: ContentPart): ContentPart {
if (part.type === "text") return { ...part }
if (part.type === "image") return { ...part }
return {
...part,
selection: cloneSelection(part.selection),
}
}
function clonePrompt(prompt: Prompt): Prompt {
return prompt.map(clonePart)
}
export const { use: usePrompt, provider: PromptProvider } = createSimpleContext({
name: "Prompt",
init: () => {
const params = useParams()
const name = createMemo(() => `${params.dir}/prompt${params.id ? "/" + params.id : ""}.v1`)
const [store, setStore, _, ready] = persisted(
name(),
createStore<{
prompt: Prompt
cursor?: number
}>({
prompt: clonePrompt(DEFAULT_PROMPT),
cursor: undefined,
}),
)
return {
ready,
current: createMemo(() => store.prompt),
cursor: createMemo(() => store.cursor),
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
set(prompt: Prompt, cursorPosition?: number) {
const next = clonePrompt(prompt)
batch(() => {
setStore("prompt", next)
if (cursorPosition !== undefined) setStore("cursor", cursorPosition)
})
},
reset() {
batch(() => {
setStore("prompt", clonePrompt(DEFAULT_PROMPT))
setStore("cursor", 0)
})
},
}
},
})

View File

@@ -13,6 +13,7 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
baseUrl: globalSDK.url,
signal: abort.signal,
directory: props.directory,
throwOnError: true,
})
const emitter = createGlobalEmitter<{

View File

@@ -1,324 +0,0 @@
import { createStore, produce } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { batch, createEffect, createMemo } from "solid-js"
import { useSync } from "./sync"
import { makePersisted } from "@solid-primitives/storage"
import { TextSelection } from "./local"
import { pipe, sumBy } from "remeda"
import { AssistantMessage, UserMessage } from "@opencode-ai/sdk/v2"
import { useParams } from "@solidjs/router"
import { base64Encode } from "@opencode-ai/util/encode"
import { useSDK } from "./sdk"
export type LocalPTY = {
id: string
title: string
rows?: number
cols?: number
buffer?: string
scrollY?: number
}
export const { use: useSession, provider: SessionProvider } = createSimpleContext({
name: "Session",
init: () => {
const sdk = useSDK()
const params = useParams()
const sync = useSync()
const name = createMemo(
() => `${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}.v2`,
)
const [store, setStore] = makePersisted(
createStore<{
messageId?: string
tabs: {
active?: string
all: string[]
}
prompt: Prompt
cursor?: number
terminals: {
active?: string
all: LocalPTY[]
}
}>({
tabs: {
all: [],
},
prompt: clonePrompt(DEFAULT_PROMPT),
cursor: undefined,
terminals: { all: [] },
}),
{
name: name(),
},
)
createEffect(() => {
if (!params.id) return
sync.session.sync(params.id)
})
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
const userMessages = createMemo(() =>
messages()
.filter((m) => m.role === "user")
.sort((a, b) => b.id.localeCompare(a.id)),
)
const lastUserMessage = createMemo(() => {
return userMessages()?.at(0)
})
const activeMessage = createMemo(() => {
if (!store.messageId) return lastUserMessage()
return userMessages()?.find((m) => m.id === store.messageId)
})
const status = createMemo(
() =>
sync.data.session_status[params.id ?? ""] ?? {
type: "idle",
},
)
const working = createMemo(() => status()?.type !== "idle")
const cost = createMemo(() => {
const total = pipe(
messages(),
sumBy((x) => (x.role === "assistant" ? x.cost : 0)),
)
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(total)
})
const last = createMemo(
() => messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage,
)
const model = createMemo(() =>
last() ? sync.data.provider.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 tokens = last().tokens
return tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write
})
const context = createMemo(() => {
const total = tokens()
const limit = model()?.limit.context
if (!total || !limit) return 0
return Math.round((total / limit) * 100)
})
return {
get id() {
return params.id
},
info,
status,
working,
diffs,
prompt: {
current: createMemo(() => store.prompt),
cursor: createMemo(() => store.cursor),
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
set(prompt: Prompt, cursorPosition?: number) {
const next = clonePrompt(prompt)
batch(() => {
setStore("prompt", next)
if (cursorPosition !== undefined) setStore("cursor", cursorPosition)
})
},
},
messages: {
all: messages,
user: userMessages,
last: lastUserMessage,
active: activeMessage,
setActive(message: UserMessage | undefined) {
setStore("messageId", message?.id)
},
},
usage: {
tokens,
cost,
context,
},
layout: {
tabs: store.tabs,
setActiveTab(tab: string | undefined) {
setStore("tabs", "active", tab)
},
setOpenedTabs(tabs: string[]) {
setStore("tabs", "all", tabs)
},
async openTab(tab: string) {
if (tab === "chat") {
setStore("tabs", "active", undefined)
return
}
if (tab !== "review") {
if (!store.tabs.all.includes(tab)) {
setStore("tabs", "all", [...store.tabs.all, tab])
}
}
setStore("tabs", "active", tab)
},
closeTab(tab: string) {
batch(() => {
setStore(
"tabs",
"all",
store.tabs.all.filter((x) => x !== tab),
)
if (store.tabs.active === tab) {
const index = store.tabs.all.findIndex((f) => f === tab)
const previous = store.tabs.all[Math.max(0, index - 1)]
setStore("tabs", "active", previous)
}
})
},
moveTab(tab: string, to: number) {
const index = store.tabs.all.findIndex((f) => f === tab)
if (index === -1) return
setStore(
"tabs",
"all",
produce((opened) => {
opened.splice(to, 0, opened.splice(index, 1)[0])
}),
)
},
},
terminal: {
all: createMemo(() => Object.values(store.terminals.all)),
active: createMemo(() => store.terminals.active),
new() {
sdk.client.pty.create({ title: `Terminal ${store.terminals.all.length + 1}` }).then((pty) => {
const id = pty.data?.id
if (!id) return
setStore("terminals", "all", [
...store.terminals.all,
{
id,
title: pty.data?.title ?? "Terminal",
},
])
setStore("terminals", "active", id)
})
},
update(pty: Partial<LocalPTY> & { id: string }) {
setStore("terminals", "all", (x) => x.map((x) => (x.id === pty.id ? { ...x, ...pty } : x)))
sdk.client.pty.update({
ptyID: pty.id,
title: pty.title,
size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined,
})
},
async clone(id: string) {
const index = store.terminals.all.findIndex((x) => x.id === id)
const pty = store.terminals.all[index]
if (!pty) return
const clone = await sdk.client.pty.create({
title: pty.title,
})
if (!clone.data) return
setStore("terminals", "all", index, {
...pty,
...clone.data,
})
if (store.terminals.active === pty.id) {
setStore("terminals", "active", clone.data.id)
}
},
open(id: string) {
setStore("terminals", "active", id)
},
async close(id: string) {
batch(() => {
setStore(
"terminals",
"all",
store.terminals.all.filter((x) => x.id !== id),
)
if (store.terminals.active === id) {
const index = store.terminals.all.findIndex((f) => f.id === id)
const previous = store.tabs.all[Math.max(0, index - 1)]
setStore("terminals", "active", previous)
}
})
await sdk.client.pty.remove({ ptyID: id })
},
move(id: string, to: number) {
const index = store.terminals.all.findIndex((f) => f.id === id)
if (index === -1) return
setStore(
"terminals",
"all",
produce((all) => {
all.splice(to, 0, all.splice(index, 1)[0])
}),
)
},
},
}
},
})
interface PartBase {
content: string
start: number
end: number
}
export interface TextPart extends PartBase {
type: "text"
}
export interface FileAttachmentPart extends PartBase {
type: "file"
path: string
selection?: TextSelection
}
export type ContentPart = TextPart | FileAttachmentPart
export type Prompt = ContentPart[]
export const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
export function isPromptEqual(promptA: Prompt, promptB: Prompt): boolean {
if (promptA.length !== promptB.length) return false
for (let i = 0; i < promptA.length; i++) {
const partA = promptA[i]
const partB = promptB[i]
if (partA.type !== partB.type) return false
if (partA.type === "text" && partA.content !== (partB as TextPart).content) {
return false
}
if (partA.type === "file" && partA.path !== (partB as FileAttachmentPart).path) {
return false
}
}
return true
}
function cloneSelection(selection?: TextSelection) {
if (!selection) return undefined
return { ...selection }
}
function clonePart(part: ContentPart): ContentPart {
if (part.type === "text") return { ...part }
return {
...part,
selection: cloneSelection(part.selection),
}
}
function clonePrompt(prompt: Prompt): Prompt {
return prompt.map(clonePart)
}

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