Compare commits

...

683 Commits

Author SHA1 Message Date
opencode
47b4cc6d53 release: v1.0.46 2025-11-08 21:48:37 +00:00
Dax Raad
ce9b758d0a tui: display 'Free' badge for zero-cost models in model selection dialog 2025-11-08 16:20:30 -05:00
Frank
f8a1a0b26f zen: fix billing button 2025-11-08 10:33:56 -05:00
GitHub Action
6ecaf83f76 chore: format code 2025-11-08 15:20:53 +00:00
Frank
30b1ae5d4b zen: rate limit 2025-11-08 10:18:21 -05:00
GitHub Action
9cd465f9c0 ignore: update download stats 2025-11-08 2025-11-08 12:04:10 +00:00
opencode
1747979568 release: v1.0.45 2025-11-08 02:25:51 +00:00
Dax Raad
062023fa06 chore(prompt): refine polaris instructions for practical behavior 2025-11-07 21:18:49 -05:00
Dax Raad
954a796b8a core: route polaris-alpha models to polaris system prompt 2025-11-07 21:07:59 -05:00
GitHub Action
34ff87d504 chore: format code 2025-11-08 01:59:02 +00:00
Dax Raad
16357e8041 chore: standardize prettier printWidth to 120 2025-11-07 20:58:17 -05:00
Dax Raad
dabb1aa719 fix(prompt): prevent title generation refusal responses 2025-11-07 20:50:15 -05:00
Aiden Cline
7af3380455 chore: adjust cfg code 2025-11-07 19:18:57 -06:00
GitHub Action
dcaa90808e chore: format code 2025-11-08 00:59:52 +00:00
Jay V
01705fd467 ignore: docs: fix type errors in sitemap generation script 2025-11-07 19:55:25 -05:00
Jay V
006f3bdeb6 ignore: docs: add sitemap.xml to gitignore 2025-11-07 19:51:47 -05:00
Jay V
1d43b4e6d7 ignore: docs: add automated sitemap generation for main app and docs routes 2025-11-07 19:51:46 -05:00
Aiden Cline
8cef7940fe allow reading of opencode.json(c) from .opencode/ 2025-11-07 17:35:08 -06:00
Sebastian Herrlinger
b2dd9fdfdf upgrade to opentui 0.1.39 supporting shift/ctrl+return/esc custom csi in ghostty 2025-11-08 00:17:07 +01:00
Aiden Cline
b82a52cb85 ignore: update log 2025-11-07 15:56:37 -06:00
Aiden Cline
7294d86778 ignore: symlink zed extension license 2025-11-07 15:41:01 -06:00
Aiden Cline
3bb3f4f2c9 ignore: fix 2025-11-07 15:32:19 -06:00
GitHub Action
d31f97343c chore: format code 2025-11-07 21:28:26 +00:00
Jay V
536d10e5ab ignore: lander add canonical urls and h1 tags to all landing pages 2025-11-07 16:27:35 -05:00
Jay V
9885c716f3 ignore: lander use h1 tags for main headings on landing and zen pages 2025-11-07 16:27:34 -05:00
opencode
39461fbbce release: v1.0.44 2025-11-07 21:24:15 +00:00
Dax Raad
1a2b3701f2 tui: show more sessions in list and fix sync timing to prevent race conditions 2025-11-07 16:19:44 -05:00
Aiden Cline
0a395d8783 ignore: update version 2025-11-07 15:18:44 -06:00
Aiden Cline
79bb22a573 ci: auto update zed extension 2025-11-07 15:17:26 -06:00
GitHub Action
4271df96d2 chore: format code 2025-11-07 21:09:47 +00:00
Aiden Cline
aa07be09e1 ignore: update zed extension 2025-11-07 15:08:50 -06:00
opencode
5d6bdca6d0 release: v1.0.43 2025-11-07 21:04:26 +00:00
Dax Raad
58bbe9e689 ci: add optional version parameter to publish workflow
Allows overriding the version when publishing releases instead of only using semantic bumping. This gives maintainers more control over release versioning for special cases or hotfixes.
2025-11-07 15:58:51 -05:00
Sebastian Herrlinger
b5a035ceab upgrade to opentui to fix disappearing content (again) and sticky scroll 2025-11-07 21:38:40 +01:00
Aiden Cline
b3c6d0b08a fix formatters 2025-11-07 14:10:20 -06:00
Aiden Cline
090d27df11 chore: rm debug logs 2025-11-07 14:00:18 -06:00
Adam
b374a6cac9 fix(desktop): stop icon size 2025-11-07 13:58:52 -06:00
Aiden Cline
73cd8a334c rework acp to compensate for changes in Zed IDE (#4050) 2025-11-07 13:57:12 -06:00
Adam
b46c3f2a26 fix(desktop): prompt input issues (wip) 2025-11-07 13:54:53 -06:00
Adam
45fabec091 fix(desktop): prompt input on non-chat tabs 2025-11-07 13:54:53 -06:00
Dax Raad
a96365fd81 Add command bar action to rename sessions 2025-11-07 14:51:44 -05:00
GitHub Action
5f7e1e099b chore: format code 2025-11-07 19:47:53 +00:00
Dax Raad
d462e380f4 fix: update references after moving message functions to MessageV2 namespace 2025-11-07 14:47:05 -05:00
Adam
c5a558f3da chore(desktop): remove dead code 2025-11-07 13:34:41 -06:00
Adam
7f51b181d4 chore(desktop): cleanup shiki theme stuff 2025-11-07 13:30:07 -06:00
Adam
7adbc3ad44 fix(desktop): code tab padding 2025-11-07 13:14:57 -06:00
Adam
89922a8598 fix(desktop): prompt input missing on new session 2025-11-07 12:56:07 -06:00
Adam
3a1d1a6284 feat(desktop): custom syntax colors 2025-11-07 12:48:17 -06:00
Dax Raad
4463d319c9 fix scroll when no session exists 2025-11-07 13:22:03 -05:00
GitHub Action
c6eea0343d chore: format code 2025-11-07 18:11:40 +00:00
opencode
e317e7e481 release: v1.0.41 2025-11-07 18:11:39 +00:00
Dax Raad
287855336d allow not specifying a limit on messages endpoint 2025-11-07 13:05:19 -05:00
opencode
d55f4f3322 release: v1.0.40 2025-11-07 17:37:31 +00:00
Dax Raad
b708d0ecec disable scrollbar temporarily because of text wrap issues 2025-11-07 12:31:28 -05:00
Jinhyeok Lee
afb831c93c vscode: Add VS Code Insiders support (#4019) 2025-11-07 10:31:16 -06:00
Adam
14397651b5 ignore: test file 2025-11-07 09:05:09 -06:00
Adam
e5804f64f9 fix(desktop): layout quirks 2025-11-07 06:26:26 -06:00
GitHub Action
ce7b73170f ignore: update download stats 2025-11-07 2025-11-07 12:04:25 +00:00
Dax Raad
9554abb56e message storage performance improvements 2025-11-07 01:11:47 -05:00
Charles David Mupende
d0f5c825bd feat: implement network IP retrieval for remote access in web command (#3945) 2025-11-06 19:37:23 -06:00
opencode-agent[bot]
9f603e39a6 Fixed ACP to respect user's default model config. (#4006)
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: GitHub Action <action@github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-06 19:07:27 -06:00
GitHub Action
da51c9dfac chore: format code 2025-11-07 01:06:38 +00:00
Nicolai van der Smagt
9e04ff013c fix: resolve Mistral API compatibility issues (#2440)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-06 20:05:59 -05:00
opencode
6bfccace0c release: v1.0.39 2025-11-07 01:04:05 +00:00
Dax Raad
b25d4f9dfb fix issue with input randomly breaking 2025-11-06 19:48:10 -05:00
GitHub Action
d1962ca5a7 chore: format code 2025-11-07 00:14:59 +00:00
Dax Raad
25f31f3096 codex tweaks 2025-11-06 19:14:01 -05:00
opencode
11a6f0886e release: v1.0.37 2025-11-06 23:14:32 +00:00
Dax
3ba7e243d0 system theme (#4010) 2025-11-06 18:00:09 -05:00
Nicolai van der Smagt
a2ab019317 fix: resolve Mistral API compatibility issues (#2440)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-06 16:53:02 -06:00
Jay V
21957406ff docs: add Deep Infra provider documentation 2025-11-06 17:08:55 -05:00
Adam
61c4747fbe fix(desktop): diff highlight rendering 2025-11-06 15:58:45 -06:00
Adam
957c43aa09 fix(desktop): review tab padding 2025-11-06 15:34:47 -06:00
Adam
96c57418f3 feat(desktop): review flow 2025-11-06 15:13:06 -06:00
Shanjai Raj
b8c51e307f Fix: Auto-scroll to bottom when selecting session from list (#3988)
Co-authored-by: shanjairaj7 <shanjairaj7@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-06 14:29:39 -06:00
Frank
6791233ca0 Add desktop-feedback 2025-11-06 14:29:11 -05:00
GitHub Action
cd6072ec58 chore: format code 2025-11-06 18:59:34 +00:00
Jay V
017e42bbcd docs: align web package favicon with console design for consistency 2025-11-06 13:58:52 -05:00
opencode
2d20582802 release: v1.0.36 2025-11-06 18:55:54 +00:00
GitHub Action
2bcc00dbf0 chore: format code 2025-11-06 18:21:59 +00:00
Dax Raad
e45e94634f only display last 100 messages in TUI 2025-11-06 13:21:15 -05:00
Aiden Cline
de1278414f fix: opencode run not parsing model string 2025-11-06 12:09:13 -06:00
Dax Raad
3c2803fd9a flaky test 2025-11-06 13:05:53 -05:00
GitHub Action
90c2b26733 chore: format code 2025-11-06 18:03:50 +00:00
Dax Raad
1ea3a8eb9b big format 2025-11-06 13:03:12 -05:00
Aiden Cline
8729edc5e0 update import command to accept share links 2025-11-06 11:55:57 -06:00
Aiden Cline
d8bcf1f5f3 ci: update auto label 2025-11-06 11:46:42 -06:00
Dax Raad
67f3c934fe fix tests 2025-11-06 11:42:46 -05:00
Aiden Cline
065f656fb0 chore: rm hanging test 2025-11-06 10:39:32 -06:00
Dax Raad
f636d937c4 fix undo command breaking other commands 2025-11-06 11:25:37 -05:00
Adam
492bf51a0d fix(desktop): sidebar collapsed width 2025-11-06 09:56:46 -06:00
Adam
81ab127f63 fix(desktop): demo type error 2025-11-06 09:49:39 -06:00
Adam
6ba7c54bab feat(desktop): collapsible sidebar 2025-11-06 09:48:51 -06:00
Adam
146bae82cb fix(desktop): button styles 2025-11-06 09:48:50 -06:00
Adam
ab345cf0da feat(desktop): better tooltips 2025-11-06 09:48:50 -06:00
GitHub Action
a1836527ce ignore: update download stats 2025-11-06 2025-11-06 12:04:56 +00:00
opencode-agent[bot]
49e4cfb286 Added big dot (●) indicator for current session in modal (#3980)
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-11-06 01:09:59 -06:00
Frank
e52bfab79d Update sst 2025-11-06 05:42:25 +00:00
opencode
cc6d5c8ddd release: v1.0.35 2025-11-06 05:42:24 +00:00
Dax Raad
afe8508949 fix homebrew upgrade 2025-11-06 00:37:44 -05:00
opencode
7c098c8849 release: v1.0.34 2025-11-06 02:35:17 +00:00
Dax Raad
11d6005b77 tui: reduce scrollbar visual prominence for less distracting interface 2025-11-05 21:30:40 -05:00
Dax Raad
2cc072b3dc enable scrollbar 2025-11-05 21:27:23 -05:00
opencode
86247b8ea9 release: v1.0.33 2025-11-06 02:18:41 +00:00
Dax Raad
0a5a02043c tui: move debug shortcuts to command palette for better discoverability 2025-11-05 21:13:35 -05:00
Sebastian Herrlinger
6e553f7e20 upgrade to opentui v0.1.36 2025-11-06 01:43:35 +00:00
opencode
bb6acc0ec6 release: v1.0.32 2025-11-06 01:43:35 +00:00
Dax Raad
5a84b9f467 temporarily use strip-ansi package till bun bug is fixed 2025-11-05 20:38:37 -05:00
opencode
c7031dfd77 release: v1.0.31 2025-11-06 01:35:40 +00:00
Dax Raad
e136a40771 ignore tmp type rrror 2025-11-05 20:31:13 -05:00
Dax Raad
ef25650ced regen bunlock 2025-11-05 20:30:40 -05:00
Dax Raad
6555a33eff type errors 2025-11-05 20:14:31 -05:00
Dax Raad
247ce44776 fix log 2025-11-05 20:01:57 -05:00
Dax Raad
4e7bfaab8b fix log 2025-11-05 20:00:09 -05:00
Sebastian Herrlinger
8b26a1f9bd upgrade to opentui 0.1.35, mitigating disappearing content and crashes 2025-11-06 01:38:58 +01:00
Dax Raad
2a9b6a85de core: ensure export command output can be piped without UI interference 2025-11-05 18:36:06 -05:00
opencode
c9ae89a38b release: v1.0.30 2025-11-05 23:32:22 +00:00
Dax Raad
e316050bf5 temporarily remove bun strip ansi due to bug 2025-11-05 18:27:19 -05:00
Dax Raad
306f45f04a add opencode import command to restore sessions from JSON exports 2025-11-05 18:05:01 -05:00
Adam
e006e3355c feat(desktop): incrementally load sessions in side nav 2025-11-05 16:32:08 -06:00
opencode
d7e31f76c4 release: v1.0.29 2025-11-05 22:14:46 +00:00
Aiden Cline
d425723249 ask instead of throwing tool error if file is outside cwd 2025-11-05 16:09:47 -06:00
Adam
c59ec71918 fix(desktop): max height on message diffs, session stats 2025-11-05 16:00:20 -06:00
Dax Raad
05ae99a09b fix sidebar modified files 2025-11-05 16:49:17 -05:00
opencode
6e22b45905 release: v1.0.28 2025-11-05 21:08:58 +00:00
Aiden Cline
c664f92829 acp: update auth method 2025-11-05 15:01:14 -06:00
Aiden Cline
f95333aaa4 acp: default to big pickle 2025-11-05 14:59:02 -06:00
opencode
ef0b5e3dcb release: v1.0.27 2025-11-05 20:44:40 +00:00
Dax Raad
b7262b8527 performance improvements 2025-11-05 15:33:23 -05:00
Dax Raad
1f44c7f750 include file count in summary 2025-11-05 14:23:10 -05:00
Dax Raad
7dba570195 ci: fix aur package 2025-11-05 14:22:25 -05:00
Haris Gušić
3d04ba26a3 fix(autocomplete): Prioritize exact matches (#3760) 2025-11-05 12:25:05 -06:00
Adam
3660e2c481 fix(desktop): local dev url 2025-11-05 12:09:30 -06:00
opencode
06ca45189b release: v1.0.26 2025-11-05 18:08:19 +00:00
Adam
674febcf60 fix(desktop): type issue 2025-11-05 11:59:10 -06:00
Adam
582d9a9622 fix(desktop): type issue 2025-11-05 11:56:32 -06:00
Adam
d525fbf829 feat(desktop): session router, interrupt agent, visual cleanup 2025-11-05 11:55:35 -06:00
OpeOginni
69a499f807 fix(tui): restructure Sidebar component to be scrollable (#3946) 2025-11-05 11:33:30 -06:00
Dax Raad
37e564139f tui: lower paste summary threshold to trigger on shorter content 2025-11-05 12:32:23 -05:00
monke-yo
ee8b81269b feat: add --attach flag to opencode run (#3889) 2025-11-05 11:31:01 -06:00
Filip
53998a2fed chore: remove unused patch tool from registry (to avoid accidental inclusions of it) (#3938) 2025-11-05 11:26:28 -06:00
Aiden Cline
af7b9e77d1 fix: eu-west-2 aws bedrock issue 2025-11-05 10:14:08 -06:00
Aiden Cline
77c65b18b5 tweak: normalize escape keybind 2025-11-05 10:13:04 -06:00
Matthew Fitzpatrick
c9dfe6d964 docs: include "limit" example (#3925)
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-11-05 09:47:11 -06:00
Aiden Cline
03f7f18260 ci: adjust auto label 2025-11-05 09:36:38 -06:00
GitHub Action
2db76fc6dd ignore: update download stats 2025-11-05 2025-11-05 12:04:41 +00:00
opencode
7269c2316d release: v1.0.25 2025-11-05 07:00:07 +00:00
Aiden Cline
1e0596bc46 ACP: update package, fix slash command bug (#3906) 2025-11-05 00:50:48 -06:00
Aiden Cline
3ebec2435a allow @ agents to work even if not first thing in prompt 2025-11-04 23:37:09 -06:00
Christian Stewart
b90c0b5fac feat(tui): add /export and /copy commands (#3883)
Signed-off-by: Christian Stewart <christian@aperture.us>
2025-11-04 23:02:45 -06:00
Err
3b1ab444fd feat: add Clojure syntax highlighting support (#3912)
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-11-04 22:47:05 -06:00
Dax Raad
234db24f1f tui: fix command validation to prevent invalid commands from being executed 2025-11-04 20:46:01 -05:00
Kyle F Butts
04546c0873 Add support for R formatter in formatters (#3918)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-04 18:18:33 -06:00
opencode
f51bd91af4 release: v1.0.24 2025-11-05 00:12:19 +00:00
Dax Raad
ebca25462e tui: fix session abort when autocomplete is visible 2025-11-04 18:45:39 -05:00
Aiden Cline
01b9148c04 fix: image reading error, also add error toast for event bus 2025-11-04 17:30:58 -06:00
Frank
d3e080894c wip: zen 2025-11-04 17:54:08 -05:00
Jay V
ee9aa24a55 ignore: update meta description to use proper OpenCode capitalization 2025-11-04 17:27:22 -05:00
Frank
16e2bded5b wip: zen 2025-11-04 17:24:20 -05:00
Frank
9fb49ab87b wip: zen 2025-11-04 17:24:20 -05:00
Frank
8d6a03cc89 zen: custom reload amount 2025-11-04 17:24:20 -05:00
Dax Raad
71b04ffa99 add command bar option to interrupt session 2025-11-04 17:11:07 -05:00
Aiden Cline
678ca757c9 fix: permissions not responding to esc 2025-11-04 16:08:31 -06:00
Jared A. Scheel
272349b8da Add support for uv format in formatters (#3916) 2025-11-04 15:40:29 -06:00
Ola
fe94bb8e50 feat(provider): add GitHub Enterprise support for Copilot (#2522)
Co-authored-by: Jon-Mikkel Korsvik <48263282+jkorsvik@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-04 15:36:12 -06:00
opencode
ba8bc1b8b4 release: v1.0.23 2025-11-04 19:38:18 +00:00
Timo Clasen
8a9a474df6 feat(TUI): add autocomplete readline style keybinds (#3717)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-11-04 13:28:03 -06:00
Aiden Cline
52e2b40610 fix: stop showing auto complete if user types a space 2025-11-04 13:22:18 -06:00
Dax Raad
ee1ff8cc07 tui: add ability to interrupt running sessions from command palette 2025-11-04 14:07:22 -05:00
opencode
434c0ff0d7 release: v1.0.22 2025-11-04 18:41:38 +00:00
Dax Raad
7a7060ef15 fix session performance issue from large diffs 2025-11-04 13:35:44 -05:00
opencode
f9af9fc221 release: v1.0.21 2025-11-04 17:47:34 +00:00
Sebastian Herrlinger
1bf1b93404 Revert "upgrade opentui to address disappearing content issues #3776, #3697"
This reverts commit 90fc3ddb02.
2025-11-04 18:38:44 +01:00
Dax Raad
bc6f4aed2b local web 2025-11-04 12:33:14 -05:00
Aiden Cline
2af3f19397 respect: disable_paste_summary 2025-11-04 11:29:12 -06:00
Aiden Cline
9275665868 fix: /undo command 2025-11-04 10:59:34 -06:00
Christian Stewart
09bb819064 fix(tui): worker path resolution in dev mode (#3778)
Signed-off-by: Christian Stewart <christian@cjs.zip>
Co-authored-by: Sebastian Herrlinger <hasta84@gmail.com>
2025-11-04 17:38:11 +01:00
Err
6f0028644e fix: support scoped npm plugins (#3785)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-04 09:15:01 -06:00
Pranshu Raj
aec44abcf6 [FIX]: Refocus prompt after session delete (#3882) 2025-11-04 08:57:59 -06:00
frankdierolf
b41e573886 fix: correct history_next keybinding description (#3891) 2025-11-04 08:52:49 -06:00
arc-source-coder
737ddab300 tui: make /mcp an alias for /status (#3894) 2025-11-04 08:52:28 -06:00
Sebastian Herrlinger
90fc3ddb02 upgrade opentui to address disappearing content issues #3776, #3697 2025-11-04 14:57:15 +01:00
Adam
15d7eebb92 fix: lander specs 2025-11-04 06:15:12 -06:00
GitHub Action
33301c94df ignore: update download stats 2025-11-04 2025-11-04 12:05:02 +00:00
Aiden Cline
d341d26e37 update brew handling 2025-11-04 00:52:26 -06:00
opencode
d49b1b25d1 release: v1.0.20 2025-11-04 05:56:24 +00:00
Dax Raad
25eb100210 tui: fix tool permission lookup to use correct session ID 2025-11-04 00:50:12 -05:00
Dax Raad
9886353715 fix: persist -m model when switching agents
Add initial model from command line to fallback chain so it persists
when switching agents with tab, matching behavior of config model.

Resolves #3863
2025-11-03 23:51:52 -05:00
Aiden Cline
f501501791 fix: piping 2025-11-03 22:36:10 -06:00
Dax Raad
c103052f93 fix: handle parsePatch errors in TUI to prevent crashes
Wrap parsePatch calls in try-catch blocks to gracefully handle malformed
diffs that can occur when undoing after tool_use/tool_result errors or
cancelled prompts. Prevents TUI from crashing with 'Added line count did not
match for hunk' error.

Fixes #3700
2025-11-03 23:36:04 -05:00
Greg Pstrucha
68039d4c71 Fix file tagging in multi line inputs (#3865) 2025-11-03 23:22:38 -05:00
Aiden Cline
d3566d3b1a ignore: delete unused code 2025-11-04 04:01:22 +00:00
Stephen Collings
b275e18d28 fix: Provide OPENCODE & AGENT env vars (#3843)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-04 04:01:22 +00:00
Dax Raad
af9a1797b5 tui: use keybind helper for history navigation to respect custom keybindings 2025-11-04 04:01:22 +00:00
opencode
29b3e40ddb release: v1.0.19 2025-11-04 04:01:22 +00:00
Dax Raad
c49f5939a2 tui: fix model selection for models with nested paths
Users can now select models with multiple slashes like 'openrouter/google/gemini-2.5-pro'
in the TUI. Previously the TUI would only parse the first two parts of the model
path, showing 'Invalid model openrouter/google' for nested models.
2025-11-03 19:52:43 -05:00
kaanmertkoc
63862b1609 feat: implement stats command (#3832) 2025-11-03 18:41:30 -06:00
Mikhail Wahib
1cf1e88b52 fix: print the modified keybind for command_list (#3859) 2025-11-03 18:29:23 -06:00
Jay V
d06afd87e5 ignore: lander 2025-11-03 18:17:32 -05:00
Frank
9fb6e81007 wip: zen 2025-11-03 17:30:18 -05:00
Adi Yeroslav
3ac82227f1 fix: update logo (#3833) 2025-11-03 16:18:38 -06:00
Aiden Cline
c1f9249c84 ci: auto label web 2025-11-03 16:17:53 -06:00
Dax Raad
9bb66946db fix: correct dirs parameter type in file search 2025-11-03 17:10:31 -05:00
Dax Raad
adcdbbddc7 tui: remove duplicate copy message command entry 2025-11-03 17:10:20 -05:00
Dax Raad
662435c5bb ci: stuff 2025-11-03 22:01:30 +00:00
opencode
36c1a05eaa release: v1.0.18 2025-11-03 22:01:30 +00:00
Dax Raad
5708e3bf1e ci: tweak 2025-11-03 16:56:41 -05:00
Dax Raad
0da1ed3fc8 tui: add copy last assistant message to session menu 2025-11-03 16:47:18 -05:00
Adam
d5179c8b63 wip: desktop work 2025-11-03 15:42:39 -06:00
Adam
bd0a4f7bbe wip: desktop work 2025-11-03 15:42:10 -06:00
Adam
3d43214075 wip: desktop work 2025-11-03 15:42:10 -06:00
Dax Raad
178a14ce3e fix dirs query param 2025-11-03 16:35:55 -05:00
Timo Clasen
8e1010dc3f feat(TUI): don't show /share hint if sharing is disabled (#3835) 2025-11-03 15:30:55 -06:00
Adi Yeroslav
9c82f1f5e9 fix: session rename functionality (#3840) 2025-11-03 15:26:30 -06:00
Dmytro Tiapukhin
e5a651eef7 fix: better mcp sanitization (#3842)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-03 15:19:49 -06:00
Dax Raad
d26605aa56 tui: add support for Ctrl+_ key combination in keybind parser 2025-11-03 16:16:09 -05:00
opencode
5cc0d337b1 release: v1.0.17 2025-11-03 21:14:52 +00:00
Dax Raad
902763b47d web command 2025-11-03 16:10:23 -05:00
Aiden Cline
55d07a139c fix: mcp error (#3847) 2025-11-03 15:04:53 -06:00
Frank
05232ead93 zen: wip 2025-11-03 15:44:06 -05:00
Tyler Gannon
7652a96064 fix: wait for stdout to flush in generate command (#3821) 2025-11-03 14:05:48 -06:00
Frank
901aae09f7 zen: filter out alpha models 2025-11-03 15:04:59 -05:00
opencode
f95799f17c release: v1.0.16 2025-11-03 17:08:32 +00:00
Dax Raad
99a6c5e44d regen sdk 2025-11-03 11:55:19 -05:00
Dax Raad
07bb75f086 core: add optional dirs parameter to file search API
Allow users to exclude directories from file search results by setting dirs=false parameter in /find/file endpoint
2025-11-03 11:53:41 -05:00
Frank
66eb846e6f zen: wip 2025-11-03 11:30:53 -05:00
Adam
34f11c699e wip: desktop work 2025-11-03 08:29:13 -06:00
Adam
7a32fec008 wip: desktop work 2025-11-03 08:29:13 -06:00
James Alexander
37a6b5177e Add unit tests for util functions: iife, lazy, timeout (#3791) 2025-11-03 09:24:45 -05:00
Haris Gušić
573ffe186b fix(tui): Show correct keybind in session delete confirmation message (#3805) 2025-11-03 09:22:05 -05:00
Alex Knight
0f7ff3fcb1 Log share link immediately after session creation (#3811) 2025-11-03 09:21:43 -05:00
frankdierolf
2c3aa330b9 fix: correct clipboard image encoding and binary handling (#3817) 2025-11-03 09:21:13 -05:00
Pranshu Raj
47b2fb79dc docs: add session_child_cycle and session_child_cycle_reverse keybinds (#3807) 2025-11-03 09:20:35 -05:00
Sebastian Herrlinger
6deaf54bb3 use new opentui getTextRange method and Bun.stringWidth instead of value.length to mitigate issues like #3734 2025-11-03 15:15:55 +01:00
GitHub Action
d549cd3213 ignore: update download stats 2025-11-03 2025-11-03 12:04:38 +00:00
Ivan Starkov
93e52f7ecf feat: Enhance task display with [subagent type] (#3772) 2025-11-03 01:09:31 -06:00
Aiden Cline
88f12b0822 core: prevent TypeError when error handling encounters non-object errors
When API errors like token limit exceeded errors are passed as strings to error checking methods, the 'in' operator would throw a TypeError. This fix adds a type guard to check that the input is an object before attempting to access its properties, allowing proper error classification even when encountering unexpected error formats from providers.
2025-11-02 23:38:56 -06:00
Zeldris
54af7f9e18 docs: use brew official formula (#3733) 2025-11-02 21:00:23 -06:00
Dax Raad
be685e95a3 docs 2025-11-03 01:57:36 +00:00
Dax Raad
dc2ab75fca ci: eventualy consistency 2025-11-03 01:57:36 +00:00
opencode
f1324e886f release: v1.0.15 2025-11-03 01:57:36 +00:00
opencode
c47fde2ca4 release: v1.0.14 2025-11-03 00:12:08 +00:00
Dax Raad
f42e1c6375 tui: fix focus management and dialog interactions 2025-11-02 19:07:22 -05:00
Dax Raad
f68374ad22 DELETE GO BUBBLETEA CRAP HOORAY 2025-11-02 18:43:33 -05:00
opencode
5e86c9b791 release: v1.0.13 2025-11-02 23:31:25 +00:00
Dax Raad
94658c31c5 add back child session cycle 2025-11-02 18:26:38 -05:00
Dax Raad
9fd672a1cb undo 2025-11-02 16:31:32 -05:00
Dax Raad
10523c4372 move dialog select keybind to input 2025-11-02 15:47:04 -05:00
Dax Raad
d1cd7d0344 ci: centralize Bun version to package.json to ensure consistent builds across CI and local development 2025-11-02 15:42:15 -05:00
Dax Raad
06ac1be226 upgrade to bun 1.3.1 2025-11-02 14:00:50 -05:00
Dax Raad
05489bc843 tui: fix file path handling when pasting images with spaces in filename
- Fixes issue where files with spaces in their names couldn't be pasted as images
- Prevents default paste behavior to avoid conflicts with image insertion
- Improves error handling for file reading operations
2025-11-02 13:45:44 -05:00
Dax Raad
3f02eecf22 tui: add /timeline command to quickly navigate to specific messages in session history 2025-11-02 18:27:42 +00:00
opencode
f5ca78ed7b release: v1.0.12 2025-11-02 18:27:42 +00:00
Dax Raad
894cbaa51e fix duplicate plugin subscriptions 2025-11-02 13:22:58 -05:00
John Eismeier
8b70b89fde fix: typos (#3757)
Signed-off-by: John E <jeis4wpi@outlook.com>
2025-11-02 09:56:40 -06:00
Aditya Mathur
f9dbc586dc chore: update hono-openapi to version 1.1.1 (#3738) 2025-11-02 09:21:55 -06:00
GitHub Action
ffeef63ca1 ignore: update download stats 2025-11-02 2025-11-02 12:04:05 +00:00
kcrommett
4da58294d9 add nightowl theme back after opentui release (#3732) 2025-11-02 04:29:14 -05:00
opencode
fa2e88f49b release: v1.0.11 2025-11-02 08:18:59 +00:00
Dax Raad
28e765ef0a fix dialog 2025-11-02 02:53:55 -05:00
Dax Raad
bfbcb5f200 tui: prevent default Enter key behavior when selecting dialog options to avoid conflicts 2025-11-02 01:19:30 -05:00
Aiden Cline
89492b3002 ci: fix regex 2025-11-01 20:23:10 -05:00
opencode-agent[bot]
2663415d47 github action: truncate PR titles to 256 chars to avoid GH api errors (#3727)
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-11-01 20:09:35 -05:00
Aiden Cline
51be67cc14 ci: stop auto assigning 2025-11-01 19:57:09 -05:00
Sebastian Herrlinger
92a1943771 upgrade to opentui 0.1.32, activates kitty keyboard 2025-11-02 01:45:38 +01:00
opencode
1e15fc273a release: v1.0.10 2025-11-01 18:06:28 +00:00
Dax
104a895a71 Light mode (#3709) 2025-11-01 13:54:01 -04:00
Dax Raad
f98e730405 docs update 2025-11-01 13:23:03 -04:00
Dax Raad
b12bef05d3 docs: update keybinds documentation with current defaults and remove deprecated bindings 2025-11-01 12:32:22 -04:00
opencode
2f1d001cc5 release: v1.0.9 2025-11-01 16:22:49 +00:00
Dax Raad
65d0b3ed6d sync 2025-11-01 12:14:15 -04:00
Haris Gušić
22a34d7958 feat: tui: Port /exit command and all command aliases (#3665) 2025-11-01 12:13:10 -04:00
Aiden Cline
cb4401ec92 ignore: update contributing md 2025-11-01 11:08:07 -05:00
opencode
febf467b03 release: v1.0.8 2025-11-01 15:58:23 +00:00
Dax Raad
d55a2fd56c tui: change delete keybind to ctrl+d in session list dialog 2025-11-01 11:53:46 -04:00
Dax Raad
40f577e5e7 fix modified files being empty 2025-11-01 11:48:47 -04:00
Dax Raad
9e49870118 remember sidebar position 2025-11-01 11:40:33 -04:00
Daniel van Strien
fe38e3ab02 docs: add Hugging Face Inference Providers documentation (#3505)
Co-authored-by: célina <hanouticelina@gmail.com>
2025-11-01 10:33:17 -05:00
Haris Gušić
0170577743 feat: tui: Add --prompt option (#3668) 2025-11-01 11:18:31 -04:00
Giuseppe Rota
7de6ea5922 fix: fix typo in commit message guidelines (#3702) 2025-11-01 10:14:53 -05:00
Yuku Kotani
2fe7d13e69 Add formatter status display to TUI status dialog (#3701) 2025-11-01 11:14:39 -04:00
Dax Raad
1bc3c98ae7 ensure wl-copy is available 2025-11-01 11:10:39 -04:00
Haris Gušić
55787f2caa fix: tui: Handle Clipboard.copy errors properly (#3685) 2025-11-01 15:34:21 +01:00
Haris Gušić
7df61a74a0 fix: tui: add toast for /share url copy (#3686) 2025-11-01 08:06:56 -05:00
GitHub Action
4f23110880 ignore: update download stats 2025-11-01 2025-11-01 12:04:18 +00:00
Aiden Cline
041353f4ff make /init a default slash command on server side (#3677) 2025-11-01 01:14:09 -05:00
Haris Gušić
c72f8b17c6 fix: tui: Fix /editor command (#3663) 2025-11-01 00:16:06 +00:00
opencode
eb304f4115 release: v1.0.7 2025-11-01 00:16:05 +00:00
Dax Raad
5565f14ef5 tab to accept autocomplete 2025-10-31 20:10:01 -04:00
Dax Raad
10a4455c6f tui: fix prompt text aggregation to exclude synthetic content 2025-10-31 20:01:27 -04:00
Dax Raad
5ded6d6ad7 docs: sync 2025-10-31 23:58:57 +00:00
opencode
849a38c30c release: v1.0.6 2025-10-31 23:58:57 +00:00
Dax Raad
68050ab802 tui: prevent clipboard operations from throwing errors on process exit 2025-10-31 19:54:15 -04:00
opencode
91d01fd4cc release: v1.0.5 2025-10-31 23:51:36 +00:00
Dax Raad
9beb0f8512 tui: improve keyboard navigation and MCP server status display 2025-10-31 19:47:08 -04:00
Dax Raad
d4cb47eadc tui: add keyboard shortcuts to cycle through recently used models
Users can now press F2 to cycle forward and Shift+F2 to cycle backward through their recently used models, making it faster to switch between commonly used AI models without opening the model selection dialog.
2025-10-31 19:42:41 -04:00
Dax Raad
261ff416a9 sync 2025-10-31 23:05:11 +00:00
opencode
d0a70cb217 release: v1.0.4 2025-10-31 23:05:10 +00:00
Aiden Cline
20fc56d020 Revert "opentui: fix: Make worker.ts path independent from cwd (#3600)"
This reverts commit d473d4ffc8.
2025-10-31 17:57:56 -05:00
opencode
a57ae3ec93 release: v1.0.3 2025-10-31 22:52:57 +00:00
Dax Raad
30f9fa12d9 tui: add session rename functionality with /rename command
- Add /rename command to autocomplete when a session is active
- Add rename dialog component for changing session names
- Add rename option to session list dialog with 'r' keybind
- Add session rename command to command registry
2025-10-31 18:44:33 -04:00
Haris Gušić
d473d4ffc8 opentui: fix: Make worker.ts path independent from cwd (#3600) 2025-10-31 17:37:31 -05:00
Haris Gušić
af50596529 fix: grep failing when pattern started with a dash 2025-10-31 17:20:22 -05:00
Dax Raad
3823d8d50e tui: simplify theme selection API by renaming setSelectedTheme to set 2025-10-31 18:11:36 -04:00
Dax Raad
7a926b32ce respect theme in config 2025-10-31 18:04:38 -04:00
Haris Gušić
a5ede68241 fix: Remove conflicting "-h" aliases in TUI spawn and thread commands (#3651) 2025-10-31 16:59:59 -05:00
Aiden Cline
60dc38050d fix: unsupported option 2025-10-31 16:53:08 -05:00
Dax Raad
31d0caee38 tui: add /editor command to autocomplete for opening external editor 2025-10-31 17:47:08 -04:00
Dax Raad
2a7ab45605 add /theme 2025-10-31 17:44:41 -04:00
Aiden Cline
019054dd1e Revert "fix: ensure flags & docs match (#3638)"
This reverts commit a018a15f32.
2025-10-31 16:43:29 -05:00
opencode-agent[bot]
a018a15f32 fix: ensure flags & docs match (#3638)
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-10-31 16:33:46 -05:00
Nathan Thomas
e630d680dd feat: allow ctrl+d to exit the app (#3636) 2025-10-31 16:27:41 -05:00
Haris Gušić
9e392f25a6 feat: Improve error boundary add button to easily create issue in github (#3639) 2025-10-31 16:20:32 -05:00
Aiden Cline
2cc4e6ad7c ci: change model 2025-10-31 16:06:22 -05:00
Adam
70d8d1ab1e wip: desktop work 2025-10-31 15:57:21 -05:00
Adam
342aa27e03 wip: desktop work 2025-10-31 15:37:50 -05:00
Adam
e1aed0cd01 wip: desktop work 2025-10-31 15:37:50 -05:00
opencode
c8ea2c5ce0 release: v1.0.2 2025-10-31 20:33:50 +00:00
Dax Raad
5e8309a353 tui: update hello command with test content 2025-10-31 16:21:30 -04:00
Dax Raad
aae0ce9921 tui: improve autocomplete component styling and update test command 2025-10-31 16:21:30 -04:00
Dax Raad
81b94d84dc ignore 2025-10-31 20:17:40 +00:00
opencode
ceab70f8d9 release: v1.0.1 2025-10-31 20:17:39 +00:00
Dax Raad
afe8cecc2b tui: add persistent key-value storage for user preferences
- Add KVProvider context for storing user preferences like theme and warnings
- Update theme context to use KV storage instead of sync config
- Move openrouter warning to persistent KV storage
- Refactor theme selection to persist user choice across sessions
2025-10-31 16:13:02 -04:00
Aiden Cline
4a292bf977 ci: auto assign 2025-10-31 14:58:18 -05:00
Aiden Cline
e249b41513 ci: autolabel action 2025-10-31 14:55:33 -05:00
Dax Raad
9021dd60a1 tui: add /session command to list available sessions 2025-10-31 15:41:36 -04:00
opencode
b9a39b816c release: v1.0.0 2025-10-31 19:28:07 +00:00
Dax Raad
1eeba770b1 docs: add v1.0 upgrade guide with UX changes documentation 2025-10-31 15:15:52 -04:00
Frank
6cff306be1 wip: zen 2025-10-31 15:09:48 -04:00
Dax
96bdeb3c7b OpenTUI is here (#2685) 2025-10-31 15:07:36 -04:00
opencode
81c617770d release: v0.15.31 2025-10-31 18:56:26 +00:00
Frank
021334509e sync 2025-10-31 14:43:51 -04:00
Frank
4bde3f7b15 zen: billing page layout 2025-10-31 14:43:51 -04:00
Haris Gušić
4355027408 fix: Missing @opencode-ai/plugin causes crash (#3615) 2025-10-31 13:41:04 -05:00
David Hill
b022cf0ed6 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-10-31 17:02:53 +00:00
David Hill
a529b0324d wip: Tweaking select styles
This ghost style is placeholder for now until we style this properly with an icon and all the states.
2025-10-31 17:02:44 +00:00
David Hill
16f5e16395 wip: Tweaking button styles 2025-10-31 17:02:12 +00:00
Adam
76e080b2cb wip: desktop work 2025-10-31 12:00:44 -05:00
Adam
ffc889b99e wip: desktop work 2025-10-31 12:00:44 -05:00
Steven Martin
36b48a44ac tweak - normalise unix-like identifiers to support git bash for windows (#2100)
Co-authored-by: Steven Martin <smartin@clearcom.com>
2025-10-31 11:41:44 -05:00
David Hill
5379abe330 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-10-31 16:03:36 +00:00
David Hill
a5bcb76bbf Added border as shadow and updated button 2025-10-31 16:03:33 +00:00
Aiden Cline
b628c580c2 update types 2025-10-31 10:49:52 -05:00
Aiden Cline
46d675b980 tweak: filter out deprecated models 2025-10-31 10:48:51 -05:00
Adam
a8bf1ad40f wip: desktop work 2025-10-31 10:08:24 -05:00
Adam
0ac943de90 wip: desktop work 2025-10-31 09:45:57 -05:00
Adam
485135cf5c wip: desktop work 2025-10-31 07:30:02 -05:00
Adam
543eee78a6 wip: desktop work 2025-10-31 07:24:47 -05:00
GitHub Action
dafb63cfb3 ignore: update download stats 2025-10-31 2025-10-31 12:04:46 +00:00
opencode
504a599473 release: v0.15.30 2025-10-31 06:12:16 +00:00
Dax Raad
750b9f80a5 sync 2025-10-31 02:00:11 -04:00
Aiden Cline
dfdd009750 fix: bash permission case 2025-10-31 00:52:46 -05:00
Haris Gušić
c1ada302f9 fix: Opencode hangs after exit (#3481)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-10-30 23:57:58 -05:00
Filip
51e4c9fc4c add optional headers field to model config (#3546) 2025-10-30 23:35:26 -05:00
Jay V
43e272e6c4 ignore: refactor header context menu to use CSS styling and router navigation 2025-10-30 20:24:29 -04:00
David Hill
2f9f189f39 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-10-30 22:53:00 +00:00
David Hill
f3c70f4ea8 Asset updates 2025-10-30 22:52:56 +00:00
David Hill
5d4441cd2b Fix download zip 2025-10-30 22:41:32 +00:00
David Hill
bf5f34ace7 Brand Assets Zip 2025-10-30 22:38:12 +00:00
David Hill
9589657d21 Configure png and svg downloads 2025-10-30 22:25:07 +00:00
David Hill
37baed99c1 Brand page 2025-10-30 22:13:48 +00:00
Ritoban Dutta
a3ba740de4 fix: resolve hanging permission prompts in headless mode (#3522)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-10-30 15:37:41 -05:00
Adam
dc96664578 chore: sanitize tool parts locally 2025-10-30 14:57:38 -05:00
Jay V
4dafc532a8 ignore: update project stats to reflect current growth 2025-10-30 15:47:41 -04:00
Adam
984fe4b769 wip: desktop work 2025-10-30 14:46:38 -05:00
Adam
48f50cf55e wip: desktop work 2025-10-30 14:41:15 -05:00
Frank
ba13f8da08 wip: fix 2025-10-30 15:15:46 -04:00
Frank
1a8b494055 wip: zen 2025-10-30 15:10:29 -04:00
Frank
4f02d7d424 zen: allow byok requests w/o a balance 2025-10-30 15:10:29 -04:00
Adam
4cebd69bf0 wip: desktop work 2025-10-30 13:54:52 -05:00
Adam
dc6e54503c wip: desktop work 2025-10-30 13:49:29 -05:00
David Hill
f18847d739 Adding links to legal 2025-10-30 17:37:30 +00:00
Adam
2a0b67d84f fix: lander space 2025-10-30 12:32:25 -05:00
Adam
89eac737a5 wip: desktop work 2025-10-30 12:30:45 -05:00
Brandon
c68607fb2b feat: Adds session id to Agent context metadata (#3559)
Co-authored-by: Brandon Wisnicki <bwisnicki@palantir.com>
2025-10-30 12:15:21 -05:00
Adam
e944ff0286 wip: desktop work 2025-10-30 12:13:02 -05:00
Adam
ee7612a31c wip: desktop work 2025-10-30 12:02:51 -05:00
Adam
582ed7c363 wip: desktop work 2025-10-30 12:02:50 -05:00
Adam
dce287a42d wip: desktop work 2025-10-30 12:02:50 -05:00
Adam
19974daa67 wip: desktop work 2025-10-30 12:02:50 -05:00
Adam
dcf865a889 wip: desktop work 2025-10-30 12:02:50 -05:00
Adam
3b20935959 wip: desktop work 2025-10-30 12:02:50 -05:00
Adam
30f4c2cf4c wip: desktop work 2025-10-30 12:02:49 -05:00
Aiden Cline
3541fdcb20 tweak: adjust deno lsp (#3581) 2025-10-30 11:59:54 -05:00
David Hill
15de97c10f Change position 2025-10-30 16:55:23 +00:00
Jay V
ee3fd3f7be ignore:lander 2025-10-30 12:40:07 -04:00
Haris Gušić
dc87659791 tweak: move zod validation for tools to ensure it always runs (#3565) 2025-10-30 11:31:44 -05:00
Andrew Pashynnyk
149f5eaa2e fix: preserve metadata from MCP tool results in tool.execute.after hook (#3573) 2025-10-30 11:10:42 -05:00
Aiden Cline
42e0b47a7d fix: better frontmatter errors 2025-10-30 10:56:40 -05:00
oribi
2d5df3ad76 fix: agent model selection priority issue (#3572) 2025-10-30 09:32:39 -05:00
David Hill
f202fa0d89 Theme aware svgs to be copied 2025-10-30 13:01:48 +00:00
David Hill
0abffdb8f8 Icon size update 2025-10-30 12:46:34 +00:00
David Hill
e533d48b51 New assets 2025-10-30 12:36:41 +00:00
David Hill
439372704d Close content menu with escape 2025-10-30 12:19:26 +00:00
David Hill
d7277fd305 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-10-30 12:16:15 +00:00
GitHub Action
5ae73637d3 ignore: update download stats 2025-10-30 2025-10-30 12:04:36 +00:00
David Hill
bf0cbf2bfa Adding polish 2025-10-30 11:37:00 +00:00
opencode
4b3a841dd9 release: v0.15.29 2025-10-30 06:49:34 +00:00
Aiden Cline
aca32eaa1c fix: tui not showing some errors 2025-10-30 01:41:28 -05:00
Matt Gillard
3ae75d7031 add optional timeout field to mcp config to allow users to use responding servers (#3558)
Co-authored-by: Matt Gillard <matt-github@gillard.biz>
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-10-30 01:25:24 -05:00
Khang Ha (Kelvin)
4b5e447961 Fix "bufio.Scanner token too long" error by replacing Scanner with Reader in SSE (#3531) 2025-10-30 01:04:06 -05:00
Aiden Cline
7a2b8eae76 tweak: catch err 2025-10-30 00:29:30 -05:00
ElecTwix
d983b9485d fix: add doom loop detection (#3445)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-10-30 00:13:18 -05:00
GitHub Action
14836de276 ignore: update download stats 2025-10-30 2025-10-30 02:28:13 +00:00
David Hill
e265efec09 Add right click menu to logo 2025-10-30 00:40:24 +00:00
David Hill
5ae00ba567 Brand page 2025-10-30 00:22:49 +00:00
Jay V
a0f032c9b9 ignore: fix email formatting to ensure proper line breaks between message and signature 2025-10-29 19:19:28 -04:00
Jay V
e6132fc6a4 docs: enterprise 2025-10-29 19:07:43 -04:00
Frank
950b608c4d zen: show browser default time 2025-10-29 17:14:28 -04:00
Adam
3210df7428 wip: desktop work 2025-10-29 16:04:34 -05:00
Adam
cdeb82e9ca wip: desktop work 2025-10-29 16:04:34 -05:00
rienkim
a9cae7b335 feat: add positional argument support to slash commands (#3456)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-10-29 14:54:24 -05:00
Frank
972c0893dd zen: fix unified endpoint for codex 2025-10-29 15:06:37 -04:00
opencode
e5d89ca567 release: v0.15.28 2025-10-29 19:00:56 +00:00
Aiden Cline
4ae70d4b0d fix: parcel watcher issue (#3544) 2025-10-29 13:52:38 -05:00
opencode
935cd7481b release: v0.15.27 2025-10-29 18:41:24 +00:00
Dax Raad
5553efea5e only generate user message summary if no diffs 2025-10-29 14:28:43 -04:00
Adam
0ff73ed8a6 wip: desktop work 2025-10-29 16:59:45 +00:00
opencode
5e792d7ac5 release: v0.15.26 2025-10-29 16:59:45 +00:00
David Hill
4a77e94e3c Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-10-29 16:16:56 +00:00
David Hill
4c563ea405 Enron update 2025-10-29 16:16:48 +00:00
Aiden Cline
5875257462 ignore: add label 2025-10-29 11:08:46 -05:00
David Hill
9701891e94 testimonial and other polish 2025-10-29 16:02:25 +00:00
opencode
a2ab37c1b6 release: v0.15.25 2025-10-29 16:01:53 +00:00
Dax Raad
4d6e2d8efc autoupgrade latest major only 2025-10-29 11:53:25 -04:00
Aiden Cline
4407d5d96f fix: ensure tool inputs are zod validated 2025-10-29 10:46:57 -05:00
Adam
244945c0e7 fix: desktop error 2025-10-29 10:43:34 -05:00
opencode
c652b2b4e8 release: v0.15.24 2025-10-29 15:38:55 +00:00
David Hill
aabeeb1431 Adding polish 2025-10-29 15:34:45 +00:00
David Hill
0fbedc5e19 Fix form submission 2025-10-29 15:34:39 +00:00
Dax Raad
12782fff14 remove log 2025-10-29 11:27:51 -04:00
Dax Raad
ca463a2346 session diff only include modified files 2025-10-29 11:26:21 -04:00
Aiden Cline
7265cdf817 ignore: rm 2025-10-29 10:19:02 -05:00
David Hill
7baa751351 First pass at adding an enterprise page 2025-10-29 15:16:17 +00:00
Adam
5b86fa9109 wip: desktop work 2025-10-29 07:32:01 -05:00
Adam
aa7e008fe1 wip: desktop work 2025-10-29 07:32:00 -05:00
GitHub Action
792664071c ignore: update download stats 2025-10-29 2025-10-29 12:04:57 +00:00
Aiden Cline
a0541ba57a zen: fix models endpoint to be openai compatible 2025-10-28 22:48:57 -05:00
Aiden Cline
4994bf1b46 ignore: rename type 2025-10-28 22:39:57 -05:00
Tyler Gannon
1e24514d61 add OpenAPI annotations to tui.ts control endpoints (#3519) 2025-10-28 22:39:22 -05:00
opencode
4b1c6300a0 release: v0.15.23 2025-10-29 01:35:27 +00:00
Dax Raad
db3fb9d316 ci: stuff 2025-10-28 21:28:44 -04:00
Dax Raad
cd79676b42 sync 2025-10-28 20:35:18 -04:00
Dax Raad
09e7e0ab70 tag majors 2025-10-28 20:28:30 -04:00
Kevin King
0e60f66604 ignore: python sdk (#2779)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-10-28 18:32:45 -05:00
Aiden Cline
fc8db6cdf9 fix: ensure timeout param passed to bash tool is positive 2025-10-28 17:32:39 -05:00
kcrommett
5cc37c4ea0 mcp: fix status() to not overwrite connected with failed (#3514) 2025-10-28 16:16:03 -05:00
Adam
46ad456718 wip: desktop work 2025-10-28 15:39:41 -05:00
Haris Gušić
832ffd2303 fix: Use process.stdout.write instead of console.log (#3508) 2025-10-28 15:38:08 -05:00
GitHub Action
b261430880 chore: format code 2025-10-28 20:29:47 +00:00
Adam
545f345848 wip: desktop work 2025-10-28 15:29:16 -05:00
Adam
77ae0b527e wip: desktop work 2025-10-28 15:29:16 -05:00
Adam
c1278109c9 wip: desktop work 2025-10-28 15:29:16 -05:00
Adam
a7a88d01ef wip: desktop work 2025-10-28 15:29:16 -05:00
Adam
4e0ab6b634 wip: desktop work 2025-10-28 15:29:16 -05:00
Adam
d36485b7af wip: desktop work 2025-10-28 15:29:15 -05:00
Adam
1da24f6adb wip: desktop work 2025-10-28 15:29:15 -05:00
Frank
e29dd27632 zen: provider affinity 2025-10-28 15:55:03 -04:00
Aiden Cline
37380e1f94 add --title flag to opencode run (#3507) 2025-10-28 13:32:36 -05:00
Dax Raad
1309ca7a81 ignore 2025-10-28 14:13:47 -04:00
Dax Raad
c1515316f5 core: fix additions and deletions counting in edit tool filediff 2025-10-28 14:08:10 -04:00
Danilo Favato
b66e7b6fce tweak: add experimental chatMaxRetries to config (#2116)
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-10-28 12:09:41 -05:00
oribi
eb398f1951 add OPENCODE_CONFIG_DIR to allow loading a custom config directory (#3504)
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-10-28 11:50:09 -05:00
Aiden Cline
643c22d21f add catch for mcp tool execution 2025-10-28 10:23:04 -05:00
Aiden Cline
74acd08ead add catch for mcp tool execution 2025-10-28 10:23:03 -05:00
opencode
49ea5aa2ad release: v0.15.20 2025-10-28 15:12:37 +00:00
Aiden Cline
ee1af0fe80 fix: blank version issue 2025-10-28 10:03:53 -05:00
GitHub Action
dfebf40471 ignore: update download stats 2025-10-28 2025-10-28 12:04:31 +00:00
opencode
6af6a1295f release: v0.15.19 2025-10-28 08:12:32 +00:00
Dax Raad
22821744ef feat: add OPENCODE_FAKE_VCS flag for VCS testing and update todo tracking instructions 2025-10-28 02:54:29 -04:00
Aiden Cline
872c9467b2 chore: rm unused import 2025-10-28 00:43:29 -05:00
Dax Raad
d8249f32a8 do not set temperature for claude models 2025-10-28 01:14:25 -04:00
Aiden Cline
982954cc1b feat (acp): mcp server support, file diffs, some default slash commands (/init, /compact), show todos properly (#3490)
The mcp server support does not mean acp didn't allow u to use mcp servers previously, it means that now you can connect new servers via ACP instead of relying on the opencode defined ones
2025-10-28 00:08:30 -05:00
Frank
4caa458232 acp: fix type error 2025-10-27 21:40:08 -04:00
Frank
6fe8e3973c zen: support 1M claude context 2025-10-27 21:36:10 -04:00
Frank
7816901713 wip: zen doc 2025-10-27 21:25:56 -04:00
Frank
71abca9571 wip: zen 2025-10-27 19:26:28 -04:00
kcrommett
7216a8c86d fix: editor paste functionality for text attachments (#3489) 2025-10-27 17:51:33 -05:00
Jay V
e3e16e58c5 docs: edit 2025-10-27 18:16:48 -04:00
Dax Raad
a2951a2702 Remove typecheck script from desktop package 2025-10-27 18:03:32 -04:00
Jay V
55453dc606 Add missing dependencies for desktop package 2025-10-27 17:49:31 -04:00
Jay V
198d7f7e5f Merge branch 'doc-acp' into dev 2025-10-27 17:49:24 -04:00
Jay V
e3e9fd7aa8 docs: edit 2025-10-27 17:48:17 -04:00
Aiden Cline
3c56dbcf58 chore: rm comment 2025-10-27 16:15:13 -05:00
Aiden Cline
ee07ed2dc4 chore: delete unused file 2025-10-27 15:44:12 -05:00
Adam
485e4520e7 wip: desktop work 2025-10-27 15:37:07 -05:00
Adam
fc115ea367 wip: desktop work 2025-10-27 15:37:07 -05:00
Adam
d03b79e61e wip: desktop work 2025-10-27 15:37:06 -05:00
Adam
0acae8211a wip: desktop work 2025-10-27 15:37:06 -05:00
Aiden Cline
0af4505756 fix: litellm error tool= param must be specified 2025-10-27 14:03:42 -05:00
Aurelien Ribon
a606e1d2ec fix: dont set reasoning effort to medium for gpt-5-pro (#3474) 2025-10-27 10:50:57 -05:00
Aiden Cline
0e65700183 update sdk 2025-10-27 10:47:04 -05:00
Aiden Cline
e6301ca5d5 tweak: rename event 2025-10-27 10:42:47 -05:00
Bernat Pericàs
b562863fcc feat: add session.started event that triggers when a new session is created (#3413) 2025-10-27 07:18:23 -05:00
GitHub Action
db85f01eff ignore: update download stats 2025-10-27 2025-10-27 12:04:50 +00:00
Aiden Cline
1a6fd018f6 Revert "fix: Explicitly exit CLI to prevent hanging subprocesses (#3083)"
This reverts commit a9624c0fff.
2025-10-27 01:30:13 -05:00
Aiden Cline
fdb5bae3c6 docs: acp 2025-10-27 00:56:00 -05:00
Haris Gušić
a9624c0fff fix: Explicitly exit CLI to prevent hanging subprocesses (#3083)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-10-27 00:03:10 -05:00
Aiden Cline
316d4c9197 wip 2025-10-26 23:49:56 -05:00
Denys Rybalka
5e886c35d5 chore: use stable URLs in PKGBUILD (#3448) 2025-10-26 19:50:45 -05:00
Aiden Cline
5162268f9d docs: update agent frontmatter permission example 2025-10-26 15:04:48 -05:00
Jérôme Benoit
0eb899a950 chore: cleanup versioned zod imports (#3460)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2025-10-26 14:50:41 -05:00
Dan McGuirk
3241f6b8bb docs: fix typos (#3454) 2025-10-26 14:37:25 -05:00
GitHub Action
2c792f17e6 ignore: update download stats 2025-10-26 2025-10-26 12:04:22 +00:00
Joscha Götzer
7d0c6860cd fix: make build script work cross-platform (#3430)
Co-authored-by: JosXa <info@josxa.dev>
2025-10-26 01:40:17 -05:00
Aiden Cline
c70e393c81 Remove claude-haiku-4.5 from default priority for GitHub Copilot session title generation 2025-10-26 01:21:34 -05:00
opencode
20963c4186 release: v0.15.18 2025-10-26 03:49:21 +00:00
Aiden Cline
0a778a2789 make title gen more reliable 2025-10-25 22:14:29 -05:00
Aiden Cline
42c1e61bf4 fix: $ invocation not .quiet() (#3449) 2025-10-25 16:27:09 -05:00
Dax Raad
795b845782 update anthropic prompt 2025-10-25 17:26:27 -04:00
Mohammad Alhashemi
2e434a459a feat: add noReply parameter (#3433)
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-10-25 14:56:54 -05:00
Paulo Edgar Castro
ae62bc8b1f fix: timeout param that allows user to disable provider timeout (#3443) 2025-10-25 12:08:27 -05:00
GitHub Action
187a5fe301 ignore: update download stats 2025-10-25 2025-10-25 12:04:18 +00:00
opencode
fc2afdc92f release: v0.15.17 2025-10-25 06:45:23 +00:00
Aiden Cline
fe5e7cfd1b ignore: rm change 2025-10-25 01:35:43 -05:00
Aiden Cline
98d51dde6a acp: slash commands, agents, permissions, @ references, code cleanup (#3403)
Co-authored-by: yetone <yetoneful@gmail.com>
2025-10-25 01:32:46 -05:00
Aiden Cline
5fec5ff424 fix: bedrock reasoning 2025-10-24 17:35:08 -05:00
Adam
fea6a357bc wip: desktop work 2025-10-24 16:04:44 -05:00
Adam
6b82153263 wip: desktop work 2025-10-24 15:51:31 -05:00
Adam
fa8e714d69 wip: desktop work 2025-10-24 15:43:47 -05:00
Adam
90515bc8c3 wip: desktop work 2025-10-24 15:02:31 -05:00
Adam
e34042e17a wip: accordion css not going to keep me down 2025-10-24 15:01:28 -05:00
Dax Raad
6ff0ce8bc5 ignore: improve session timeline debugging and message display functionality 2025-10-24 15:45:37 -04:00
Aiden Cline
e88b659545 make plan agent whitelist more conservative (#3424) 2025-10-24 14:40:36 -05:00
Dax Raad
74048ece2d ignore: fix new session message loading with retry logic to handle server processing delays 2025-10-24 15:35:53 -04:00
Dax Raad
6646f7264a ignore: highlight active session in sidebar to improve visual feedback 2025-10-24 15:27:20 -04:00
Dax Raad
18e549a474 ignore: fix session activation after creation to ensure proper state management 2025-10-24 15:24:17 -04:00
Adam
82249754e7 fix: pierre dep 2025-10-24 13:51:24 -05:00
Aiden Cline
5a0228897b ignore: reword 2025-10-24 13:46:47 -05:00
Adam
e2920c06a3 wip: desktop work 2025-10-24 13:01:22 -05:00
Dax Raad
4da3aa2eb2 add missing dep 2025-10-24 13:43:33 -04:00
Adam
efe7f01f41 wip: desktop work 2025-10-24 12:38:00 -05:00
Adam
9ae3d74adc wip: desktop work 2025-10-24 12:32:02 -05:00
Adam
477b6c584d wip: desktop work 2025-10-24 12:16:33 -05:00
Adam
86447b5764 wip: desktop work 2025-10-24 12:16:33 -05:00
Adam
fe8f6d7a3e wip: desktop work 2025-10-24 12:16:33 -05:00
Adam
59b5f53509 wip: desktop work 2025-10-24 12:16:32 -05:00
Adam
3eb2db98ed wip: desktop work 2025-10-24 12:16:32 -05:00
Adam
35dec0649d wip: desktop work 2025-10-24 12:16:32 -05:00
Adam
78a7f79143 wip: ui package demo page 2025-10-24 12:16:32 -05:00
Aiden Cline
707ed72381 adjust edit tool multiple matches error wording (#3418) 2025-10-24 12:14:49 -05:00
Dax Raad
21880e199d mroe summary tweaks 2025-10-24 12:37:23 -04:00
Dax Raad
736a85d427 track finish reason 2025-10-24 12:23:24 -04:00
Dax Raad
fb40dc6b20 generate user message title and body 2025-10-24 11:50:42 -04:00
Hieu Nguyen
483fcdaddb feat: support lua lsp (#3402) 2025-10-24 10:37:11 -05:00
Andrew Pashynnyk
883b71ac36 fix: respect local config for autoupdate settings (#3408)
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-24 09:50:09 -05:00
Dax Raad
3e574c71cb potentially fix fetch failed timeout errors 2025-10-24 09:46:28 -04:00
Dax Raad
4cab66da6c test cleanup 2025-10-24 09:29:38 -04:00
Dax Raad
7003efd2da fix summary 2025-10-24 09:24:58 -04:00
Dax Raad
06fe87b361 fix failing migration 2025-10-24 09:20:15 -04:00
GitHub Action
944fda45e6 ignore: update download stats 2025-10-24 2025-10-24 12:05:02 +00:00
opencode
343471b98d release: v0.15.16 2025-10-24 04:52:27 +00:00
Dax Raad
56528493dc codex does not have reasoning effort set 2025-10-24 00:29:34 -04:00
opencode
e66156c86e release: v0.15.15 2025-10-24 03:51:17 +00:00
Dax Raad
8b9b8ca15b update codex prompt 2025-10-23 23:36:32 -04:00
Haris Gušić
50cc641288 fix: Opencode fails with ENOENT posix_spawn '/usr/bin/rg' (#3396) 2025-10-23 18:05:01 -05:00
Bernat Pericàs
4c90bf3e07 refactor: whitelist some safe bash tools in Plan agent (#3288)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-10-23 17:11:08 -05:00
Aiden Cline
4216c1c2a9 adjust changelog generation 2025-10-23 16:16:11 -05:00
Dax Raad
4bd7646ccb regen sdk 2025-10-23 16:33:00 -04:00
Dax Raad
cee7106054 session summaries in data 2025-10-23 16:28:20 -04:00
Dax Raad
f4dfae0bb0 ignore: diff stuff 2025-10-23 16:04:58 -04:00
Aiden Cline
9b5fe10df6 add flag wildcard parsing support for bash tool (#3390) 2025-10-23 13:35:09 -05:00
Aiden Cline
b5f336c0ea test: rm flaky test 2025-10-23 12:52:08 -05:00
Aiden Cline
913c3ae799 tweak: split out title before newline 2025-10-23 12:44:43 -05:00
Thierry Delafontaine
a68111ca77 fix: move zod-to-json-schema to dependencies (#3387) 2025-10-23 12:16:03 -05:00
Aiden Cline
5f8a3a574e docs: fix numbers 2025-10-23 11:53:21 -05:00
Aiden Cline
d69e8e5528 docs: tweak google vertex 2025-10-23 11:49:53 -05:00
Yuku Kotani
e5df43f9b7 docs: Add Google Vertex AI provider documentation (#3349) 2025-10-23 11:44:06 -05:00
Andrew Pashynnyk
3c7b229d8b fix: allow tool.execute.after hook to modify MCP tool output (#3381) 2025-10-23 10:38:55 -05:00
Mani Sundararajan
9ab4414aef docs: rm winget as a recommended installation method under windows (#3382)
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-10-23 09:25:42 -05:00
GitHub Action
c2cf6fb904 ignore: update download stats 2025-10-23 2025-10-23 12:04:55 +00:00
opencode
5e69bdbef4 release: v0.15.14 2025-10-23 05:55:28 +00:00
Yesh Yendamuri
f81e28c673 feat: add model management to ACP sessions (#3358) 2025-10-23 00:43:28 -05:00
Aiden Cline
61899d4fa7 regen sdk 2025-10-22 23:00:03 -05:00
Aiden Cline
7c7ebb0a9d feat: retry parts (#3369) 2025-10-22 18:31:36 -05:00
Dax Raad
9def7cff2d summary tweaks 2025-10-22 19:03:08 -04:00
geril07
c2ef930d2a add option to allow agent switches to not change model (#3356) 2025-10-22 17:51:46 -05:00
Adam
3c3d2f5a6e wip: desktop work 2025-10-22 17:51:16 -05:00
Dax Raad
f435049d36 sync 2025-10-22 18:49:57 -04:00
Dax Raad
1f80de2fa6 core: add experimental turn summarization to compact conversation history 2025-10-22 18:33:46 -04:00
Adam
f194a784b0 wip: desktop work 2025-10-22 17:33:08 -05:00
Adam
89b703c387 wip: desktop work 2025-10-22 17:31:49 -05:00
Aiden Cline
eff12cb484 vscode: eslint fix 2025-10-22 17:17:47 -05:00
Aiden Cline
593e89b4f4 vscode: fix script 2025-10-22 17:11:51 -05:00
Aiden Cline
4d3f703715 vscode: adjust tsconfig 2025-10-22 16:43:06 -05:00
Aiden Cline
123dcc10cc ignore: cleanup bun.lock w/ bun i 2025-10-22 15:34:41 -05:00
Dax Raad
28d8af48a0 add parent id to assistant messages 2025-10-22 15:01:13 -04:00
Kyle Galbraith
10ff6e9830 docs: fix typo in SDK documentation (#3355) 2025-10-22 11:43:28 -05:00
theVinchi
a7b43d82ab add Amazon Nova models to us-* prefix requirement list (#3357) 2025-10-22 11:41:38 -05:00
Aiden Cline
9005fd31ed tweak 2025-10-22 11:29:10 -05:00
Aiden Cline
d2bded23c3 tweak 2025-10-22 11:28:06 -05:00
Aiden Cline
c0cbc37f85 tweak: model priority 2025-10-22 11:12:32 -05:00
Aiden Cline
9df61055e2 change default title model 2025-10-22 10:41:08 -05:00
GitHub Action
074136b1e8 ignore: update download stats 2025-10-22 2025-10-22 12:04:58 +00:00
Affaan Mustafa
8db5951287 feat: Improve editor detection with auto-discovery and better error messages (#3155)
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-10-21 20:49:42 -05:00
Haris Gušić
97c7e941eb fix: opencode run shouldn't print to stderr (#3341) 2025-10-21 19:53:09 -05:00
Aiden Cline
354f5c3281 ignore: fix acp test (#3339) 2025-10-21 15:27:59 -05:00
opencode
833706cda4 release: v0.15.13 2025-10-21 20:24:20 +00:00
Aiden Cline
2a951cea38 ignore: reword 2025-10-21 14:43:34 -05:00
Dax Raad
d9a8d2032a fix sourcemapping so errors show proper stack trace 2025-10-21 15:32:24 -04:00
Dax Raad
d7cdabe8b7 refactor acp args 2025-10-21 15:24:09 -04:00
Aiden Cline
e7c74d13cc ignore: reword contributing.md 2025-10-21 14:12:33 -05:00
opencode
6ac5a447c2 release: v0.15.12 2025-10-21 17:31:56 +00:00
Aiden Cline
cb4670e6de ignore: add label 2025-10-21 11:12:52 -05:00
Aiden Cline
ca0f3902b7 fix: provider option transforms (#3331) 2025-10-21 11:08:21 -05:00
Dax Raad
e9996342a7 core: provide line-level statistics in file diffs to help users understand the scale of changes 2025-10-21 11:54:41 -04:00
opencode
a84826061d release: v0.15.11 2025-10-21 15:04:22 +00:00
Dax Raad
7a20f77ebf core: improve error handling with console output for debugging 2025-10-21 10:55:10 -04:00
Giuseppe Rota
731122bf99 fix(acp): use newer acp package (#3317) 2025-10-21 09:35:09 -05:00
GitHub Action
f9036734eb ignore: update download stats 2025-10-21 2025-10-21 12:06:20 +00:00
kcrommett
a99bd3aa2c tweak: adjust file api to encode images (#3292) 2025-10-21 00:52:39 -05:00
zWing
96efede846 fix: openai options when using @ai-sdk/azure (#3315) 2025-10-20 23:30:13 -05:00
Cheol Kang
2f66055d25 feat: add -f/--file flag to opencode run command (#3295)
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-10-20 23:25:54 -05:00
opencode
6995dab1dc release: v0.15.10 2025-10-20 22:21:03 +00:00
Dax Raad
a0a09f421c core: add session diff API to show file changes between snapshots 2025-10-20 17:59:26 -04:00
Joe Schmitt
f3f21194ae feat: Add ACP (Agent Client Protocol) support (#2947)
Co-authored-by: opencode-bot <devnull@opencode.local>
Co-authored-by: Dax Raad <d@ironbay.co>
Co-authored-by: GitHub Action <action@github.com>
2025-10-20 16:55:22 -05:00
opencode
835fa9fb81 release: v0.15.9 2025-10-20 21:16:57 +00:00
Aiden Cline
96ae6d51aa ignore: link labels 2025-10-20 16:02:23 -05:00
Frank
075ef0fa34 wip: share 2025-10-20 16:17:51 -04:00
Aiden Cline
89b72e4442 fix: bash tool permission merges (#3302) 2025-10-20 15:15:14 -05:00
Frank
7a7b3c6315 wip: zen 2025-10-20 13:22:43 -04:00
Rui Ferrão
3d48c14d29 docs: clarify the effects of the subtask command option with primary … (#3299) 2025-10-20 12:16:09 -05:00
GitHub Action
bfa79ed44b ignore: update download stats 2025-10-20 2025-10-20 12:04:59 +00:00
Hieu Nguyen
4d8268c818 feat: support astro lsp (#3242) 2025-10-19 22:49:06 -05:00
Squibid
95d413bec6 theme: add mellow theme (#3198) 2025-10-19 22:34:04 -05:00
Joscha Götzer
1cb5a70382 feat(theme): Add Night Owl theme (#3269) 2025-10-19 22:30:04 -05:00
Walter
6adc16ca8a fix(tui): Allow modals to handle ESC key before force closing (#3279) 2025-10-19 22:16:54 -05:00
Zeldris
10ebe9ae09 docs: add scoop & winget to readme (#3215) 2025-10-19 21:50:51 -05:00
Aiden Cline
43a07c6aca hide invalid option 2025-10-19 20:51:33 -05:00
Aiden Cline
5d3a88f34f fix: snapshot undo bug (#3290) 2025-10-19 18:42:34 -05:00
Ivan Uhalin
e47edfffe4 docs: fix Grep by Vercel link in documentation (#3280) 2025-10-19 11:51:57 -05:00
GitHub Action
141097fc73 ignore: update download stats 2025-10-19 2025-10-19 12:04:24 +00:00
Aiden Cline
c8898463a7 ignore: adjust template 2025-10-18 23:45:01 -05:00
Aiden Cline
1c7bd6365e replace all $ARGUMENTS (#3276) 2025-10-18 23:37:17 -05:00
Aiden Cline
290d15a80f ignore: add version to bug template 2025-10-18 14:56:06 -05:00
Aiden Cline
233a018fe5 docs: contributing.md (#3248)
Co-authored-by: Jay <air@live.ca>
2025-10-18 14:10:14 -05:00
opencode
d69beec087 release: v0.15.8 2025-10-18 18:36:52 +00:00
Aiden Cline
1f869bccc1 make compact interruptable (#3251) 2025-10-18 11:49:29 -05:00
GitHub Action
8da8c9e78c ignore: update download stats 2025-10-18 2025-10-18 12:04:14 +00:00
Adam
335d833655 wip: desktop work 2025-10-17 15:22:11 -05:00
Haris Gušić
1dba01e057 Improve typo directory error (#3247) 2025-10-17 14:34:37 -05:00
Jay V
a3de43f3de ignore: clarify that workspaces are free for teams during beta 2025-10-17 15:19:19 -04:00
Jay V
22ad4f5365 ignore: help users discover that teams feature is free during beta 2025-10-17 14:33:21 -04:00
Jay V
5bfbec60b5 docs: clarify team collaboration features and pricing 2025-10-17 13:58:10 -04:00
Adam
cc18b58ff9 wip: desktop work 2025-10-17 12:07:58 -05:00
Adam
887a819f24 wip: desktop work 2025-10-17 12:06:36 -05:00
mgrachten
fe8b3a2515 set 755 permissions (#3237) 2025-10-17 10:42:42 -05:00
GitHub Action
86079353ef ignore: update download stats 2025-10-17 2025-10-17 12:04:43 +00:00
Frank
b7c8690414 wip: zen 2025-10-17 02:23:45 -04:00
Frank
c25b9bf65a wip: zen 2025-10-17 01:15:25 -04:00
Matt Gillard
ddb2e6957c added AU inference for bedrock haiku 4.5 (#3206)
Co-authored-by: Matt Gillard <matt-github@gillard.biz>
2025-10-16 23:48:41 -05:00
Frank
a590b32a10 wip: zen 2025-10-17 00:26:00 -04:00
Frank
5f7bba11fd wip: zen 2025-10-16 23:43:49 -04:00
Frank
4663ea5faa wip: zen 2025-10-16 23:19:25 -04:00
Frank
dd581e8577 wip: zen 2025-10-17 03:17:07 +00:00
opencode
bad01d76de release: v0.15.7 2025-10-17 03:17:07 +00:00
Aiden Cline
d69366b00c Revert "try to avoid persisting empty thinking/text blocks"
This reverts commit d8a15e7bc9.
2025-10-16 22:04:55 -05:00
Frank
1947580b08 wip: zen 2025-10-16 22:28:34 -04:00
Frank
ca9b13e8a2 wip: zen 2025-10-16 22:28:09 -04:00
Hieu Nguyen
92d9a0ec61 feat: deno lsp (#3210)
Co-authored-by: hiunguynx <hieu.nm1@teko.vn>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2025-10-16 18:08:36 -05:00
opencode
2be9ed2590 release: v0.15.6 2025-10-16 23:02:59 +00:00
Aiden Cline
25861f6d0d sync 2025-10-16 17:49:02 -05:00
Aiden Cline
b24f4e3d2c fix: timeout option (#3229) 2025-10-16 17:47:41 -05:00
Frank
729ad1cb75 wip: zen 2025-10-16 17:50:32 -04:00
Haris Gušić
fb4105a46c docs: fix Ctrl+K styling in documentation (#3021) 2025-10-16 15:30:28 -05:00
Frank
7abc3e9794 ci: fix 2025-10-16 16:04:25 -04:00
Frank
88fef05923 wip: zen 2025-10-16 15:58:51 -04:00
GitHub Action
8552f3555e chore: format code 2025-10-16 19:55:30 +00:00
Adam
47d9e01765 wip: css/ui and desktop work 2025-10-16 14:53:44 -05:00
Aiden Cline
fc18fc8a08 fix: bash hangs & orphans (#3225) 2025-10-16 14:39:36 -05:00
Dax Raad
7474788778 ci: fix 2025-10-16 15:36:12 -04:00
GitHub Action
26d0d20e4d chore: format code 2025-10-16 19:08:21 +00:00
Adam
20229f147b wip: css/ui and desktop work 2025-10-16 14:07:37 -05:00
Aiden Cline
149cb6a9ec fix: connection closed 2025-10-16 14:00:07 -05:00
Frank
7ec5e49e19 zen: support stripe link 2025-10-16 14:59:46 -04:00
Aiden Cline
1c1380d3c8 adjust action 2025-10-16 13:15:27 -05:00
Adam
10680f0cf0 wip: css/ui work 2025-10-16 18:06:59 +00:00
opencode
2517b22552 release: v0.15.5 2025-10-16 18:06:59 +00:00
Aiden Cline
64617c113a ignore: tweak permissions 2025-10-16 12:48:39 -05:00
Aiden Cline
860c6338fc fix: github action (#3223) 2025-10-16 12:33:33 -05:00
Aiden Cline
4a7551e87b ci: fix changelog generation 2025-10-16 11:29:58 -05:00
Moisès Macià
285cc4b9fd docs: fix misspelled word (#3211) 2025-10-16 10:04:11 -05:00
Dax Raad
d8a15e7bc9 try to avoid persisting empty thinking/text blocks 2025-10-16 10:54:10 -04:00
935 changed files with 63143 additions and 40599 deletions

59
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: Bug report
description: Report an issue that should be fixed
labels: ["bug"]
body:
- type: textarea
id: description
attributes:
label: Description
description: Describe the bug you encountered
placeholder: What happened?
validations:
required: true
- type: input
id: opencode-version
attributes:
label: OpenCode version
description: What version of OpenCode are you using?
validations:
required: false
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
description: How can we reproduce this issue?
placeholder: |
1.
2.
3.
validations:
required: false
- type: textarea
id: screenshot-or-link
attributes:
label: Screenshot and/or share link
description: Run `/share` to get a share link, or attach a screenshot
placeholder: Paste link or drag and drop screenshot here
validations:
required: false
- type: input
id: os
attributes:
label: Operating System
description: what OS are you using?
placeholder: e.g., macOS 26.0.1, Ubuntu 22.04, Windows 11
validations:
required: false
- type: input
id: terminal
attributes:
label: Terminal
description: what terminal are you using?
placeholder: e.g., iTerm2, Ghostty, Alacritty, Windows Terminal
validations:
required: false

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: 💬 Discord Community
url: https://discord.gg/opencode
about: For quick questions or real-time discussion. Note that issues are searchable and help others with the same question.

View File

@@ -0,0 +1,20 @@
name: 🚀 Feature Request
description: Suggest an idea, feature, or enhancement
labels: [discussion]
title: "[FEATURE]:"
body:
- type: checkboxes
id: verified
attributes:
label: Feature hasn't been suggested before.
options:
- label: I have verified this feature I'm about to request hasn't been suggested before.
required: true
- type: textarea
attributes:
label: Describe the enhancement you want to request
description: What do you want to change or add? What are the benefits of implementing this? Try to be detailed so we can understand your request better :)
validations:
required: true

11
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
name: Question
description: Ask a question
labels: ["question"]
body:
- type: textarea
id: question
attributes:
label: Question
description: What's your question?
validations:
required: true

View File

@@ -5,6 +5,8 @@ runs:
steps:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: package.json
- name: Cache ~/.bun
id: cache-bun

71
.github/publish-python-sdk.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
#
# This file is intentionally in the wrong dir, will move and add later....
#
# name: publish-python-sdk
# on:
# release:
# types: [published]
# workflow_dispatch:
# jobs:
# publish:
# runs-on: ubuntu-latest
# permissions:
# contents: read
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4
# - name: Setup Bun
# uses: oven-sh/setup-bun@v1
# with:
# bun-version: 1.2.21
# - name: Install dependencies (JS/Bun)
# run: bun install
# - name: Install uv
# shell: bash
# run: curl -LsSf https://astral.sh/uv/install.sh | sh
# - name: Generate Python SDK from OpenAPI (CLI)
# shell: bash
# run: |
# ~/.local/bin/uv run --project packages/sdk/python python packages/sdk/python/scripts/generate.py --source cli
# - name: Sync Python dependencies
# shell: bash
# run: |
# ~/.local/bin/uv sync --dev --project packages/sdk/python
# - name: Set version from release tag
# shell: bash
# run: |
# TAG="${GITHUB_REF_NAME:-}"
# if [ -z "$TAG" ]; then
# TAG="$(git describe --tags --abbrev=0 || echo 0.0.0)"
# fi
# echo "Using version: $TAG"
# VERSION="$TAG" ~/.local/bin/uv run --project packages/sdk/python python - <<'PY'
# import os, re, pathlib
# root = pathlib.Path('packages/sdk/python')
# pt = (root / 'pyproject.toml').read_text()
# version = os.environ.get('VERSION','0.0.0').lstrip('v')
# pt = re.sub(r'(?m)^(version\s*=\s*")[^"]+("\s*)$', f"\\1{version}\\2", pt)
# (root / 'pyproject.toml').write_text(pt)
# # Also update generator config override for consistency
# cfgp = root / 'openapi-python-client.yaml'
# if cfgp.exists():
# cfg = cfgp.read_text()
# cfg = re.sub(r'(?m)^(package_version_override:\s*)\S+$', f"\\1{version}", cfg)
# cfgp.write_text(cfg)
# PY
# - name: Build and publish to PyPI
# env:
# PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
# shell: bash
# run: |
# ~/.local/bin/uv run --project packages/sdk/python python packages/sdk/python/scripts/publish.py

55
.github/workflows/auto-label-tui.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Auto-label TUI Issues
on:
issues:
types: [opened]
jobs:
auto-label:
runs-on: ubuntu-latest
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);
if (isWebRelated) {
// Add web label
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ['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
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ['opentui']
});
}

View File

@@ -8,7 +8,9 @@ jobs:
opencode:
if: |
contains(github.event.comment.body, ' /oc') ||
contains(github.event.comment.body, ' /opencode')
startsWith(github.event.comment.body, '/oc') ||
contains(github.event.comment.body, ' /opencode') ||
startsWith(github.event.comment.body, '/opencode')
runs-on: ubuntu-latest
permissions:
id-token: write
@@ -24,4 +26,4 @@ jobs:
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
with:
model: opencode/sonic
model: opencode/glm-4.6

View File

@@ -24,6 +24,10 @@ jobs:
- run: git fetch --force --tags
- run: bun install -g @vscode/vsce
- name: Install extension dependencies
run: bun install
working-directory: ./sdks/vscode
- name: Publish
run: |
./script/publish

View File

@@ -12,6 +12,10 @@ on:
- major
- minor
- patch
version:
description: "Override version (optional)"
required: false
type: string
concurrency: ${{ github.workflow }}-${{ github.ref }}
@@ -53,13 +57,18 @@ jobs:
- 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
- name: 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 }}
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}

View File

@@ -5,6 +5,7 @@ on:
branches:
- dev
- opentui
- v0
concurrency: ${{ github.workflow }}-${{ github.ref }}

3
.gitignore vendored
View File

@@ -5,8 +5,11 @@ node_modules
.env
.idea
.vscode
*~
openapi.json
playground
tmp
dist
.turbo
**/.serena
.serena/

View File

@@ -1,2 +1,2 @@
#!/bin/sh
bun run typecheck
bun typecheck

View File

@@ -13,7 +13,7 @@ avoid repeating the title of the page, should be 5-10 words long
Chunks of text should not be more than 2 sentences long
Each section is spearated by a divider of 3 dashes
Each section is separated by a divider of 3 dashes
The section titles are short with only the first letter of the word capitalized

View File

@@ -18,3 +18,6 @@ For anything in the packages/app use the ignore: prefix.
prefer to explain WHY something was done from an end user perspective instead of
WHAT was done.
do not do generic messages like "improved agent experience" be very specific
about what user facing changes were made

View File

@@ -1,5 +1,5 @@
---
description: hello world
description: hello world iaosd ioasjdoiasjd oisadjoisajd osiajd oisaj dosaij dsoajsajdaijdoisa jdoias jdoias jdoia jois jo jdois jdoias jdoias j djoasdj
---
hey there $ARGUMENTS

17
.opencode/opencode.json Normal file
View File

@@ -0,0 +1,17 @@
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-openai-codex-auth"],
"mcp": {
"weather": {
"type": "local",
"command": ["bun", "x", "@h1deya/mcp-server-weather"]
},
"context7": {
"type": "remote",
"url": "https://mcp.context7.com/mcp",
"headers": {
"CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}"
}
}
}
}

View File

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

68
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,68 @@
# Contributing to OpenCode
We want to make it easy for you to contribute to OpenCode. Here are the most common type of changes that get merged:
- Bug fixes
- Additional LSPs / Formatters
- Improvements to LLM performance
- Support for new providers
- Fixes for environment-specific quirks
- Missing standard behavior
- Documentation improvements
However, any UI or core product feature must go through a design review with the core team before implementation.
If you are unsure if a PR would be accepted, feel free to ask a maintainer or look for issues with any of the following labels:
- [`help wanted`](https://github.com/sst/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Ahelp-wanted)
- [`good first issue`](https://github.com/sst/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22)
- [`bug`](https://github.com/sst/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug)
- [`perf`](https://github.com/sst/opencode/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22perf%22)
> [!NOTE]
> PRs that ignore these guardrails will likely be closed.
Want to take on an issue? Leave a comment and a maintainer may assign it to you unless it is something we are already working on.
## Developing OpenCode
- Requirements: Bun 1.3+
- Install dependencies and start the dev server from the repo root:
```bash
bun install
bun dev
```
- Core pieces:
- `packages/opencode`: OpenCode core business logic & server.
- `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui)
- `packages/plugin`: Source for `@opencode-ai/plugin`
> [!NOTE]
> After touching `packages/opencode/src/server/server.ts`, run "./packages/sdk/js/script/build.ts" to regenerate the JS sdk.
## Pull Request Expectations
- Try to keep pull requests small and focused.
- Link relevant issue(s) in the description
- Explain the issue and why your change fixes it
- Avoid having verbose LLM generated PR descriptions
- Before adding new functions or functionality, ensure that such behavior doesn't already exist elsewhere in the codebase.
### Style Preferences
These are not strictly enforced, they are just general guidelines:
- **Functions:** Keep logic within a single function unless breaking it out adds clear reuse or composition benefits.
- **Destructuring:** Do not do unnecessary destructuring of variables.
- **Control flow:** Avoid `else` statements.
- **Error handling:** Prefer `.catch(...)` instead of `try`/`catch` when possible.
- **Types:** Reach for precise types and avoid `any`.
- **Variables:** Stick to immutable patterns and avoid `let`.
- **Naming:** Choose concise single-word identifiers when they remain descriptive.
- **Runtime APIs:** Use Bun helpers such as `Bun.file()` when they fit the use case.
## Feature Requests
For net-new functionality, start with a design conversation. Open an issue describing the problem, your proposed approach (optional), and why it belongs in OpenCode. The core team will help decide whether it should move forward; please wait for that approval instead of opening a feature PR directly.

View File

@@ -26,7 +26,9 @@ curl -fsSL https://opencode.ai/install | bash
# Package managers
npm i -g opencode-ai@latest # or bun/pnpm/yarn
brew install sst/tap/opencode # macOS and Linux
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
```
@@ -54,41 +56,7 @@ For more info on how to configure OpenCode [**head over to our docs**](https://o
### Contributing
OpenCode is an opinionated tool so any fundamental feature needs to go through a
design process with the core team.
> [!IMPORTANT]
> We do not accept PRs for core features.
However we still merge a ton of PRs - you can contribute:
- Bug fixes
- Improvements to LLM performance
- Support for new providers
- Fixes for env specific quirks
- Missing standard behavior
- Documentation
Take a look at the git history to see what kind of PRs we end up merging.
> [!NOTE]
> If you do not follow the above guidelines we might close your PR.
To run OpenCode locally you need.
- Bun 1.3 or higher
- Golang 1.24.x
And run.
```bash
$ bun install
$ bun dev
```
#### Development Notes
**API Client**: After making changes to the TypeScript API endpoints in `packages/opencode/src/server/server.ts`, you will need the OpenCode team to generate a new stainless sdk for the clients.
If you're interested in contributing to OpenCode, please read our [contributing docs](./CONTRIBUTING.md) before submitting a pull request.
### FAQ

244
STATS.md
View File

@@ -1,112 +1,136 @@
# 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) |
| 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) |

961
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -104,7 +104,7 @@ To test locally:
- `MODEL`: The model used by opencode. Same as the `MODEL` defined in the GitHub workflow.
- `ANTHROPIC_API_KEY`: Your model provider API key. Same as the keys defined in the GitHub workflow.
- `GITHUB_RUN_ID`: Dummy value to emulate GitHub action environment.
- `MOCK_TOKEN`: A GitHub persontal access token. This token is used to verify you have `admin` or `write` access to the test repo. Generate a token [here](https://github.com/settings/personal-access-tokens).
- `MOCK_TOKEN`: A GitHub personal access token. This token is used to verify you have `admin` or `write` access to the test repo. Generate a token [here](https://github.com/settings/personal-access-tokens).
- `MOCK_EVENT`: Mock GitHub event payload (see templates below).
- `/path/to/opencode`: Path to your cloned opencode repo. `bun /path/to/opencode/github/index.ts` runs your local version of `opencode`.
@@ -118,7 +118,7 @@ Replace:
- `"owner":"sst"` with repo owner
- `"repo":"hello-world"` with repo name
- `"actor":"fwang"` with the GitHub username of commentor
- `"actor":"fwang"` with the GitHub username of commenter
- `"number":4` with the GitHub issue id
- `"body":"hey opencode, summarize thread"` with comment body

View File

@@ -6,15 +6,11 @@ branding:
inputs:
model:
description: "The model to use with opencode. Takes the format of `provider/model`."
description: "Model to use"
required: true
share:
description: "Whether to share the opencode session. Defaults to true for public repositories."
required: false
token:
description: "Optional GitHub access token for performing operations such as creating comments, committing changes, and opening pull requests. Defaults to the installation access token from the opencode GitHub App."
description: "Share the opencode session (defaults to true for public repos)"
required: false
runs:
@@ -24,20 +20,10 @@ runs:
shell: bash
run: curl -fsSL https://opencode.ai/install | bash
- name: Install bun
shell: bash
run: npm install -g bun
- name: Install dependencies
shell: bash
run: |
cd ${GITHUB_ACTION_PATH}
bun install
- name: Run opencode
shell: bash
run: bun ${GITHUB_ACTION_PATH}/index.ts
id: run_opencode
run: opencode github run
env:
MODEL: ${{ inputs.model }}
SHARE: ${{ inputs.share }}
TOKEN: ${{ inputs.token }}

View File

@@ -152,6 +152,9 @@ try {
return session.id.slice(-8)
})()
console.log("opencode session", session.id)
if (shareId) {
console.log("Share link:", `${useShareUrl()}/s/${shareId}`)
}
// Handle 3 cases
// 1. Issue
@@ -730,12 +733,13 @@ async function updateComment(body: string) {
async function createPR(base: string, branch: string, title: string, body: string) {
console.log("Creating pull request...")
const { repo } = useContext()
const truncatedTitle = title.length > 256 ? title.slice(0, 253) + "..." : title
const pr = await octoRest.rest.pulls.create({
owner: repo.owner,
repo: repo.repo,
head: branch,
base,
title,
title: truncatedTitle,
body,
})
return pr.data.number

2
github/sst-env.d.ts vendored
View File

@@ -6,4 +6,4 @@
/// <reference path="../sst-env.d.ts" />
import "sst"
export {}
export {}

View File

@@ -3,6 +3,7 @@ import { domain } from "./stage"
const GITHUB_APP_ID = new sst.Secret("GITHUB_APP_ID")
const GITHUB_APP_PRIVATE_KEY = new sst.Secret("GITHUB_APP_PRIVATE_KEY")
export const EMAILOCTOPUS_API_KEY = new sst.Secret("EMAILOCTOPUS_API_KEY")
const ADMIN_SECRET = new sst.Secret("ADMIN_SECRET")
const bucket = new sst.cloudflare.Bucket("Bucket")
export const api = new sst.cloudflare.Worker("Api", {
@@ -12,7 +13,7 @@ export const api = new sst.cloudflare.Worker("Api", {
WEB_DOMAIN: domain,
},
url: true,
link: [bucket, GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY],
link: [bucket, GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, ADMIN_SECRET],
transform: {
worker: (args) => {
args.logpush = true

View File

@@ -97,7 +97,8 @@ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint",
],
})
const ZEN_MODELS = new sst.Secret("ZEN_MODELS")
const ZEN_MODELS1 = new sst.Secret("ZEN_MODELS1")
const ZEN_MODELS2 = new sst.Secret("ZEN_MODELS2")
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
properties: { value: auth.url.apply((url) => url!) },
@@ -105,6 +106,7 @@ const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
properties: { value: stripeWebhook.secret },
})
const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
////////////////
// CONSOLE
@@ -130,10 +132,18 @@ new sst.cloudflare.x.SolidStart("Console", {
AUTH_API_URL,
STRIPE_WEBHOOK_SECRET,
STRIPE_SECRET_KEY,
ZEN_MODELS,
ZEN_MODELS1,
ZEN_MODELS2,
EMAILOCTOPUS_API_KEY,
AWS_SES_ACCESS_KEY_ID,
AWS_SES_SECRET_ACCESS_KEY,
...($dev
? [
new sst.Secret("CLOUDFLARE_DEFAULT_ACCOUNT_ID", process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID!),
new sst.Secret("CLOUDFLARE_API_TOKEN", process.env.CLOUDFLARE_API_TOKEN!),
]
: []),
gatewayKv,
],
environment: {
//VITE_DOCS_URL: web.url.apply((url) => url!),

13
install
View File

@@ -10,10 +10,14 @@ NC='\033[0m' # No Color
requested_version=${VERSION:-}
os=$(uname -s | tr '[:upper:]' '[:lower:]')
if [[ "$os" == "darwin" ]]; then
os="darwin"
fi
raw_os=$(uname -s)
os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
# Normalize various Unix-like identifiers
case "$raw_os" in
Darwin*) os="darwin" ;;
Linux*) os="linux" ;;
MINGW*|MSYS*|CYGWIN*) os="windows" ;;
esac
arch=$(uname -m)
if [[ "$arch" == "aarch64" ]]; then
@@ -96,6 +100,7 @@ download_and_install() {
curl -# -L -o "$filename" "$url"
unzip -q "$filename"
mv opencode "$INSTALL_DIR"
chmod 755 "${INSTALL_DIR}/opencode"
cd .. && rm -rf opencodetmp
}

View File

@@ -0,0 +1,15 @@
{
"keep": {
"days": true,
"amount": 14
},
"auditLog": "/home/thdxr/dev/projects/sst/opencode/logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json",
"files": [
{
"date": 1759827172859,
"name": "/home/thdxr/dev/projects/sst/opencode/logs/mcp-puppeteer-2025-10-07.log",
"hash": "a3d98b26edd793411b968a0d24cfeee8332138e282023c3b83ec169d55c67f16"
}
],
"hashType": "sha256"
}

View File

@@ -0,0 +1,48 @@
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:52:52.879"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:52:52.880"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:52:56.191"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:52:56.192"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:52:59.267"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:52:59.268"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:20.276"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:20.277"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:30.838"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:30.839"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:42.452"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:42.452"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:46.499"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:46.500"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:54:02.295"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:54:02.295"}
{"arguments":{"url":"https://google.com"},"level":"debug","message":"Tool call received","service":"mcp-puppeteer","timestamp":"2025-10-07 04:54:37.150","tool":"puppeteer_navigate"}
{"0":"n","1":"p","2":"x","level":"info","message":"Launching browser with config:","service":"mcp-puppeteer","timestamp":"2025-10-07 04:54:37.150"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:55:08.488"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:55:08.489"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:11.815"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:11.816"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:21.934"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:21.935"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:32.544"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:32.544"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:41.154"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:41.155"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:55.426"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:55.427"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:24:15.715"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:24:15.716"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:24:25.063"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:24:25.064"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:24:48.567"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:24:48.568"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:25:08.937"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:25:08.938"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 22:38:37.120"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 22:38:37.121"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 22:38:52.490"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 22:38:52.491"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 22:39:25.524"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 22:39:25.525"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 22:40:57.126"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 22:40:57.127"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 22:42:24.175"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 22:42:24.176"}

View File

@@ -1,3 +0,0 @@
{
"$schema": "https://opencode.ai/config.json"
}

View File

@@ -1,13 +1,15 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "opencode",
"description": "AI-powered development tool",
"private": true,
"type": "module",
"packageManager": "bun@1.3.0",
"packageManager": "bun@1.3.1",
"scripts": {
"dev": "bun run packages/opencode/src/index.ts",
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
"typecheck": "bun turbo typecheck",
"prepare": "husky"
"prepare": "husky",
"random": "echo 'Random script'"
},
"workspaces": {
"packages": [
@@ -19,12 +21,16 @@
"catalog": {
"@types/bun": "1.3.0",
"@hono/zod-validator": "0.4.2",
"ulid": "3.0.1",
"@kobalte/core": "0.13.11",
"@types/node": "22.13.9",
"@tsconfig/node22": "22.0.2",
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/precision-diffs": "0.4.4",
"@solidjs/meta": "0.29.4",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
"ai": "5.0.8",
"hono": "4.7.10",
@@ -35,8 +41,9 @@
"zod": "4.1.8",
"remeda": "2.26.0",
"solid-js": "1.9.9",
"solid-list": "0.3.0",
"tailwindcss": "4.1.11",
"@tailwindcss/vite": "4.1.11",
"virtua": "0.42.3",
"vite": "7.1.4",
"vite-plugin-solid": "2.11.8"
}
@@ -45,12 +52,12 @@
"@tsconfig/bun": "catalog:",
"husky": "9.1.7",
"prettier": "3.6.2",
"sst": "3.17.19",
"sst": "3.17.23",
"turbo": "2.5.6"
},
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/script": "workspace:*"
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*"
},
"repository": {
"type": "git",

View File

@@ -23,6 +23,9 @@ app.config.timestamp_*.js
# Temp
gitignore
# Generated files
public/sitemap.xml
# System Files
.DS_Store
Thumbs.db

View File

@@ -5,15 +5,17 @@
"typecheck": "tsgo --noEmit",
"dev": "vinxi dev --host 0.0.0.0",
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
"build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
"build": "./script/generate-sitemap.ts && vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
"start": "vinxi start",
"version": "0.15.4"
"version": "1.0.46"
},
"dependencies": {
"@ibm/plex": "6.4.1",
"@kobalte/core": "catalog:",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@opencode-ai/console-core": "workspace:*",
"@opencode-ai/console-mail": "workspace:*",
"@openauthjs/openauth": "catalog:",
"@kobalte/core": "catalog:",
"@jsx-email/render": "1.1.1",
"@opencode-ai/console-resource": "workspace:*",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.0",

Binary file not shown.

View File

@@ -0,0 +1,103 @@
#!/usr/bin/env bun
import { readdir, writeFile } from "fs/promises"
import { join, dirname } from "path"
import { fileURLToPath } from "url"
import { config } from "../src/config.js"
const __dirname = dirname(fileURLToPath(import.meta.url))
const BASE_URL = config.baseUrl
const PUBLIC_DIR = join(__dirname, "../public")
const ROUTES_DIR = join(__dirname, "../src/routes")
const DOCS_DIR = join(__dirname, "../../../web/src/content/docs")
interface SitemapEntry {
url: string
priority: number
changefreq: string
}
async function getMainRoutes(): Promise<SitemapEntry[]> {
const routes: SitemapEntry[] = []
// Add main static routes
const staticRoutes = [
{ path: "/", priority: 1.0, changefreq: "daily" },
{ path: "/enterprise", priority: 0.8, changefreq: "weekly" },
{ path: "/brand", priority: 0.6, changefreq: "monthly" },
{ path: "/zen", priority: 0.8, changefreq: "weekly" },
]
for (const route of staticRoutes) {
routes.push({
url: `${BASE_URL}${route.path}`,
priority: route.priority,
changefreq: route.changefreq,
})
}
return routes
}
async function getDocsRoutes(): Promise<SitemapEntry[]> {
const routes: SitemapEntry[] = []
try {
const files = await readdir(DOCS_DIR)
for (const file of files) {
if (!file.endsWith(".mdx")) continue
const slug = file.replace(".mdx", "")
const path = slug === "index" ? "/docs/" : `/docs/${slug}`
routes.push({
url: `${BASE_URL}${path}`,
priority: slug === "index" ? 0.9 : 0.7,
changefreq: "weekly",
})
}
} catch (error) {
console.error("Error reading docs directory:", error)
}
return routes
}
function generateSitemapXML(entries: SitemapEntry[]): string {
const urls = entries
.map(
(entry) => ` <url>
<loc>${entry.url}</loc>
<changefreq>${entry.changefreq}</changefreq>
<priority>${entry.priority}</priority>
</url>`,
)
.join("\n")
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls}
</urlset>`
}
async function main() {
console.log("Generating sitemap...")
const mainRoutes = await getMainRoutes()
const docsRoutes = await getDocsRoutes()
const allRoutes = [...mainRoutes, ...docsRoutes]
console.log(`Found ${mainRoutes.length} main routes`)
console.log(`Found ${docsRoutes.length} docs routes`)
console.log(`Total: ${allRoutes.length} routes`)
const xml = generateSitemapXML(allRoutes)
const outputPath = join(PUBLIC_DIR, "sitemap.xml")
await writeFile(outputPath, xml, "utf-8")
console.log(`✓ Sitemap generated at ${outputPath}`)
}
main()

View File

@@ -12,7 +12,7 @@ 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 AI coding agent built for the terminal." />
<Suspense>{props.children}</Suspense>
</MetaProvider>
)}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,16 @@
<svg width="240" height="300" viewBox="0 0 240 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1401_86283)">
<mask id="mask0_1401_86283" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="240" height="300">
<path d="M240 0H0V300H240V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_1401_86283)">
<path d="M180 240H60V120H180V240Z" fill="#4B4646"/>
<path d="M180 60H60V240H180V60ZM240 300H0V0H240V300Z" fill="#F1ECEC"/>
</g>
</g>
<defs>
<clipPath id="clip0_1401_86283">
<rect width="240" height="300" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,16 @@
<svg width="240" height="300" viewBox="0 0 240 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1401_86274)">
<mask id="mask0_1401_86274" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="240" height="300">
<path d="M240 0H0V300H240V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_1401_86274)">
<path d="M180 240H60V120H180V240Z" fill="#CFCECD"/>
<path d="M180 60H60V240H180V60ZM240 300H0V0H240V300Z" fill="#211E1E"/>
</g>
</g>
<defs>
<clipPath id="clip0_1401_86274">
<rect width="240" height="300" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,30 @@
<svg width="641" height="115" viewBox="0 0 641 115" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1401_86292)">
<mask id="mask0_1401_86292" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="641" height="115">
<path d="M640.714 0H0V115H640.714V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_1401_86292)">
<path d="M49.2868 82.1433H16.4297V49.2861H49.2868V82.1433Z" fill="#4B4646"/>
<path d="M49.2857 32.8573H16.4286V82.143H49.2857V32.8573ZM65.7143 98.5716H0V16.4287H65.7143V98.5716Z" fill="#B7B1B1"/>
<path d="M131.427 82.1433H98.5703V49.2861H131.427V82.1433Z" fill="#4B4646"/>
<path d="M98.5692 82.143H131.426V32.8573H98.5692V82.143ZM147.855 98.5716H98.5692V115H82.1406V16.4287H147.855V98.5716Z" fill="#B7B1B1"/>
<path d="M229.997 65.7139V82.1424H180.711V65.7139H229.997Z" fill="#4B4646"/>
<path d="M230.003 65.7144H180.718V82.143H230.003V98.5716H164.289V16.4287H230.003V65.7144ZM180.718 49.2859H213.575V32.8573H180.718V49.2859Z" fill="#B7B1B1"/>
<path d="M295.717 98.5718H262.859V49.2861H295.717V98.5718Z" fill="#4B4646"/>
<path d="M295.715 32.8573H262.858V98.5716H246.43V16.4287H295.715V32.8573ZM312.144 98.5716H295.715V32.8573H312.144V98.5716Z" fill="#B7B1B1"/>
<path d="M394.286 82.1433H345V49.2861H394.286V82.1433Z" fill="#4B4646"/>
<path d="M394.285 32.8573H344.999V82.143H394.285V98.5716H328.57V16.4287H394.285V32.8573Z" fill="#F1ECEC"/>
<path d="M459.998 82.1433H427.141V49.2861H459.998V82.1433Z" fill="#4B4646"/>
<path d="M459.997 32.8573H427.14V82.143H459.997V32.8573ZM476.425 98.5716H410.711V16.4287H476.425V98.5716Z" fill="#F1ECEC"/>
<path d="M542.146 82.1433H509.289V49.2861H542.146V82.1433Z" fill="#4B4646"/>
<path d="M542.145 32.8571H509.288V82.1429H542.145V32.8571ZM558.574 98.5714H492.859V16.4286H542.145V0H558.574V98.5714Z" fill="#F1ECEC"/>
<path d="M640.715 65.7139V82.1424H591.43V65.7139H640.715Z" fill="#4B4646"/>
<path d="M591.429 32.8573V49.2859H624.286V32.8573H591.429ZM640.714 65.7144H591.429V82.143H640.714V98.5716H575V16.4287H640.714V65.7144Z" fill="#F1ECEC"/>
</g>
</g>
<defs>
<clipPath id="clip0_1401_86292">
<rect width="640.714" height="115" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,30 @@
<svg width="640" height="115" viewBox="0 0 640 115" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1401_86330)">
<mask id="mask0_1401_86330" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="640" height="115">
<path d="M640 0H0V115H640V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_1401_86330)">
<path d="M49.2346 82.1433H16.4141V49.2861H49.2346V82.1433Z" fill="#CFCECD"/>
<path d="M49.2308 32.8573H16.4103V82.143H49.2308V32.8573ZM65.641 98.5716H0V16.4287H65.641V98.5716Z" fill="#656363"/>
<path d="M131.281 82.1433H98.4609V49.2861H131.281V82.1433Z" fill="#CFCECD"/>
<path d="M98.4649 82.143H131.285V32.8573H98.4649V82.143ZM147.696 98.5716H98.4649V115H82.0547V16.4287H147.696V98.5716Z" fill="#656363"/>
<path d="M229.746 65.7139V82.1424H180.516V65.7139H229.746Z" fill="#CFCECD"/>
<path d="M229.743 65.7144H180.512V82.143H229.743V98.5716H164.102V16.4287H229.743V65.7144ZM180.512 49.2859H213.332V32.8573H180.512V49.2859Z" fill="#656363"/>
<path d="M295.383 98.5718H262.562V49.2861H295.383V98.5718Z" fill="#CFCECD"/>
<path d="M295.387 32.8573H262.567V98.5716H246.156V16.4287H295.387V32.8573ZM311.797 98.5716H295.387V32.8573H311.797V98.5716Z" fill="#656363"/>
<path d="M393.848 82.1433H344.617V49.2861H393.848V82.1433Z" fill="#CFCECD"/>
<path d="M393.844 32.8573H344.613V82.143H393.844V98.5716H328.203V16.4287H393.844V32.8573Z" fill="#211E1E"/>
<path d="M459.485 82.1433H426.664V49.2861H459.485V82.1433Z" fill="#CFCECD"/>
<path d="M459.489 32.8573H426.668V82.143H459.489V32.8573ZM475.899 98.5716H410.258V16.4287H475.899V98.5716Z" fill="#211E1E"/>
<path d="M541.539 82.1433H508.719V49.2861H541.539V82.1433Z" fill="#CFCECD"/>
<path d="M541.535 32.8571H508.715V82.1428H541.535V32.8571ZM557.946 98.5714H492.305V16.4286H541.535V0H557.946V98.5714Z" fill="#211E1E"/>
<path d="M639.996 65.7139V82.1424H590.766V65.7139H639.996Z" fill="#CFCECD"/>
<path d="M590.77 32.8573V49.2859H623.59V32.8573H590.77ZM640 65.7144H590.77V82.143H640V98.5716H574.359V16.4287H640V65.7144Z" fill="#211E1E"/>
</g>
</g>
<defs>
<clipPath id="clip0_1401_86330">
<rect width="640" height="115" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,22 @@
<svg width="641" height="115" viewBox="0 0 641 115" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1401_86315)">
<mask id="mask0_1401_86315" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="641" height="115">
<path d="M640.714 0H0V115H640.714V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_1401_86315)">
<path d="M49.2857 32.8573H16.4286V82.143H49.2857V32.8573ZM65.7143 98.5716H0V16.4287H65.7143V98.5716Z" fill="white"/>
<path d="M98.5692 82.143H131.426V32.8573H98.5692V82.143ZM147.855 98.5716H98.5692V115H82.1406V16.4287H147.855V98.5716Z" fill="white"/>
<path d="M230.003 65.7144H180.718V82.143H230.003V98.5716H164.289V16.4287H230.003V65.7144ZM180.718 49.2859H213.575V32.8573H180.718V49.2859Z" fill="white"/>
<path d="M295.715 32.8573H262.858V98.5716H246.43V16.4287H295.715V32.8573ZM312.144 98.5716H295.715V32.8573H312.144V98.5716Z" fill="white"/>
<path d="M394.285 32.8573H344.999V82.143H394.285V98.5716H328.57V16.4287H394.285V32.8573Z" fill="white"/>
<path d="M459.997 32.8573H427.14V82.143H459.997V32.8573ZM476.425 98.5716H410.711V16.4287H476.425V98.5716Z" fill="white"/>
<path d="M542.145 32.8571H509.288V82.1429H542.145V32.8571ZM558.574 98.5714H492.859V16.4286H542.145V0H558.574V98.5714Z" fill="white"/>
<path d="M591.429 32.8573V49.2859H624.286V32.8573H591.429ZM640.714 65.7144H591.429V82.143H640.714V98.5716H575V16.4287H640.714V65.7144Z" fill="white"/>
</g>
</g>
<defs>
<clipPath id="clip0_1401_86315">
<rect width="640.714" height="115" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,22 @@
<svg width="640" height="115" viewBox="0 0 640 115" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1401_86353)">
<mask id="mask0_1401_86353" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="640" height="115">
<path d="M640 0H0V115H640V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_1401_86353)">
<path d="M49.2308 32.8573H16.4103V82.143H49.2308V32.8573ZM65.641 98.5716H0V16.4287H65.641V98.5716Z" fill="black"/>
<path d="M98.4649 82.143H131.285V32.8573H98.4649V82.143ZM147.696 98.5716H98.4649V115H82.0547V16.4287H147.696V98.5716Z" fill="black"/>
<path d="M229.743 65.7144H180.512V82.143H229.743V98.5716H164.102V16.4287H229.743V65.7144ZM180.512 49.2859H213.332V32.8573H180.512V49.2859Z" fill="black"/>
<path d="M295.387 32.8573H262.567V98.5716H246.156V16.4287H295.387V32.8573ZM311.797 98.5716H295.387V32.8573H311.797V98.5716Z" fill="black"/>
<path d="M393.844 32.8573H344.613V82.143H393.844V98.5716H328.203V16.4287H393.844V32.8573Z" fill="black"/>
<path d="M459.489 32.8573H426.668V82.143H459.489V32.8573ZM475.899 98.5716H410.258V16.4287H475.899V98.5716Z" fill="black"/>
<path d="M541.535 32.8571H508.715V82.1428H541.535V32.8571ZM557.946 98.5714H492.305V16.4286H541.535V0H557.946V98.5714Z" fill="black"/>
<path d="M590.77 32.8573V49.2859H623.59V32.8573H590.77ZM640 65.7144H590.77V82.143H640V98.5716H574.359V16.4287H640V65.7144Z" fill="black"/>
</g>
</g>
<defs>
<clipPath id="clip0_1401_86353">
<rect width="640" height="115" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,10 @@
<svg width="22" height="26" viewBox="0 0 22 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 7H7V19H15V7ZM19 23H3V3H19V23Z" fill="url(#paint0_linear_1311_94922)" stroke="#F1ECEC"/>
<path d="M3 0V26M19 0V26M15 0V26M7 0V26M0 3H22M0 7H22M0 19H22M0 23H22" stroke="#4B4646" stroke-opacity="0.4"/>
<defs>
<linearGradient id="paint0_linear_1311_94922" x1="11" y1="3" x2="11" y2="23" gradientUnits="userSpaceOnUse">
<stop stop-color="#1B1818"/>
<stop offset="1" stop-color="#2D2828"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 526 B

View File

@@ -0,0 +1,10 @@
<svg width="22" height="26" viewBox="0 0 22 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 7H7V19H15V7ZM19 23H3V3H19V23Z" fill="url(#paint0_linear_1311_94913)" stroke="#8E8B8B"/>
<path d="M3 0V26M19 0V26M15 0V26M7 0V26M0 3H22M0 7H22M0 19H22M0 23H22" stroke="#110000" stroke-opacity="0.121569"/>
<defs>
<linearGradient id="paint0_linear_1311_94913" x1="11" y1="3" x2="11" y2="23" gradientUnits="userSpaceOnUse">
<stop stop-color="#F9F8F8"/>
<stop offset="1" stop-color="#E9E8E8"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,11 @@
<svg width="22" height="26" viewBox="0 0 22 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1311_94916)">
<path d="M15 19H7V11H15V19Z" fill="#4B4646"/>
<path d="M15 7H7V19H15V7ZM19 23H3V3H19V23Z" fill="#F1ECEC"/>
</g>
<defs>
<clipPath id="clip0_1311_94916">
<rect width="16" height="20" fill="white" transform="translate(3 3)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 385 B

View File

@@ -0,0 +1,11 @@
<svg width="22" height="26" viewBox="0 0 22 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1311_94907)">
<path d="M15 19H7V11H15V19Z" fill="#BCBBBB"/>
<path d="M15 7H7V19H15V7ZM19 23H3V3H19V23Z" fill="#211E1E"/>
</g>
<defs>
<clipPath id="clip0_1311_94907">
<rect width="16" height="20" fill="white" transform="translate(3 3)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 385 B

View File

@@ -0,0 +1,11 @@
<svg width="32" height="40" viewBox="0 0 32 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1311_94973)">
<path d="M24 32H8V16H24V32Z" fill="#4B4646"/>
<path d="M24 8H8V32H24V8ZM32 40H0V0H32V40Z" fill="#F1ECEC"/>
</g>
<defs>
<clipPath id="clip0_1311_94973">
<rect width="32" height="40" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 358 B

View File

@@ -0,0 +1,11 @@
<svg width="32" height="40" viewBox="0 0 32 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1311_94969)">
<path d="M24 32H8V16H24V32Z" fill="#BCBBBB"/>
<path d="M24 8H8V32H24V8ZM32 40H0V0H32V40Z" fill="#211E1E"/>
</g>
<defs>
<clipPath id="clip0_1311_94969">
<rect width="32" height="40" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 358 B

View File

@@ -0,0 +1,25 @@
<svg width="234" height="42" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1311_95032)">
<path d="M18 30H6V18H18V30Z" fill="#4B4646"/>
<path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="#B7B1B1"/>
<path d="M48 30H36V18H48V30Z" fill="#4B4646"/>
<path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="#B7B1B1"/>
<path d="M84 24V30H66V24H84Z" fill="#4B4646"/>
<path d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="#B7B1B1"/>
<path d="M108 36H96V18H108V36Z" fill="#4B4646"/>
<path d="M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z" fill="#B7B1B1"/>
<path d="M144 30H126V18H144V30Z" fill="#4B4646"/>
<path d="M144 12H126V30H144V36H120V6H144V12Z" fill="#F1ECEC"/>
<path d="M168 30H156V18H168V30Z" fill="#4B4646"/>
<path d="M168 12H156V30H168V12ZM174 36H150V6H174V36Z" fill="#F1ECEC"/>
<path d="M198 30H186V18H198V30Z" fill="#4B4646"/>
<path d="M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z" fill="#F1ECEC"/>
<path d="M234 24V30H216V24H234Z" fill="#4B4646"/>
<path d="M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z" fill="#F1ECEC"/>
</g>
<defs>
<clipPath id="clip0_1311_95032">
<rect width="234" height="42" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,25 @@
<svg width="234" height="42" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1311_95049)">
<path d="M18 30H6V18H18V30Z" fill="#CFCECD"/>
<path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="#656363"/>
<path d="M48 30H36V18H48V30Z" fill="#CFCECD"/>
<path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="#656363"/>
<path d="M84 24V30H66V24H84Z" fill="#CFCECD"/>
<path d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="#656363"/>
<path d="M108 36H96V18H108V36Z" fill="#CFCECD"/>
<path d="M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z" fill="#656363"/>
<path d="M144 30H126V18H144V30Z" fill="#CFCECD"/>
<path d="M144 12H126V30H144V36H120V6H144V12Z" fill="#211E1E"/>
<path d="M168 30H156V18H168V30Z" fill="#CFCECD"/>
<path d="M168 12H156V30H168V12ZM174 36H150V6H174V36Z" fill="#211E1E"/>
<path d="M198 30H186V18H198V30Z" fill="#CFCECD"/>
<path d="M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z" fill="#211E1E"/>
<path d="M234 24V30H216V24H234Z" fill="#CFCECD"/>
<path d="M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z" fill="#211E1E"/>
</g>
<defs>
<clipPath id="clip0_1311_95049">
<rect width="234" height="42" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,3 @@
<svg width="22" height="26" viewBox="0 0 22 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.33203 7.99967V6.33301H10.9987M17.6654 7.99967V6.33301H10.9987M10.9987 6.33301V19.6663M10.9987 19.6663H9.33203M10.9987 19.6663H12.6654" stroke="#F1ECEC" stroke-width="2" stroke-linecap="square"/>
</svg>

After

Width:  |  Height:  |  Size: 310 B

View File

@@ -0,0 +1,3 @@
<svg width="22" height="26" viewBox="0 0 22 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.33203 7.99967V6.33301H10.9987M17.6654 7.99967V6.33301H10.9987M10.9987 6.33301V19.6663M10.9987 19.6663H9.33203M10.9987 19.6663H12.6654" stroke="black" stroke-width="2" stroke-linecap="square"/>
</svg>

After

Width:  |  Height:  |  Size: 308 B

View File

@@ -77,4 +77,4 @@
background-color: var(--color-accent-alpha);
}
}
}
}

View File

@@ -1,6 +1,7 @@
import { createAsync } from "@solidjs/router"
import { createMemo } from "solid-js"
import { github } from "~/lib/github"
import { config } from "~/config"
export function Footer() {
const githubData = createAsync(() => github())
@@ -10,13 +11,13 @@ export function Footer() {
notation: "compact",
compactDisplay: "short",
}).format(githubData()!.stars!)
: "25K",
: config.github.starsFormatted.compact,
)
return (
<footer data-component="footer">
<div data-slot="cell">
<a href="https://github.com/sst/opencode" target="_blank">
<a href={config.github.repoUrl} target="_blank">
GitHub <span>[{starCount()}]</span>
</a>
</div>
@@ -27,7 +28,7 @@ export function Footer() {
<a href="/discord">Discord</a>
</div>
<div data-slot="cell">
<a href="https://x.com/opencode">X</a>
<a href={config.social.twitter}>X</a>
</div>
</footer>
)

View File

@@ -0,0 +1,63 @@
.context-menu {
position: fixed;
z-index: 1000;
min-width: 160px;
border-radius: 8px;
background-color: var(--color-background);
box-shadow:
0 0 0 1px rgba(19, 16, 16, 0.08),
0 6px 8px -4px rgba(19, 16, 16, 0.12),
0 4px 3px -2px rgba(19, 16, 16, 0.12),
0 1px 2px -1px rgba(19, 16, 16, 0.12);
padding: 6px;
@media (prefers-color-scheme: dark) {
box-shadow: 0 0 0 1px rgba(247, 237, 237, 0.1);
}
}
.context-menu-item {
display: flex;
gap: 12px;
width: 100%;
padding: 8px 16px 8px 8px;
font-weight: 500;
cursor: pointer;
background: none;
border: none;
align-items: center;
color: var(--color-text);
font-size: var(--font-size-sm);
text-align: left;
border-radius: 2px;
transition: background-color 0.2s ease;
[data-slot="copy dark"] {
display: none;
}
@media (prefers-color-scheme: dark) {
[data-slot="copy light"] {
display: none;
}
[data-slot="copy dark"] {
display: block;
}
}
&:hover {
background-color: var(--color-background-weak-hover);
color: var(--color-text-strong);
}
img {
width: 22px;
height: 26px;
}
}
.context-menu-divider {
border: none;
border-top: 1px solid var(--color-border);
margin: var(--space-1) 0;
}

View File

@@ -1,47 +1,168 @@
import logoLight from "../asset/logo-ornate-light.svg"
import logoDark from "../asset/logo-ornate-dark.svg"
import { A, createAsync } from "@solidjs/router"
import copyLogoLight from "../asset/lander/logo-light.svg"
import copyLogoDark from "../asset/lander/logo-dark.svg"
import copyWordmarkLight from "../asset/lander/wordmark-light.svg"
import copyWordmarkDark from "../asset/lander/wordmark-dark.svg"
import copyBrandAssetsLight from "../asset/lander/brand-assets-light.svg"
import copyBrandAssetsDark from "../asset/lander/brand-assets-dark.svg"
// SVG files for copying (separate from button icons)
// Replace these with your actual SVG files for copying
import copyLogoSvgLight from "../asset/lander/opencode-logo-light.svg"
import copyLogoSvgDark from "../asset/lander/opencode-logo-dark.svg"
import copyWordmarkSvgLight from "../asset/lander/opencode-wordmark-light.svg"
import copyWordmarkSvgDark from "../asset/lander/opencode-wordmark-dark.svg"
import { A, createAsync, useNavigate } from "@solidjs/router"
import { createMemo, Match, Show, Switch } from "solid-js"
import { createStore } from "solid-js/store"
import { github } from "~/lib/github"
import { queryIsLoggedIn } from "~/routes/workspace/common"
import { createEffect, onCleanup } from "solid-js"
import { config } from "~/config"
import "./header-context-menu.css"
const isDarkMode = () => window.matchMedia("(prefers-color-scheme: dark)").matches
const fetchSvgContent = async (svgPath: string): Promise<string> => {
try {
const response = await fetch(svgPath)
const svgText = await response.text()
return svgText
} catch (err) {
console.error("Failed to fetch SVG content:", err)
throw err
}
}
export function Header(props: { zen?: boolean }) {
const navigate = useNavigate()
const githubData = createAsync(() => github())
const isLoggedIn = createAsync(() => queryIsLoggedIn())
const starCount = createMemo(() =>
githubData()?.stars
? new Intl.NumberFormat("en-US", {
notation: "compact",
compactDisplay: "short",
}).format(githubData()?.stars!)
: "25K",
: config.github.starsFormatted.compact,
)
const [store, setStore] = createStore({
mobileMenuOpen: false,
contextMenuOpen: false,
contextMenuPosition: { x: 0, y: 0 },
})
createEffect(() => {
const handleClickOutside = () => {
setStore("contextMenuOpen", false)
}
const handleContextMenu = (event: MouseEvent) => {
event.preventDefault()
setStore("contextMenuOpen", false)
}
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setStore("contextMenuOpen", false)
}
}
if (store.contextMenuOpen) {
document.addEventListener("click", handleClickOutside)
document.addEventListener("contextmenu", handleContextMenu)
document.addEventListener("keydown", handleKeyDown)
onCleanup(() => {
document.removeEventListener("click", handleClickOutside)
document.removeEventListener("contextmenu", handleContextMenu)
document.removeEventListener("keydown", handleKeyDown)
})
}
})
const handleLogoContextMenu = (event: MouseEvent) => {
event.preventDefault()
const logoElement = (event.currentTarget as HTMLElement).querySelector("a")
if (logoElement) {
const rect = logoElement.getBoundingClientRect()
setStore("contextMenuPosition", {
x: rect.left - 16,
y: rect.bottom + 8,
})
}
setStore("contextMenuOpen", true)
}
const copyWordmarkToClipboard = async () => {
try {
const isDark = isDarkMode()
const wordmarkSvgPath = isDark ? copyWordmarkSvgDark : copyWordmarkSvgLight
const wordmarkSvg = await fetchSvgContent(wordmarkSvgPath)
await navigator.clipboard.writeText(wordmarkSvg)
} catch (err) {
console.error("Failed to copy wordmark to clipboard:", err)
}
}
const copyLogoToClipboard = async () => {
try {
const isDark = isDarkMode()
const logoSvgPath = isDark ? copyLogoSvgDark : copyLogoSvgLight
const logoSvg = await fetchSvgContent(logoSvgPath)
await navigator.clipboard.writeText(logoSvg)
} catch (err) {
console.error("Failed to copy logo to clipboard:", err)
}
}
return (
<section data-component="top">
<A href="/">
<img data-slot="logo light" src={logoLight} alt="opencode logo light" />
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
</A>
<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" />
</A>
</div>
<Show when={store.contextMenuOpen}>
<div
class="context-menu"
style={`left: ${store.contextMenuPosition.x}px; top: ${store.contextMenuPosition.y}px;`}
>
<button class="context-menu-item" onClick={copyLogoToClipboard}>
<img data-slot="copy light" src={copyLogoLight} alt="Logo" />
<img data-slot="copy dark" src={copyLogoDark} alt="Logo" />
Copy logo as SVG
</button>
<button class="context-menu-item" onClick={copyWordmarkToClipboard}>
<img data-slot="copy light" src={copyWordmarkLight} alt="Wordmark" />
<img data-slot="copy dark" src={copyWordmarkDark} alt="Wordmark" />
Copy wordmark as SVG
</button>
<button class="context-menu-item" onClick={() => navigate("/brand")}>
<img data-slot="copy light" src={copyBrandAssetsLight} alt="Brand Assets" />
<img data-slot="copy dark" src={copyBrandAssetsDark} alt="Brand Assets" />
Brand assets
</button>
</div>
</Show>
<nav data-component="nav-desktop">
<ul>
<li>
<a href="https://github.com/sst/opencode" target="_blank">
<a href={config.github.repoUrl} target="_blank">
GitHub <span>[{starCount()}]</span>
</a>
</li>
<li>
<a href="/docs">Docs</a>
</li>
<li>
<A href="/enterprise">Enterprise</A>
</li>
<li>
<Switch>
<Match when={props.zen}>
<a href="/auth">{isLoggedIn() ? "Workspace" : "Login"}</a>
<a href="/auth">Login</a>
</Match>
<Match when={!props.zen}>
<A href="/zen">Zen</A>
@@ -102,17 +223,20 @@ export function Header(props: { zen?: boolean }) {
<A href="/">Home</A>
</li>
<li>
<a href="https://github.com/sst/opencode" target="_blank">
<a href={config.github.repoUrl} target="_blank">
GitHub <span>[{starCount()}]</span>
</a>
</li>
<li>
<a href="/docs">Docs</a>
</li>
<li>
<A href="/enterprise">Enterprise</A>
</li>
<li>
<Switch>
<Match when={props.zen}>
<a href="/auth">{isLoggedIn() ? "Workspace" : "Login"}</a>
<a href="/auth">Login</a>
</Match>
<Match when={!props.zen}>
<A href="/zen">Zen</A>

View File

@@ -100,6 +100,17 @@ export function IconCreditCard(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
)
}
export function IconStripe(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24">
<path
fill="currentColor"
d="M15.827 12.506c0 .672-.31 1.175-.771 1.175-.293 0-.468-.106-.589-.237l-.007-1.855c.13-.143.31-.247.596-.247.456-.001.771.51.771 1.164zm3.36-1.253c-.312 0-.659.236-.659.798h1.291c0-.562-.325-.798-.632-.798zm4.813-5.253v12c0 1.104-.896 2-2 2h-20c-1.104 0-2-.896-2-2v-12c0-1.104.896-2 2-2h20c1.104 0 2 .896 2 2zm-17.829 7.372c0-1.489-1.909-1.222-1.909-1.784 0-.195.162-.271.424-.271.38 0 .862.116 1.242.321v-1.176c-.414-.165-.827-.228-1.241-.228-1.012.001-1.687.53-1.687 1.414 0 1.382 1.898 1.158 1.898 1.754 0 .231-.201.305-.479.305-.414 0-.947-.171-1.366-.399v1.192c.464.2.935.283 1.365.283 1.038.001 1.753-.512 1.753-1.411zm2.422-3.054h-.949l-.001-1.084-1.219.259-.005 4.006c0 .739.556 1.285 1.297 1.285.408 0 .71-.074.876-.165v-1.016c-.16.064-.948.293-.948-.443v-1.776h.948v-1.066zm2.596 0c-.166-.06-.75-.169-1.042.369l-.078-.369h-1.079v4.377h1.248v-2.967c.295-.388.793-.313.952-.262v-1.148zm1.554 0h-1.253v4.377h1.253v-4.377zm0-1.664l-1.253.266v1.017l1.253-.266v-1.017zm4.314 3.824c0-1.454-.826-2.244-1.703-2.243-.489 0-.805.23-.978.392l-.065-.309h-1.099v5.828l1.249-.265.003-1.413c.179.131.446.316.883.316.893 0 1.71-.719 1.71-2.306zm3.943.045c0-1.279-.619-2.288-1.805-2.288-1.188 0-1.911 1.01-1.911 2.281 0 1.506.852 2.267 2.068 2.267.597 0 1.045-.136 1.384-.324v-1.006c-.34.172-.731.276-1.227.276-.487 0-.915-.172-.971-.758h2.444l.018-.448z"
/>
</svg>
)
}
export function IconChevron(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} width="8" height="6" viewBox="0 0 8 6" fill="none" xmlns="http://www.w3.org/2000/svg">

View File

@@ -1,9 +1,14 @@
import { A } from "@solidjs/router"
export function Legal() {
return (
<div data-component="legal">
<span>
©{new Date().getFullYear()} <a href="https://anoma.ly">Anomaly</a>
</span>
<span>
<A href="/brand">Brand</A>
</span>
</div>
)
}

View File

@@ -63,4 +63,4 @@
font-weight: 600;
color: var(--color-text);
}
}
}

View File

@@ -0,0 +1,29 @@
/**
* Application-wide constants and configuration
*/
export const config = {
// Base URL
baseUrl: "https://opencode.ai",
// GitHub
github: {
repoUrl: "https://github.com/sst/opencode",
starsFormatted: {
compact: "30K",
full: "30,000",
},
},
// Social links
social: {
twitter: "https://x.com/opencode",
discord: "https://discord.gg/opencode",
},
// Static stats (used on landing page)
stats: {
contributors: "250",
commits: "3,500",
monthlyUsers: "300,000",
},
} as const

View File

@@ -1,5 +1,5 @@
import { getRequestEvent } from "solid-js/web"
import { and, Database, eq, inArray, sql } from "@opencode-ai/console-core/drizzle/index.js"
import { and, Database, eq, inArray, isNull, sql } from "@opencode-ai/console-core/drizzle/index.js"
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
import { redirect } from "@solidjs/router"
import { Actor } from "@opencode-ai/console-core/actor.js"
@@ -56,7 +56,13 @@ export const getActor = async (workspace?: string): Promise<Actor.Info> => {
tx
.select()
.from(UserTable)
.where(and(eq(UserTable.workspaceID, workspace), inArray(UserTable.accountID, accounts)))
.where(
and(
eq(UserTable.workspaceID, workspace),
isNull(UserTable.timeDeleted),
inArray(UserTable.accountID, accounts),
),
)
.limit(1)
.execute()
.then((x) => x[0]),

View File

@@ -1,4 +1,5 @@
import { query } from "@solidjs/router"
import { config } from "~/config"
export const github = query(async () => {
"use server"
@@ -6,11 +7,12 @@ export const github = query(async () => {
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
}
const apiBaseUrl = config.github.repoUrl.replace("https://github.com/", "https://api.github.com/repos/")
try {
const [meta, releases, contributors] = await Promise.all([
fetch("https://api.github.com/repos/sst/opencode", { headers }).then((res) => res.json()),
fetch("https://api.github.com/repos/sst/opencode/releases", { headers }).then((res) => res.json()),
fetch("https://api.github.com/repos/sst/opencode/contributors?per_page=1", { headers }),
fetch(apiBaseUrl, { headers }).then((res) => res.json()),
fetch(`${apiBaseUrl}/releases`, { headers }).then((res) => res.json()),
fetch(`${apiBaseUrl}/contributors?per_page=1`, { headers }),
])
const [release] = releases
const contributorCount = Number.parseInt(

View File

@@ -0,0 +1,46 @@
import type { APIEvent } from "@solidjs/start/server"
import { AWS } from "@opencode-ai/console-core/aws.js"
interface EnterpriseFormData {
name: string
role: string
email: string
message: string
}
export async function POST(event: APIEvent) {
try {
const body = (await event.request.json()) as EnterpriseFormData
// Validate required fields
if (!body.name || !body.role || !body.email || !body.message) {
return Response.json({ error: "All fields are required" }, { status: 400 })
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(body.email)) {
return Response.json({ error: "Invalid email format" }, { status: 400 })
}
// Create email content
const emailContent = `
${body.message}<br><br>
--<br>
${body.name}<br>
${body.role}<br>
${body.email}`.trim()
// Send email using AWS SES
await AWS.sendEmail({
to: "contact@anoma.ly",
subject: `Enterprise Inquiry from ${body.name}`,
body: emailContent,
})
return Response.json({ success: true, message: "Form submitted successfully" }, { status: 200 })
} catch (error) {
console.error("Error processing enterprise form:", error)
return Response.json({ error: "Internal server error" }, { status: 500 })
}
}

View File

@@ -1,30 +1,11 @@
import { Actor } from "@opencode-ai/console-core/actor.js"
import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js"
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
import { redirect } from "@solidjs/router"
import type { APIEvent } from "@solidjs/start/server"
import { withActor } from "~/context/auth.withActor"
import { getLastSeenWorkspaceID } from "../workspace/common"
export async function GET(input: APIEvent) {
try {
const workspaces = await withActor(async () => {
const actor = Actor.assert("account")
return Database.transaction(async (tx) =>
tx
.select({ id: WorkspaceTable.id })
.from(UserTable)
.innerJoin(WorkspaceTable, eq(UserTable.workspaceID, WorkspaceTable.id))
.where(
and(
eq(UserTable.accountID, actor.properties.accountID),
isNull(UserTable.timeDeleted),
isNull(WorkspaceTable.timeDeleted),
),
),
)
})
return redirect(`/workspace/${workspaces[0].id}`)
const workspaceID = await getLastSeenWorkspaceID()
return redirect(`/workspace/${workspaceID}`)
} catch {
return redirect("/auth/authorize")
}

View File

@@ -0,0 +1,526 @@
::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="enterprise"] {
--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;
@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 index.css */
[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;
gap: 48px;
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;
}
}
}
@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;
}
}
/* Mobile: third column on its own row */
@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"] {
}
[data-component="brand-content"] {
padding: 4rem 5rem;
h1 {
font-size: 1.5rem;
font-weight: 500;
color: var(--color-text-strong);
margin-bottom: 1rem;
}
h3 {
font-size: 1.25rem;
font-weight: 500;
color: var(--color-text-strong);
margin: 2rem 0 1rem 0;
}
p {
line-height: 1.6;
margin-bottom: 2.5rem;
color: var(--color-text);
}
[data-component="download-button"] {
padding: 8px 12px 8px 20px;
background: var(--color-background-strong);
color: var(--color-text-inverted);
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
display: flex;
width: fit-content;
align-items: center;
gap: 12px;
transition: all 0.2s ease;
text-decoration: none;
&:hover:not(:disabled) {
background: var(--color-background-strong-hover);
}
&:active {
transform: scale(0.98);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
[data-component="brand-grid"] {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-top: 4rem;
margin-bottom: 2rem;
}
[data-component="brand-grid"] img {
width: 100%;
height: auto;
display: block;
border-radius: 4px;
border: 1px solid var(--color-border-weak);
}
[data-component="brand-grid"] > div {
position: relative;
}
[data-component="actions"] {
position: absolute;
background: rgba(4, 0, 0, 0.08);
border-radius: 4px;
bottom: 0;
right: 0;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
opacity: 0;
transition: opacity 0.2s ease;
@media (max-width: 40rem) {
position: static;
opacity: 1;
background: none;
margin-top: 1rem;
justify-content: start;
}
}
[data-component="brand-grid"] > div:hover [data-component="actions"] {
opacity: 1;
@media (max-width: 40rem) {
opacity: 1;
}
}
[data-component="actions"] button {
padding: 6px 12px;
background: var(--color-background);
color: var(--color-text);
border: none;
border-radius: 4px;
font-weight: 500;
display: flex;
align-items: center;
gap: 12px;
transition: all 0.2s ease;
cursor: pointer;
box-shadow:
0 0 0 1px rgba(19, 16, 16, 0.08),
0 6px 8px -4px rgba(19, 16, 16, 0.12),
0 4px 3px -2px rgba(19, 16, 16, 0.12),
0 1px 2px -1px rgba(19, 16, 16, 0.12);
@media (max-width: 40rem) {
box-shadow: 0 0 0 1px rgba(19, 16, 16, 0.16);
}
&:hover {
background: var(--color-background);
}
&:active {
transform: scale(0.98);
box-shadow:
0 0 0 1px rgba(19, 16, 16, 0.08),
0 6px 8px -8px rgba(19, 16, 16, 0.5);
}
}
@media (max-width: 60rem) {
padding: 2rem 1.5rem;
}
}
[data-component="faq"] {
border-top: 1px solid var(--color-border-weak);
padding: 4rem 5rem;
@media (max-width: 60rem) {
padding: 2rem 1.5rem;
}
[data-slot="section-title"] {
margin-bottom: 24px;
h3 {
font-size: 16px;
font-weight: 500;
color: var(--color-text-strong);
margin-bottom: 12px;
}
p {
margin-bottom: 12px;
color: var(--color-text);
}
}
ul {
padding: 0;
li {
list-style: none;
margin-bottom: 24px;
line-height: 200%;
@media (max-width: 60rem) {
line-height: 180%;
}
}
}
[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;
[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;
color: var(--color-text);
}
}
[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;
}
}
a {
color: var(--color-text-strong);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
&:hover {
text-decoration-thickness: 2px;
}
}
}

View File

@@ -0,0 +1,252 @@
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"
import previewLogoLight from "../../asset/brand/preview-opencode-logo-light.png"
import previewLogoDark from "../../asset/brand/preview-opencode-logo-dark.png"
import previewWordmarkLight from "../../asset/brand/preview-opencode-wordmark-light.png"
import previewWordmarkDark from "../../asset/brand/preview-opencode-wordmark-dark.png"
import previewWordmarkSimpleLight from "../../asset/brand/preview-opencode-wordmark-simple-light.png"
import previewWordmarkSimpleDark from "../../asset/brand/preview-opencode-wordmark-simple-dark.png"
import logoLightPng from "../../asset/brand/opencode-logo-light.png"
import logoDarkPng from "../../asset/brand/opencode-logo-dark.png"
import wordmarkLightPng from "../../asset/brand/opencode-wordmark-light.png"
import wordmarkDarkPng from "../../asset/brand/opencode-wordmark-dark.png"
import wordmarkSimpleLightPng from "../../asset/brand/opencode-wordmark-simple-light.png"
import wordmarkSimpleDarkPng from "../../asset/brand/opencode-wordmark-simple-dark.png"
import logoLightSvg from "../../asset/brand/opencode-logo-light.svg"
import logoDarkSvg from "../../asset/brand/opencode-logo-dark.svg"
import wordmarkLightSvg from "../../asset/brand/opencode-wordmark-light.svg"
import wordmarkDarkSvg from "../../asset/brand/opencode-wordmark-dark.svg"
import wordmarkSimpleLightSvg from "../../asset/brand/opencode-wordmark-simple-light.svg"
import wordmarkSimpleDarkSvg from "../../asset/brand/opencode-wordmark-simple-dark.svg"
const brandAssets = "/opencode-brand-assets.zip"
export default function Brand() {
const downloadFile = async (url: string, filename: string) => {
try {
const response = await fetch(url)
const blob = await response.blob()
const blobUrl = window.URL.createObjectURL(blob)
const link = document.createElement("a")
link.href = blobUrl
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(blobUrl)
} catch (error) {
console.error("Download failed:", error)
const link = document.createElement("a")
link.href = url
link.target = "_blank"
link.rel = "noopener noreferrer"
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
}
return (
<main data-page="enterprise">
<Title>OpenCode | Brand</Title>
<Link rel="canonical" href={`${config.baseUrl}/brand`} />
<Meta name="description" content="OpenCode brand guidelines" />
<div data-component="container">
<Header />
<div data-component="content">
<section data-component="brand-content">
<h1>Brand guidelines</h1>
<p>Resources and assets to help you work with the OpenCode brand.</p>
<button
data-component="download-button"
onClick={() => downloadFile(brandAssets, "opencode-brand-assets.zip")}
>
Download all assets
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
<div data-component="brand-grid">
<div>
<img src={previewLogoLight} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(logoLightPng, "opencode-logo-light.png")}>
PNG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
<button onClick={() => downloadFile(logoLightSvg, "opencode-logo-light.svg")}>
SVG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
</div>
</div>
<div>
<img src={previewLogoDark} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(logoDarkPng, "opencode-logo-dark.png")}>
PNG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
<button onClick={() => downloadFile(logoDarkSvg, "opencode-logo-dark.svg")}>
SVG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
</div>
</div>
<div>
<img src={previewWordmarkLight} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(wordmarkLightPng, "opencode-wordmark-light.png")}>
PNG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
<button onClick={() => downloadFile(wordmarkLightSvg, "opencode-wordmark-light.svg")}>
SVG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
</div>
</div>
<div>
<img src={previewWordmarkDark} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(wordmarkDarkPng, "opencode-wordmark-dark.png")}>
PNG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
<button onClick={() => downloadFile(wordmarkDarkSvg, "opencode-wordmark-dark.svg")}>
SVG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
</div>
</div>
<div>
<img src={previewWordmarkSimpleLight} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(wordmarkSimpleLightPng, "opencode-wordmark-simple-light.png")}>
PNG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
<button onClick={() => downloadFile(wordmarkSimpleLightSvg, "opencode-wordmark-simple-light.svg")}>
SVG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
</div>
</div>
<div>
<img src={previewWordmarkSimpleDark} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(wordmarkSimpleDarkPng, "opencode-wordmark-simple-dark.png")}>
PNG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
<button onClick={() => downloadFile(wordmarkSimpleDarkSvg, "opencode-wordmark-simple-dark.svg")}>
SVG
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</button>
</div>
</div>
</div>
</section>
</div>
<Footer />
</div>
<Legal />
</main>
)
}

View File

@@ -0,0 +1,5 @@
import { redirect } from "@solidjs/router"
export async function GET() {
return redirect("https://discord.gg/h5TNnkFVNy")
}

View File

@@ -0,0 +1,550 @@
::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="enterprise"] {
--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;
@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 index.css */
[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;
gap: 48px;
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;
}
}
}
@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;
}
}
/* Mobile: third column on its own row */
@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"] {
}
[data-component="enterprise-content"] {
padding: 4rem 0;
@media (max-width: 60rem) {
padding: 2rem 0;
}
}
[data-component="enterprise-columns"] {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
padding: 4rem 5rem;
@media (max-width: 80rem) {
gap: 3rem;
}
@media (max-width: 60rem) {
grid-template-columns: 1fr;
gap: 3rem;
padding: 2rem 1.5rem;
}
}
[data-component="enterprise-column-1"] {
h1 {
font-size: 1.5rem;
font-weight: 500;
color: var(--color-text-strong);
margin-bottom: 1rem;
}
h3 {
font-size: 1.25rem;
font-weight: 500;
color: var(--color-text-strong);
margin: 2rem 0 1rem 0;
}
p {
line-height: 1.6;
margin-bottom: 1.5rem;
color: var(--color-text);
}
[data-component="testimonial"] {
margin-top: 4rem;
font-weight: 500;
color: var(--color-text-strong);
[data-component="quotation"] {
svg {
margin-bottom: 1rem;
opacity: 20%;
}
}
[data-component="testimonial-logo"] {
svg {
margin-top: 1.5rem;
}
}
}
}
[data-component="enterprise-column-2"] {
[data-component="enterprise-form"] {
padding: 0;
h2 {
font-size: 1.5rem;
font-weight: 500;
color: var(--color-text-strong);
margin-bottom: 1.5rem;
}
[data-component="form-group"] {
margin-bottom: 1.5rem;
label {
display: block;
font-weight: 500;
color: var(--color-text-weak);
margin-bottom: 0.5rem;
font-size: 0.875rem;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
transition: background-color 5000000s ease-in-out 0s;
}
input:-webkit-autofill {
-webkit-text-fill-color: var(--color-text-strong) !important;
}
input:-moz-autofill {
-moz-text-fill-color: var(--color-text-strong) !important;
}
input,
textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--color-border-weak);
border-radius: 4px;
background: var(--color-background-weak);
color: var(--color-text-strong);
font-family: inherit;
&::placeholder {
color: var(--color-text-weak);
}
&:focus {
background: var(--color-background-interactive-weaker);
outline: none;
border: none;
color: var(--color-text-strong);
border: 1px solid var(--color-background-strong);
box-shadow: 0 0 0 3px var(--color-background-interactive);
@media (prefers-color-scheme: dark) {
box-shadow: none;
border: 1px solid var(--color-background-interactive);
}
}
}
textarea {
resize: vertical;
min-height: 120px;
}
}
[data-component="submit-button"] {
padding: 0.5rem 1.5rem;
background: var(--color-background-strong);
color: var(--color-text-inverted);
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease;
&:hover:not(:disabled) {
background: var(--color-background-strong-hover);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
[data-component="success-message"] {
margin-top: 1rem;
padding: 1rem 0;
color: var(--color-text-success);
text-align: left;
}
}
}
[data-component="faq"] {
border-top: 1px solid var(--color-border-weak);
padding: 4rem 5rem;
@media (max-width: 60rem) {
padding: 2rem 1.5rem;
}
[data-slot="section-title"] {
margin-bottom: 24px;
h3 {
font-size: 16px;
font-weight: 500;
color: var(--color-text-strong);
margin-bottom: 12px;
}
p {
margin-bottom: 12px;
color: var(--color-text);
}
}
ul {
padding: 0;
li {
list-style: none;
margin-bottom: 24px;
line-height: 200%;
@media (max-width: 60rem) {
line-height: 180%;
}
}
}
[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;
[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;
color: var(--color-text);
}
}
[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;
}
}
a {
color: var(--color-text-strong);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
&:hover {
text-decoration-thickness: 2px;
}
}
}

View File

@@ -0,0 +1,251 @@
import "./index.css"
import { Title, Meta, Link } from "@solidjs/meta"
import { createSignal, Show } from "solid-js"
import { config } from "~/config"
import { Header } from "~/component/header"
import { Footer } from "~/component/footer"
import { Legal } from "~/component/legal"
import { Faq } from "~/component/faq"
export default function Enterprise() {
const [formData, setFormData] = createSignal({
name: "",
role: "",
email: "",
message: "",
})
const [isSubmitting, setIsSubmitting] = createSignal(false)
const [showSuccess, setShowSuccess] = createSignal(false)
const handleInputChange = (field: string) => (e: Event) => {
const target = e.target as HTMLInputElement | HTMLTextAreaElement
setFormData((prev) => ({ ...prev, [field]: target.value }))
}
const handleSubmit = async (e: Event) => {
e.preventDefault()
setIsSubmitting(true)
try {
const response = await fetch("/api/enterprise", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData()),
})
if (response.ok) {
setShowSuccess(true)
setFormData({
name: "",
role: "",
email: "",
message: "",
})
setTimeout(() => setShowSuccess(false), 5000)
}
} catch (error) {
console.error("Failed to submit form:", error)
} finally {
setIsSubmitting(false)
}
}
return (
<main data-page="enterprise">
<Title>OpenCode | Enterprise solutions for your organisation</Title>
<Link rel="canonical" href={`${config.baseUrl}/enterprise`} />
<Meta name="description" content="Contact OpenCode for enterprise solutions" />
<div data-component="container">
<Header />
<div data-component="content">
<section data-component="enterprise-content">
<div data-component="enterprise-columns">
<div data-component="enterprise-column-1">
<h1>Your code is yours</h1>
<p>
OpenCode operates securely inside your organization with no data or context stored and no licensing
restrictions or ownership claims. Start a trial with your team, then deploy it across your
organization by integrating it with your SSO and internal AI gateway.
</p>
<p>Let us know and how we can help.</p>
<Show when={false}>
<div data-component="testimonial">
<div data-component="quotation">
<svg width="20" height="17" viewBox="0 0 20 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M19.4118 0L16.5882 9.20833H20V17H12.2353V10.0938L16 0H19.4118ZM7.17647 0L4.35294 9.20833H7.76471V17H0V10.0938L3.76471 0H7.17647Z"
fill="currentColor"
/>
</svg>
</div>
Thanks to OpenCode, we found a way to create software to track all our assets even the imaginary
ones.
<div data-component="testimonial-logo">
<svg width="80" height="79" viewBox="0 0 80 79" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M0 39.3087L10.0579 29.251L15.6862 34.7868L13.7488 36.7248L10.3345 33.2186L8.48897 35.0639L11.8111 38.4781L9.96557 40.4156L6.55181 37.0018L4.06028 39.4928L7.56674 42.9991L5.62884 44.845L0 39.3087Z"
fill="#0083C6"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M17.7182 36.8164L20.2094 39.4003L16.6108 46.9666L22.2393 41.3374L24.3615 43.46L14.2118 53.5179L11.9047 51.1187L15.4112 43.3677L9.78254 49.0888L7.66016 46.9666L17.7182 36.8164Z"
fill="#0083C6"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M42.8139 61.915L45.3055 64.4064L41.6145 71.9731L47.243 66.3441L49.3652 68.4663L39.3077 78.5244L36.9088 76.1252L40.5072 68.374L34.7866 74.0953L32.6641 71.9731L42.8139 61.915Z"
fill="#0083C6"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M16.4258 55.7324L26.4833 45.582L28.6061 47.7042C31.0049 50.1034 32.3892 51.9497 30.1746 54.1642C28.7902 55.548 27.6831 56.0094 26.1145 54.9016L26.0222 54.994C27.2218 56.1941 26.9448 57.1162 25.4688 58.5931L23.9 60.1615C23.4383 60.6232 22.8847 61.2693 22.7927 62.0067L20.6705 59.8845C20.7625 59.146 21.3161 58.5008 21.778 58.1316L23.5307 56.3788C24.269 55.6403 23.715 54.2555 23.254 53.8872L22.8847 53.4256L18.548 57.7623L16.4258 55.7324ZM24.3611 51.9495C25.4689 53.0563 26.4833 53.3332 27.4984 52.3178C28.5134 51.3957 28.2367 50.3802 27.1295 49.1812L24.3611 51.9495Z"
fill="#0083C6"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M33.4952 66.9899C31.096 69.3891 28.8815 68.4659 27.4047 66.9899C26.021 65.6062 25.0978 63.3907 27.4972 60.9003L31.8336 56.6548C34.2333 54.2556 36.4478 55.0864 37.9241 56.5635C39.308 58.0396 40.2311 60.2541 37.8315 62.6531L33.4952 66.9899ZM29.0659 63.5752C28.6048 64.0369 28.6048 64.7753 29.1583 65.3292C29.6196 65.8821 30.4502 65.7897 30.8194 65.4215L36.2633 59.9769C36.7246 59.6076 36.7246 58.7779 36.171 58.3164C35.7097 57.7626 34.8791 57.7626 34.5101 58.2241L29.0659 63.5752Z"
fill="#0083C6"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M78.5267 39.308L68.2845 29.0654L47.5231 49.735L49.6453 51.8572L68.2845 33.2179L74.3746 39.308L47.2461 66.3435L49.3683 68.4657L78.5267 39.308Z"
fill="#0083C6"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M49.6443 51.8577L43.3695 45.4902L64.0386 24.8215L53.7969 14.4873L33.0352 35.2482L35.1574 37.3705L53.7969 18.7315L59.7947 24.8215L39.1251 45.4902L47.5221 53.9799L49.6443 51.8577Z"
fill="#2D9C5C"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M35.1564 37.3706L28.7896 31.0038L49.5515 10.3347L39.3088 0L10.0586 29.2507L12.1804 31.2804L39.3088 4.24476L45.3066 10.3347L24.6377 31.0038L33.0342 39.4008L35.1564 37.3706Z"
fill="#E92A35"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M77.2332 52.4105C76.0336 52.4105 75.111 51.4884 75.111 50.196C75.111 48.9046 76.0336 47.9814 77.2332 47.9814C78.3405 47.9814 79.263 48.9046 79.263 50.196C79.263 51.4884 78.3405 52.4105 77.2332 52.4105ZM77.2332 52.9643C78.7098 52.9643 80.0015 51.6729 80.0015 50.196C80.0015 48.6276 78.7096 47.4287 77.2332 47.4287C75.6644 47.4287 74.4648 48.6278 74.4648 50.196C74.4647 51.6731 75.6643 52.9643 77.2332 52.9643ZM76.1259 51.7653H76.6797V50.3804H77.0485L77.8788 51.7653H78.4332L77.6023 50.3804C78.1558 50.2881 78.4332 50.0122 78.4332 49.5507C78.4332 48.9046 78.0633 48.6276 77.3253 48.6276H76.1257V51.7653H76.1259ZM76.6797 49.0892H77.2332C77.5102 49.0892 77.8788 49.0892 77.8788 49.4586C77.8788 49.9202 77.6023 49.9202 77.2332 49.9202H76.6797V49.0892Z"
fill="#0083C6"
/>
</svg>
</div>
</div>
</Show>
</div>
<div data-component="enterprise-column-2">
<div data-component="enterprise-form">
<form onSubmit={handleSubmit}>
<div data-component="form-group">
<label for="name">Full name</label>
<input
id="name"
type="text"
required
value={formData().name}
onInput={handleInputChange("name")}
placeholder="Jeff Bezos"
/>
</div>
<div data-component="form-group">
<label for="role">Role</label>
<input
id="role"
type="text"
required
value={formData().role}
onInput={handleInputChange("role")}
placeholder="Executive Chairman"
/>
</div>
<div data-component="form-group">
<label for="email">Company email</label>
<input
id="email"
type="email"
required
value={formData().email}
onInput={handleInputChange("email")}
placeholder="jeff@amazon.com"
/>
</div>
<div data-component="form-group">
<label for="message">What problem are you trying to solve?</label>
<textarea
id="message"
required
rows={5}
value={formData().message}
onInput={handleInputChange("message")}
placeholder="We need help with..."
/>
</div>
<button type="submit" disabled={isSubmitting()} data-component="submit-button">
{isSubmitting() ? "Sending..." : "Send"}
</button>
</form>
{showSuccess() && <div data-component="success-message">Message sent, we'll be in touch soon.</div>}
</div>
</div>
</div>
</section>
<section data-component="faq">
<div data-slot="section-title">
<h3>FAQ</h3>
</div>
<ul>
<li>
<Faq question="What is OpenCode Enterprise?">
OpenCode Enterprise is for organizations that want to ensure that their code and data never leaves
their infrastructure. It can do this by using a centralized config that integrates with your SSO and
internal AI gateway.
</Faq>
</li>
<li>
<Faq question="How do I get started with OpenCode Enterprise?">
Simply start with an internal trial with your team. OpenCode by default does not store your code or
context data, making it easy to get started. Then contact us to discuss pricing and implementation
options.
</Faq>
</li>
<li>
<Faq question="How does enterprise pricing work?">
We offer per-seat enterprise pricing. If you have your own LLM gateway, we do not charge for tokens
used. For further details, contact us for a custom quote based on your organization's needs.
</Faq>
</li>
<li>
<Faq question="Is my data secure with OpenCode Enterprise?">
Yes. OpenCode does not store your code or context data. All processing happens locally or through
direct API calls to your AI provider. With central config and SSO integration, your data remains
secure within your organization's infrastructure.
</Faq>
</li>
</ul>
</section>
</div>
<Footer />
</div>
<Legal />
</main>
)
}

View File

@@ -56,7 +56,6 @@ body {
}
}
@supports (background: -webkit-named-image(i)) {
[data-page="opencode"] {
border-top: 1px solid var(--color-border-weak);
@@ -118,7 +117,6 @@ body {
display: none !important;
}
[data-component="growth-stat"] {
display: flex;
flex-direction: column;
@@ -149,14 +147,12 @@ body {
display: none;
}
}
}
[data-component="stat-illustration"] {
width: 100%;
height: 100%;
display: block;
width: 100%;
height: 100%;
display: block;
svg {
margin: 0;
@@ -164,10 +160,9 @@ body {
height: auto;
display: block;
}
}
}
}
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
@@ -184,7 +179,6 @@ body {
-moz-text-fill-color: var(--color-text-strong) !important;
}
[data-component="container"] {
max-width: 67.5rem;
margin: 0 auto;
@@ -328,10 +322,10 @@ body {
display: flex;
flex-direction: column;
max-width: 100%;
padding: calc(var(--vertical-padding)*2) var(--padding);
padding: calc(var(--vertical-padding) * 2) var(--padding);
@media (max-width: 30rem) {
padding: var(--vertical-padding) var(--padding)
padding: var(--vertical-padding) var(--padding);
}
@media (prefers-color-scheme: dark) {
@@ -352,7 +346,6 @@ body {
@media (max-width: 550px) {
width: calc(100vw - 48px);
}
}
[data-component="tabs"] {
@@ -368,7 +361,7 @@ body {
padding: 0 20px;
@media (max-width: 60rem) {
gap: 20px;
gap: 32px;
overflow-x: auto;
width: 100%;
}
@@ -443,7 +436,6 @@ body {
@media (max-width: 35rem) {
width: calc(100% - 40px) !important;
}
}
[data-slot="highlight"] {
@@ -487,7 +479,7 @@ body {
border-bottom: 1px solid var(--color-border-weak);
}
strong {
h1 {
font-size: 28px;
color: var(--color-text-strong);
font-weight: 500;
@@ -496,7 +488,6 @@ body {
@media (max-width: 60rem) {
font-size: 22px;
}
}
p {
@@ -579,7 +570,6 @@ body {
margin-bottom: 24px;
max-width: 100%;
div {
display: flex;
gap: 12px;
@@ -827,16 +817,12 @@ body {
outline: none;
border: none;
color: var(--color-text-strong);
border: 1px solid var(--color-background-strong); /* Tailwind blue-600 as example */
/* Tailwind-style ring */
border: 1px solid var(--color-background-strong);
box-shadow: 0 0 0 3px var(--color-background-interactive);
/* mimics "ring-2 ring-blue-600/50" */
@media (prefers-color-scheme: dark) {
box-shadow: none;
border: 1px solid var(--color-background-interactive)
border: 1px solid var(--color-background-interactive);
}
}
@@ -896,7 +882,6 @@ body {
margin-top: 2px;
}
[data-slot="faq-icon-plus"] {
flex-shrink: 0;
color: var(--color-text-weak);
@@ -1025,7 +1010,6 @@ body {
}
[data-component="copy-status"] {
[data-slot="copy"] {
display: block;
width: var(--space-4);
@@ -1058,12 +1042,10 @@ body {
border-bottom: 1px solid var(--color-border-weak);
}
[data-slot="cell"] {
flex: 1;
text-align: center;
a {
text-decoration: none;
padding: 2rem 0;
@@ -1076,7 +1058,6 @@ body {
@media (max-width: 40rem) {
display: none;
}
}
}
@@ -1115,10 +1096,18 @@ body {
[data-component="legal"] {
color: var(--color-text-weak);
text-align: center;
display: flex;
gap: 32px;
justify-content: center;
a {
color: var(--color-text-weak);
text-decoration: none;
}
a:hover {
color: var(--color-text);
text-decoration: underline;
}
}
}

View File

@@ -13,6 +13,7 @@ import { Footer } from "~/component/footer"
import { Legal } from "~/component/legal"
import { github } from "~/lib/github"
import { createMemo } from "solid-js"
import { config } from "~/config"
function CopyStatus() {
return (
@@ -43,6 +44,7 @@ export default function Home() {
<main data-page="opencode">
<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />
<Title>OpenCode | The AI coding agent built for the terminal</Title>
<Link rel="canonical" href={config.baseUrl} />
<Link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<Meta property="og:image" content="/social-share.png" />
<Meta name="twitter:image" content="/social-share.png" />
@@ -52,14 +54,10 @@ export default function Home() {
<div data-component="content">
<section data-component="hero">
<div data-slot="hero-copy">
<a
data-slot="releases"
href={release()?.url ?? "https://github.com/sst/opencode/releases"}
target="_blank"
>
<a data-slot="releases" href={release()?.url ?? `${config.github.repoUrl}/releases`} target="_blank">
Whats new in {release()?.name ?? "the latest release"}
</a>
<strong>The AI coding agent built for the terminal</strong>
<h1>The AI coding agent built for the terminal</h1>
<p>
OpenCode is fully open source, giving you control and freedom to use any provider, any model, and any
editor.
@@ -219,9 +217,10 @@ export default function Home() {
<div>
<span>[*]</span>
<p>
With over <strong>26,000</strong> GitHub stars, <strong>188</strong> contributors, and almost{" "}
<strong>3,000</strong> commits, OpenCode is used and trusted by over <strong>200,000</strong>{" "}
developers every month.
With over <strong>{config.github.starsFormatted.full}</strong> GitHub stars,{" "}
<strong>{config.stats.contributors}</strong> contributors, and almost{" "}
<strong>{config.stats.commits}</strong> commits, OpenCode is used and trusted by over{" "}
<strong>{config.stats.monthlyUsers}</strong> developers every month.
</p>
</div>
@@ -274,7 +273,7 @@ export default function Home() {
</svg>
</div>
<span>
<figure>Fig 1.</figure> <strong>26K</strong> GitHub Stars
<figure>Fig 1.</figure> <strong>{config.github.starsFormatted.compact}</strong> GitHub Stars
</span>
</div>
@@ -577,7 +576,7 @@ export default function Home() {
</svg>
</div>
<span>
<figure>Fig 2.</figure> <strong>188</strong> Contributors
<figure>Fig 2.</figure> <strong>{config.stats.contributors}</strong> Contributors
</span>
</div>
@@ -619,7 +618,7 @@ export default function Home() {
</svg>
</div>
<span>
<figure>Fig 3.</figure> <strong>200K</strong> Monthly Devs
<figure>Fig 3.</figure> <strong>{config.stats.monthlyUsers}</strong> Monthly Devs
</span>
</div>
</div>
@@ -688,11 +687,11 @@ export default function Home() {
<li>
<Faq question="Is OpenCode open source?">
Yes, OpenCode is fully open source. The source code is public on{" "}
<a href="https://github.com/sst/opencode" target="_blank">
<a href={config.github.repoUrl} target="_blank">
GitHub
</a>{" "}
under the{" "}
<a href="https://github.com/sst/opencode?tab=MIT-1-ov-file#readme" target="_blank">
<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

View File

@@ -13,122 +13,144 @@ export async function POST(input: APIEvent) {
input.request.headers.get("stripe-signature")!,
Resource.STRIPE_WEBHOOK_SECRET.value,
)
console.log(body.type, JSON.stringify(body, null, 2))
if (body.type === "customer.updated") {
// check default payment method changed
const prevInvoiceSettings = body.data.previous_attributes?.invoice_settings ?? {}
if (!("default_payment_method" in prevInvoiceSettings)) return
const customerID = body.data.object.id
const paymentMethodID = body.data.object.invoice_settings.default_payment_method as string
return (async () => {
if (body.type === "customer.updated") {
// check default payment method changed
const prevInvoiceSettings = body.data.previous_attributes?.invoice_settings ?? {}
if (!("default_payment_method" in prevInvoiceSettings)) return "ignored"
if (!customerID) throw new Error("Customer ID not found")
if (!paymentMethodID) throw new Error("Payment method ID not found")
const customerID = body.data.object.id
const paymentMethodID = body.data.object.invoice_settings.default_payment_method as string
const paymentMethod = await Billing.stripe().paymentMethods.retrieve(paymentMethodID)
await Database.use(async (tx) => {
await tx
.update(BillingTable)
.set({
paymentMethodID,
paymentMethodLast4: paymentMethod.card!.last4,
})
.where(eq(BillingTable.customerID, customerID))
})
}
if (body.type === "checkout.session.completed") {
const workspaceID = body.data.object.metadata?.workspaceID
const customerID = body.data.object.customer as string
const paymentID = body.data.object.payment_intent as string
const invoiceID = body.data.object.invoice as string
const amount = body.data.object.amount_total
if (!customerID) throw new Error("Customer ID not found")
if (!paymentMethodID) throw new Error("Payment method ID not found")
if (!workspaceID) throw new Error("Workspace ID not found")
if (!customerID) throw new Error("Customer ID not found")
if (!amount) throw new Error("Amount not found")
if (!paymentID) throw new Error("Payment ID not found")
if (!invoiceID) throw new Error("Invoice ID not found")
await Actor.provide("system", { workspaceID }, async () => {
const customer = await Billing.get()
if (customer?.customerID && customer.customerID !== customerID) throw new Error("Customer ID mismatch")
// set customer metadata
if (!customer?.customerID) {
await Billing.stripe().customers.update(customerID, {
metadata: {
workspaceID,
},
})
}
// get payment method for the payment intent
const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
expand: ["payment_method"],
})
const paymentMethod = paymentIntent.payment_method
if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
await Database.transaction(async (tx) => {
const paymentMethod = await Billing.stripe().paymentMethods.retrieve(paymentMethodID)
await Database.use(async (tx) => {
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} + ${centsToMicroCents(Billing.CHARGE_AMOUNT)}`,
customerID,
paymentMethodID: paymentMethod.id,
paymentMethodLast4: paymentMethod.card!.last4,
reload: true,
reloadError: null,
timeReloadError: null,
paymentMethodID,
paymentMethodLast4: paymentMethod.card?.last4 ?? null,
paymentMethodType: paymentMethod.type,
})
.where(eq(BillingTable.customerID, customerID))
})
}
if (body.type === "checkout.session.completed") {
const workspaceID = body.data.object.metadata?.workspaceID
const amountInCents = body.data.object.metadata?.amount && parseInt(body.data.object.metadata?.amount)
const customerID = body.data.object.customer as string
const paymentID = body.data.object.payment_intent as string
const invoiceID = body.data.object.invoice as string
if (!workspaceID) throw new Error("Workspace ID not found")
if (!customerID) throw new Error("Customer ID not found")
if (!amountInCents) throw new Error("Amount not found")
if (!paymentID) throw new Error("Payment ID not found")
if (!invoiceID) throw new Error("Invoice ID not found")
await Actor.provide("system", { workspaceID }, async () => {
const customer = await Billing.get()
if (customer?.customerID && customer.customerID !== customerID) throw new Error("Customer ID mismatch")
// set customer metadata
if (!customer?.customerID) {
await Billing.stripe().customers.update(customerID, {
metadata: {
workspaceID,
},
})
}
// get payment method for the payment intent
const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
expand: ["payment_method"],
})
const paymentMethod = paymentIntent.payment_method
if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
await Database.transaction(async (tx) => {
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} + ${centsToMicroCents(amountInCents)}`,
customerID,
paymentMethodID: paymentMethod.id,
paymentMethodLast4: paymentMethod.card?.last4 ?? null,
paymentMethodType: paymentMethod.type,
// enable reload if first time enabling billing
...(customer?.customerID
? {}
: {
reload: true,
reloadError: null,
timeReloadError: null,
}),
})
.where(eq(BillingTable.workspaceID, workspaceID))
await tx.insert(PaymentTable).values({
workspaceID,
id: Identifier.create("payment"),
amount: centsToMicroCents(amountInCents),
paymentID,
invoiceID,
customerID,
})
.where(eq(BillingTable.workspaceID, workspaceID))
await tx.insert(PaymentTable).values({
workspaceID,
id: Identifier.create("payment"),
amount: centsToMicroCents(Billing.CHARGE_AMOUNT),
paymentID,
invoiceID,
customerID,
})
})
}
if (body.type === "charge.refunded") {
const customerID = body.data.object.customer as string
const paymentIntentID = body.data.object.payment_intent as string
if (!customerID) throw new Error("Customer ID not found")
if (!paymentIntentID) throw new Error("Payment ID not found")
const workspaceID = await Database.use((tx) =>
tx
.select({
workspaceID: BillingTable.workspaceID,
})
.from(BillingTable)
.where(eq(BillingTable.customerID, customerID))
.then((rows) => rows[0]?.workspaceID),
)
if (!workspaceID) throw new Error("Workspace ID not found")
const amount = await Database.use((tx) =>
tx
.select({
amount: PaymentTable.amount,
})
.from(PaymentTable)
.where(and(eq(PaymentTable.paymentID, paymentIntentID), eq(PaymentTable.workspaceID, workspaceID)))
.then((rows) => rows[0]?.amount),
)
if (!amount) throw new Error("Payment not found")
await Database.transaction(async (tx) => {
await tx
.update(PaymentTable)
.set({
timeRefunded: new Date(body.created * 1000),
})
.where(and(eq(PaymentTable.paymentID, paymentIntentID), eq(PaymentTable.workspaceID, workspaceID)))
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} - ${amount}`,
})
.where(eq(BillingTable.workspaceID, workspaceID))
})
}
})()
.then((message) => {
return Response.json({ message: message ?? "done" }, { status: 200 })
})
}
if (body.type === "charge.refunded") {
const customerID = body.data.object.customer as string
const paymentIntentID = body.data.object.payment_intent as string
if (!customerID) throw new Error("Customer ID not found")
if (!paymentIntentID) throw new Error("Payment ID not found")
const workspaceID = await Database.use((tx) =>
tx
.select({
workspaceID: BillingTable.workspaceID,
})
.from(BillingTable)
.where(eq(BillingTable.customerID, customerID))
.then((rows) => rows[0]?.workspaceID),
)
if (!workspaceID) throw new Error("Workspace ID not found")
await Database.transaction(async (tx) => {
await tx
.update(PaymentTable)
.set({
timeRefunded: new Date(body.created * 1000),
})
.where(and(eq(PaymentTable.paymentID, paymentIntentID), eq(PaymentTable.workspaceID, workspaceID)))
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} - ${centsToMicroCents(Billing.CHARGE_AMOUNT)}`,
})
.where(eq(BillingTable.workspaceID, workspaceID))
.catch((error: any) => {
return Response.json({ message: error.message }, { status: 500 })
})
}
console.log("finished handling")
return Response.json("ok", { status: 200 })
}

View File

@@ -121,7 +121,7 @@ export default function Home() {
<h3 data-component="title">homebrew</h3>
<button data-copy data-slot="button">
<span>
brew install <strong>sst/tap/opencode</strong>
brew install <strong>opencode</strong>
</span>
<CopyStatus />
</button>

View File

@@ -14,4 +14,4 @@
color: var(--color-danger);
}
}
}
}

View File

@@ -71,4 +71,4 @@
color: var(--color-text-muted);
}
}
}
}

View File

@@ -23,7 +23,13 @@ const getWorkspaces = query(async () => {
})
.from(UserTable)
.innerJoin(WorkspaceTable, eq(UserTable.workspaceID, WorkspaceTable.id))
.where(and(eq(UserTable.accountID, Actor.account()), isNull(WorkspaceTable.timeDeleted))),
.where(
and(
eq(UserTable.accountID, Actor.account()),
isNull(WorkspaceTable.timeDeleted),
isNull(UserTable.timeDeleted),
),
),
)
})
}, "workspaces")

View File

@@ -104,4 +104,4 @@
}
}
}
}
}

View File

@@ -12,7 +12,7 @@ const getUserEmail = query(async (workspaceID: string) => {
"use server"
return withActor(async () => {
const actor = Actor.assert("user")
const email = await User.getAccountEmail(actor.properties.userID)
const email = await User.getAuthEmail(actor.properties.userID)
return email
}, workspaceID)
}, "userEmail")

View File

@@ -16,38 +16,110 @@
justify-content: flex-end;
}
[data-component="workspace-nav-items"] {
display: flex;
flex-direction: column;
gap: var(--space-2);
/* Desktop Navigation */
[data-component="nav-desktop"] {
display: block;
[data-nav-button] {
padding: var(--space-3) var(--space-4);
border-radius: var(--border-radius-sm);
color: var(--color-text-muted);
text-decoration: none;
font-size: var(--font-size-sm);
font-weight: 500;
transition: all 0.15s ease;
@media (max-width: 48rem) {
display: none;
}
&:hover {
color: var(--color-text);
[data-component="workspace-nav-items"] {
display: flex;
flex-direction: column;
gap: var(--space-2);
[data-nav-button] {
padding: var(--space-3) var(--space-4);
border-radius: var(--border-radius-sm);
color: var(--color-text-muted);
text-decoration: none;
font-size: var(--font-size-sm);
font-weight: 500;
transition: all 0.15s ease;
&:hover {
color: var(--color-text);
}
&.active {
color: var(--color-text);
font-weight: 700;
position: relative;
&::before {
content: "";
position: absolute;
left: calc(-1 * var(--space-0-5));
top: 0;
bottom: 0;
width: 2px;
background-color: var(--color-text);
border-radius: 0 2px 2px 0;
}
}
}
}
}
&.active {
color: var(--color-text);
font-weight: 700;
/* Mobile Navigation */
[data-component="nav-mobile"] {
display: none;
@media (max-width: 48rem) {
display: flex;
align-items: stretch;
justify-content: flex-start;
width: 100%;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}
[data-component="workspace-nav-items"] {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--space-1);
min-width: max-content;
height: 100%;
[data-nav-button] {
padding: var(--space-2) var(--space-3);
padding-bottom: calc(var(--space-2) + 4px);
border-radius: var(--border-radius-sm);
color: var(--color-text-muted);
text-decoration: none;
font-size: var(--font-size-sm);
font-weight: 500;
transition: all 0.15s ease;
white-space: nowrap;
flex-shrink: 0;
position: relative;
&::before {
content: '';
position: absolute;
left: calc(-1 * var(--space-0-5));
top: 0;
bottom: 0;
width: 2px;
background-color: var(--color-text);
border-radius: 0 2px 2px 0;
&:hover {
color: var(--color-text);
}
&.active {
color: var(--color-text);
font-weight: 700;
&::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background-color: var(--color-text);
border-radius: 2px 2px 0 0;
}
}
}
}
@@ -221,5 +293,16 @@
border-right: none;
border-bottom: 1px solid var(--color-border);
padding: var(--space-4);
justify-content: flex-start;
overflow-x: auto;
overflow-y: hidden;
flex-shrink: 0;
min-height: fit-content;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}
}
}

View File

@@ -6,29 +6,54 @@ import "./[id].css"
export default function WorkspaceLayout(props: RouteSectionProps) {
const params = useParams()
const userInfo = createAsync(() => querySessionInfo(params.id))
return (
<main data-page="workspace">
<div data-component="workspace-container">
<nav data-component="workspace-nav">
<div data-component="workspace-nav-items">
<A href={`/workspace/${params.id}`} end activeClass="active" data-nav-button>
Zen
</A>
<A href={`/workspace/${params.id}/keys`} activeClass="active" data-nav-button>
API Keys
</A>
<A href={`/workspace/${params.id}/members`} activeClass="active" data-nav-button>
Members
</A>
<Show when={userInfo()?.isAdmin}>
<A href={`/workspace/${params.id}/billing`} activeClass="active" data-nav-button>
Billing
<nav data-component="nav-desktop">
<div data-component="workspace-nav-items">
<A href={`/workspace/${params.id}`} end activeClass="active" data-nav-button>
Zen
</A>
<A href={`/workspace/${params.id}/settings`} activeClass="active" data-nav-button>
Settings
<A href={`/workspace/${params.id}/keys`} activeClass="active" data-nav-button>
API Keys
</A>
</Show>
</div>
<A href={`/workspace/${params.id}/members`} activeClass="active" data-nav-button>
Members
</A>
<Show when={userInfo()?.isAdmin}>
<A href={`/workspace/${params.id}/billing`} activeClass="active" data-nav-button>
Billing
</A>
<A href={`/workspace/${params.id}/settings`} activeClass="active" data-nav-button>
Settings
</A>
</Show>
</div>
</nav>
<nav data-component="nav-mobile">
<div data-component="workspace-nav-items">
<A href={`/workspace/${params.id}`} end activeClass="active" data-nav-button>
Zen
</A>
<A href={`/workspace/${params.id}/keys`} activeClass="active" data-nav-button>
API Keys
</A>
<A href={`/workspace/${params.id}/members`} activeClass="active" data-nav-button>
Members
</A>
<Show when={userInfo()?.isAdmin}>
<A href={`/workspace/${params.id}/billing`} activeClass="active" data-nav-button>
Billing
</A>
<A href={`/workspace/${params.id}/settings`} activeClass="active" data-nav-button>
Settings
</A>
</Show>
</div>
</nav>
</nav>
<div data-component="workspace-content">{props.children}</div>
</div>

View File

@@ -4,9 +4,6 @@
align-items: center;
justify-content: space-between;
gap: var(--space-4);
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
p {
color: var(--color-danger);
@@ -24,27 +21,116 @@
}
}
[data-slot="payment"] {
[data-slot="section-content"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
min-width: 14.5rem;
width: fit-content;
}
[data-slot="balance-display"] {
display: flex;
align-items: flex-start;
gap: var(--space-3);
@media (max-width: 30rem) {
width: 100%;
flex-direction: column;
align-items: flex-start;
gap: var(--space-2);
}
[data-slot="balance-amount"] {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg-surface);
align-self: stretch;
[data-slot="balance-label"] {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
margin-top: var(--space-2);
font-weight: 400;
}
[data-slot="balance-value"] {
font-size: var(--font-size-2xl);
font-weight: 600;
color: var(--color-text);
}
}
[data-slot="balance-right-section"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
flex: 1;
}
[data-slot="add-balance-form-container"] {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
[data-slot="add-balance-form"] {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--space-3);
label {
font-size: var(--font-size-sm);
font-weight: 500;
color: var(--color-text-muted);
white-space: nowrap;
}
input[data-component="input"] {
padding: var(--space-2) var(--space-3);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
line-height: 1.5;
&:focus {
outline: none;
border-color: var(--color-accent);
box-shadow: 0 0 0 3px var(--color-accent-alpha);
}
&::placeholder {
color: var(--color-text-disabled);
}
}
[data-slot="form-actions"] {
display: flex;
gap: var(--space-2);
}
}
[data-slot="form-error"] {
color: var(--color-danger);
font-size: var(--font-size-sm);
line-height: 1.4;
}
[data-slot="credit-card"] {
padding: var(--space-3-5) var(--space-4);
padding: var(--space-2) var(--space-4);
background-color: var(--color-bg-surface);
border-radius: var(--border-radius-sm);
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
min-width: 150px;
align-self: flex-start;
[data-slot="card-icon"] {
display: flex;
@@ -56,56 +142,44 @@
display: flex;
align-items: baseline;
gap: var(--space-1);
flex: 1;
justify-content: flex-end;
[data-slot="secret"] {
position: relative;
bottom: 2px;
font-size: var(--font-size-lg);
font-size: var(--font-size-sm);
color: var(--color-text-muted);
font-weight: 400;
}
[data-slot="number"] {
font-size: var(--font-size-3xl);
font-size: var(--font-size-sm);
font-weight: 500;
color: var(--color-text);
color: var(--color-text-muted);
}
}
}
[data-slot="button-row"] {
display: flex;
gap: var(--space-2);
align-items: center;
@media (max-width: 30rem) {
flex-direction: column;
>button {
width: 100%;
[data-slot="type"] {
font-size: var(--font-size-sm);
font-weight: 400;
color: var(--color-text-muted);
}
}
[data-slot="create-form"] {
margin: 0;
button {
white-space: nowrap;
flex-shrink: 0;
}
}
/* Make Enable Billing button full width when it's the only button */
>button {
flex: 1;
}
button {
align-self: flex-start;
white-space: nowrap;
flex-shrink: 0;
}
}
[data-slot="usage"] {
p {
font-size: var(--font-size-sm);
line-height: 1.5;
color: var(--color-text-secondary);
b {
font-weight: 600;
}
}
[data-slot="enable-billing-button"] {
align-self: flex-start;
padding: var(--space-4);
min-width: 150px;
}
}
}

View File

@@ -1,66 +1,93 @@
import { json, query, action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router"
import { createMemo, Show } from "solid-js"
import { action, useParams, useAction, createAsync, useSubmission, json } from "@solidjs/router"
import { createMemo, Match, Show, Switch, createEffect } from "solid-js"
import { createStore } from "solid-js/store"
import { Billing } from "@opencode-ai/console-core/billing.js"
import { withActor } from "~/context/auth.withActor"
import { IconCreditCard } from "~/component/icon"
import { IconCreditCard, IconStripe } from "~/component/icon"
import styles from "./billing-section.module.css"
import { Database, eq } from "@opencode-ai/console-core/drizzle/index.js"
import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js"
import { createCheckoutUrl } from "../../common"
const reload = action(async (form: FormData) => {
"use server"
const workspaceID = form.get("workspaceID")?.toString()
if (!workspaceID) return { error: "Workspace ID is required" }
return json(await withActor(() => Billing.reload(), workspaceID), { revalidate: getBillingInfo.key })
}, "billing.reload")
const setReload = action(async (form: FormData) => {
"use server"
const workspaceID = form.get("workspaceID")?.toString()
if (!workspaceID) return { error: "Workspace ID is required" }
const reload = form.get("reload")?.toString() === "true"
return json(
await Database.use((tx) =>
tx
.update(BillingTable)
.set({
reload,
})
.where(eq(BillingTable.workspaceID, workspaceID)),
),
{ revalidate: getBillingInfo.key },
)
}, "billing.setReload")
import { createCheckoutUrl, formatBalance, queryBillingInfo } from "../../common"
const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
"use server"
return withActor(() => Billing.generateSessionUrl({ returnUrl }), workspaceID)
return json(
await withActor(
() =>
Billing.generateSessionUrl({ returnUrl })
.then((data) => ({ error: undefined, data }))
.catch((e) => ({
error: e.message as string,
data: undefined,
})),
workspaceID,
),
{ revalidate: queryBillingInfo.key },
)
}, "sessionUrl")
const getBillingInfo = query(async (workspaceID: string) => {
"use server"
return withActor(async () => {
return await Billing.get()
}, workspaceID)
}, "billing.get")
export function BillingSection() {
const params = useParams()
// ORIGINAL CODE - COMMENTED OUT FOR TESTING
const balanceInfo = createAsync(() => getBillingInfo(params.id))
const createCheckoutUrlAction = useAction(createCheckoutUrl)
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
const createSessionUrlAction = useAction(createSessionUrl)
const createSessionUrlSubmission = useSubmission(createSessionUrl)
const setReloadSubmission = useSubmission(setReload)
const reloadSubmission = useSubmission(reload)
const billingInfo = createAsync(() => queryBillingInfo(params.id))
const checkoutAction = useAction(createCheckoutUrl)
const checkoutSubmission = useSubmission(createCheckoutUrl)
const sessionAction = useAction(createSessionUrl)
const sessionSubmission = useSubmission(createSessionUrl)
const [store, setStore] = createStore({
showAddBalanceForm: false,
addBalanceAmount: billingInfo()?.reloadAmount.toString() ?? "",
checkoutRedirecting: false,
sessionRedirecting: false,
})
createEffect(() => {
const info = billingInfo()
if (info) {
setStore("addBalanceAmount", info.reloadAmount.toString())
}
})
const balance = createMemo(() => formatBalance(billingInfo()?.balance ?? 0))
async function onClickCheckout() {
const amount = parseInt(store.addBalanceAmount)
const baseUrl = window.location.href
const checkout = await checkoutAction(params.id, amount, baseUrl, baseUrl)
if (checkout && checkout.data) {
setStore("checkoutRedirecting", true)
window.location.href = checkout.data
}
}
async function onClickSession() {
const baseUrl = window.location.href
const sessionUrl = await sessionAction(params.id, baseUrl)
if (sessionUrl && sessionUrl.data) {
setStore("sessionRedirecting", true)
window.location.href = sessionUrl.data
}
}
function showAddBalanceForm() {
while (true) {
checkoutSubmission.clear()
if (!checkoutSubmission.result) break
}
setStore({
showAddBalanceForm: true,
})
}
function hideAddBalanceForm() {
setStore("showAddBalanceForm", false)
checkoutSubmission.clear()
}
// DUMMY DATA FOR TESTING - UNCOMMENT ONE OF THE SCENARIOS BELOW
// Scenario 1: User has not added billing details and has no balance
// const balanceInfo = () => ({
// balance: 0,
// paymentMethodType: null as string | null,
// paymentMethodLast4: null as string | null,
// reload: false,
// reloadError: null as string | null,
@@ -70,6 +97,7 @@ export function BillingSection() {
// Scenario 2: User has not added billing details but has a balance
// const balanceInfo = () => ({
// balance: 1500000000, // $15.00
// paymentMethodType: null as string | null,
// paymentMethodLast4: null as string | null,
// reload: false,
// reloadError: null as string | null,
@@ -79,6 +107,7 @@ export function BillingSection() {
// Scenario 3: User has added billing details (reload enabled)
// const balanceInfo = () => ({
// balance: 750000000, // $7.50
// paymentMethodType: "card",
// paymentMethodLast4: "4242",
// reload: true,
// reloadError: null as string | null,
@@ -88,19 +117,22 @@ export function BillingSection() {
// Scenario 4: User has billing details but reload failed
// const balanceInfo = () => ({
// balance: 250000000, // $2.50
// paymentMethodType: "card",
// paymentMethodLast4: "4242",
// reload: true,
// reloadError: "Your card was declined." as string,
// timeReloadError: new Date(Date.now() - 3600000) as Date // 1 hour ago
// })
const balanceAmount = createMemo(() => {
return ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
})
const hasBalance = createMemo(() => {
return (balanceInfo()?.balance ?? 0) > 0 && balanceAmount() !== "0.00"
})
// Scenario 5: User has Link payment method
// const balanceInfo = () => ({
// balance: 500000000, // $5.00
// paymentMethodType: "link",
// paymentMethodLast4: null as string | null,
// reload: true,
// reloadError: null as string | null,
// timeReloadError: null as Date | null
// })
return (
<section class={styles.root}>
@@ -111,118 +143,97 @@ export function BillingSection() {
</p>
</div>
<div data-slot="section-content">
<Show when={balanceInfo()?.reloadError}>
<div data-slot="reload-error">
<p>
Reload failed at{" "}
{balanceInfo()?.timeReloadError!.toLocaleString("en-US", {
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
})}
. Reason: {balanceInfo()?.reloadError?.replace(/\.$/, "")}. Please update your payment method and try
again.
</p>
<form action={reload} method="post" data-slot="create-form">
<input type="hidden" name="workspaceID" value={params.id} />
<button data-color="primary" type="submit" disabled={reloadSubmission.pending}>
{reloadSubmission.pending ? "Reloading..." : "Reload"}
</button>
</form>
<div data-slot="balance-display">
<div data-slot="balance-amount">
<span data-slot="balance-value">${balance()}</span>
<span data-slot="balance-label">Current Balance</span>
</div>
</Show>
<div data-slot="payment">
<div data-slot="credit-card">
<div data-slot="card-icon">
<IconCreditCard style={{ width: "32px", height: "32px" }} />
</div>
<div data-slot="card-details">
<Show when={balanceInfo()?.paymentMethodLast4} fallback={<span data-slot="number">----</span>}>
<span data-slot="secret"></span>
<span data-slot="number">{balanceInfo()?.paymentMethodLast4}</span>
</Show>
</div>
</div>
<div data-slot="button-row">
<Show
when={balanceInfo()?.reload}
fallback={
<Show
when={hasBalance()}
fallback={
<button
data-color="primary"
disabled={createCheckoutUrlSubmission.pending}
onClick={async () => {
const baseUrl = window.location.href
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
if (checkoutUrl) {
window.location.href = checkoutUrl
}
}}
>
{createCheckoutUrlSubmission.pending ? "Loading..." : "Enable Billing"}
</button>
}
>
<form action={setReload} method="post" data-slot="create-form">
<input type="hidden" name="workspaceID" value={params.id} />
<input type="hidden" name="reload" value="true" />
<button data-color="primary" type="submit" disabled={setReloadSubmission.pending}>
{setReloadSubmission.pending ? "Enabling..." : "Enable Billing"}
</button>
</form>
</Show>
}
>
<button
data-color="primary"
disabled={createSessionUrlSubmission.pending}
onClick={async () => {
const baseUrl = window.location.href
const sessionUrl = await createSessionUrlAction(params.id, baseUrl)
if (sessionUrl) {
window.location.href = sessionUrl
}
}}
<Show when={billingInfo()?.customerID}>
<div data-slot="balance-right-section">
<Show
when={!store.showAddBalanceForm}
fallback={
<div data-slot="add-balance-form-container">
<div data-slot="add-balance-form">
<label>Add $</label>
<input
data-component="input"
type="number"
min={billingInfo()?.reloadAmountMin.toString()}
step="1"
value={store.addBalanceAmount}
onInput={(e) => {
setStore("addBalanceAmount", e.currentTarget.value)
checkoutSubmission.clear()
}}
placeholder="Enter amount"
/>
<div data-slot="form-actions">
<button data-color="ghost" type="button" onClick={() => hideAddBalanceForm()}>
Cancel
</button>
<button
data-color="primary"
type="button"
disabled={!store.addBalanceAmount || checkoutSubmission.pending || store.checkoutRedirecting}
onClick={onClickCheckout}
>
{checkoutSubmission.pending || store.checkoutRedirecting ? "Loading..." : "Add"}
</button>
</div>
</div>
<Show when={checkoutSubmission.result && (checkoutSubmission.result as any).error}>
{(err: any) => <div data-slot="form-error">{err()}</div>}
</Show>
</div>
}
>
{createSessionUrlSubmission.pending ? "Loading..." : "Manage Payment Methods"}
</button>
<form action={setReload} method="post" data-slot="create-form">
<input type="hidden" name="workspaceID" value={params.id} />
<input type="hidden" name="reload" value="false" />
<button data-color="ghost" type="submit" disabled={setReloadSubmission.pending}>
{setReloadSubmission.pending ? "Disabling..." : "Disable"}
<button data-color="primary" onClick={() => showAddBalanceForm()}>
Add Balance
</button>
</form>
</Show>
</div>
</div>
<div data-slot="usage">
<Show when={!balanceInfo()?.reload}>
<Show
when={hasBalance()}
fallback={
<p>
We'll load <b>$20</b> (+$1.23 processing fee) and reload it when it reaches <b>$5</b>.
</p>
}
>
<p>
You have <b data-slot="value">${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()}</b> remaining in
your account. You can continue using the API with your remaining balance.
</p>
</Show>
</Show>
<Show when={balanceInfo()?.reload && !balanceInfo()?.reloadError}>
<p>
Your current balance is <b data-slot="value">${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()}</b>
. We'll automatically reload <b>$20</b> (+$1.23 processing fee) when it reaches <b>$5</b>.
</p>
</Show>
<div data-slot="credit-card">
<div data-slot="card-icon">
<Switch fallback={<IconCreditCard style={{ width: "24px", height: "24px" }} />}>
<Match when={billingInfo()?.paymentMethodType === "link"}>
<IconStripe style={{ width: "24px", height: "24px" }} />
</Match>
</Switch>
</div>
<div data-slot="card-details">
<Switch>
<Match when={billingInfo()?.paymentMethodType === "card"}>
<Show when={billingInfo()?.paymentMethodLast4} fallback={<span data-slot="number">----</span>}>
<span data-slot="secret"></span>
<span data-slot="number">{billingInfo()?.paymentMethodLast4}</span>
</Show>
</Match>
<Match when={billingInfo()?.paymentMethodType === "link"}>
<span data-slot="type">Linked to Stripe</span>
</Match>
</Switch>
</div>
<button
data-color="ghost"
disabled={sessionSubmission.pending || store.sessionRedirecting}
onClick={onClickSession}
>
{sessionSubmission.pending || store.sessionRedirecting ? "Loading..." : "Manage"}
</button>
</div>
</div>
</Show>
</div>
<Show when={!billingInfo()?.customerID}>
<button
data-slot="enable-billing-button"
data-color="primary"
disabled={checkoutSubmission.pending || store.checkoutRedirecting}
onClick={onClickCheckout}
>
{checkoutSubmission.pending || store.checkoutRedirecting ? "Loading..." : "Enable Billing"}
</button>
</Show>
</div>
</section>
)

View File

@@ -1,21 +1,26 @@
import { MonthlyLimitSection } from "./monthly-limit-section"
import { BillingSection } from "./billing-section"
import { ReloadSection } from "./reload-section"
import { PaymentSection } from "./payment-section"
import { Show } from "solid-js"
import { createAsync, useParams } from "@solidjs/router"
import { querySessionInfo } from "../../common"
import { queryBillingInfo, querySessionInfo } from "../../common"
export default function () {
const params = useParams()
const userInfo = createAsync(() => querySessionInfo(params.id))
const billingInfo = createAsync(() => queryBillingInfo(params.id))
return (
<div data-page="workspace-[id]">
<div data-slot="sections">
<Show when={userInfo()?.isAdmin}>
<BillingSection />
<MonthlyLimitSection />
<PaymentSection />
<Show when={billingInfo()?.customerID}>
<ReloadSection />
<MonthlyLimitSection />
<PaymentSection />
</Show>
</Show>
</div>
</div>

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