Compare commits

...

498 Commits

Author SHA1 Message Date
Aiden Cline eb6b86c2c6 adjust action
publish-github-action / publish (push) Waiting to run
2025-10-16 13:15:27 -05:00
Adam 8f2667f395 wip: css/ui work 2025-10-16 18:06:59 +00:00
opencode 0e2bfb8a68 release: v0.15.5 2025-10-16 18:06:59 +00:00
Aiden Cline 9fd29cd301 ignore: tweak permissions 2025-10-16 12:48:39 -05:00
Aiden Cline 7e15176513 fix: github action (#3223) 2025-10-16 12:33:33 -05:00
Aiden Cline cc9f894b6a ci: fix changelog generation 2025-10-16 11:29:58 -05:00
Moisès Macià de19dd1d0a docs: fix misspelled word (#3211) 2025-10-16 10:04:11 -05:00
Dax Raad 2a49448e46 try to avoid persisting empty thinking/text blocks 2025-10-16 10:54:10 -04:00
opencode 20d03f40bb release: v0.15.4 2025-10-16 14:53:32 +00:00
GitHub Action dbb74ee4e0 ignore: update download stats 2025-10-16 2025-10-16 12:04:48 +00:00
seridescent c58da112ab fix: use ai-sdk openai chat language model instead of completion language model (#3204) 2025-10-16 00:59:49 -05:00
Aiden Cline 4973f281ff Revert "fix: Text content blocks must contain non-whitespace text" (#3200) 2025-10-15 20:02:17 -05:00
Dax Raad fa3cf830cb core: filter out alpha status models from provider list 2025-10-15 20:12:37 -04:00
Dax Raad fddfa2df68 ci: stuff 2025-10-15 19:59:46 -04:00
Dax Raad 093e6d7f88 snapshot publish everything 2025-10-15 19:53:14 -04:00
Dax Raad 1947cbd391 sync 2025-10-15 19:48:57 -04:00
Aiden Cline 01f635b7ba ignore: update readme 2025-10-15 16:06:11 -05:00
Aiden Cline 11d1eb5ccf Revert "fix: spawns hanging (#3192)"
This reverts commit 18fb335e6d.
2025-10-15 15:20:14 -05:00
Fabian Kukuck 5e394521d4 feat: make compact feature use streaming API (#3079)
Co-authored-by: fku <fabian.kukuck@ipt.ch>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-10-15 13:44:16 -05:00
Aiden Cline 18fb335e6d fix: spawns hanging (#3192) 2025-10-15 13:01:54 -05:00
Aiden Cline ac1b7b63b1 fix: Text content blocks must contain non-whitespace text (#3194) 2025-10-15 13:00:26 -05:00
Frank 5e074568b9 zen: support haiku 4.5 2025-10-15 13:53:00 -04:00
Matt Gillard e594d5a1ae updated bedrock provider for the new Australian sonnet 4.5 cross region inference (#3050)
Co-authored-by: Matt Gillard <matt-github@gillard.biz>
2025-10-15 11:09:22 -05:00
Haris Gušić a859ac532f Improve http error codes (#3186) 2025-10-15 10:53:09 -05:00
GitHub Action 4c3a4805e7 ignore: update download stats 2025-10-15 2025-10-15 12:04:51 +00:00
Frank 06a41df87b wip: zen 2025-10-15 02:55:01 -04:00
Aiden Cline 45caf66145 tweak: consolidate session lock logic (#3185) 2025-10-15 01:12:51 -05:00
Frank 557729caa0 wip: zen 2025-10-15 01:07:57 -04:00
Frank 2469578db8 wip: zen 2025-10-15 00:02:38 -04:00
Frank 471b915050 wip: zen 2025-10-14 23:45:06 -04:00
Frank f2698c4c4a wip: zen 2025-10-14 23:38:21 -04:00
Frank 333aadf23d wip: zen 2025-10-14 23:04:41 -04:00
Frank f5b00aa060 wip: zen 2025-10-14 23:03:13 -04:00
Dax Raad 38a2a69743 ci: run typecheck before tests to catch type errors early 2025-10-14 18:36:03 -04:00
Dax Raad 8aa4ea0eb1 try tsgo 2025-10-14 18:30:32 -04:00
Jay V bc10aca1d8 ignore: prompt 2025-10-14 17:45:10 -04:00
Jay V 6fef731af0 web: colocate copy button styles with components that use them 2025-10-14 17:41:17 -04:00
Frank 7052fff09f ci: fix 2025-10-14 17:22:29 -04:00
Frank 682a658819 wip: zen 2025-10-14 17:18:39 -04:00
Frank 3ab1bcd89b wip: zen 2025-10-14 17:13:21 -04:00
Frank 0a7e4d3320 wip: zen 2025-10-14 17:06:49 -04:00
Frank a4e0b35b9c wip: zen 2025-10-14 17:06:49 -04:00
Jay V fcf70461ac docs: improve MCP server configuration guidance with examples and caveats 2025-10-14 16:43:59 -04:00
Dax Raad 4170660b41 ci: channels 2025-10-14 15:09:18 -04:00
Dax Raad 926b8e37d7 sync 2025-10-14 14:57:34 -04:00
GitHub Action 889bafc911 chore: format code 2025-10-14 18:56:54 +00:00
Dax Raad d338407078 ci: test 2025-10-14 14:56:21 -04:00
Dax Raad d1aa28f417 ci: version stuff 2025-10-14 14:52:05 -04:00
Dax Raad 38bcff5998 ci: fix 2025-10-14 18:44:22 +00:00
opencode 00c74dbb72 release: v0.15.3 2025-10-14 18:44:21 +00:00
Dax Raad befa103109 ci: stuff 2025-10-14 14:35:04 -04:00
Aiden Cline 7520515273 fix: false positive package manager detection in upgrade (#3181) 2025-10-14 13:18:40 -05:00
Frank d15110074c ci: fix 2025-10-14 13:58:54 -04:00
Frank e91a051982 wip: zen 2025-10-14 13:52:30 -04:00
GitHub Action 1aa3dd2b56 chore: format code 2025-10-14 17:08:18 +00:00
Adam cf479404c3 wip: css/ui work 2025-10-14 12:07:45 -05:00
Adam 6b88402f08 wip: css/ui work 2025-10-14 12:06:18 -05:00
Adam b8cd2b62ca wip: desktop work 2025-10-14 12:06:17 -05:00
Netanel Draiman 97c4179bc5 feat(cli): add session option to attach command (#3167) 2025-10-14 11:04:32 -05:00
Adam afc475f9a4 wip: css and ui packages 2025-10-14 07:16:24 -05:00
Adam 4af079fd59 wip: desktop work 2025-10-14 07:15:08 -05:00
Adam 17f1cb3664 wip: desktop work 2025-10-14 07:15:08 -05:00
GitHub Action b4911ef1f1 ignore: update download stats 2025-10-14 2025-10-14 12:04:50 +00:00
Dax cacb086356 feat: use realtime events for live tool call updates in Slack (#3163) 2025-10-14 03:13:48 -04:00
Dax Raad 15409fc218 fix: update tsconfig for Slack package 2025-10-14 02:55:21 -04:00
Dax Raad de634a3f13 fix: export trimDiff function from edit tool 2025-10-14 02:55:02 -04:00
Dax Raad 07ac47ac3d fix: resolve TypeScript errors in SDK and Slack package 2025-10-14 02:54:37 -04:00
Dax Raad c64b823198 feat: add Slack integration package with Bolt framework 2025-10-14 02:53:55 -04:00
Dax Raad 87ff866318 core: improve dependency management and error handling for more reliable builds 2025-10-14 01:33:25 -04:00
Dax Raad b03301a967 core: standardize dependency versions for better reliability
Ensures consistent versions across packages by using workspace catalog for
tailwindcss and tsconfig dependencies, reducing potential conflicts and
installation issues.
2025-10-14 01:27:17 -04:00
Dax Raad 6e591ce3cb core: reduce dependency conflicts by standardizing package versions through catalog
This eliminates duplicate package versions that were causing build issues and
inconsistent behavior across the monorepo. Dependencies now resolve to single
versions through the workspace catalog, making installs faster and more reliable.
2025-10-14 01:23:54 -04:00
opencode 08840f2f32 release: v0.15.2 2025-10-14 04:56:00 +00:00
Alberto Fanton 17f1bb96f4 fix: disable GPG signing in snapshot tests (#3102) 2025-10-13 23:40:41 -05:00
Aiden Cline b22df18e0f fix: config dir overrides (#3160) 2025-10-13 23:25:53 -05:00
Aiden Cline 0beda25798 fix: custom model (#3156) 2025-10-13 19:58:19 -05:00
pancake 841135c435 fix: clang formatter name (#3042)
Co-authored-by: pancake <pancake@nopcode.org>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-10-13 19:55:18 -05:00
Haris Gušić d4bc87f3c1 fix: make shell more robust (#3051) 2025-10-13 17:37:35 -05:00
maple 67e5252734 docs: typo in custom-tools.mdx (#3152) 2025-10-13 17:19:20 -05:00
Aiden Cline a0600e82d9 fix: typecheck (#3149) 2025-10-13 14:51:12 -05:00
AB 15d5c03f64 fix: add useCompletionUrls option to fix certain azure setups (#2528)
Co-authored-by: andreas.blass <andreas.blass@outlook.com>
Co-authored-by: GitHub Action <action@github.com>
2025-10-13 14:16:21 -05:00
Tommy D. Rossi e6373142c1 tweak: include stack trace in server error responses (#3134) 2025-10-13 14:10:35 -05:00
opencode 65b9b7acf0 release: v0.15.1 2025-10-13 18:14:52 +00:00
Dax Raad 6cc97ede65 fix: disable workspace symbol lookup to prevent LSP performance issues 2025-10-13 14:05:54 -04:00
Aiden Cline f8132999b6 fix: pass options to compact (#3136) 2025-10-13 10:42:39 -05:00
Aiden Cline 40f77361d2 docs: update recommended models list (#3121) 2025-10-12 21:35:31 -05:00
Aiden Cline 85bd863914 fix: adjust list tool prompt to handle cwd better (#3115) 2025-10-12 16:48:03 -05:00
Ravshan Samandarov 7122edc44f docs: Update README.md (#3100) 2025-10-12 16:23:25 -04:00
Jay 864a53775a docs: Fix formatting of num_ctx in providers.mdx 2025-10-12 16:01:32 -04:00
Aiden Cline 083fb7075f docs: ollama num_ctx (#3111) 2025-10-12 10:40:51 -05:00
GitHub Action 5c26d08fe7 ignore: update download stats 2025-10-12 2025-10-12 12:04:09 +00:00
OpeOginni 2e870df2b7 fix: add timeout to fetch models.dev refresh request (#3059) 2025-10-12 00:20:22 -05:00
opencode 8680e728d7 release: v0.15.0 2025-10-12 05:12:15 +00:00
Dax Raad e025027d20 ci: centralize bun setup to reduce duplication and improve caching 2025-10-12 00:46:37 -04:00
Dax Raad bac8e924be ci: tweak 2025-10-12 00:40:59 -04:00
Tommy D. Rossi c015ced81f fix: rg hanging forever when run in bash, waiting for stdin (#3103)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-10-11 23:24:48 -05:00
Dax Raad 4951bc5515 ci: re-enable aur 2025-10-12 00:22:16 -04:00
Frank 8f37e46b49 wip: zen 2025-10-11 16:17:39 -04:00
Frank b5114e1aea ci: fix 2025-10-11 15:08:45 -04:00
Frank 47a76e92d5 wip: zen 2025-10-11 15:07:06 -04:00
Frank 59a3df970f wip: zen 2025-10-11 14:48:34 -04:00
Frank cbe402ca4a ci: fix 2025-10-11 14:41:52 -04:00
Frank 39262874ec wip: zen 2025-10-11 14:38:53 -04:00
Frank 45e55b730b Bump sst to latest 2025-10-11 14:38:45 -04:00
Frank cfc3b20826 wip: zen 2025-10-11 10:51:17 -04:00
Frank a1f7dc98fa wip: zen 2025-10-11 09:29:26 -04:00
Frank eb20c411b9 wip: zen 2025-10-11 08:27:32 -04:00
Frank c8f39d6065 wip: zen 2025-10-11 08:27:32 -04:00
Frank b7478b7be3 wip: zen 2025-10-11 08:27:32 -04:00
GitHub Action a31601dd37 ignore: update download stats 2025-10-11 2025-10-11 12:04:02 +00:00
Frank 7e8c7c329a wip: zen refactor selector 2025-10-11 07:54:57 -04:00
Frank 64d857aa08 wip: zen fix drop down style 2025-10-11 07:15:19 -04:00
Frank 0d7543c5b4 wip: zen fix template 2025-10-11 06:33:47 -04:00
Dax Raad 0271b3847f ci: fixes 2025-10-11 00:24:35 -04:00
Dax Raad a2e1a61cfb ci: stuff 2025-10-11 00:06:46 -04:00
Dax Raad 5685689512 ci: fix 2025-10-10 23:43:32 -04:00
Frank d180adb7fc wip: zen 2025-10-10 21:26:16 -04:00
Frank efa75c9697 Merge branch 'console-workspaces' into dev 2025-10-10 21:24:05 -04:00
Frank dfd154e760 wip: zen 2025-10-10 21:21:55 -04:00
Frank 70bd275955 wip: zen 2025-10-10 20:32:28 -04:00
Frank 77c0b1b724 wip: zen 2025-10-10 20:30:13 -04:00
Frank 2222036bd2 wip: zen 2025-10-10 20:16:44 -04:00
Frank d93dcc7a0a wip: zen 2025-10-10 20:02:17 -04:00
Frank 3026dafb3d wip: zen 2025-10-10 19:56:40 -04:00
Frank 3b30bdf8ec wip: zen 2025-10-10 19:49:59 -04:00
Frank 2a9f5bbd38 wip: zen 2025-10-10 19:39:01 -04:00
Dax Raad 171c591728 ci: fixes 2025-10-10 18:17:10 -04:00
Dax Raad fd928f3692 ci: fix 2025-10-10 18:11:19 -04:00
Dax Raad a35544f5a2 ci: stuff 2025-10-10 18:04:08 -04:00
Dax Raad d574e62490 ci: stuff 2025-10-10 17:56:33 -04:00
Dax Raad 93c84b47ae upgrade to bun 1.3.0 2025-10-10 17:53:32 -04:00
Frank b0a6e71560 wip: zen 2025-10-10 16:42:27 -04:00
Frank b97f117628 wip: zen 2025-10-10 16:34:07 -04:00
Frank 9e482a74ae wip: zen 2025-10-10 16:04:06 -04:00
Frank 2aa7d6469a wip: zen 2025-10-10 14:54:49 -04:00
Frank c278117d31 wip: zen 2025-10-10 14:19:06 -04:00
Frank 5f04fe3783 wip: zen 2025-10-10 14:11:48 -04:00
Frank 82c0dff231 wip: zen 2025-10-10 13:52:54 -04:00
Frank 5722df88b8 wip: zen style members 2025-10-10 13:48:56 -04:00
Frank 8fa81a2f30 wip: zen style api keys 2025-10-10 13:45:06 -04:00
Frank dcc0d265d8 wip: zen 2025-10-10 13:26:39 -04:00
Frank df3df34f58 wip: zen 2025-10-10 13:21:51 -04:00
Frank e216501503 wip: zen 2025-10-10 12:46:42 -04:00
Rustafarian Dev b2f35389fb fix: perl6 file extension (#3066) 2025-10-10 11:28:49 -05:00
Frank 218e6f1b3d wip: zen 2025-10-10 12:22:36 -04:00
Frank d397a4e236 wip: sync 2025-10-10 12:16:57 -04:00
Frank 7bbb3a4dcf wip: zen settings 2025-10-10 12:04:02 -04:00
Frank c9ab1ab8aa wip: zen 2025-10-10 09:03:49 -04:00
GitHub Action c5e080fc6f ignore: update download stats 2025-10-10 2025-10-10 12:04:18 +00:00
Frank bff8338a97 wip: zen style byok 2025-10-10 03:15:55 -04:00
Frank 09dbfd3e28 wip: zen style byok 2025-10-10 02:53:05 -04:00
Frank 2bf5d8d823 wip: zen style byok 2025-10-10 02:37:50 -04:00
Frank 931dc855ce wip: style byok 2025-10-10 02:34:06 -04:00
Frank 5de6ade823 wip: zen 2025-10-10 01:36:15 -04:00
Frank 95f3c7fda5 wip: zen style nav bar 2025-10-10 00:56:16 -04:00
Frank 36c73b3046 wip: zen style model 2025-10-10 00:02:04 -04:00
Dax Raad f7d8b41976 core: improve directory validation error messages to help users fix invalid directory names 2025-10-09 22:40:23 -04:00
Frank d4f0b8b483 wip: zen style header 2025-10-09 22:38:42 -04:00
Dax Raad 367cf7eac3 ensure @opencode-ai/plugin is available in .opencode folder 2025-10-09 21:18:49 -04:00
opencode 7ddc833674 release: v0.14.7 2025-10-10 01:02:37 +00:00
Dax Raad 1076fc244c drop excess dependency in opencode sdk 2025-10-09 20:55:25 -04:00
Dax Raad a0844a6b85 core: use platform-specific watcher backends for better file system monitoring 2025-10-09 18:29:18 -04:00
Dax Raad 21e2f45f77 core: replace chokidar with @parcel/watcher for better performance and cross-platform support 2025-10-09 18:21:38 -04:00
Frank 000ed1a066 wip: zen 2025-10-09 17:18:55 -04:00
Dax Raad c50d7a4275 ignore: add common build and framework directories to ignore list 2025-10-09 16:35:56 -04:00
Frank 9e50700b50 wip: zen nav bar 2025-10-09 16:01:52 -04:00
Dax Raad 92f106f740 ignore: improve file ignore performance and cross-platform support
- Replace glob patterns with Set lookup for common folders to speed up matching
- Use path.sep for cross-platform compatibility on Windows/Unix systems
- Add comprehensive test coverage for nested and non-nested folder matching
- Simplify implementation by removing unnecessary caching complexity
2025-10-09 15:54:01 -04:00
Adam 611a719fb5 feat: experimental skip bootstrap 2025-10-09 14:51:11 -05:00
Aiden Cline bf447bd5ae docs: fix bugged example (#3068) 2025-10-09 12:21:44 -05:00
Jay V 84a75f3d93 docs: adding tools doc 2025-10-09 13:19:51 -04:00
Jay V b407c00c84 docs: document ripgrep .ignore file override in tools 2025-10-09 13:19:51 -04:00
Jay V 38aeafea30 docs: adding spellcheck command 2025-10-09 13:19:51 -04:00
Aiden Cline 73cc3e1a95 feat: allow read tool to handle images (#3052) 2025-10-09 09:05:11 -05:00
GitHub Action 330e5c3386 ignore: update download stats 2025-10-09 2025-10-09 12:04:27 +00:00
Dax Raad 03d8a3f6a2 lsp: fix root detection to use instance directory instead of worktree 2025-10-09 04:30:30 -04:00
Dax Raad 73c7dacc09 lsp: simplify language server root detection to use lock files
Improves project boundary detection by focusing on package manager lock files instead of config files, providing more reliable workspace identification across different project types.
2025-10-09 04:22:38 -04:00
Haris Gušić bf2382d900 fix: process.stdout.write instead of console.log for export cmd (#3049) 2025-10-09 00:46:19 -05:00
Frank f5fe5252d7 wip: zen 2025-10-08 22:33:20 -04:00
Frank ceca472e8c wip: zen 2025-10-08 18:59:41 -04:00
Frank 28335d273b wip: zen 2025-10-08 17:03:42 -04:00
Frank 5df90e43df wip: zen 2025-10-08 15:20:50 -04:00
Frank 9f5111d21f wip: zen 2025-10-08 13:31:15 -04:00
Jay V d02ff9c5b1 docs: fix permission docs 2025-10-08 12:13:42 -04:00
GitHub Action 7717e3f355 ignore: update download stats 2025-10-08 2025-10-08 12:04:31 +00:00
Adam cfe96f6551 fix(tui): suggestions gap on home page 2025-10-08 06:56:18 -05:00
Dax Raad 8fcf52a3cd add free workspace 2025-10-08 02:27:01 -04:00
Frank 861fdc01cf wip: zen 2025-10-08 01:14:39 -04:00
Frank df53506975 wip: zen 2025-10-08 00:03:36 -04:00
opencode 7cae3cc86a release: v0.14.6 2025-10-07 19:59:08 +00:00
Dax Raad b47f5ac785 fix issue with blank new version popup 2025-10-07 15:51:59 -04:00
Frank 9071c94b13 wip: zen 2025-10-07 13:37:38 -04:00
Aiden Cline 21d4459d5e docs: troubleshooting ProviderModelNotFoundError (#3016) 2025-10-07 11:50:37 -05:00
Sai 6f4e3f519e docs: add agent specific permission example (#3009) 2025-10-07 10:08:52 -05:00
Frank cdd652e830 wip: zen 2025-10-07 09:17:08 -04:00
GitHub Action a6491a2f4e ignore: update download stats 2025-10-07 2025-10-07 12:04:53 +00:00
Dax Raad 06fc3bc28f core: improve MCP reliability and add status monitoring
- Added 5-second timeout to MCP client verification to prevent hanging connections
- New GET /mcp endpoint to monitor server connection status
- Automatically removes unresponsive MCP clients during initialization
2025-10-07 04:04:19 -04:00
opencode b5ff5ae621 release: v0.14.5 2025-10-07 06:21:31 +00:00
Aiden Cline cbf11adac7 fix: mcp error (#3006) 2025-10-07 00:45:46 -05:00
Aiden Cline 546c4667a6 tweak: grep tool to handle single file better (#3004) 2025-10-06 23:24:00 -05:00
Frank 83253ffabf wip: zen 2025-10-06 23:57:55 -04:00
Frank e52712ca97 wip: zen 2025-10-06 23:57:54 -04:00
opencode 9389c710f9 release: v0.14.4 2025-10-07 03:32:10 +00:00
Dax Raad ac12364c27 ignore: 2025-10-06 23:25:01 -04:00
Dax Raad cb40e8c622 core: restore bash command security validation to prevent accidental directory traversal
The permission validation that prevents commands from accessing paths outside the project directory was accidentally disabled, which could allow commands like 'cd ../' to escape the workspace. This restores the security check that keeps your commands safely contained within your project boundaries.
2025-10-06 23:24:18 -04:00
Dax Raad 87f71e2183 core: improve session API reliability with proper input validation 2025-10-06 19:37:44 -04:00
Dax Raad fb6713c2b8 Update todo tool to use centralized Todo module 2025-10-06 18:54:05 -04:00
Dax Raad b7e953c077 Add missing files and fix type aliases for opentui features 2025-10-06 18:53:35 -04:00
Dax Raad a06cba9d2e Add todo list and session forking API endpoints 2025-10-06 18:51:57 -04:00
Dax Raad 389bc1f8da Add session forking functionality and simplify remove logic 2025-10-06 18:50:56 -04:00
Frank eeff55f681 wip: zen 2025-10-06 17:23:10 -04:00
Frank 176069efe8 wip: zen 2025-10-06 17:17:02 -04:00
Frank 994a0842eb wip: zen 2025-10-06 17:13:19 -04:00
Frank 687d68eee6 wip: zen 2025-10-06 17:13:19 -04:00
Jay V 81faff475f docs: update footer 2025-10-06 17:05:45 -04:00
Jay V ac88e0937c docs: fix styles and zen doc, closes #2912 2025-10-06 17:00:10 -04:00
Jay V e9729e4b8c docs: share page css 2025-10-06 16:13:21 -04:00
GitHub Action a9230df6b8 ignore: update download stats 2025-10-06 2025-10-06 12:04:17 +00:00
Aiden Cline 140f1a0600 fix: add timeout message if command times out (#2986) 2025-10-05 23:55:01 -05:00
Mani Sundararajan 6d6fc43bb5 fix: file references & grep tool for windows (#2980) 2025-10-05 14:32:07 -05:00
GitHub Action 181c38c9b6 ignore: update download stats 2025-10-05 2025-10-05 12:03:55 +00:00
opencode 601b282459 release: v0.14.3 2025-10-05 11:22:57 +00:00
Dax Raad b57897e377 ci: publish 2025-10-05 07:14:52 -04:00
Dax Raad 62a52c5539 ci: disable aur 2025-10-05 07:12:33 -04:00
Dax Raad fd61b7dbc0 sdk: simplify getting started with single createOpencode function
Makes it easier for developers to get started by providing a single function that creates both server and client, removing the need to manually coordinate separate server and client creation
2025-10-05 07:01:32 -04:00
Dax Raad b59be49095 sdk: simplify getting started with single createOpencode function
Makes it easier for developers to get started by providing a single function that creates both server and client, removing the need to manually coordinate separate server and client creation
2025-10-05 07:00:29 -04:00
Aiden Cline 431312634c fix: max output tokens when using large thinking budget (#2976) 2025-10-04 23:38:41 -05:00
Dax Raad 9348fdec34 sync 2025-10-04 21:33:47 -04:00
Aiden Cline 8f6c63c017 fix: allow LSP filename matching when extension is missing (#2975) 2025-10-04 20:30:53 -05:00
iwauo 4603820839 docs: java-lsp support (#2958) 2025-10-04 11:28:09 -05:00
GitHub Action 7b359f8cd8 ignore: update download stats 2025-10-04 2025-10-04 12:03:53 +00:00
Frank a357abefe1 wip: zen 2025-10-04 01:12:32 -04:00
Yuku Kotani 4c60276d10 feat: Vertex AI support; add google-vertex and google-vertex-anthropic providers (#2347) 2025-10-04 01:10:38 -04:00
Aiden Cline 929ef83d3d add command to debug config (#2962) 2025-10-03 23:07:58 -05:00
Frank 7b627b7610 wip: zen 2025-10-03 23:48:34 -04:00
Dax Raad a61ca2e5bf ci: try regional hostname again 2025-10-03 19:05:32 -04:00
Rovshan Muradov 247f9f8e10 docs: Update models.mdx (#2916) 2025-10-03 17:06:20 -04:00
Frank e55a161f4d wip: zen 2025-10-03 16:32:53 -04:00
Jay V b7fc1a16f9 docs: rename opencode to OpenCode 2025-10-03 13:46:56 -04:00
Frank 31534913a4 wip: zen 2025-10-03 12:54:52 -04:00
Adam 165959715e fix: github stats 2025-10-03 09:34:17 -05:00
Adam bc9d3b308a fix: user-agent 2025-10-03 09:30:51 -05:00
Adam 4ac32d5932 chore: user-agent header 2025-10-03 09:27:12 -05:00
Adam 18d58612ad chore: logging errors 2025-10-03 09:19:54 -05:00
Adam 28bb12bae2 chore: logging errors 2025-10-03 09:10:33 -05:00
Adam 9cad9ffc23 chore: app -> desktop 2025-10-03 09:04:28 -05:00
Frank 44e295600a wip: zen 2025-10-03 08:25:51 -04:00
GitHub Action 7cf05a027b ignore: update download stats 2025-10-03 2025-10-03 12:04:07 +00:00
Frank 0fe315d210 wip: zen 2025-10-03 07:36:16 -04:00
Frank c688305dfc wip: zen 2025-10-03 07:36:16 -04:00
Frank 97a825a8b6 wip: zen 2025-10-03 07:36:16 -04:00
Dax Raad fbfaf15bfc ci: stuff 2025-10-03 10:53:10 +00:00
opencode e4c33da6d4 release: v0.14.1 2025-10-03 10:53:10 +00:00
Dax Raad 2cb3255556 turn on codex medium reasoning again 2025-10-03 06:46:07 -04:00
David Hill cc62b8d8b6 Firefox email input fix 2025-10-02 23:50:51 +01:00
Jay 61003ff55d docs: update theme (#2929)
Co-authored-by: David Hill <iamdavidhill@gmail.com>
2025-10-02 23:20:30 +01:00
David Hill 95daf247d3 Update index.css 2025-10-02 23:19:53 +01:00
Jay 1952e5f46f ignore: Workspace updates (#2930)
Co-authored-by: David Hill <iamdavidhill@gmail.com>
2025-10-02 23:18:57 +01:00
Frank 68fd879f76 wip: zen 2025-10-02 18:16:29 -04:00
Frank af83f14bb7 wip: zen 2025-10-02 17:55:54 -04:00
Frank b58c12d47a wip: zen 2025-10-02 16:09:42 -04:00
Frank 9168c974c5 wip: zen 2025-10-02 13:58:40 -04:00
Jay cc4e4d37d1 docs: README 2025-10-02 12:33:32 -04:00
Jay V c31b0ec014 docs: update README 2025-10-02 12:31:58 -04:00
David Hill 4659e0ed35 Copy tweaks 2025-10-02 17:02:33 +01:00
David Hill 215943e39f Border top only on safari fix 2025-10-02 16:55:48 +01:00
David Hill 493d9194d7 Update favicon.svg 2025-10-02 16:55:29 +01:00
Aiden Cline f58fe4e866 fix: run cmd json format when running command (#2926) 2025-10-02 10:37:42 -05:00
David Hill 8831a57e5f Style fixes 2025-10-02 16:35:23 +01:00
David Hill e1e0015be4 Update dock.png 2025-10-02 16:24:14 +01:00
David Hill fdbdf5ef20 Update dock.png 2025-10-02 16:14:52 +01:00
David Hill a3711c0c81 Email input color fix 2025-10-02 16:11:55 +01:00
David Hill 7b23a64e82 Update dock.png 2025-10-02 15:50:56 +01:00
David Hill 2164190b93 Testimonial tweaks 2025-10-02 15:37:36 +01:00
David Hill 78cac1a32c Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-10-02 15:37:13 +01:00
David Hill f1a916d788 faq icon fix 2025-10-02 15:37:10 +01:00
Frank 50f2bf2432 ignore: fix 2025-10-02 10:10:07 -04:00
David Hill 08fe1472a3 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-10-02 15:03:59 +01:00
David Hill f3ca14cff4 Body background fix 2025-10-02 15:03:56 +01:00
David Hill b614a6f03a Mobile nav icon fix 2025-10-02 15:02:57 +01:00
David Hill a69708f3f3 Mobile nav icon fix 2025-10-02 15:02:44 +01:00
David Hill 5c2fea2b30 Mobile nav fix 2025-10-02 15:01:22 +01:00
Adam cc58dbe6b4 fix: broken links 2025-10-02 09:00:33 -05:00
David Hill db10eac89e Update posters for videos 2025-10-02 14:55:38 +01:00
David Hill fcded3dc6e Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-10-02 14:54:16 +01:00
David Hill 38b587153e Testimonial tweak 2025-10-02 14:53:54 +01:00
Adam 14ad4e4318 chore: add email signup back 2025-10-02 08:46:25 -05:00
David Hill ad4d778e31 Update faq.tsx 2025-10-02 14:42:49 +01:00
David Hill e1f0b4d247 Faq icon color fix 2025-10-02 14:41:57 +01:00
Adam decfbeee01 fix: testimonial link 2025-10-02 08:34:22 -05:00
Adam 99e438c806 wip: desktop work 2025-10-02 08:34:01 -05:00
GitHub Action 5f1ac0af9b ignore: update download stats 2025-10-02 2025-10-02 12:04:06 +00:00
opencode dc159ecfc1 release: v0.14.0 2025-10-02 05:33:58 +00:00
Dax Raad 802334c3e9 ci: fix 2025-10-02 01:25:27 -04:00
Dax Raad 5e390b15d3 ci: publsih 2025-10-02 01:24:33 -04:00
Dax Raad 3195f8eb80 ci: test 2025-10-02 01:19:53 -04:00
Dax Raad 0ee8e1b05f ci: install opencode 2025-10-02 01:19:53 -04:00
Dax Raad 5dfb03ea0a ci: fix 2025-10-02 05:18:01 +00:00
opencode 93b3607250 release: v0.13.9 2025-10-02 05:18:01 +00:00
Dax Raad 9f73e3c9b6 ci: sync 2025-10-02 01:09:58 -04:00
Dax Raad 43c7b7a765 ci: stuff 2025-10-02 01:08:29 -04:00
Aiden Cline b842cc5e7c docs: fix install fmt (#2914) 2025-10-01 23:32:39 -05:00
Aiden Cline 8fb3c65eee docs: fix more links (#2913) 2025-10-01 23:22:17 -05:00
Sandip Wane cf363fe67a chore: rm empty try-catch block (#2769)
Co-authored-by: Sandip Wane <sandip.wane@cloudhedge.io>
2025-10-01 23:21:57 -05:00
Dax Raad d0008024f1 core: fix file search limit handling and ensure File module initialization 2025-10-02 00:18:18 -04:00
opencode f7c5281d47 release: v0.13.8 2025-10-02 04:16:48 +00:00
Dax Raad 8cd3078df4 core: improve file search reliability and performance 2025-10-02 00:09:02 -04:00
Aiden Cline 628a15d382 docs: fix (#2910) 2025-10-01 22:20:35 -05:00
Jay V ab51603d1b docs: hide email signup 2025-10-01 20:16:02 -04:00
Jay V f3d895f652 docs: testimonials 2025-10-01 19:57:17 -04:00
Dax Raad e220fa841a wip: lander 2025-10-01 19:54:58 -04:00
Jay V 30feacb006 docs: fix link 2025-10-01 19:52:31 -04:00
Dax Raad cb4c962d71 wip: lander 2025-10-01 19:49:00 -04:00
Dax Raad c3416825d8 ignore: fix 2025-10-01 19:40:56 -04:00
Dax dcce094f1e Zen lander (#2907)
Co-authored-by: David Hill <iamdavidhill@gmail.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
Co-authored-by: Jay V <air@live.ca>
2025-10-01 19:38:15 -04:00
Frank 3cdb7b2d6f wip: zen 2025-10-01 19:34:37 -04:00
Frank 69d504f4be doc: update zen pricing 2025-10-01 17:48:12 -04:00
GitHub Action f84e912391 ignore: update download stats 2025-10-01 2025-10-01 12:04:26 +00:00
Dax Raad 7810be5885 core: remove redundant patch integration test
The integration test was duplicating coverage already provided by the comprehensive
patch namespace tests. Users benefit from faster test runs without losing any
coverage of patch functionality. The remaining tests provide complete validation
of patch parsing, application, and tool integration.
2025-10-01 06:49:19 -04:00
Dax Raad c338173f7a core: make patch tool more reliable and consistent with other editing tools
The patch tool now works seamlessly alongside other file editing tools with improved
error handling and a more intuitive permission system. Users will experience:

- More reliable patch application with better error messages
- Consistent permission prompts that match other editing tools
- Smoother integration when applying complex multi-file changes
- Better feedback on what changes are being made before applying patches

This refactoring leverages the robust patch parsing engine while making the tool
feel native to the opencode workflow, reducing friction when making bulk changes
to your codebase.
2025-10-01 06:45:43 -04:00
opencode 71da1ec86a release: v0.13.7 2025-10-01 09:13:16 +00:00
Dax Raad 282c1e1d9d codex should not have reasoning effort 2025-10-01 05:06:37 -04:00
Dax Raad ee4e201045 core: prevent file deletion when reverting changes to existing files 2025-10-01 05:06:37 -04:00
opencode 2e1ed2fc65 release: v0.13.6 2025-10-01 07:44:18 +00:00
Dax Raad 8ad4813f46 overhaul file search and support @ mentioning directories 2025-10-01 03:37:01 -04:00
Aiden Cline af71bc7084 fix: adjust model dialog to handle same model id but different names (#2881) 2025-09-30 11:43:57 -05:00
GitHub Action c59928a840 ignore: update download stats 2025-09-30 2025-09-30 12:04:27 +00:00
Dax Raad a82a7f8bd9 update anthropic system prompts 2025-09-30 04:41:36 -04:00
Aiden Cline 64b3b456ff fix: tui stuck saying generating... even when it is done (#2872) 2025-09-29 23:55:47 -05:00
Aiden Cline 539d438d97 fix: worktree cmd (#2870) 2025-09-29 22:21:54 -05:00
Frank abbae010da wip: zen 2025-09-29 14:17:53 -04:00
opencode 6700d00543 release: v0.13.5 2025-09-29 14:12:58 +00:00
Aiden Cline 08b8ff2671 fix: bash regex (#2858) 2025-09-29 08:51:46 -05:00
GitHub Action 4e21bfce23 ignore: update download stats 2025-09-29 2025-09-29 12:04:29 +00:00
Giuseppe Rota edf274ff9d docs: document model id behavior (#2856) 2025-09-29 06:52:26 -05:00
opencode 83484d52de release: v0.13.4 2025-09-29 05:53:07 +00:00
Dax Raad 370a9572ed ci: fix homebrew 2025-09-29 01:40:27 -04:00
Dax Raad 3f2da89007 release: v0.13.2 2025-09-29 01:38:53 -04:00
Dax Raad 112a3685fb ci: publish 2025-09-29 01:24:35 -04:00
Dax Raad 3cb9218b98 ci: fix 2025-09-29 01:22:39 -04:00
Dax Raad 3542e6b051 ci: fix 2025-09-29 01:06:12 -04:00
Aiden Cline 2a41938ad8 ignore: go mod tidy (#2851) 2025-09-28 23:05:56 -05:00
Frank 75d871fd49 wip: zen 2025-09-28 19:55:40 -04:00
Frank 12740d1d17 wip: zen 2025-09-28 19:55:40 -04:00
Dax Raad adab4bcddf core: improve webfetch tool content negotiation and format handling 2025-09-28 17:57:50 -04:00
Frank f6406c2a60 wip: zen 2025-09-28 12:49:28 -04:00
GitHub Action 4be998a4f5 ignore: update download stats 2025-09-28 2025-09-28 12:03:57 +00:00
Frank 27c0709de8 wip: zen 2025-09-27 21:53:50 -04:00
Aiden Cline e1e0b58991 respect model id in opencode.json (#2833) 2025-09-27 17:09:21 -05:00
Aurélien Tollard e98b50d197 Fix: Set OPENCODE_CALLER env in a more portable way for vscode extension (#2230) 2025-09-27 10:36:24 -04:00
Frank be0a55d146 wip: zen 2025-09-27 10:19:58 -04:00
Frank c012dbc85a wip: zen 2025-09-27 10:05:19 -04:00
GitHub Action 3913040ea1 ignore: update download stats 2025-09-27 2025-09-27 12:04:01 +00:00
Dax Raad 6de9e29979 ci: tweaks 2025-09-27 04:12:55 -04:00
Dax Raad f5218e2577 sync 2025-09-27 04:10:56 -04:00
Arjun Singh 72295261e6 Include step-start and step-finish for cost tracking (#2810) 2025-09-27 03:45:36 -04:00
Dax Raad 8eee14c19c ci: wtf 2025-09-27 03:20:08 -04:00
Dax Raad 96e5bfdd4f ci: fix 2025-09-27 03:19:34 -04:00
Dax Raad 139113f9c3 core: configure turbo to avoid building opencode for web tests 2025-09-27 03:19:29 -04:00
Dax Raad d69068dabd ci: fix 2025-09-27 03:18:12 -04:00
Dax Raad 3a20194084 ci: test 2025-09-27 03:17:22 -04:00
Dax Raad 1908ed7a49 core: fix config test by removing model field expectation 2025-09-27 03:10:01 -04:00
Dax Raad 29541c54ce ci: fix git identity in test workflow 2025-09-27 03:07:37 -04:00
Dax Raad fc1cf01a26 ci: improve test coverage 2025-09-27 03:04:42 -04:00
Dax Raad 56d0e8acbf sync 2025-09-27 02:53:20 -04:00
Dax Raad ffe37d6d98 ci: format 2025-09-27 02:22:03 -04:00
Dax Raad 4855f3133d wip: bun test improvements 2025-09-27 02:17:08 -04:00
Dax Raad 7eba61cd25 ci: give up 2025-09-27 01:51:54 -04:00
Dax Raad b24319fe80 ci: add regional hostname 2025-09-27 01:28:11 -04:00
Dax Raad 7bb0ff2fc6 fix zen cookie 2025-09-27 01:12:25 -04:00
opencode cec0844cd6 release: v0.12.1 2025-09-26 21:18:45 +00:00
Frank 27bbecd0e1 zen: accept tax id 2025-09-26 17:10:45 -04:00
Timo Clasen 76f06b08cb fix(tool): follow symlinks when looking for tools (#2809) 2025-09-26 16:01:08 -05:00
opencode 5c58cd7b3f release: v0.12.0 2025-09-26 20:55:00 +00:00
Frank 95313b660e zen: model management helper 2025-09-26 15:18:24 -04:00
sonsulee fb9ca7162c docs: add TUI configuration options and examples (#2212)
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Jay <air@live.ca>
2025-09-26 13:09:46 -04:00
Yihui Khuu cfb031681e fix: config loading not considering symlinks (#2800) 2025-09-26 09:46:49 -05:00
GitHub Action a053b93ac8 ignore: update download stats 2025-09-26 2025-09-26 12:04:26 +00:00
Dax Raad 05767142ca ci: fix deploy 2025-09-26 11:57:49 +00:00
opencode f669e88e2a release: v0.11.8 2025-09-26 11:57:49 +00:00
Dax Raad eb38e0b40e disable aggressive config validation 2025-09-26 07:51:53 -04:00
opencode 76fc05e0e3 release: v0.11.7 2025-09-26 11:19:15 +00:00
Adam d9bd7de976 fix(tui): cursor position 2025-09-26 06:13:23 -05:00
Dax Raad 0e65537c97 core: add themes to allowed config directories 2025-09-26 06:40:41 -04:00
opencode 48d6bbc90c release: v0.11.6 2025-09-26 09:52:23 +00:00
Dax Raad 180f493ac0 tui: fix cursor position offset in home screen 2025-09-26 05:46:16 -04:00
opencode ca1622aef9 release: v0.11.5 2025-09-26 09:40:16 +00:00
Dax Raad 766ca1b32a fix erroring on custom tool folder 2025-09-26 05:33:38 -04:00
Dax Raad 9afee84247 validate config directory 2025-09-26 03:23:25 -04:00
Dax Raad 6b4c0076e8 core: add config update endpoint and functionality 2025-09-26 02:37:19 -04:00
Dax Raad e314b5a3b8 ci: ignore 2025-09-26 02:01:19 -04:00
Dax Raad ef37a4fe55 ci: setup husky pre-push hook to run typecheck 2025-09-26 02:01:19 -04:00
opencode f4b2b4934b release: v0.11.4 2025-09-26 05:56:03 +00:00
Dax Raad 8c44ba84cd tui: fix opencode logo spacing in home view 2025-09-26 01:40:32 -04:00
Dax Raad fd44632de8 turn reasoning summaries back on by default for zen 2025-09-26 01:37:44 -04:00
Dax Raad 50fd5fc640 core: enable reasoning.encrypted_content and reasoningSummary for opencode provider\ntui: adjust editorY position calculation 2025-09-26 01:27:53 -04:00
Aiden Cline 428b443795 fix: openai reasoning issue (#2780) 2025-09-26 01:23:30 -04:00
Aiden Cline d467db43b0 tweak: include usage by default for openai compatible providers (#2788) 2025-09-25 21:06:58 -05:00
David Hill d11ad9226a Update opencode logo 2025-09-25 23:22:50 +01:00
Frank fc262ad255 zen: track tps 2025-09-25 17:56:41 -04:00
Frank cfbe5fd908 zen: check balance on enable billing 2025-09-25 17:47:50 -04:00
Frank 42263fe0c5 zen: fix parsing cache write tokens 2025-09-25 17:02:23 -04:00
Adam 22b70d6510 wip: better desktop file status state and timeline 2025-09-25 14:41:31 -05:00
Adam 0d05ab5be7 wip: watch select .git files in watcher 2025-09-25 14:41:31 -05:00
GitHub Action fd322e28e8 ignore: update download stats 2025-09-25 2025-09-25 12:04:27 +00:00
Frank f4e79269fc zen: billing 2025-09-24 21:26:04 -04:00
GitHub Action 0db03c4b4e chore: format code 2025-09-24 23:10:17 +00:00
Frank 91ac1783d9 zen: billing 2025-09-24 19:09:28 -04:00
Adam d8089f9bbd fix(app): changes view 2025-09-24 15:49:40 -05:00
Adam f66689254e feat(app): changes view 2025-09-24 15:46:33 -05:00
Adam a3eea40128 fix: select dialog hover 2025-09-24 15:08:58 -05:00
Adam 595e3d48f7 feat: fuzzy file open 2025-09-24 12:40:54 -05:00
Filip 89cb0f9a01 feat(app): added command palette (#2630)
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
2025-09-24 11:05:15 -05:00
GitHub Action c394c696b2 ignore: update download stats 2025-09-24 2025-09-24 12:04:37 +00:00
opencode e28486d5cd release: v0.11.3 2025-09-24 03:43:46 +00:00
Dax Raad df7abea5fe update copilot auth 2025-09-23 23:36:27 -04:00
GitHub Action 6789cac430 chore: format code 2025-09-23 21:59:09 +00:00
Frank 4cd86236a1 zen: refund 2025-09-23 17:58:27 -04:00
GitHub Action e41a9b47fe chore: format code 2025-09-23 21:37:20 +00:00
Jay V 8e0e09e163 docs: add codex to zen 2025-09-23 17:36:40 -04:00
Jay V f0eb23c4d5 docs: edit 2025-09-23 17:34:52 -04:00
Jay V 7590ee788e docs: edits 2025-09-23 17:34:52 -04:00
Jay V 3bb2db5de3 docs: edit 2025-09-23 17:34:52 -04:00
Siddhant Choudhary e83e6c595c feat: output-format flag to stream json output (#2471)
Co-authored-by: Siddhant Choudhary <sid@treaps.com>
Co-authored-by: rekram1-node <aidenpcline@gmail.com>
2025-09-23 16:19:32 -05:00
opencode fde0faf6d8 release: v0.11.2 2025-09-23 21:03:59 +00:00
Dax Raad f8ba5e9572 fix codex errors 2025-09-23 16:56:40 -04:00
Dax Raad ef1f2943a5 fix: remove file existence check from LSP debug and format storage code 2025-09-23 16:49:57 -04:00
Alain Schlesser f9b415839b ignore: more reliable install script, handle non prettified json responses (#2745)
Co-authored-by: rekram1-node <aidenpcline@gmail.com>
2025-09-23 14:58:04 -05:00
Frank 815d6401c8 wip: remove 2025-09-23 14:21:39 -04:00
Frank 7231c6848e zen: update logged endpoints 2025-09-23 14:21:39 -04:00
Adam 3415dfda3b fix: open text files 2025-09-23 12:43:24 -05:00
David Hill f1f980c642 Merge branch 'dev' of https://github.com/sst/opencode into dev 2025-09-23 18:36:27 +01:00
David Hill 7c9616bf25 ignore: Copy update 2025-09-23 18:35:42 +01:00
David Hill 8be86f4b38 ignore: Footer update 2025-09-23 18:30:06 +01:00
Adam af202caf23 fix: exclude generated css file 2025-09-23 12:08:49 -05:00
GitHub Action 93cc1f11c9 chore: format code 2025-09-23 17:05:32 +00:00
Adam e20d12de23 fix: theme css format 2025-09-23 12:04:50 -05:00
GitHub Action ce87bc3592 chore: format code 2025-09-23 16:41:57 +00:00
Adam 0425aa413a wip: desktop file updates 2025-09-23 11:41:15 -05:00
Grégoire Paris c83dbb56dd docs: fix grammar mistake in lsp docs(#2744) 2025-09-23 08:12:46 -05:00
GitHub Action 52f88c0a88 ignore: update download stats 2025-09-23 2025-09-23 12:04:24 +00:00
Aiden Cline ed8369cd6b ignore: lsp debug file check (#2743) 2025-09-22 22:16:03 -05:00
Aiden Cline 0bff3e9c21 docs: github copilot model enable note (#2741) 2025-09-22 21:29:16 -05:00
Aiden Cline 5af4c7e066 fix: lsp extension undefined handle (#2739) 2025-09-22 21:14:55 -05:00
Joseph Hanson cf44950c18 docs: fix permissions link (#2738) 2025-09-22 20:28:54 -05:00
Adam Hosker 7232c702d6 docs: Add editor setup tip (#2638) 2025-09-22 16:01:27 -04:00
GitHub Action acd8819aca chore: format code 2025-09-22 19:59:29 +00:00
Jay V c55af547cf docs: edit tools doc 2025-09-22 15:58:48 -04:00
Aiden Cline e1265fb91f fix: enforce extensions requirement for custom lsp servers (#2734) 2025-09-22 11:45:47 -05:00
GitHub Action 98d111d551 ignore: update download stats 2025-09-22 2025-09-22 12:04:25 +00:00
Dax Raad b93a997ebd ci: bump 2025-09-22 01:47:28 -04:00
Dax Raad 7a32db138c ci: snapshot builds 2025-09-22 01:45:23 -04:00
iwauo dbfb523fd7 feat: add Java LSP server support (#2547)
Co-authored-by: rekram1-node <aidenpcline@gmail.com>
2025-09-21 23:55:15 -05:00
Aiden Cline 112cf7b59d fix: lsp spawn logic (#2723) 2025-09-21 11:25:47 -05:00
GitHub Action 375d7e1369 ignore: update download stats 2025-09-21 2025-09-21 12:04:06 +00:00
opencode 25d5454434 release: v0.11.1 2025-09-21 08:51:11 +00:00
Dax Raad a38b99b21c core: filter models without keys in opencode provider 2025-09-21 04:43:32 -04:00
opencode eb57274696 release: v0.11.0 2025-09-21 08:04:21 +00:00
GitHub Action 4f0f4f94b7 chore: format code 2025-09-21 04:23:40 +00:00
Frank 069baca7db wip: zen 2025-09-21 00:23:05 -04:00
Frank b1a72b006d wip: zen 2025-09-21 00:06:50 -04:00
Aiden Cline 1851bec769 ignore: lsp log (#2715) 2025-09-20 23:02:06 -05:00
opencode 31b4157b82 release: v0.10.4 2025-09-20 23:00:32 +00:00
Dax Raad da362e9460 turn back on compaction summaries 2025-09-20 18:52:30 -04:00
Osinachi Okpara 22e4625fb3 Enhance theme documentation with links (#2707)
Co-authored-by: rekram1-node <aidenpcline@gmail.com>
2025-09-20 11:25:12 -05:00
GitHub Action f68cbb7c6a ignore: update download stats 2025-09-20 2025-09-20 12:04:03 +00:00
Aiden Cline ca0b378090 fix: no payment method (#2706) 2025-09-20 04:15:44 -04:00
Jay V 59d536f010 docs: zen 2025-09-19 20:13:57 -04:00
opencode 227a797484 release: v0.10.3 2025-09-19 21:22:00 +00:00
GitHub Action 0903ca2e28 chore: format code 2025-09-19 18:18:56 +00:00
Frank 07c1d2492f zen: data share 2025-09-19 14:16:53 -04:00
Aiden Cline ad2b200f16 docs: fix bad docs (#2691) 2025-09-19 12:40:40 -05:00
GitHub Action ab6fef3e77 chore: format code 2025-09-19 17:29:44 +00:00
Jay V 0343083ab8 ignore: zen 2025-09-19 13:29:04 -04:00
Adam 86fa58ea41 wip: desktop progress 2025-09-19 10:53:49 -05:00
opencode 2fa657e875 release: v0.10.2 2025-09-19 15:18:11 +00:00
Adam b4cc733715 fix: remove grok msg 2025-09-19 09:59:47 -05:00
Adam 4755dba755 wip: desktop cleanup 2025-09-19 09:55:46 -05:00
Adam 24d56ba2ae wip: desktop cleanup 2025-09-19 09:42:39 -05:00
Adam a0a8bb7e0a wip: desktop visual tweaks 2025-09-19 09:38:10 -05:00
neriousy 8cf57b8130 chore: opencode favicon 2025-09-19 09:33:28 -05:00
GitHub Action 25c333f73a chore: format code 2025-09-19 14:32:55 +00:00
Adam 7bb5a17dcb chore: normalize theme hex 2025-09-19 09:32:19 -05:00
GitHub Action 0ae668a8ff chore: format code 2025-09-19 12:19:13 +00:00
Adam e175335790 wip: desktop work 2025-09-19 07:18:39 -05:00
Adam e6cb3f9f3b wip: desktop work 2025-09-19 07:18:39 -05:00
Adam 0dcc35c647 wip: desktop work 2025-09-19 07:18:39 -05:00
Adam 84ccad86dc wip: desktop starting state 2025-09-19 07:18:38 -05:00
GitHub Action abdf169190 ignore: update download stats 2025-09-19 2025-09-19 12:04:16 +00:00
GitHub Action 9e6234396b chore: format code 2025-09-19 10:14:30 +00:00
Dax Raad 3753c1e9cf ignore: fix 2025-09-19 06:13:45 -04:00
Dax Raad f1c1c942d1 ci: stuff 2025-09-19 06:10:27 -04:00
GitHub Action 26b0566811 chore: format code 2025-09-19 09:29:24 +00:00
Dax Raad 96caaf208e ci: stuff 2025-09-19 05:28:46 -04:00
GitHub Action b6cebc6b61 chore: format code 2025-09-19 09:12:20 +00:00
Dax Raad 41065c3353 ignore: rework bootstrap so server lazy starts it 2025-09-19 05:11:29 -04:00
opencode ec562bb9d8 release: v0.10.1 2025-09-19 05:15:32 +00:00
Dax Raad ce124c7a87 fix task tool ui disappearing once done 2025-09-19 01:09:52 -04:00
Aiden Cline ff95b17fc3 docs: subtask config option (#2682) 2025-09-18 17:52:23 -05:00
Aiden Cline 668c823ea8 docs: rm incorrect lsp mention (#2677) 2025-09-18 16:08:50 -05:00
1583 changed files with 40265 additions and 11846 deletions
+20
View File
@@ -0,0 +1,20 @@
name: "Setup Bun"
description: "Setup Bun with caching and install dependencies"
runs:
using: "composite"
steps:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Cache ~/.bun
id: cache-bun
uses: actions/cache@v4
with:
path: ~/.bun
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lockb', 'bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: bun install
shell: bash
+1 -5
View File
@@ -15,11 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.21
- run: bun install
- uses: ./.github/actions/setup-bun
- run: bun sst deploy --stage=${{ github.ref_name }}
env:
+1 -4
View File
@@ -20,13 +20,10 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.21
uses: ./.github/actions/setup-bun
- name: run
run: |
bun install
./script/format.ts
env:
CI: true
+4 -2
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/kimi-k2
+1 -4
View File
@@ -19,16 +19,13 @@ jobs:
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.21
- uses: ./.github/actions/setup-bun
- run: git fetch --force --tags
- run: bun install -g @vscode/vsce
- name: Publish
run: |
bun install
./script/publish
working-directory: ./sdks/vscode
env:
+6 -14
View File
@@ -35,18 +35,7 @@ jobs:
cache: true
cache-dependency-path: go.sum
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.21
- name: Cache ~/.bun
id: cache-bun
uses: actions/cache@v3
with:
path: ~/.bun
key: ${{ runner.os }}-bun-1-2-21-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1-2-21-
- uses: ./.github/actions/setup-bun
- name: Install makepkg
run: |
@@ -60,14 +49,17 @@ jobs:
git config --global user.email "opencode@sst.dev"
git config --global user.name "opencode"
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
- name: Install dependencies
run: bun install
- name: Install OpenCode
run: curl -fsSL https://opencode.ai/install | bash
- name: Publish
run: |
./script/publish.ts
env:
OPENCODE_BUMP: ${{ inputs.bump }}
OPENCODE_CHANNEL: latest
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }}
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+34
View File
@@ -0,0 +1,34 @@
name: snapshot
on:
push:
branches:
- dev
- opentui
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: git fetch --force --tags
- uses: actions/setup-go@v5
with:
go-version: ">=1.24.0"
cache: true
cache-dependency-path: go.sum
- uses: ./.github/actions/setup-bun
- name: Publish
run: |
./script/publish.ts
env:
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
+1 -3
View File
@@ -16,9 +16,7 @@ jobs:
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
uses: ./.github/actions/setup-bun
- name: Run stats script
run: bun script/stats.ts
+30
View File
@@ -0,0 +1,30 @@
name: test
on:
push:
branches-ignore:
- production
pull_request:
branches-ignore:
- production
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: run
run: |
git config --global user.email "bot@opencode.ai"
git config --global user.name "opencode"
bun turbo typecheck
bun turbo test
env:
CI: true
+1 -6
View File
@@ -13,12 +13,7 @@ jobs:
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.21
- name: Install dependencies
run: bun install
uses: ./.github/actions/setup-bun
- name: Run typecheck
run: bun typecheck
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
bun run typecheck
+1 -1
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
+11
View File
@@ -1,3 +1,7 @@
---
description: Git commit and push
---
commit and push
make sure it includes a prefix like
@@ -7,3 +11,10 @@ core:
ci:
ignore:
wip:
For anything in the packages/web use the docs: prefix.
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.
+5
View File
@@ -0,0 +1,5 @@
---
description: Spellcheck all markdown file changes
---
Look at all the unstaged changes to markdown (.md, .mdx) files, pull out the lines that have changed, and check for spelling and grammar errors.
-11
View File
@@ -1,11 +0,0 @@
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "call this tool when you want to give up",
args: {
message: tool.schema.string().describe("give up message"),
},
async execute(args) {
return "Hey fuck you!"
},
})
+31
View File
@@ -14,3 +14,34 @@
## Debugging
- To test opencode in the `packages/opencode` directory you can run `bun dev`
## Tool Calling
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE. Here is an example illustrating how to execute 3 parallel file reads in this chat environnement:
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"
}
}
]
}
}
+14 -13
View File
@@ -1,20 +1,20 @@
<p align="center">
<a href="https://opencode.ai">
<picture>
<source srcset="packages/web/src/assets/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/web/src/assets/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
<img src="packages/web/src/assets/logo-ornate-light.svg" alt="opencode logo">
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
</picture>
</a>
</p>
<p align="center">AI coding agent, built for the terminal.</p>
<p align="center">The AI coding agent built for the terminal.</p>
<p align="center">
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
<a href="https://github.com/sst/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/sst/opencode/publish.yml?style=flat-square&branch=dev" /></a>
</p>
[![opencode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
---
@@ -50,11 +50,11 @@ XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
### Documentation
For more info on how to configure opencode [**head over to our docs**](https://opencode.ai/docs).
For more info on how to configure OpenCode [**head over to our docs**](https://opencode.ai/docs).
### Contributing
opencode is an opinionated tool so any fundamental feature needs to go through a
OpenCode is an opinionated tool so any fundamental feature needs to go through a
design process with the core team.
> [!IMPORTANT]
@@ -74,9 +74,9 @@ 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.
To run OpenCode locally you need.
- Bun
- Bun 1.3 or higher
- Golang 1.24.x
And run.
@@ -88,7 +88,7 @@ $ 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.
**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.
### FAQ
@@ -97,9 +97,10 @@ $ bun dev
It's very similar to Claude Code in terms of capability. Here are the key differences:
- 100% open source
- Not coupled to any provider. Although Anthropic is recommended, opencode can be used with OpenAI, Google or even local models. As models evolve the gaps between them will close and pricing will drop so being provider-agnostic is important.
- A focus on TUI. opencode is built by neovim users and the creators of [terminal.shop](https://terminal.shop); we are going to push the limits of what's possible in the terminal.
- A client/server architecture. This for example can allow opencode to run on your computer, while you can drive it remotely from a mobile app. Meaning that the TUI frontend is just one of the possible clients.
- Not coupled to any provider. Although Anthropic is recommended, OpenCode can be used with OpenAI, Google or even local models. As models evolve the gaps between them will close and pricing will drop so being provider-agnostic is important.
- Out of the box LSP support
- A focus on TUI. OpenCode is built by neovim users and the creators of [terminal.shop](https://terminal.shop); we are going to push the limits of what's possible in the terminal.
- A client/server architecture. This for example can allow OpenCode to run on your computer, while you can drive it remotely from a mobile app. Meaning that the TUI frontend is just one of the possible clients.
#### What's the other repo?
+110 -83
View File
@@ -1,85 +1,112 @@
# 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) |
| 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) |
+1456 -782
View File
File diff suppressed because it is too large Load Diff
+4 -18
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 }}
+1 -1
View File
@@ -14,6 +14,6 @@
"@actions/github": "6.0.1",
"@octokit/graphql": "9.0.1",
"@octokit/rest": "22.0.0",
"@opencode-ai/sdk": "0.5.4"
"@opencode-ai/sdk": "workspace:*"
}
}
+1 -1
View File
@@ -6,4 +6,4 @@
/// <reference path="../sst-env.d.ts" />
import "sst"
export {}
export {}
+1
View File
@@ -2,6 +2,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 bucket = new sst.cloudflare.Bucket("Bucket")
export const api = new sst.cloudflare.Worker("Api", {
+11 -15
View File
@@ -1,5 +1,5 @@
import { WebhookEndpoint } from "pulumi-stripe"
import { domain } from "./stage"
import { EMAILOCTOPUS_API_KEY } from "./app"
////////////////
// DATABASE
@@ -68,13 +68,14 @@ export const auth = new sst.cloudflare.Worker("AuthApi", {
// GATEWAY
////////////////
export const stripeWebhook = new WebhookEndpoint("StripeWebhookEndpoint", {
export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint", {
url: $interpolate`https://${domain}/stripe/webhook`,
enabledEvents: [
"checkout.session.async_payment_failed",
"checkout.session.async_payment_succeeded",
"checkout.session.completed",
"checkout.session.expired",
"charge.refunded",
"customer.created",
"customer.deleted",
"customer.updated",
@@ -93,17 +94,10 @@ export const stripeWebhook = new WebhookEndpoint("StripeWebhookEndpoint", {
"customer.subscription.resumed",
"customer.subscription.trial_will_end",
"customer.subscription.updated",
"customer.tax_id.created",
"customer.tax_id.deleted",
"customer.tax_id.updated",
],
})
const ANTHROPIC_API_KEY = new sst.Secret("ANTHROPIC_API_KEY")
const OPENAI_API_KEY = new sst.Secret("OPENAI_API_KEY")
const XAI_API_KEY = new sst.Secret("XAI_API_KEY")
const BASETEN_API_KEY = new sst.Secret("BASETEN_API_KEY")
const FIREWORKS_API_KEY = new sst.Secret("FIREWORKS_API_KEY")
const ZEN_MODELS = new sst.Secret("ZEN_MODELS")
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!) },
@@ -116,6 +110,9 @@ const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
// CONSOLE
////////////////
const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
let logProcessor
if ($app.stage === "production" || $app.stage === "frank") {
const HONEYCOMB_API_KEY = new sst.Secret("HONEYCOMB_API_KEY")
@@ -133,11 +130,10 @@ new sst.cloudflare.x.SolidStart("Console", {
AUTH_API_URL,
STRIPE_WEBHOOK_SECRET,
STRIPE_SECRET_KEY,
ANTHROPIC_API_KEY,
OPENAI_API_KEY,
XAI_API_KEY,
BASETEN_API_KEY,
FIREWORKS_API_KEY,
ZEN_MODELS,
EMAILOCTOPUS_API_KEY,
AWS_SES_ACCESS_KEY_ID,
AWS_SES_SECRET_ACCESS_KEY,
],
environment: {
//VITE_DOCS_URL: web.url.apply((url) => url!),
+2 -2
View File
@@ -2,9 +2,9 @@ import { domain } from "./stage"
new sst.cloudflare.StaticSite("Desktop", {
domain: "desktop." + domain,
path: "packages/app",
path: "packages/desktop",
build: {
command: "bun run build",
command: "bun turbo build",
output: "./dist",
},
})
+8
View File
@@ -3,3 +3,11 @@ export const domain = (() => {
if ($app.stage === "dev") return "dev.opencode.ai"
return `${$app.stage}.dev.opencode.ai`
})()
export const zoneID = "430ba34c138cfb5360826c4909f99be8"
new cloudflare.RegionalHostname("RegionalHostname", {
hostname: domain,
regionKey: "us",
zoneId: zoneID,
})
+1 -1
View File
@@ -46,7 +46,7 @@ mkdir -p "$INSTALL_DIR"
if [ -z "$requested_version" ]; then
url="https://github.com/sst/opencode/releases/latest/download/$filename"
specific_version=$(curl -s https://api.github.com/repos/sst/opencode/releases/latest | awk -F'"' '/"tag_name": "/ {gsub(/^v/, "", $4); print $4}')
specific_version=$(curl -s https://api.github.com/repos/sst/opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
if [[ $? -ne 0 || -z "$specific_version" ]]; then
echo -e "${RED}Failed to fetch version information${NC}"
+30 -11
View File
@@ -3,40 +3,55 @@
"name": "opencode",
"private": true,
"type": "module",
"packageManager": "bun@1.2.21",
"packageManager": "bun@1.3.0",
"scripts": {
"dev": "bun run --conditions=development packages/opencode/src/index.ts",
"dev": "bun run packages/opencode/src/index.ts",
"typecheck": "bun turbo typecheck",
"generate": "(cd packages/sdk && ./js/script/generate.ts) && (cd packages/sdk/stainless && ./generate.ts)",
"postinstall": "./script/hooks"
"prepare": "husky"
},
"workspaces": {
"packages": [
"packages/*",
"packages/console/*",
"packages/sdk/js"
"packages/sdk/js",
"packages/slack"
],
"catalog": {
"@types/bun": "1.2.21",
"@types/bun": "1.3.0",
"@hono/zod-validator": "0.4.2",
"@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",
"diff": "8.0.2",
"ai": "5.0.8",
"hono": "4.7.10",
"fuzzysort": "3.1.0",
"luxon": "3.6.1",
"typescript": "5.8.2",
"@typescript/native-preview": "7.0.0-dev.20251014.1",
"zod": "4.1.8",
"remeda": "2.26.0",
"solid-js": "1.9.9"
"solid-js": "1.9.9",
"tailwindcss": "4.1.11",
"@tailwindcss/vite": "4.1.11",
"vite": "7.1.4",
"vite-plugin-solid": "2.11.8"
}
},
"dependencies": {
"pulumi-stripe": "0.0.24"
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"husky": "9.1.7",
"prettier": "3.6.2",
"sst": "3.17.13",
"sst": "3.17.19",
"turbo": "2.5.6"
},
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/script": "workspace:*"
},
"repository": {
"type": "git",
"url": "https://github.com/sst/opencode"
@@ -56,5 +71,9 @@
],
"patchedDependencies": {
"@solidjs/start@1.1.7": "patches/@solidjs%2Fstart@1.1.7.patch"
},
"overrides": {
"@types/bun": "catalog:",
"@types/node": "catalog:"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because it is too large Load Diff
-601
View File
@@ -1,601 +0,0 @@
import { transformerNotationDiff } from "@shikijs/transformers"
import { marked } from "marked"
import markedShiki from "marked-shiki"
import { codeToHtml } from "shiki"
import { createResource } from "solid-js"
const markedWithShiki = marked.use(
markedShiki({
highlight(code, lang) {
return codeToHtml(code, {
// structure: "inline",
lang: lang || "text",
tabindex: false,
theme: {
colors: {
"actionBar.toggledBackground": "var(--theme-background-element)",
"activityBarBadge.background": "var(--theme-accent)",
"checkbox.border": "var(--theme-border)",
"editor.background": "transparent",
"editor.foreground": "var(--theme-text)",
"editor.inactiveSelectionBackground": "var(--theme-background-element)",
"editor.selectionHighlightBackground": "var(--theme-border-active)",
"editorIndentGuide.activeBackground1": "var(--theme-border-subtle)",
"editorIndentGuide.background1": "var(--theme-border-subtle)",
"input.placeholderForeground": "var(--theme-text-muted)",
"list.activeSelectionIconForeground": "var(--theme-text)",
"list.dropBackground": "var(--theme-background-element)",
"menu.background": "var(--theme-background-panel)",
"menu.border": "var(--theme-border)",
"menu.foreground": "var(--theme-text)",
"menu.selectionBackground": "var(--theme-primary)",
"menu.separatorBackground": "var(--theme-border)",
"ports.iconRunningProcessForeground": "var(--theme-success)",
"sideBarSectionHeader.background": "transparent",
"sideBarSectionHeader.border": "var(--theme-border-subtle)",
"sideBarTitle.foreground": "var(--theme-text-muted)",
"statusBarItem.remoteBackground": "var(--theme-success)",
"statusBarItem.remoteForeground": "var(--theme-text)",
"tab.lastPinnedBorder": "var(--theme-border-subtle)",
"tab.selectedBackground": "var(--theme-background-element)",
"tab.selectedForeground": "var(--theme-text-muted)",
"terminal.inactiveSelectionBackground": "var(--theme-background-element)",
"widget.border": "var(--theme-border)",
},
displayName: "opencode",
name: "opencode",
semanticHighlighting: true,
semanticTokenColors: {
customLiteral: "var(--theme-syntax-function)",
newOperator: "var(--theme-syntax-operator)",
numberLiteral: "var(--theme-syntax-number)",
stringLiteral: "var(--theme-syntax-string)",
},
tokenColors: [
{
scope: [
"meta.embedded",
"source.groovy.embedded",
"string meta.image.inline.markdown",
"variable.legacy.builtin.python",
],
settings: {
foreground: "var(--theme-text)",
},
},
{
scope: "emphasis",
settings: {
fontStyle: "italic",
},
},
{
scope: "strong",
settings: {
fontStyle: "bold",
},
},
{
scope: "header",
settings: {
foreground: "var(--theme-markdown-heading)",
},
},
{
scope: "comment",
settings: {
foreground: "var(--theme-syntax-comment)",
},
},
{
scope: "constant.language",
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: [
"constant.numeric",
"variable.other.enummember",
"keyword.operator.plus.exponent",
"keyword.operator.minus.exponent",
],
settings: {
foreground: "var(--theme-syntax-number)",
},
},
{
scope: "constant.regexp",
settings: {
foreground: "var(--theme-syntax-operator)",
},
},
{
scope: "entity.name.tag",
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: ["entity.name.tag.css", "entity.name.tag.less"],
settings: {
foreground: "var(--theme-syntax-operator)",
},
},
{
scope: "entity.other.attribute-name",
settings: {
foreground: "var(--theme-syntax-variable)",
},
},
{
scope: [
"entity.other.attribute-name.class.css",
"source.css entity.other.attribute-name.class",
"entity.other.attribute-name.id.css",
"entity.other.attribute-name.parent-selector.css",
"entity.other.attribute-name.parent.less",
"source.css entity.other.attribute-name.pseudo-class",
"entity.other.attribute-name.pseudo-element.css",
"source.css.less entity.other.attribute-name.id",
"entity.other.attribute-name.scss",
],
settings: {
foreground: "var(--theme-syntax-operator)",
},
},
{
scope: "invalid",
settings: {
foreground: "var(--theme-error)",
},
},
{
scope: "markup.underline",
settings: {
fontStyle: "underline",
},
},
{
scope: "markup.bold",
settings: {
fontStyle: "bold",
foreground: "var(--theme-markdown-strong)",
},
},
{
scope: "markup.heading",
settings: {
fontStyle: "bold",
foreground: "var(--theme-markdown-heading)",
},
},
{
scope: "markup.italic",
settings: {
fontStyle: "italic",
},
},
{
scope: "markup.strikethrough",
settings: {
fontStyle: "strikethrough",
},
},
{
scope: "markup.inserted",
settings: {
foreground: "var(--theme-diff-added)",
},
},
{
scope: "markup.deleted",
settings: {
foreground: "var(--theme-diff-removed)",
},
},
{
scope: "markup.changed",
settings: {
foreground: "var(--theme-diff-context)",
},
},
{
scope: "punctuation.definition.quote.begin.markdown",
settings: {
foreground: "var(--theme-markdown-block-quote)",
},
},
{
scope: "punctuation.definition.list.begin.markdown",
settings: {
foreground: "var(--theme-markdown-list-enumeration)",
},
},
{
scope: "markup.inline.raw",
settings: {
foreground: "var(--theme-markdown-code)",
},
},
{
scope: "punctuation.definition.tag",
settings: {
foreground: "var(--theme-syntax-punctuation)",
},
},
{
scope: ["meta.preprocessor", "entity.name.function.preprocessor"],
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: "meta.preprocessor.string",
settings: {
foreground: "var(--theme-syntax-string)",
},
},
{
scope: "meta.preprocessor.numeric",
settings: {
foreground: "var(--theme-syntax-number)",
},
},
{
scope: "meta.structure.dictionary.key.python",
settings: {
foreground: "var(--theme-syntax-variable)",
},
},
{
scope: "meta.diff.header",
settings: {
foreground: "var(--theme-diff-hunk-header)",
},
},
{
scope: "storage",
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: "storage.type",
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: ["storage.modifier", "keyword.operator.noexcept"],
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: ["string", "meta.embedded.assembly"],
settings: {
foreground: "var(--theme-syntax-string)",
},
},
{
scope: "string.tag",
settings: {
foreground: "var(--theme-syntax-string)",
},
},
{
scope: "string.value",
settings: {
foreground: "var(--theme-syntax-string)",
},
},
{
scope: "string.regexp",
settings: {
foreground: "var(--theme-syntax-operator)",
},
},
{
scope: [
"punctuation.definition.template-expression.begin",
"punctuation.definition.template-expression.end",
"punctuation.section.embedded",
],
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: ["meta.template.expression"],
settings: {
foreground: "var(--theme-text)",
},
},
{
scope: [
"support.type.vendored.property-name",
"support.type.property-name",
"source.css variable",
"source.coffee.embedded",
],
settings: {
foreground: "var(--theme-syntax-variable)",
},
},
{
scope: "keyword",
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: "keyword.control",
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: "keyword.operator",
settings: {
foreground: "var(--theme-syntax-operator)",
},
},
{
scope: [
"keyword.operator.new",
"keyword.operator.expression",
"keyword.operator.cast",
"keyword.operator.sizeof",
"keyword.operator.alignof",
"keyword.operator.typeid",
"keyword.operator.alignas",
"keyword.operator.instanceof",
"keyword.operator.logical.python",
"keyword.operator.wordlike",
],
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: "keyword.other.unit",
settings: {
foreground: "var(--theme-syntax-number)",
},
},
{
scope: ["punctuation.section.embedded.begin.php", "punctuation.section.embedded.end.php"],
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: "support.function.git-rebase",
settings: {
foreground: "var(--theme-syntax-variable)",
},
},
{
scope: "constant.sha.git-rebase",
settings: {
foreground: "var(--theme-syntax-number)",
},
},
{
scope: [
"storage.modifier.import.java",
"variable.language.wildcard.java",
"storage.modifier.package.java",
],
settings: {
foreground: "var(--theme-text)",
},
},
{
scope: "variable.language",
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: [
"entity.name.function",
"support.function",
"support.constant.handlebars",
"source.powershell variable.other.member",
"entity.name.operator.custom-literal",
],
settings: {
foreground: "var(--theme-syntax-function)",
},
},
{
scope: [
"support.class",
"support.type",
"entity.name.type",
"entity.name.namespace",
"entity.other.attribute",
"entity.name.scope-resolution",
"entity.name.class",
"storage.type.numeric.go",
"storage.type.byte.go",
"storage.type.boolean.go",
"storage.type.string.go",
"storage.type.uintptr.go",
"storage.type.error.go",
"storage.type.rune.go",
"storage.type.cs",
"storage.type.generic.cs",
"storage.type.modifier.cs",
"storage.type.variable.cs",
"storage.type.annotation.java",
"storage.type.generic.java",
"storage.type.java",
"storage.type.object.array.java",
"storage.type.primitive.array.java",
"storage.type.primitive.java",
"storage.type.token.java",
"storage.type.groovy",
"storage.type.annotation.groovy",
"storage.type.parameters.groovy",
"storage.type.generic.groovy",
"storage.type.object.array.groovy",
"storage.type.primitive.array.groovy",
"storage.type.primitive.groovy",
],
settings: {
foreground: "var(--theme-syntax-type)",
},
},
{
scope: [
"meta.type.cast.expr",
"meta.type.new.expr",
"support.constant.math",
"support.constant.dom",
"support.constant.json",
"entity.other.inherited-class",
"punctuation.separator.namespace.ruby",
],
settings: {
foreground: "var(--theme-syntax-type)",
},
},
{
scope: [
"keyword.control",
"source.cpp keyword.operator.new",
"keyword.operator.delete",
"keyword.other.using",
"keyword.other.directive.using",
"keyword.other.operator",
"entity.name.operator",
],
settings: {
foreground: "var(--theme-syntax-operator)",
},
},
{
scope: [
"variable",
"meta.definition.variable.name",
"support.variable",
"entity.name.variable",
"constant.other.placeholder",
],
settings: {
foreground: "var(--theme-syntax-variable)",
},
},
{
scope: ["variable.other.constant", "variable.other.enummember"],
settings: {
foreground: "var(--theme-syntax-variable)",
},
},
{
scope: ["meta.object-literal.key"],
settings: {
foreground: "var(--theme-syntax-variable)",
},
},
{
scope: [
"support.constant.property-value",
"support.constant.font-name",
"support.constant.media-type",
"support.constant.media",
"constant.other.color.rgb-value",
"constant.other.rgb-value",
"support.constant.color",
],
settings: {
foreground: "var(--theme-syntax-string)",
},
},
{
scope: [
"punctuation.definition.group.regexp",
"punctuation.definition.group.assertion.regexp",
"punctuation.definition.character-class.regexp",
"punctuation.character.set.begin.regexp",
"punctuation.character.set.end.regexp",
"keyword.operator.negation.regexp",
"support.other.parenthesis.regexp",
],
settings: {
foreground: "var(--theme-syntax-string)",
},
},
{
scope: [
"constant.character.character-class.regexp",
"constant.other.character-class.set.regexp",
"constant.other.character-class.regexp",
"constant.character.set.regexp",
],
settings: {
foreground: "var(--theme-syntax-operator)",
},
},
{
scope: ["keyword.operator.or.regexp", "keyword.control.anchor.regexp"],
settings: {
foreground: "var(--theme-syntax-operator)",
},
},
{
scope: "keyword.operator.quantifier.regexp",
settings: {
foreground: "var(--theme-syntax-operator)",
},
},
{
scope: ["constant.character", "constant.other.option"],
settings: {
foreground: "var(--theme-syntax-keyword)",
},
},
{
scope: "constant.character.escape",
settings: {
foreground: "var(--theme-syntax-operator)",
},
},
{
scope: "entity.name.label",
settings: {
foreground: "var(--theme-text-muted)",
},
},
],
type: "dark",
},
transformers: [transformerNotationDiff()],
})
},
}),
)
function strip(text: string): string {
const wrappedRe = /^\s*<([A-Za-z]\w*)>\s*([\s\S]*?)\s*<\/\1>\s*$/
const match = text.match(wrappedRe)
return match ? match[2] : text
}
export default function Markdown(props: { text: string; class?: string }) {
const [html] = createResource(
() => strip(props.text),
async (markdown) => {
return markedWithShiki.parse(markdown)
},
)
return (
<div
class={`min-w-0 max-w-full text-xs overflow-auto no-scrollbar prose ${props.class ?? ""}`}
innerHTML={html()}
/>
)
}
@@ -1,28 +0,0 @@
import { useSync, useLocal } from "@/context"
import { Button, Tooltip } from "@/ui"
import { VList } from "virtua/solid"
export default function SessionList() {
const sync = useSync()
const local = useLocal()
return (
<VList data={sync.data.session} class="p-2 no-scrollbar">
{(session) => (
<Tooltip placement="right" value={session.title} class="w-full min-w-0">
<Button
size="sm"
variant="ghost"
classList={{
"w-full min-w-0 py-1 text-left truncate justify-start text-text-muted text-xs": true,
"text-text!": local.session.active()?.id === session.id,
}}
onClick={() => local.session.setActive(session.id)}
>
<span class="truncate">{session.title}</span>
</Button>
</Tooltip>
)}
</VList>
)
}
-409
View File
@@ -1,409 +0,0 @@
import { createStore, produce, reconcile } from "solid-js/store"
import { batch, createContext, createEffect, createMemo, useContext, type ParentProps } from "solid-js"
import { useSync } from "./sync"
import { uniqueBy } from "remeda"
import type { FileContent, FileNode } from "@opencode-ai/sdk"
import { useSDK } from "./sdk"
export type LocalFile = FileNode &
Partial<{
loaded: boolean
pinned: boolean
expanded: boolean
content: FileContent
selection: { startLine: number; startChar: number; endLine: number; endChar: number }
scrollTop: number
view: "raw" | "diff-unified" | "diff-split"
folded: string[]
selectedChange: number
}>
export type TextSelection = LocalFile["selection"]
export type View = LocalFile["view"]
function init() {
const sdk = useSDK()
const sync = useSync()
const agents = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent"))
const agent = (() => {
const [store, setStore] = createStore<{
current: string
}>({
current: agents()[0].name,
})
return {
current() {
return agents().find((x) => x.name === store.current)!
},
move(direction: 1 | -1) {
let next = agents().findIndex((x) => x.name === store.current) + direction
if (next < 0) next = agents().length - 1
if (next >= agents().length) next = 0
const value = agents()[next]
setStore("current", value.name)
if (value.model)
model.set({
providerID: value.model.providerID,
modelID: value.model.modelID,
})
},
}
})()
const model = (() => {
const [store, setStore] = createStore<{
model: Record<
string,
{
providerID: string
modelID: string
}
>
recent: {
providerID: string
modelID: string
}[]
}>({
model: {},
recent: [],
})
const value = localStorage.getItem("model")
setStore("recent", JSON.parse(value ?? "[]"))
createEffect(() => {
localStorage.setItem("model", JSON.stringify(store.recent))
})
const fallback = createMemo(() => {
if (store.recent.length) return store.recent[0]
const provider = sync.data.provider[0]
const model = Object.values(provider.models)[0]
return {
providerID: provider.id,
modelID: model.id,
}
})
const current = createMemo(() => {
const a = agent.current()
return store.model[agent.current().name] ?? (a.model ? a.model : fallback())
})
return {
current,
recent() {
return store.recent
},
parsed: createMemo(() => {
const value = current()
const provider = sync.data.provider.find((x) => x.id === value.providerID)!
const model = provider.models[value.modelID]
return {
provider: provider.name ?? value.providerID,
model: model.name ?? value.modelID,
}
}),
set(model: { providerID: string; modelID: string }, options?: { recent?: boolean }) {
batch(() => {
setStore("model", agent.current().name, model)
if (options?.recent) {
const uniq = uniqueBy([model, ...store.recent], (x) => x.providerID + x.modelID)
if (uniq.length > 5) uniq.pop()
setStore("recent", uniq)
}
})
},
}
})()
const file = (() => {
const [store, setStore] = createStore<{
node: Record<string, LocalFile>
opened: string[]
active?: string
}>({
node: Object.fromEntries(sync.data.node.map((x) => [x.path, x])),
opened: [],
})
const active = createMemo(() => {
if (!store.active) return undefined
return store.node[store.active]
})
const opened = createMemo(() => store.opened.map((x) => store.node[x]))
const changes = createMemo(() => new Set(sync.data.changes.map((f) => f.path)))
const status = (path: string) => sync.data.changes.find((f) => f.path === path)
const changed = (path: string) => {
const set = changes()
if (set.has(path)) return true
for (const p of set) {
if (p.startsWith(path ? path + "/" : "")) return true
}
return false
}
const resetNode = (path: string) => {
setStore("node", path, {
loaded: undefined,
pinned: undefined,
content: undefined,
selection: undefined,
scrollTop: undefined,
folded: undefined,
view: undefined,
selectedChange: undefined,
})
}
const load = async (path: string) =>
sdk.file.read({ query: { path } }).then((x) => {
setStore(
"node",
path,
produce((draft) => {
draft.loaded = true
draft.content = x.data
}),
)
})
const open = async (path: string) => {
const relative = path.replace(sync.data.path.directory + "/", "")
if (!store.node[relative]) {
const parent = relative.split("/").slice(0, -1).join("/")
if (parent) {
await list(parent)
}
}
setStore("opened", (x) => {
if (x.includes(relative)) return x
return [
...opened()
.filter((x) => x.pinned)
.map((x) => x.path),
relative,
]
})
setStore("active", relative)
if (store.node[relative].loaded) return
return load(relative)
}
const list = async (path: string) => {
return sdk.file.list({ query: { path: path + "/" } }).then((x) => {
setStore(
"node",
produce((draft) => {
x.data!.forEach((node) => {
if (node.path in draft) return
draft[node.path] = node
})
}),
)
})
}
sdk.event.subscribe().then(async (events) => {
for await (const event of events.stream) {
switch (event.type) {
case "message.part.updated":
const part = event.properties.part
if (part.type === "tool" && part.state.status === "completed") {
switch (part.tool) {
case "read":
console.log("read", part.state.input)
break
case "edit":
const absolute = part.state.input["filePath"] as string
const path = absolute.replace(sync.data.path.directory + "/", "")
load(path)
break
default:
break
}
}
break
}
}
})
return {
active,
opened,
node: (path: string) => store.node[path],
update: (path: string, node: LocalFile) => setStore("node", path, reconcile(node)),
open,
load,
close(path: string) {
setStore("opened", (opened) => opened.filter((x) => x !== path))
if (store.active === path) {
const index = store.opened.findIndex((f) => f === path)
const previous = store.opened[Math.max(0, index - 1)]
setStore("active", previous)
}
resetNode(path)
},
expand(path: string) {
setStore("node", path, "expanded", true)
if (store.node[path].loaded) return
setStore("node", path, "loaded", true)
list(path)
},
collapse(path: string) {
setStore("node", path, "expanded", false)
},
select(path: string, selection: TextSelection | undefined) {
setStore("node", path, "selection", selection)
},
scroll(path: string, scrollTop: number) {
setStore("node", path, "scrollTop", scrollTop)
},
move(path: string, to: number) {
const index = store.opened.findIndex((f) => f === path)
if (index === -1) return
setStore(
"opened",
produce((opened) => {
opened.splice(to, 0, opened.splice(index, 1)[0])
}),
)
setStore("node", path, "pinned", true)
},
view(path: string): View {
const n = store.node[path]
return n && n.view ? n.view : "raw"
},
setView(path: string, view: View) {
setStore("node", path, "view", view)
},
unfold(path: string, key: string) {
setStore("node", path, "folded", (xs) => {
const a = xs ?? []
if (a.includes(key)) return a
return [...a, key]
})
},
fold(path: string, key: string) {
setStore("node", path, "folded", (xs) => (xs ?? []).filter((k) => k !== key))
},
folded(path: string) {
const n = store.node[path]
return n && n.folded ? n.folded : []
},
changeIndex(path: string) {
return store.node[path]?.selectedChange
},
setChangeIndex(path: string, index: number | undefined) {
setStore("node", path, "selectedChange", index)
},
changed,
status,
children(path: string) {
return Object.values(store.node).filter(
(x) =>
x.path.startsWith(path) &&
x.path !== path &&
!x.path.replace(new RegExp(`^${path + "/"}`), "").includes("/"),
)
},
}
})()
const layout = (() => {
const [store, setStore] = createStore<{
rightPane: boolean
leftWidth: number
rightWidth: number
}>({
rightPane: false,
leftWidth: 200, // Default 50 * 4px (w-50 = 12.5rem = 200px)
rightWidth: 320, // Default 80 * 4px (w-80 = 20rem = 320px)
})
const value = localStorage.getItem("layout")
if (value) {
const v = JSON.parse(value)
if (typeof v?.rightPane === "boolean") setStore("rightPane", v.rightPane)
if (typeof v?.leftWidth === "number") setStore("leftWidth", Math.max(150, Math.min(400, v.leftWidth)))
if (typeof v?.rightWidth === "number") setStore("rightWidth", Math.max(200, Math.min(500, v.rightWidth)))
}
createEffect(() => {
localStorage.setItem("layout", JSON.stringify(store))
})
return {
rightPane() {
return store.rightPane
},
leftWidth() {
return store.leftWidth
},
rightWidth() {
return store.rightWidth
},
toggleRightPane() {
setStore("rightPane", (x) => !x)
},
openRightPane() {
setStore("rightPane", true)
},
closeRightPane() {
setStore("rightPane", false)
},
setLeftWidth(width: number) {
setStore("leftWidth", Math.max(150, Math.min(400, width)))
},
setRightWidth(width: number) {
setStore("rightWidth", Math.max(200, Math.min(500, width)))
},
}
})()
const session = (() => {
const [store, setStore] = createStore<{
active?: string
}>({})
const active = createMemo(() => {
if (!store.active) return undefined
return sync.session.get(store.active)
})
return {
active,
setActive(sessionId: string | undefined) {
setStore("active", sessionId)
},
clearActive() {
setStore("active", undefined)
},
}
})()
const result = {
model,
agent,
file,
layout,
session,
}
return result
}
type LocalContext = ReturnType<typeof init>
const ctx = createContext<LocalContext>()
export function LocalProvider(props: ParentProps) {
const value = init()
return <ctx.Provider value={value}>{props.children}</ctx.Provider>
}
export function useLocal() {
const value = useContext(ctx)
if (!value) {
throw new Error("useLocal must be used within a LocalProvider")
}
return value
}
-165
View File
@@ -1,165 +0,0 @@
import type { Message, Agent, Provider, Session, Part, Config, Path, File, FileNode } from "@opencode-ai/sdk"
import { createStore, produce, reconcile } from "solid-js/store"
import { useSDK } from "./sdk"
import { createContext, Show, useContext, type ParentProps } from "solid-js"
import { Binary } from "@/utils/binary"
function init() {
const [store, setStore] = createStore<{
ready: boolean
provider: Provider[]
agent: Agent[]
config: Config
path: Path
session: Session[]
message: {
[sessionID: string]: Message[]
}
part: {
[messageID: string]: Part[]
}
node: FileNode[]
changes: File[]
}>({
config: {},
path: { state: "", config: "", worktree: "", directory: "" },
ready: false,
agent: [],
provider: [],
session: [],
message: {},
part: {},
node: [],
changes: [],
})
const sdk = useSDK()
sdk.event.subscribe().then(async (events) => {
for await (const event of events.stream) {
switch (event.type) {
case "session.updated": {
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
if (result.found) {
setStore("session", result.index, reconcile(event.properties.info))
break
}
setStore(
"session",
produce((draft) => {
draft.splice(result.index, 0, event.properties.info)
}),
)
break
}
case "message.updated": {
const messages = store.message[event.properties.info.sessionID]
if (!messages) {
setStore("message", event.properties.info.sessionID, [event.properties.info])
break
}
const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
if (result.found) {
setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
break
}
setStore(
"message",
event.properties.info.sessionID,
produce((draft) => {
draft.splice(result.index, 0, event.properties.info)
}),
)
break
}
case "message.part.updated": {
const parts = store.part[event.properties.part.messageID]
if (!parts) {
setStore("part", event.properties.part.messageID, [event.properties.part])
break
}
const result = Binary.search(parts, event.properties.part.id, (p) => p.id)
if (result.found) {
setStore("part", event.properties.part.messageID, result.index, reconcile(event.properties.part))
break
}
setStore(
"part",
event.properties.part.messageID,
produce((draft) => {
draft.splice(result.index, 0, event.properties.part)
}),
)
break
}
}
}
})
Promise.all([
sdk.config.providers().then((x) => setStore("provider", x.data!.providers)),
sdk.path.get().then((x) => setStore("path", x.data!)),
sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
sdk.session.list().then((x) =>
setStore(
"session",
(x.data ?? []).slice().sort((a, b) => a.id.localeCompare(b.id)),
),
),
sdk.config.get().then((x) => setStore("config", x.data!)),
sdk.file.status().then((x) => setStore("changes", x.data!)),
sdk.file.list({ query: { path: "/" } }).then((x) => setStore("node", x.data!)),
]).then(() => setStore("ready", true))
return {
data: store,
set: setStore,
session: {
get(sessionID: string) {
const match = Binary.search(store.session, sessionID, (s) => s.id)
if (match.found) return store.session[match.index]
return undefined
},
async sync(sessionID: string) {
const [session, messages] = await Promise.all([
sdk.session.get({ path: { id: sessionID } }),
sdk.session.messages({ path: { id: sessionID } }),
])
setStore(
produce((draft) => {
const match = Binary.search(draft.session, sessionID, (s) => s.id)
draft.session[match.index] = session.data!
draft.message[sessionID] = messages
.data!.map((x) => x.info)
.slice()
.sort((a, b) => a.id.localeCompare(b.id))
for (const message of messages.data!) {
draft.part[message.info.id] = message.parts.slice().sort((a, b) => a.id.localeCompare(b.id))
}
}),
)
},
},
}
}
type SyncContext = ReturnType<typeof init>
const ctx = createContext<SyncContext>()
export function SyncProvider(props: ParentProps) {
const value = init()
return (
<Show when={value.data.ready}>
<ctx.Provider value={value}>{props.children}</ctx.Provider>
</Show>
)
}
export function useSync() {
const value = useContext(ctx)
if (!value) {
throw new Error("useSync must be used within a SyncProvider")
}
return value
}
-608
View File
@@ -1,608 +0,0 @@
import { FileIcon, Icon, IconButton, Tooltip } from "@/ui"
import { Tabs } from "@/ui/tabs"
import FileTree from "@/components/file-tree"
import { createSignal, For, Match, onCleanup, onMount, Show, Switch } from "solid-js"
import { useLocal, useSDK } from "@/context"
import { Code } from "@/components/code"
import {
DragDropProvider,
DragDropSensors,
DragOverlay,
SortableProvider,
createSortable,
closestCenter,
useDragDropContext,
} from "@thisbeyond/solid-dnd"
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
import type { LocalFile } from "@/context/local"
import SessionList from "@/components/session-list"
import SessionTimeline from "@/components/session-timeline"
export default function Page() {
const sdk = useSDK()
const local = useLocal()
const [clickTimer, setClickTimer] = createSignal<number | undefined>()
const [activeItem, setActiveItem] = createSignal<string | undefined>(undefined)
const [inputValue, setInputValue] = createSignal("")
const [isDragging, setIsDragging] = createSignal<"left" | "right" | undefined>(undefined)
const [leftScrolled, setLeftScrolled] = createSignal(false)
// TODO: remove
local.model.set({ providerID: "opencode", modelID: "grok-code" })
let inputRef: HTMLInputElement | undefined = undefined
const MOD = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) ? "Meta" : "Control"
onMount(() => {
document.addEventListener("keydown", handleKeyDown)
})
onCleanup(() => {
document.removeEventListener("keydown", handleKeyDown)
})
const handleKeyDown = (e: KeyboardEvent) => {
const inputFocused = document.activeElement === inputRef
if (inputFocused) {
if (e.key === "Escape") {
inputRef?.blur()
}
return
}
if (local.file.active()) {
if (e.getModifierState(MOD)) {
if (e.key.toLowerCase() === "a") {
return
}
if (e.key.toLowerCase() === "c") {
return
}
}
}
if (e.key.length === 1 && e.key !== "Unidentified") {
inputRef?.focus()
}
}
const navigateChange = (dir: 1 | -1) => {
const active = local.file.active()
if (!active) return
const current = local.file.changeIndex(active.path)
const next = current == undefined ? (dir === 1 ? 0 : -1) : current + dir
local.file.setChangeIndex(active.path, next)
}
const resetClickTimer = () => {
if (!clickTimer()) return
clearTimeout(clickTimer())
setClickTimer(undefined)
}
const startClickTimer = () => {
const newClickTimer = setTimeout(() => {
setClickTimer(undefined)
}, 300)
setClickTimer(newClickTimer as unknown as number)
}
const handleFileClick = async (file: LocalFile) => {
if (clickTimer()) {
resetClickTimer()
local.file.update(file.path, { ...file, pinned: true })
} else {
local.file.open(file.path)
startClickTimer()
}
}
const handleTabChange = (path: string) => {
local.file.open(path)
}
const handleTabClose = (file: LocalFile) => {
local.file.close(file.path)
}
const onDragStart = (event: any) => {
setActiveItem(event.draggable.id as string)
}
const onDragOver = (event: DragEvent) => {
const { draggable, droppable } = event
if (draggable && droppable) {
const currentFiles = local.file.opened().map((f) => f.path)
const fromIndex = currentFiles.indexOf(draggable.id.toString())
const toIndex = currentFiles.indexOf(droppable.id.toString())
if (fromIndex !== toIndex) {
local.file.move(draggable.id.toString(), toIndex)
}
}
}
const onDragEnd = () => {
setActiveItem(undefined)
}
const handleLeftDragStart = (e: MouseEvent) => {
e.preventDefault()
setIsDragging("left")
const startX = e.clientX
const startWidth = local.layout.leftWidth()
const handleMouseMove = (e: MouseEvent) => {
const deltaX = e.clientX - startX
const newWidth = startWidth + deltaX
local.layout.setLeftWidth(newWidth)
}
const handleMouseUp = () => {
setIsDragging(undefined)
document.removeEventListener("mousemove", handleMouseMove)
document.removeEventListener("mouseup", handleMouseUp)
}
document.addEventListener("mousemove", handleMouseMove)
document.addEventListener("mouseup", handleMouseUp)
}
const handleRightDragStart = (e: MouseEvent) => {
e.preventDefault()
setIsDragging("right")
const startX = e.clientX
const startWidth = local.layout.rightWidth()
const handleMouseMove = (e: MouseEvent) => {
const deltaX = startX - e.clientX
const newWidth = startWidth + deltaX
local.layout.setRightWidth(newWidth)
}
const handleMouseUp = () => {
setIsDragging(undefined)
document.removeEventListener("mousemove", handleMouseMove)
document.removeEventListener("mouseup", handleMouseUp)
}
document.addEventListener("mousemove", handleMouseMove)
document.addEventListener("mouseup", handleMouseUp)
}
const handleSubmit = async (e: SubmitEvent) => {
e.preventDefault()
const prompt = inputValue()
setInputValue("")
inputRef?.blur()
const session =
(local.layout.rightPane() ? local.session.active() : undefined) ??
(await sdk.session.create().then((x) => x.data!))
local.session.setActive(session!.id)
local.layout.openRightPane()
const response = await sdk.session.prompt({
path: { id: session!.id },
body: {
agent: local.agent.current()!.name,
model: local.model.current(),
parts: [
{
type: "text",
text: prompt,
},
...local.file
.opened()
.filter((f) => f.selection || local.file.active()?.path === f.path)
.flatMap((f) => [
{
type: "file" as const,
mime: "text/plain",
url: `file://${f.absolute}${f.selection ? `?start=${f.selection.startLine}&end=${f.selection.endLine}` : ""}`,
filename: f.name,
source: {
type: "file" as const,
text: {
value: "@" + f.name,
start: 0, // f.start,
end: 0, // f.end,
},
path: f.absolute,
},
},
]),
],
},
})
console.log("response", response)
}
return (
<div class="relative">
<div
class="fixed top-0 left-0 h-full border-r border-border-subtle/30 flex flex-col overflow-hidden"
style={`width: ${local.layout.leftWidth()}px`}
>
<Tabs class="relative flex flex-col h-full" defaultValue="files">
<div class="sticky top-0 shrink-0 flex">
<Tabs.List class="grow w-full after:hidden">
<Tabs.Trigger value="files" class="flex-1 justify-center">
Files
</Tabs.Trigger>
<Tabs.Trigger value="changes" class="flex-1 justify-center">
Changes
</Tabs.Trigger>
</Tabs.List>
</div>
<Tabs.Content
value="files"
class="grow min-h-0 py-2 bg-background"
onScroll={(e: Event & { currentTarget: HTMLDivElement }) => setLeftScrolled(e.currentTarget.scrollTop > 0)}
>
<FileTree path="" onFileClick={handleFileClick} />
<Show when={leftScrolled()}>
<div
class="pointer-events-none sticky top-20 left-px h-4
bg-gradient-to-t from-transparent to-background"
style={`width: ${local.layout.leftWidth() - 2}px`}
/>
</Show>
<div
class="pointer-events-none fixed bottom-0 left-px h-4
bg-gradient-to-b from-transparent to-background"
style={`width: ${local.layout.leftWidth() - 2}px`}
/>
</Tabs.Content>
<Tabs.Content value="changes" class="grow min-h-0 py-2 bg-background">
<div class="px-2 text-sm text-text-muted">No changes yet</div>
</Tabs.Content>
</Tabs>
</div>
<div
class="fixed top-0 h-full w-1.5 bg-transparent cursor-col-resize z-50 group"
style={`left: ${local.layout.leftWidth()}px`}
onMouseDown={(e) => handleLeftDragStart(e)}
>
<div
class="w-0.5 h-full bg-transparent group-hover:bg-border-active transition-colors"
classList={{
"bg-border-active": isDragging() === "left",
}}
/>
</div>
<Show when={local.layout.rightPane()}>
<div
class="fixed top-0 right-0 h-full border-l border-border-subtle/30 flex flex-col overflow-hidden"
style={`width: ${local.layout.rightWidth()}px`}
>
<div class="relative flex-1 min-h-0 overflow-y-auto no-scrollbar">
<Show when={local.session.active()} fallback={<SessionList />}>
{(activeSession) => (
<div class="relative">
<div class="sticky top-0 bg-background z-50 p-2 h-9 border-b border-border-subtle/30">
<div class="flex items-center gap-2">
<IconButton
size="xs"
variant="ghost"
onClick={() => local.session.clearActive()}
class="text-text-muted hover:text-text"
>
<Icon name="arrow-left" size={14} />
</IconButton>
<h2 class="text-sm font-medium text-text truncate">
{activeSession().title || "Untitled Session"}
</h2>
</div>
</div>
<SessionTimeline session={activeSession().id} />
</div>
)}
</Show>
<div
class="pointer-events-none fixed top-0 right-px h-4
bg-gradient-to-t from-transparent to-background"
style={`width: ${local.layout.rightWidth() - 2}px`}
/>
<div
class="pointer-events-none fixed bottom-0 right-px h-4
bg-gradient-to-b from-transparent to-background"
style={`width: ${local.layout.rightWidth() - 2}px`}
/>
</div>
</div>
<div
class="fixed top-0 h-full w-1.5 bg-transparent cursor-col-resize z-50 group flex justify-end"
style={`right: ${local.layout.rightWidth()}px`}
onMouseDown={(e) => handleRightDragStart(e)}
>
<div
class="w-0.5 h-full bg-transparent group-hover:bg-border-active transition-colors"
classList={{ "bg-border-active": isDragging() === "right" }}
/>
</div>
</Show>
<div
style={`margin-left: ${local.layout.leftWidth()}px; margin-right: ${local.layout.rightPane() ? local.layout.rightWidth() : 0}px`}
>
<DragDropProvider
onDragStart={onDragStart}
onDragEnd={onDragEnd}
onDragOver={onDragOver}
collisionDetector={closestCenter}
>
<DragDropSensors />
<ConstrainDragYAxis />
<Tabs
class="relative grow w-full flex flex-col h-screen"
value={local.file.active()?.path}
onChange={handleTabChange}
>
<div class="sticky top-0 shrink-0 flex">
<Tabs.List class="grow">
<SortableProvider ids={local.file.opened().map((f) => f.path)}>
<For each={local.file.opened()}>
{(file) => <SortableTab file={file} onTabClick={handleFileClick} onTabClose={handleTabClose} />}
</For>
</SortableProvider>
</Tabs.List>
<div class="shrink-0 h-full flex items-center gap-1 px-2 border-b border-border-subtle/40">
<Show when={local.file.active() && local.file.active()!.content?.diff}>
{(() => {
const f = local.file.active()!
const view = local.file.view(f.path)
return (
<div class="flex items-center gap-1">
<Show when={view !== "raw"}>
<div class="mr-1 flex items-center gap-1">
<Tooltip value="Previous change" placement="bottom">
<IconButton size="xs" variant="ghost" onClick={() => navigateChange(-1)}>
<Icon name="arrow-up" size={14} />
</IconButton>
</Tooltip>
<Tooltip value="Next change" placement="bottom">
<IconButton size="xs" variant="ghost" onClick={() => navigateChange(1)}>
<Icon name="arrow-down" size={14} />
</IconButton>
</Tooltip>
</div>
</Show>
<Tooltip value="Raw" placement="bottom">
<IconButton
size="xs"
variant="ghost"
classList={{
"text-text": view === "raw",
"text-text-muted/70": view !== "raw",
"bg-background-element": view === "raw",
}}
onClick={() => local.file.setView(f.path, "raw")}
>
<Icon name="file-text" size={14} />
</IconButton>
</Tooltip>
<Tooltip value="Unified diff" placement="bottom">
<IconButton
size="xs"
variant="ghost"
classList={{
"text-text": view === "diff-unified",
"text-text-muted/70": view !== "diff-unified",
"bg-background-element": view === "diff-unified",
}}
onClick={() => local.file.setView(f.path, "diff-unified")}
>
<Icon name="checklist" size={14} />
</IconButton>
</Tooltip>
<Tooltip value="Split diff" placement="bottom">
<IconButton
size="xs"
variant="ghost"
classList={{
"text-text": view === "diff-split",
"text-text-muted/70": view !== "diff-split",
"bg-background-element": view === "diff-split",
}}
onClick={() => local.file.setView(f.path, "diff-split")}
>
<Icon name="columns" size={14} />
</IconButton>
</Tooltip>
</div>
)
})()}
</Show>
<Tooltip value={local.layout.rightPane() ? "Close pane" : "Open pane"} placement="bottom">
<IconButton size="xs" variant="ghost" onClick={() => local.layout.toggleRightPane()}>
<Icon name={local.layout.rightPane() ? "close-pane" : "open-pane"} size={14} />
</IconButton>
</Tooltip>
</div>
</div>
<For each={local.file.opened()}>
{(file) => (
<Tabs.Content value={file.path} class="grow h-full pt-1 select-text">
{(() => {
const view = local.file.view(file.path)
const showRaw = view === "raw" || !file.content?.diff
const code = showRaw ? (file.content?.content ?? "") : (file.content?.diff ?? "")
return <Code path={file.path} code={code} />
})()}
</Tabs.Content>
)}
</For>
</Tabs>
<DragOverlay>
{activeItem() &&
(() => {
const draggedFile = local.file.node(activeItem()!)
return (
<div
class="relative px-3 h-9 flex items-center
text-sm font-medium text-text whitespace-nowrap
shrink-0 bg-background-panel
border-x border-border-subtle/40 border-b border-b-transparent"
>
<TabVisual file={draggedFile} />
</div>
)
})()}
</DragOverlay>
</DragDropProvider>
<form
onSubmit={handleSubmit}
class="peer/editor absolute bottom-8 z-50 flex items-center justify-center"
style={`left: ${local.layout.leftWidth() + 40}px; right: ${local.layout.rightPane() ? local.layout.rightWidth() + 40 : 40}px`}
>
<div
class="w-full max-w-2xl min-w-1/2 p-2 mx-auto rounded-lg isolate backdrop-blur-xs
flex flex-col gap-1
bg-gradient-to-b from-background-panel/90 to-background/90
ring-1 ring-border-active/50 border border-transparent
shadow-[0_0_33px_rgba(0,0,0,0.8)]
focus-within:ring-2 focus-within:ring-primary/40 focus-within:border-primary"
>
<div class="flex flex-wrap gap-1">
<Show when={local.file.active()}>
<FileTag
default
file={local.file.active()!}
onClose={() => local.file.close(local.file.active()?.path ?? "")}
/>
</Show>
<For each={local.file.opened().filter((x) => x.selection)}>
{(file) => <FileTag file={file} onClose={() => local.file.select(file.path, undefined)} />}
</For>
</div>
<input
ref={(el) => (inputRef = el)}
type="text"
value={inputValue()}
onInput={(e) => setInputValue(e.currentTarget.value)}
placeholder="It all starts with a prompt..."
class="w-full p-1 pb-4 text-text font-light placeholder-text-muted/70 text-sm focus:outline-none"
/>
<div class="px-1 flex justify-between items-center text-xs text-text-muted">
<span>
<span class="text-primary uppercase">{local.agent.current()?.name ?? "unknown"}</span> /{" "}
{local.model.parsed().provider} / {local.model.parsed().model}
</span>
<div class="flex gap-1 items-center">
<IconButton class="text-text-muted" size="xs" variant="ghost">
<Icon name="photo" size={16} />
</IconButton>
<IconButton class="text-background-panel! bg-primary rounded-full!" size="xs" variant="ghost">
<Icon name="arrow-up" size={14} />
</IconButton>
</div>
</div>
</div>
</form>
</div>
</div>
)
}
const TabVisual = (props: { file: LocalFile }) => {
const local = useLocal()
return (
<div class="flex items-center gap-x-1.5">
<FileIcon node={props.file} class="" />
<span
classList={{ "text-xs": true, "text-primary": local.file.changed(props.file.path), italic: !props.file.pinned }}
>
{props.file.name}
</span>
<span class="text-xs opacity-70">
<Switch>
<Match when={local.file.status(props.file.path)?.status === "modified"}>
<span class="text-primary">M</span>
</Match>
<Match when={local.file.status(props.file.path)?.status === "added"}>
<span class="text-success">A</span>
</Match>
<Match when={local.file.status(props.file.path)?.status === "deleted"}>
<span class="text-error">D</span>
</Match>
</Switch>
</span>
</div>
)
}
const SortableTab = (props: {
file: LocalFile
onTabClick: (file: LocalFile) => void
onTabClose: (file: LocalFile) => void
}) => {
const sortable = createSortable(props.file.path)
return (
// @ts-ignore
<div use:sortable classList={{ "opacity-0": sortable.isActiveDraggable }}>
<Tooltip value={props.file.path} placement="bottom">
<div class="relative">
<Tabs.Trigger value={props.file.path} class="peer/tab pr-7" onClick={() => props.onTabClick(props.file)}>
<TabVisual file={props.file} />
</Tabs.Trigger>
<IconButton
class="absolute right-1 top-2 opacity-0 text-text-muted/60
peer-data-[selected]/tab:opacity-100 peer-data-[selected]/tab:text-text
peer-data-[selected]/tab:hover:bg-border-subtle
hover:opacity-100 peer-hover/tab:opacity-100"
size="xs"
variant="ghost"
onClick={() => props.onTabClose(props.file)}
>
<Icon name="close" size={16} />
</IconButton>
</div>
</Tooltip>
</div>
)
}
const FileTag = (props: { file: LocalFile; default?: boolean; onClose: () => void }) => (
<div
class="flex items-center bg-background group/tag
border border-border-subtle/60 border-dashed
rounded-md text-xs text-text-muted"
>
<IconButton class="text-text-muted" size="xs" variant="ghost" onClick={props.onClose}>
<Switch fallback={<FileIcon node={props.file} class="group-hover/tag:hidden size-3!" />}>
<Match when={props.default}>
<Icon name="file" class="group-hover/tag:hidden" size={12} />
</Match>
</Switch>
<Icon name="close" class="hidden group-hover/tag:block" size={12} />
</IconButton>
<div class="pr-1 flex gap-1 items-center">
<span>{props.file.name}</span>
<Show when={!props.default && props.file.selection}>
<span class="">
({props.file.selection!.startLine}-{props.file.selection!.endLine})
</span>
</Show>
</div>
</div>
)
const ConstrainDragYAxis = () => {
const context = useDragDropContext()
if (!context) return <></>
const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context
const transformer: Transformer = {
id: "constrain-y-axis",
order: 100,
callback: (transform) => ({ ...transform, y: 0 }),
}
onDragStart((event: any) => {
addTransformer("draggables", event.draggable.id, transformer)
})
onDragEnd((event: any) => {
removeTransformer("draggables", event.draggable.id, transformer.id)
})
return <></>
}
-49
View File
@@ -1,49 +0,0 @@
import { Button as KobalteButton } from "@kobalte/core/button"
import { splitProps } from "solid-js"
import type { ComponentProps } from "solid-js"
export interface ButtonProps extends ComponentProps<typeof KobalteButton> {
variant?: "primary" | "secondary" | "outline" | "ghost"
size?: "sm" | "md" | "lg"
}
export const buttonStyles = {
base: "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 cursor-pointer",
variants: {
primary: "bg-primary text-background hover:bg-secondary focus-visible:ring-primary data-[disabled]:opacity-50",
secondary:
"bg-background-panel text-text hover:bg-background-element focus-visible:ring-secondary data-[disabled]:opacity-50",
outline:
"border border-border bg-transparent text-text hover:bg-background-panel focus-visible:ring-border-active data-[disabled]:border-border-subtle data-[disabled]:text-text-muted",
ghost: "text-text hover:bg-background-panel focus-visible:ring-border-active data-[disabled]:text-text-muted",
},
sizes: {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base",
},
}
export function getButtonClasses(
variant: keyof typeof buttonStyles.variants = "primary",
size: keyof typeof buttonStyles.sizes = "md",
className?: string,
) {
return `${buttonStyles.base} ${buttonStyles.variants[variant]} ${buttonStyles.sizes[size]}${className ? ` ${className}` : ""}`
}
export function Button(props: ButtonProps) {
const [local, others] = splitProps(props, ["variant", "size", "class", "classList"])
return (
<KobalteButton
classList={{
...(local.classList ?? {}),
[buttonStyles.base]: true,
[buttonStyles.variants[local.variant || "primary"]]: true,
[buttonStyles.sizes[local.size || "md"]]: true,
[local.class ?? ""]: !!local.class,
}}
{...others}
/>
)
}
-15
View File
@@ -1,15 +0,0 @@
import { A } from "@solidjs/router"
import { splitProps } from "solid-js"
import type { ComponentProps } from "solid-js"
import { getButtonClasses } from "./button"
export interface LinkProps extends ComponentProps<typeof A> {
variant?: "primary" | "secondary" | "outline" | "ghost"
size?: "sm" | "md" | "lg"
}
export function Link(props: LinkProps) {
const [local, others] = splitProps(props, ["variant", "size", "class"])
const classes = local.variant ? getButtonClasses(local.variant, local.size, local.class) : local.class
return <A class={classes} {...others} />
}
-9
View File
@@ -1,9 +0,0 @@
export function getFilename(path: string) {
const parts = path.split("/")
return parts[parts.length - 1]
}
export function getFileExtension(path: string) {
const parts = path.split(".")
return parts[parts.length - 1]
}
-14
View File
@@ -1,14 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "solid-js",
"types": ["vite/client"],
"lib": ["DOM", "DOM.Iterable"],
"customConditions": ["development"],
"paths": {
"@/*": ["./src/*"]
}
}
}
+11 -4
View File
@@ -1,23 +1,30 @@
{
"name": "@opencode/console-app",
"name": "@opencode-ai/console-app",
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit",
"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",
"start": "vinxi start",
"version": "0.10.0"
"version": "0.15.5"
},
"dependencies": {
"@ibm/plex": "6.4.1",
"@kobalte/core": "catalog:",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@opencode-ai/console-core": "workspace:*",
"@opencode-ai/console-resource": "workspace:*",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.0",
"@solidjs/start": "^1.1.0",
"solid-js": "catalog:",
"vinxi": "^0.5.7",
"@opencode/console-core": "workspace:*"
"zod": "catalog:"
},
"devDependencies": {
"typescript": "catalog:",
"@typescript/native-preview": "catalog:"
},
"engines": {
"node": ">=22"
+1
View File
@@ -0,0 +1 @@
../../mail/emails/templates/static
@@ -0,0 +1,23 @@
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="400" height="400" fill="#FDFCFC"/>
<path d="M96 122.001V70.001H148V122.001H96Z" fill="#17181C"/>
<path d="M148.004 122.001V70.001H200.004V122.001H148.004Z" fill="#17181C"/>
<path d="M200.008 122.001V70.001H252.008V122.001H200.008Z" fill="#17181C"/>
<path d="M251.996 122.001V70.001H303.996V122.001H251.996Z" fill="#17181C"/>
<path d="M251.996 173.988V121.988H303.996V173.988H251.996Z" fill="#17181C"/>
<path d="M96 225.998V173.998H148V225.998H96Z" fill="#CFCECD"/>
<rect width="52" height="52" transform="translate(148.004 173.998)" fill="#17181C"/>
<path d="M148.004 225.998V173.998H200.004V225.998H148.004Z" fill="#17181C" fill-opacity="0.1"/>
<path d="M200.008 225.998V173.998H252.008V225.998H200.008Z" fill="#17181C"/>
<path d="M252.016 225.998V173.998H304.016V225.998H252.016Z" fill="#CFCECD"/>
<rect width="52" height="52" transform="translate(96 226.002)" fill="#17181C"/>
<path d="M96 278.002V226.002H148V278.002H96Z" fill="#17181C" fill-opacity="0.1"/>
<rect width="52" height="52" transform="translate(148.004 226.002)" fill="white"/>
<path d="M148.004 278.002V226.002H200.004V278.002H148.004Z" fill="#CFCECD"/>
<path d="M200.008 278.002V226.002H252.008V278.002H200.008Z" fill="#CFCECD"/>
<path d="M252.016 278.002V226.002H304.016V278.002H252.016Z" fill="#CFCECD"/>
<path d="M96 330.012V278.012H148V330.012H96Z" fill="#17181C"/>
<path d="M148.004 330.012V278.012H200.004V330.012H148.004Z" fill="#17181C"/>
<path d="M200.008 329.99V277.99H252.008V329.99H200.008Z" fill="#17181C"/>
<path d="M251.996 330.012V278.012H303.996V330.012H251.996Z" fill="#17181C"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+3 -4
View File
@@ -1,5 +1,4 @@
<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="600" height="600" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M115 180H300V420H115V180ZM253.75 229.044H161.25V370.405H253.75V229.044Z" fill="white"/>
<path d="M346.25 180H485V229.044H392.5V370.405H485V419.449H346.25V180Z" fill="white"/>
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="400" height="400" fill="#0E0E0E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M312 340H88V60H312V340ZM256 116H144V284H256V116Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 377 B

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

@@ -1,2 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"/></svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 14.3581L10.0541 17.7027L18 7" stroke="#8E8B8B" stroke-width="1.5" stroke-linecap="square"/>
</svg>

Before

Width:  |  Height:  |  Size: 212 B

After

Width:  |  Height:  |  Size: 207 B

@@ -1,2 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512"><rect width="336" height="336" x="128" y="128" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" rx="57" ry="57"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"/></svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.75 8.75V2.75H21.25V15.25H15.25M15.25 8.75H2.75V21.25H15.25V8.75Z" stroke="#8E8B8B" stroke-width="1.5" stroke-linecap="square"/>
</svg>

Before

Width:  |  Height:  |  Size: 443 B

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 902 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 998 KiB

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 592 KiB

After

Width:  |  Height:  |  Size: 136 KiB

@@ -1,19 +1,18 @@
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.5 16.5H24.5V33H8.5V16.5Z" fill="white" fill-opacity="0.2"/>
<path d="M48.5 16.5H64.5V33H48.5V16.5Z" fill="white" fill-opacity="0.2"/>
<path d="M120.5 16.5H136.5V33H120.5V16.5Z" fill="white" fill-opacity="0.2"/>
<path d="M160.5 16.5H176.5V33H160.5V16.5Z" fill="white" fill-opacity="0.2"/>
<path d="M192.5 16.5H208.5V33H192.5V16.5Z" fill="white" fill-opacity="0.2"/>
<path d="M232.5 16.5H248.5V33H232.5V16.5Z" fill="white" fill-opacity="0.2"/>
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="white" fill-opacity="0.95"/>
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="white" fill-opacity="0.95"/>
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="white" fill-opacity="0.95"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="white" fill-opacity="0.95"/>
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="white" fill-opacity="0.5"/>
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="white" fill-opacity="0.5"/>
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="white" fill-opacity="0.5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="white" fill-opacity="0.5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="white" fill-opacity="0.5"/>
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="white" fill-opacity="0.95"/>
<svg width="234" height="42" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<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"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,18 +1,18 @@
<svg width="288" height="50" viewBox="0 0 288 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 16.5H24V33H8V16.5Z" fill="black" fill-opacity="0.15"/>
<path d="M48 16.5H64V33H48V16.5Z" fill="black" fill-opacity="0.15"/>
<path d="M120 16.5H136V33H120V16.5Z" fill="black" fill-opacity="0.15"/>
<path d="M160 16.5H176V33H160V16.5Z" fill="black" fill-opacity="0.15"/>
<path d="M192 16.5H208V33H192V16.5Z" fill="black" fill-opacity="0.15"/>
<path d="M232 16.5H248V33H232V16.5Z" fill="black" fill-opacity="0.15"/>
<path d="M264 0H288V8.5H272V16.5H288V25H272V33H288V41.5H264V0Z" fill="black" fill-opacity="0.95"/>
<path d="M248 0H224V41.5H248V33H232V8.5H248V0Z" fill="black" fill-opacity="0.95"/>
<path d="M256 8.5H248V33H256V8.5Z" fill="black" fill-opacity="0.95"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M184 0H216V41.5H184V0ZM208 8.5H192V33H208V8.5Z" fill="black" fill-opacity="0.95"/>
<path d="M144 8.5H136V41.5H144V8.5Z" fill="black" fill-opacity="0.55"/>
<path d="M136 0H112V41.5H120V8.5H136V0Z" fill="black" fill-opacity="0.55"/>
<path d="M80 0H104V8.5H88V16.5H104V25H88V33H104V41.5H80V0Z" fill="black" fill-opacity="0.55"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M40 0H72V41.5H48V49.5H40V0ZM64 8.5H48V33H64V8.5Z" fill="black" fill-opacity="0.55"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="black" fill-opacity="0.55"/>
<path d="M152 0H176V8.5H160V33H176V41.5H152V0Z" fill="black" fill-opacity="0.95"/>
<svg width="234" height="42" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<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"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

+17 -11
View File
@@ -1,12 +1,18 @@
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="black"/>
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="black"/>
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="black"/>
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="black"/>
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="black"/>
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="black"/>
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="black"/>
<svg width="234" height="42" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<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"/>
</svg>

Before

Width:  |  Height:  |  Size: 981 B

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,8 @@
<svg width="84" height="30" viewBox="0 0 84 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24 24H6V18H18V12H24V24ZM6 18H0V12H6V18Z" fill="#4B4646"/>
<path d="M6 24H24V30H0V18H6V24ZM18 18H6V12H18V18ZM24 12H18V6H0V0H24V12Z" fill="#F1ECEC"/>
<path d="M54 18V24H36V18H54Z" fill="#4B4646"/>
<path d="M54 18H36V24H54V30H30V0H54V18ZM36 12H48V6H36V12Z" fill="#F1ECEC"/>
<path d="M78 30H66V12H78V30Z" fill="#4B4646"/>
<path d="M78 6H66V30H60V0H78V6ZM84 30H78V6H84V30Z" fill="#F1ECEC"/>
</svg>

After

Width:  |  Height:  |  Size: 499 B

@@ -0,0 +1,8 @@
<svg width="84" height="30" viewBox="0 0 84 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24 24H6V18H18V12H24V24ZM6 18H0V12H6V18Z" fill="#CFCECD"/>
<path d="M6 24H24V30H0V18H6V24ZM18 18H6V12H18V18ZM24 12H18V6H0V0H24V12Z" fill="#211E1E"/>
<path d="M54 18V24H36V18H54Z" fill="#CFCECD"/>
<path d="M54 18H36V24H54V30H30V0H54V18ZM36 12H48V6H36V12Z" fill="#211E1E"/>
<path d="M78 30H66V12H78V30Z" fill="#CFCECD"/>
<path d="M78 6H66V30H60V0H78V6ZM84 30H78V6H84V30Z" fill="#211E1E"/>
</svg>

After

Width:  |  Height:  |  Size: 499 B

@@ -0,0 +1,80 @@
[data-component="dropdown"] {
position: relative;
[data-slot="trigger"] {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
border: none;
border-radius: var(--border-radius-sm);
background-color: transparent;
color: var(--color-text);
font-size: var(--font-size-sm);
font-family: var(--font-sans);
cursor: pointer;
transition: all 0.15s ease;
&:hover {
background-color: var(--color-surface-hover);
}
span {
flex: 1;
text-align: left;
font-weight: 500;
}
}
[data-slot="chevron"] {
flex-shrink: 0;
color: var(--color-text-secondary);
}
[data-slot="dropdown"] {
position: absolute;
top: 100%;
z-index: 1000;
margin-top: var(--space-1);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
min-width: 160px;
&[data-align="left"] {
left: 0;
}
&[data-align="right"] {
right: 0;
}
@media (prefers-color-scheme: dark) {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
}
[data-slot="item"] {
display: block;
width: 100%;
padding: var(--space-2-5) var(--space-3);
border: none;
background: none;
color: var(--color-text);
font-size: var(--font-size-sm);
font-family: var(--font-sans);
text-align: left;
cursor: pointer;
transition: background-color 0.15s ease;
&:hover {
background-color: var(--color-bg-surface);
}
&[data-selected="true"] {
background-color: var(--color-accent-alpha);
}
}
}
@@ -0,0 +1,79 @@
import { JSX, Show, createEffect, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import { IconChevron } from "./icon"
import "./dropdown.css"
interface DropdownProps {
trigger: JSX.Element | string
children: JSX.Element
open?: boolean
onOpenChange?: (open: boolean) => void
align?: "left" | "right"
class?: string
}
export function Dropdown(props: DropdownProps) {
const [store, setStore] = createStore({
isOpen: props.open ?? false,
})
let dropdownRef: HTMLDivElement | undefined
createEffect(() => {
if (props.open !== undefined) {
setStore("isOpen", props.open)
}
})
createEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef && !dropdownRef.contains(event.target as Node)) {
setStore("isOpen", false)
props.onOpenChange?.(false)
}
}
document.addEventListener("click", handleClickOutside)
onCleanup(() => document.removeEventListener("click", handleClickOutside))
})
const toggle = () => {
const newValue = !store.isOpen
setStore("isOpen", newValue)
props.onOpenChange?.(newValue)
}
return (
<div data-component="dropdown" class={props.class} ref={dropdownRef}>
<button data-slot="trigger" type="button" onClick={toggle}>
{typeof props.trigger === "string" ? <span>{props.trigger}</span> : props.trigger}
<IconChevron data-slot="chevron" />
</button>
<Show when={store.isOpen}>
<div data-slot="dropdown" data-align={props.align ?? "left"}>
{props.children}
</div>
</Show>
</div>
)
}
interface DropdownItemProps {
children: JSX.Element
selected?: boolean
onClick?: () => void
type?: "button" | "submit" | "reset"
}
export function DropdownItem(props: DropdownItemProps) {
return (
<button
data-slot="item"
data-selected={props.selected ?? false}
type={props.type ?? "button"}
onClick={props.onClick}
>
{props.children}
</button>
)
}
@@ -0,0 +1,51 @@
import { action, useSubmission } from "@solidjs/router"
import dock from "../asset/lander/dock.png"
import { Resource } from "@opencode-ai/console-resource"
import { Show } from "solid-js"
const emailSignup = action(async (formData: FormData) => {
"use server"
const emailAddress = formData.get("email")!
const listId = "8b9bb82c-9d5f-11f0-975f-0df6fd1e4945"
const response = await fetch(`https://api.emailoctopus.com/lists/${listId}/contacts`, {
method: "PUT",
headers: {
Authorization: `Bearer ${Resource.EMAILOCTOPUS_API_KEY.value}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email_address: emailAddress,
}),
})
console.log(response)
return true
})
export function EmailSignup() {
const submission = useSubmission(emailSignup)
return (
<section data-component="email">
<div data-slot="dock">
<img src={dock} alt="" />
</div>
<div data-slot="section-title">
<h3>OpenCode will be available on desktop soon</h3>
<p>Join the waitlist for early access.</p>
</div>
<form data-slot="form" action={emailSignup} method="post">
<input type="email" name="email" placeholder="Email address" required />
<button type="submit" disabled={submission.pending}>
Subscribe
</button>
</form>
<Show when={submission.result}>
<div style="color: #03B000; margin-top: 24px;">
Almost done, check your inbox and confirm your email address
</div>
</Show>
<Show when={submission.error}>
<div style="color: #FF408F; margin-top: 24px;">{submission.error}</div>
</Show>
</section>
)
}
@@ -0,0 +1,33 @@
import { Collapsible } from "@kobalte/core/collapsible"
import { ParentProps } from "solid-js"
export function Faq(props: ParentProps & { question: string }) {
return (
<Collapsible data-slot="faq-item">
<Collapsible.Trigger data-slot="faq-question">
<svg
data-slot="faq-icon-plus"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12.5 11.5H19V12.5H12.5V19H11.5V12.5H5V11.5H11.5V5H12.5V11.5Z" fill="currentColor" />
</svg>
<svg
data-slot="faq-icon-minus"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5 11.5H19V12.5H5Z" fill="currentColor" />
</svg>
<div data-slot="faq-question-text">{props.question}</div>
</Collapsible.Trigger>
<Collapsible.Content data-slot="faq-answer">{props.children}</Collapsible.Content>
</Collapsible>
)
}
@@ -0,0 +1,34 @@
import { createAsync } from "@solidjs/router"
import { createMemo } from "solid-js"
import { github } from "~/lib/github"
export function Footer() {
const githubData = createAsync(() => github())
const starCount = createMemo(() =>
githubData()?.stars
? new Intl.NumberFormat("en-US", {
notation: "compact",
compactDisplay: "short",
}).format(githubData()!.stars!)
: "25K",
)
return (
<footer data-component="footer">
<div data-slot="cell">
<a href="https://github.com/sst/opencode" target="_blank">
GitHub <span>[{starCount()}]</span>
</a>
</div>
<div data-slot="cell">
<a href="/docs">Docs</a>
</div>
<div data-slot="cell">
<a href="/discord">Discord</a>
</div>
<div data-slot="cell">
<a href="https://x.com/opencode">X</a>
</div>
</footer>
)
}
@@ -0,0 +1,129 @@
import logoLight from "../asset/logo-ornate-light.svg"
import logoDark from "../asset/logo-ornate-dark.svg"
import { A, createAsync } 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"
export function Header(props: { zen?: boolean }) {
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",
)
const [store, setStore] = createStore({
mobileMenuOpen: false,
})
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>
<nav data-component="nav-desktop">
<ul>
<li>
<a href="https://github.com/sst/opencode" target="_blank">
GitHub <span>[{starCount()}]</span>
</a>
</li>
<li>
<a href="/docs">Docs</a>
</li>
<li>
<Switch>
<Match when={props.zen}>
<a href="/auth">{isLoggedIn() ? "Workspace" : "Login"}</a>
</Match>
<Match when={!props.zen}>
<A href="/zen">Zen</A>
</Match>
</Switch>
</li>
</ul>
</nav>
<nav data-component="nav-mobile">
<button
type="button"
data-component="nav-mobile-toggle"
aria-expanded="false"
aria-controls="nav-mobile-menu"
class="nav-toggle"
onClick={() => setStore("mobileMenuOpen", !store.mobileMenuOpen)}
>
<span class="sr-only">Open menu</span>
<Switch>
<Match when={store.mobileMenuOpen}>
<svg
class="icon icon-close"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.7071 11.9993L18.0104 17.3026L17.3033 18.0097L12 12.7064L6.6967 18.0097L5.98959 17.3026L11.2929 11.9993L5.98959 6.69595L6.6967 5.98885L12 11.2921L17.3033 5.98885L18.0104 6.69595L12.7071 11.9993Z"
fill="currentColor"
/>
</svg>
</Match>
<Match when={!store.mobileMenuOpen}>
<svg
class="icon icon-hamburger"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M19 17H5V16H19V17Z" fill="currentColor" />
<path d="M19 8H5V7H19V8Z" fill="currentColor" />
</svg>
</Match>
</Switch>
</button>
<Show when={store.mobileMenuOpen}>
<div id="nav-mobile-menu" data-component="nav-mobile">
<nav data-component="nav-mobile-menu-list">
<ul>
<li>
<A href="/">Home</A>
</li>
<li>
<a href="https://github.com/sst/opencode" target="_blank">
GitHub <span>[{starCount()}]</span>
</a>
</li>
<li>
<a href="/docs">Docs</a>
</li>
<li>
<Switch>
<Match when={props.zen}>
<a href="/auth">{isLoggedIn() ? "Workspace" : "Login"}</a>
</Match>
<Match when={!props.zen}>
<A href="/zen">Zen</A>
</Match>
</Switch>
</li>
</ul>
</nav>
</div>
</Show>
</nav>
</section>
)
}
+169 -48
View File
@@ -2,70 +2,89 @@ import { JSX } from "solid-js"
export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="currentColor" />
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="currentColor" />
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="currentColor" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z"
<svg width="64" height="32" viewBox="0 0 64 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 9.14333V4.5719H4.57143V9.14333H0Z" fill="currentColor" />
<path d="M4.57178 9.14333V4.5719H9.14321V9.14333H4.57178Z" fill="currentColor" />
<path d="M9.1438 9.14333V4.5719H13.7152V9.14333H9.1438Z" fill="currentColor" />
<path d="M13.7124 9.14333V4.5719H18.2838V9.14333H13.7124Z" fill="currentColor" />
<path d="M13.7124 13.7136V9.14221H18.2838V13.7136H13.7124Z" fill="currentColor" />
<path d="M0 18.2857V13.7142H4.57143V18.2857H0Z" fill="currentColor" fill-opacity="0.2" />
<rect width="4.57143" height="4.57143" transform="translate(4.57178 13.7141)" fill="currentColor" />
<path d="M4.57178 18.2855V13.7141H9.14321V18.2855H4.57178Z" fill="currentColor" fill-opacity="0.2" />
<path d="M9.1438 18.2855V13.7141H13.7152V18.2855H9.1438Z" fill="currentColor" />
<path d="M13.7156 18.2855V13.7141H18.287V18.2855H13.7156Z" fill="currentColor" fill-opacity="0.2" />
<rect width="4.57143" height="4.57143" transform="translate(0 18.2859)" fill="currentColor" />
<path d="M0 22.8572V18.2858H4.57143V22.8572H0Z" fill="currentColor" fill-opacity="0.2" />
<rect
width="4.57143"
height="4.57143"
transform="translate(4.57178 18.2859)"
fill="currentColor"
fill-opacity="0.2"
/>
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="currentColor" />
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="currentColor" />
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="currentColor" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z"
fill="currentColor"
/>
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="currentColor" />
<path d="M4.57178 22.8573V18.2859H9.14321V22.8573H4.57178Z" fill="currentColor" />
<path d="M9.1438 22.8573V18.2859H13.7152V22.8573H9.1438Z" fill="currentColor" fill-opacity="0.2" />
<path d="M13.7156 22.8573V18.2859H18.287V22.8573H13.7156Z" fill="currentColor" fill-opacity="0.2" />
<path d="M0 27.4292V22.8578H4.57143V27.4292H0Z" fill="currentColor" />
<path d="M4.57178 27.4292V22.8578H9.14321V27.4292H4.57178Z" fill="currentColor" />
<path d="M9.1438 27.4276V22.8562H13.7152V27.4276H9.1438Z" fill="currentColor" />
<path d="M13.7124 27.4292V22.8578H18.2838V27.4292H13.7124Z" fill="currentColor" />
<path d="M22.8572 9.14333V4.5719H27.4286V9.14333H22.8572Z" fill="currentColor" />
<path d="M27.426 9.14333V4.5719H31.9975V9.14333H27.426Z" fill="currentColor" />
<path d="M32.001 9.14333V4.5719H36.5724V9.14333H32.001Z" fill="currentColor" />
<path d="M36.5698 9.14333V4.5719H41.1413V9.14333H36.5698Z" fill="currentColor" />
<path d="M22.8572 13.7152V9.1438H27.4286V13.7152H22.8572Z" fill="currentColor" />
<path d="M36.5698 13.7152V9.1438H41.1413V13.7152H36.5698Z" fill="currentColor" />
<path d="M22.8572 18.2855V13.7141H27.4286V18.2855H22.8572Z" fill="currentColor" />
<path d="M27.4292 18.2855V13.7141H32.0006V18.2855H27.4292Z" fill="currentColor" />
<path d="M32.001 18.2855V13.7141H36.5724V18.2855H32.001Z" fill="currentColor" />
<path d="M36.5698 18.2855V13.7141H41.1413V18.2855H36.5698Z" fill="currentColor" />
<path d="M22.8572 22.8573V18.2859H27.4286V22.8573H22.8572Z" fill="currentColor" />
<path d="M27.4292 22.8573V18.2859H32.0006V22.8573H27.4292Z" fill="currentColor" fill-opacity="0.2" />
<path d="M32.001 22.8573V18.2859H36.5724V22.8573H32.001Z" fill="currentColor" fill-opacity="0.2" />
<path d="M36.5698 22.8573V18.2859H41.1413V22.8573H36.5698Z" fill="currentColor" fill-opacity="0.2" />
<path d="M22.8572 27.4292V22.8578H27.4286V27.4292H22.8572Z" fill="currentColor" />
<path d="M27.4292 27.4276V22.8562H32.0006V27.4276H27.4292Z" fill="currentColor" />
<path d="M32.001 27.4276V22.8562H36.5724V27.4276H32.001Z" fill="currentColor" />
<path d="M36.5698 27.4292V22.8578H41.1413V27.4292H36.5698Z" fill="currentColor" />
<path d="M45.7144 9.14333V4.5719H50.2858V9.14333H45.7144Z" fill="currentColor" />
<path d="M50.2861 9.14333V4.5719H54.8576V9.14333H50.2861Z" fill="currentColor" />
<path d="M54.855 9.14333V4.5719H59.4264V9.14333H54.855Z" fill="currentColor" />
<path d="M45.7144 13.7136V9.14221H50.2858V13.7136H45.7144Z" fill="currentColor" />
<path d="M59.4299 13.7152V9.1438H64.0014V13.7152H59.4299Z" fill="currentColor" />
<path d="M45.7144 18.2855V13.7141H50.2858V18.2855H45.7144Z" fill="currentColor" />
<path d="M50.2861 18.2857V13.7142H54.8576V18.2857H50.2861Z" fill="currentColor" fill-opacity="0.2" />
<path d="M54.8579 18.2855V13.7141H59.4293V18.2855H54.8579Z" fill="currentColor" fill-opacity="0.2" />
<path d="M59.4299 18.2855V13.7141H64.0014V18.2855H59.4299Z" fill="currentColor" />
<path d="M45.7144 22.8573V18.2859H50.2858V22.8573H45.7144Z" fill="currentColor" />
<path d="M50.2861 22.8572V18.2858H54.8576V22.8572H50.2861Z" fill="currentColor" fill-opacity="0.2" />
<path d="M54.8579 22.8573V18.2859H59.4293V22.8573H54.8579Z" fill="currentColor" fill-opacity="0.2" />
<path d="M59.4299 22.8573V18.2859H64.0014V22.8573H59.4299Z" fill="currentColor" />
<path d="M45.7144 27.4292V22.8578H50.2858V27.4292H45.7144Z" fill="currentColor" />
<path d="M50.2861 27.4286V22.8572H54.8576V27.4286H50.2861Z" fill="currentColor" fill-opacity="0.2" />
<path d="M54.8579 27.4285V22.8571H59.4293V27.4285H54.8579Z" fill="currentColor" fill-opacity="0.2" />
<path d="M59.4299 27.4292V22.8578H64.0014V27.4292H59.4299Z" fill="currentColor" />
</svg>
)
}
export function IconCopy(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 512 512">
<rect
width="336"
height="336"
x="128"
y="128"
fill="none"
stroke="currentColor"
stroke-linejoin="round"
stroke-width="32"
rx="57"
ry="57"
></rect>
<svg {...props} width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill="none"
d="M8.75 8.75V2.75H21.25V15.25H15.25M15.25 8.75H2.75V21.25H15.25V8.75Z"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="32"
d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"
></path>
stroke-width="1.5"
stroke-linecap="square"
/>
</svg>
)
}
export function IconCheck(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24">
<path
fill="currentColor"
d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"
></path>
<svg {...props} width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.75 15.0938L9 20.25L21.25 3.75" stroke="#03B000" stroke-width="2" stroke-linecap="square" />
</svg>
)
}
@@ -80,3 +99,105 @@ export function IconCreditCard(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
</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">
<path
fill="currentColor"
d="M4.00024 5.04041L7.37401 1.66663L6.66691 0.959525L4.00024 3.62619L1.33357 0.959525L0.626465 1.66663L4.00024 5.04041Z"
/>
</svg>
)
}
export function IconWorkspaceLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} width="24" height="30" viewBox="0 0 24 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6H6V24H18V6ZM24 30H0V0H24V30Z" fill="currentColor" />
</svg>
)
}
export function IconOpenAI(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" fill="none">
<path
d="M8.43799 8.06943V6.09387C8.43799 5.92749 8.50347 5.80267 8.65601 5.71959L12.8206 3.43211C13.3875 3.1202 14.0635 2.9747 14.7611 2.9747C17.3775 2.9747 19.0347 4.9087 19.0347 6.96734C19.0347 7.11288 19.0347 7.27926 19.0128 7.44564L14.6956 5.03335C14.434 4.88785 14.1723 4.88785 13.9107 5.03335L8.43799 8.06943ZM18.1624 15.7637V11.0431C18.1624 10.7519 18.0315 10.544 17.7699 10.3984L12.2972 7.36234L14.0851 6.3849C14.2377 6.30182 14.3686 6.30182 14.5212 6.3849L18.6858 8.67238C19.8851 9.3379 20.6917 10.7519 20.6917 12.1243C20.6917 13.7047 19.7106 15.1604 18.1624 15.7636V15.7637ZM7.15158 11.6047L5.36369 10.6066C5.21114 10.5235 5.14566 10.3986 5.14566 10.2323V5.65735C5.14566 3.43233 6.93355 1.7478 9.35381 1.7478C10.2697 1.7478 11.1199 2.039 11.8396 2.55886L7.54424 4.92959C7.28268 5.07508 7.15181 5.28303 7.15181 5.57427V11.6049L7.15158 11.6047ZM11 13.7258L8.43799 12.3533V9.44209L11 8.06965L13.5618 9.44209V12.3533L11 13.7258ZM12.6461 20.0476C11.7303 20.0476 10.8801 19.7564 10.1604 19.2366L14.4557 16.8658C14.7173 16.7203 14.8482 16.5124 14.8482 16.2211V10.1905L16.658 11.1886C16.8105 11.2717 16.876 11.3965 16.876 11.563V16.1379C16.876 18.3629 15.0662 20.0474 12.6461 20.0474V20.0476ZM7.47863 15.4103L3.314 13.1229C2.11471 12.4573 1.30808 11.0433 1.30808 9.67088C1.30808 8.06965 2.31106 6.6348 3.85903 6.03168V10.773C3.85903 11.0642 3.98995 11.2721 4.25151 11.4177L9.70253 14.4328L7.91464 15.4103C7.76209 15.4934 7.63117 15.4934 7.47863 15.4103ZM7.23892 18.8207C4.77508 18.8207 2.96533 17.0531 2.96533 14.8696C2.96533 14.7032 2.98719 14.5368 3.00886 14.3704L7.30418 16.7412C7.56574 16.8867 7.82752 16.8867 8.08909 16.7412L13.5618 13.726V15.7015C13.5618 15.8679 13.4964 15.9927 13.3438 16.0758L9.17918 18.3633C8.61225 18.6752 7.93631 18.8207 7.23869 18.8207H7.23892ZM12.6461 21.2952C15.2844 21.2952 17.4865 19.5069 17.9882 17.1362C20.4301 16.5331 22 14.3495 22 12.1245C22 10.6688 21.346 9.25482 20.1685 8.23581C20.2775 7.79908 20.343 7.36234 20.343 6.92582C20.343 3.95215 17.8137 1.72691 14.892 1.72691C14.3034 1.72691 13.7365 1.80999 13.1695 1.99726C12.1882 1.08223 10.8364 0.5 9.35381 0.5C6.71557 0.5 4.51352 2.28829 4.01185 4.65902C1.56987 5.26214 0 7.44564 0 9.67067C0 11.1264 0.654039 12.5404 1.83147 13.5594C1.72246 13.9961 1.65702 14.4328 1.65702 14.8694C1.65702 17.8431 4.1863 20.0683 7.108 20.0683C7.69661 20.0683 8.26354 19.9852 8.83046 19.7979C9.81155 20.713 11.1634 21.2952 12.6461 21.2952Z"
fill="currentColor"
/>
</svg>
)
}
export function IconAnthropic(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M13.7891 3.93188L20.2223 20.068H23.7502L17.317 3.93188H13.7891Z" />
<path
fill="currentColor"
d="M6.32538 13.6827L8.52662 8.01201L10.7279 13.6827H6.32538ZM6.68225 3.93188L0.25 20.068H3.84652L5.16202 16.6794H11.8914L13.2067 20.068H16.8033L10.371 3.93188H6.68225Z"
/>
</svg>
)
}
export function IconXai(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path
d="M9.16861 16.0529L17.2018 9.85156C17.5957 9.54755 18.1586 9.66612 18.3463 10.1384C19.3339 12.6288 18.8926 15.6217 16.9276 17.6766C14.9626 19.7314 12.2285 20.1821 9.72948 19.1557L6.9995 20.4775C10.9151 23.2763 15.6699 22.5841 18.6411 19.4749C20.9979 17.0103 21.7278 13.6508 21.0453 10.6214L21.0515 10.6278C20.0617 6.17736 21.2948 4.39847 23.8207 0.760904C23.8804 0.674655 23.9402 0.588405 24 0.5L20.6762 3.97585V3.96506L9.16658 16.0551"
fill="currentColor"
/>
<path
d="M7.37742 16.7017C4.67579 14.0395 5.14158 9.91963 7.44676 7.54383C9.15135 5.78544 11.9442 5.06779 14.3821 6.12281L17.0005 4.87559C16.5288 4.52392 15.9242 4.14566 15.2305 3.87986C12.0948 2.54882 8.34069 3.21127 5.79171 5.8386C3.33985 8.36779 2.56881 12.2567 3.89286 15.5751C4.88192 18.0552 3.26056 19.8094 1.62731 21.5801C1.04853 22.2078 0.467774 22.8355 0 23.5L7.3754 16.7037"
fill="currentColor"
/>
</svg>
)
}
export function IconAlibaba(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" fill="none">
<path
fill-rule="evenodd"
clip-rule="evenodd"
fill="currentColor"
d="M11.6043 0.340162C11.9973 1.03016 12.3883 1.72215 12.7783 2.41514C12.7941 2.44286 12.8169 2.46589 12.8445 2.48187C12.8721 2.49786 12.9034 2.50624 12.9353 2.50614H18.4873C18.6612 2.50614 18.8092 2.61614 18.9332 2.83314L20.3872 5.40311C20.5772 5.74011 20.6272 5.88111 20.4112 6.24011C20.1512 6.6701 19.8982 7.1041 19.6512 7.54009L19.2842 8.19809C19.1782 8.39409 19.0612 8.47809 19.2442 8.71008L21.8962 13.347C22.0682 13.648 22.0072 13.841 21.8532 14.117C21.4162 14.902 20.9712 15.681 20.5182 16.457C20.3592 16.729 20.1662 16.832 19.8382 16.827C19.0612 16.811 18.2863 16.817 17.5113 16.843C17.4946 16.8439 17.4785 16.8489 17.4644 16.8576C17.4502 16.8664 17.4385 16.8785 17.4303 16.893C16.5361 18.4773 15.6344 20.0573 14.7253 21.633C14.5563 21.926 14.3453 21.996 14.0003 21.997C13.0033 22 11.9983 22.001 10.9833 21.999C10.8889 21.9987 10.7961 21.9735 10.7145 21.9259C10.6328 21.8783 10.5652 21.8101 10.5184 21.728L9.18337 19.405C9.1756 19.3898 9.16368 19.3771 9.14898 19.3684C9.13429 19.3598 9.11743 19.3554 9.10037 19.356H3.98244C3.69744 19.386 3.42944 19.355 3.17745 19.264L1.57447 16.494C1.52706 16.412 1.50193 16.319 1.50158 16.2243C1.50123 16.1296 1.52567 16.0364 1.57247 15.954L2.77945 13.834C2.79665 13.8041 2.80569 13.7701 2.80569 13.7355C2.80569 13.701 2.79665 13.667 2.77945 13.637C2.15073 12.5485 1.52573 11.4579 0.904476 10.3651L0.114486 8.97008C-0.0455115 8.66008 -0.0585113 8.47409 0.209485 8.00509C0.674479 7.1921 1.13647 6.38011 1.59647 5.56911C1.72847 5.33512 1.90046 5.23512 2.18046 5.23412C3.04344 5.23048 3.90644 5.23015 4.76943 5.23312C4.79123 5.23295 4.81259 5.22704 4.83138 5.21597C4.85016 5.20491 4.8657 5.1891 4.87643 5.17012L7.68239 0.275163C7.72491 0.200697 7.78631 0.138751 7.86039 0.0955646C7.93448 0.0523783 8.01863 0.0294762 8.10439 0.0291651C8.62838 0.0281651 9.15737 0.029165 9.68736 0.0231651L10.7044 0.000165317C11.0453 -0.00283466 11.4283 0.032165 11.6043 0.340162ZM8.17238 0.743158C8.16185 0.743152 8.15149 0.745921 8.14236 0.751187C8.13323 0.756454 8.12565 0.764031 8.12038 0.773158L5.25442 5.78811C5.24066 5.81174 5.22097 5.83137 5.19729 5.84505C5.17361 5.85873 5.14677 5.86599 5.11942 5.86611H2.25346C2.19746 5.86611 2.18346 5.89111 2.21246 5.94011L8.02239 16.096C8.04739 16.138 8.03539 16.158 7.98839 16.159L5.19342 16.174C5.15256 16.1727 5.11214 16.1828 5.07678 16.2033C5.04141 16.2238 5.01253 16.2539 4.99342 16.29L3.67344 18.6C3.62944 18.678 3.65244 18.718 3.74144 18.718L9.45737 18.726C9.50337 18.726 9.53737 18.746 9.56137 18.787L10.9643 21.241C11.0103 21.322 11.0563 21.323 11.1033 21.241L16.1093 12.481L16.8923 11.0991C16.897 11.0905 16.904 11.0834 16.9125 11.0785C16.9209 11.0735 16.9305 11.0709 16.9403 11.0709C16.9501 11.0709 16.9597 11.0735 16.9681 11.0785C16.9765 11.0834 16.9835 11.0905 16.9883 11.0991L18.4123 13.629C18.4229 13.648 18.4385 13.6637 18.4573 13.6746C18.4761 13.6855 18.4975 13.6912 18.5193 13.691L21.2822 13.671C21.2893 13.6711 21.2963 13.6693 21.3024 13.6658C21.3086 13.6623 21.3137 13.6572 21.3172 13.651C21.3206 13.6449 21.3224 13.638 21.3224 13.631C21.3224 13.624 21.3206 13.6172 21.3172 13.611L18.4173 8.52508C18.4068 8.50809 18.4013 8.48853 18.4013 8.46859C18.4013 8.44864 18.4068 8.42908 18.4173 8.41209L18.7102 7.90509L19.8302 5.92811C19.8542 5.88711 19.8422 5.86611 19.7952 5.86611H8.20038C8.14138 5.86611 8.12738 5.84011 8.15738 5.78911L9.59137 3.28413C9.60211 3.26706 9.60781 3.24731 9.60781 3.22714C9.60781 3.20697 9.60211 3.18721 9.59137 3.17014L8.22538 0.774158C8.22016 0.764697 8.21248 0.756822 8.20315 0.751365C8.19382 0.745909 8.18319 0.743073 8.17238 0.743158ZM14.4623 8.76308C14.5083 8.76308 14.5203 8.78308 14.4963 8.82308L13.6643 10.2881L11.0513 14.873C11.0464 14.8819 11.0392 14.8894 11.0304 14.8945C11.0216 14.8996 11.0115 14.9022 11.0013 14.902C10.9912 14.902 10.9813 14.8993 10.9725 14.8942C10.9637 14.8891 10.9564 14.8818 10.9513 14.873L7.49839 8.84108C7.47839 8.80708 7.48839 8.78908 7.52639 8.78708L7.74239 8.77508L14.4643 8.76308H14.4623Z"
/>
</svg>
)
}
export function IconMoonshotAI(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M20.052 6.0364C18.7527 4.28846 16.8758 2.94985 14.6066 2.34296C12.3374 1.73606 10.0411 1.95816 8.04092 2.82331L20.052 6.0364ZM3.75774 6.34071C4.64576 5.04902 5.81872 3.99836 7.16325 3.25459L16.0115 5.62191C15.4308 5.95635 14.8088 6.53268 14.3273 7.16626L21.6025 9.11263C21.809 9.79398 21.9435 10.5003 22 11.2213L3.75774 6.34071ZM21.6866 14.5876C21.584 14.9707 21.4603 15.3425 21.3172 15.7019L2.10543 10.5623C2.16147 10.1792 2.24079 9.7957 2.34339 9.41263C2.58479 8.51262 2.94172 7.67459 3.39435 6.91016L12.7957 9.42554C12.4194 9.96271 12.0766 10.5464 11.7749 11.1709L21.8517 13.8671C21.8056 14.1077 21.7504 14.3478 21.6862 14.5884L21.6866 14.5876ZM2.58134 15.3529C2.11535 14.05 1.91662 12.6408 2.03215 11.2088L10.8985 13.5808C10.8347 13.7823 10.7748 13.9863 10.7192 14.1933C10.6062 14.6147 10.514 15.0352 10.4424 15.4527L20.0166 18.0142C19.5709 18.6051 19.0631 19.1406 18.5057 19.6132L2.58134 15.3529ZM9.42338 21.6568C6.40111 20.8481 4.07415 18.7424 2.88266 16.1001L10.0976 18.0305C10.0859 18.7024 10.1278 19.3571 10.2196 19.9851L15.4619 21.3874C13.5906 22.0743 11.496 22.2112 9.42338 21.6568Z"
fill="currentColor"
/>
</svg>
)
}
export function IconZai(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12.105 2L9.927 4.953H.653L2.83 2h9.276zM23.254 19.048L21.078 22h-9.242l2.174-2.952h9.244zM24 2L9.264 22H0L14.736 2H24z"></path>
</svg>
)
}
export function IconStealth(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 18" fill="none">
<path
d="M24 15.5L18.1816 14.2871L18.1328 14.3115C16.9036 11.7879 15.6135 9.29301 14.2607 6.82812C14.0172 6.38435 13.771 5.94188 13.5234 5.5C13.6911 5.97998 13.8606 6.45942 14.0322 6.9375C14.9902 9.60529 16.012 12.2429 17.0947 14.8516L12 17.5L6.9043 14.8516C7.98712 12.2428 9.00977 9.6054 9.96777 6.9375C10.1394 6.45942 10.3089 5.97998 10.4766 5.5C10.229 5.94188 9.98281 6.38435 9.73926 6.82812C8.38629 9.29339 7.09557 11.7884 5.86621 14.3125L5.81738 14.2871L0 15.5L12 0.5L24 15.5Z"
fill="currentColor"
/>
</svg>
)
}
@@ -0,0 +1,9 @@
export function Legal() {
return (
<div data-component="legal">
<span>
©{new Date().getFullYear()} <a href="https://anoma.ly">Anomaly</a>
</span>
</div>
)
}
@@ -0,0 +1,66 @@
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
[data-component="modal"][data-slot="overlay"] {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.5);
animation: fadeIn 0.2s ease;
@media (prefers-color-scheme: dark) {
background-color: rgba(0, 0, 0, 0.7);
}
[data-slot="content"] {
background-color: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-md);
padding: var(--space-6);
min-width: 400px;
max-width: 90vw;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
animation: slideUp 0.2s ease;
@media (max-width: 30rem) {
min-width: 300px;
padding: var(--space-4);
}
@media (prefers-color-scheme: dark) {
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}
}
[data-slot="title"] {
margin: 0 0 var(--space-4) 0;
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--color-text);
}
}
@@ -0,0 +1,24 @@
import { JSX, Show } from "solid-js"
import "./modal.css"
interface ModalProps {
open: boolean
onClose: () => void
title?: string
children: JSX.Element
}
export function Modal(props: ModalProps) {
return (
<Show when={props.open}>
<div data-component="modal" data-slot="overlay" onClick={props.onClose}>
<div data-slot="content" onClick={(e) => e.stopPropagation()}>
<Show when={props.title}>
<h2 data-slot="title">{props.title}</h2>
</Show>
{props.children}
</div>
</div>
</Show>
)
}
@@ -1,25 +0,0 @@
export function formatDateForTable(date: Date) {
const options: Intl.DateTimeFormatOptions = {
day: "numeric",
month: "short",
hour: "numeric",
minute: "2-digit",
hour12: true,
}
return date.toLocaleDateString("en-GB", options).replace(",", ",")
}
export function formatDateUTC(date: Date) {
const options: Intl.DateTimeFormatOptions = {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
timeZoneName: "short",
timeZone: "UTC",
}
return date.toLocaleDateString("en-US", options)
}
@@ -15,6 +15,7 @@ export function useAuthSession() {
return useSession<AuthSession>({
password: "0".repeat(32),
name: "auth",
maxAge: 60 * 60 * 24 * 365,
cookie: {
secure: false,
httpOnly: true,
+21 -19
View File
@@ -1,10 +1,8 @@
import { getRequestEvent } from "solid-js/web"
import { and, Database, eq, inArray } from "@opencode/console-core/drizzle/index.js"
import { WorkspaceTable } from "@opencode/console-core/schema/workspace.sql.js"
import { UserTable } from "@opencode/console-core/schema/user.sql.js"
import { and, Database, eq, inArray, 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 { AccountTable } from "@opencode/console-core/schema/account.sql.js"
import { Actor } from "@opencode/console-core/actor.js"
import { Actor } from "@opencode-ai/console-core/actor.js"
import { createClient } from "@openauthjs/openauth/client"
import { useAuthSession } from "./auth.session"
@@ -54,25 +52,29 @@ export const getActor = async (workspace?: string): Promise<Actor.Info> => {
}
const accounts = Object.keys(auth.data.account ?? {})
if (accounts.length) {
const result = await Database.transaction(async (tx) => {
return await tx
.select({
user: UserTable,
})
.from(AccountTable)
.innerJoin(UserTable, and(eq(UserTable.email, AccountTable.email)))
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
.where(and(inArray(AccountTable.id, accounts), eq(WorkspaceTable.id, workspace)))
const user = await Database.use((tx) =>
tx
.select()
.from(UserTable)
.where(and(eq(UserTable.workspaceID, workspace), inArray(UserTable.accountID, accounts)))
.limit(1)
.execute()
.then((x) => x[0])
})
if (result) {
.then((x) => x[0]),
)
if (user) {
await Database.use((tx) =>
tx
.update(UserTable)
.set({ timeSeen: sql`now()` })
.where(and(eq(UserTable.workspaceID, workspace), eq(UserTable.id, user.id))),
)
return {
type: "user",
properties: {
userID: result.user.id,
workspaceID: result.user.workspaceID,
userID: user.id,
workspaceID: user.workspaceID,
accountID: user.accountID,
role: user.role,
},
}
}
@@ -1,4 +1,4 @@
import { Actor } from "@opencode/console-core/actor.js"
import { Actor } from "@opencode-ai/console-core/actor.js"
import { getActor } from "./auth"
export async function withActor<T>(fn: () => T, workspace?: string) {
+34
View File
@@ -0,0 +1,34 @@
import { query } from "@solidjs/router"
export const github = query(async () => {
"use server"
const headers = {
"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",
}
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 }),
])
const [release] = releases
const contributorCount = Number.parseInt(
contributors.headers
.get("Link")!
.match(/&page=(\d+)>; rel="last"/)!
.at(1)!,
)
return {
stars: meta.stargazers_count,
release: {
name: release.name,
url: release.html_url,
},
contributors: contributorCount,
}
} catch (e) {
console.error(e)
}
return undefined
}, "github")
+20 -2
View File
@@ -1,11 +1,29 @@
import { Account } from "@opencode/console-core/account.js"
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"
export async function GET(input: APIEvent) {
try {
const workspaces = await withActor(async () => Account.workspaces())
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}`)
} catch {
return redirect("/auth/authorize")
@@ -1,7 +1,7 @@
import type { APIEvent } from "@solidjs/start/server"
import { json } from "@solidjs/router"
import { Database } from "@opencode/console-core/drizzle/index.js"
import { UserTable } from "@opencode/console-core/schema/user.sql.js"
import { Database } from "@opencode-ai/console-core/drizzle/index.js"
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
export async function GET(evt: APIEvent) {
return json({
File diff suppressed because it is too large Load Diff
+771 -157
View File
@@ -1,14 +1,18 @@
import "./index.css"
import { Title } from "@solidjs/meta"
import { onCleanup, onMount } from "solid-js"
import logoLight from "../asset/logo-ornate-light.svg"
import logoDark from "../asset/logo-ornate-dark.svg"
import IMG_SPLASH from "../asset/lander/screenshot-splash.png"
import { Title, Meta, Link } from "@solidjs/meta"
import { HttpHeader } from "@solidjs/start"
import video from "../asset/lander/opencode-min.mp4"
import videoPoster from "../asset/lander/opencode-poster.png"
import { IconCopy, IconCheck } from "../component/icon"
import { createAsync, query } from "@solidjs/router"
import { getActor } from "~/context/auth"
import { withActor } from "~/context/auth.withActor"
import { Account } from "@opencode/console-core/account.js"
import { A, createAsync } from "@solidjs/router"
import { EmailSignup } from "~/component/email-signup"
import { Tabs } from "@kobalte/core/tabs"
import { Faq } from "~/component/faq"
import { Header } from "~/component/header"
import { Footer } from "~/component/footer"
import { Legal } from "~/component/legal"
import { github } from "~/lib/github"
import { createMemo } from "solid-js"
function CopyStatus() {
return (
@@ -19,165 +23,775 @@ function CopyStatus() {
)
}
const defaultWorkspace = query(async () => {
"use server"
const actor = await getActor()
if (actor.type === "account") {
const workspaces = await withActor(() => Account.workspaces())
return workspaces[0].id
}
}, "defaultWorkspace")
export default function Home() {
const workspace = createAsync(() => defaultWorkspace())
onMount(() => {
const commands = document.querySelectorAll("[data-copy]")
for (const button of commands) {
const callback = () => {
const text = button.textContent
if (text) {
navigator.clipboard.writeText(text)
button.setAttribute("data-copied", "")
setTimeout(() => {
button.removeAttribute("data-copied")
}, 1500)
}
}
button.addEventListener("click", callback)
onCleanup(() => {
button.removeEventListener("click", callback)
})
const githubData = createAsync(() => github())
const release = createMemo(() => githubData()?.release)
const handleCopyClick = (event: Event) => {
const button = event.currentTarget as HTMLButtonElement
const text = button.textContent
if (text) {
navigator.clipboard.writeText(text)
button.setAttribute("data-copied", "")
setTimeout(() => {
button.removeAttribute("data-copied")
}, 1500)
}
})
}
return (
<main data-page="home">
<Title>opencode | AI coding agent built for the terminal</Title>
<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="icon" type="image/svg+xml" href="/favicon.svg" />
<Meta property="og:image" content="/social-share.png" />
<Meta name="twitter:image" content="/social-share.png" />
<div data-component="container">
<Header />
<div data-component="content">
<section data-component="top">
<img data-slot="logo light" src={logoLight} alt="opencode logo light" />
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
<h1 data-slot="title">The AI coding agent built for the terminal</h1>
<div data-slot="login">
<a href="/auth">opencode zen</a>
</div>
</section>
<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"
>
Whats new in {release()?.name ?? "the latest release"}
</a>
<strong>The AI coding agent built for the terminal</strong>
<p>
OpenCode is fully open source, giving you control and freedom to use any provider, any model, and any
editor.
</p>
<a href="/docs">
<span>Read docs </span>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.5 12L17 12M13 16.5L17.5 12L13 7.5"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="square"
/>
</svg>
</a>
</div>
<div data-slot="installation">
<Tabs
as="section"
aria-label="Install options"
class="tabs"
data-component="tabs"
data-active="curl"
defaultValue="curl"
>
<Tabs.List data-slot="tablist">
<Tabs.Trigger value="curl" data-slot="tab">
curl
</Tabs.Trigger>
<Tabs.Trigger value="npm" data-slot="tab">
npm
</Tabs.Trigger>
<Tabs.Trigger value="bun" data-slot="tab">
bun
</Tabs.Trigger>
<Tabs.Trigger value="brew" data-slot="tab">
brew
</Tabs.Trigger>
<Tabs.Trigger value="paru" data-slot="tab">
paru
</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<div data-slot="panels">
<Tabs.Content as="pre" data-slot="panel" value="curl">
<button data-copy data-slot="command" onClick={handleCopyClick}>
<span data-slot="command-script">
<span>curl -fsSL </span>
<span data-slot="protocol">https://</span>
<span data-slot="highlight">opencode.ai/install</span>
<span> | bash</span>
</span>
<CopyStatus />
</button>
</Tabs.Content>
<Tabs.Content as="pre" data-slot="panel" value="npm">
<button data-copy data-slot="command" onClick={handleCopyClick}>
<span>
<span data-slot="protocol">npm i -g </span>
<span data-slot="highlight">opencode-ai</span>
</span>
<CopyStatus />
</button>
</Tabs.Content>
<Tabs.Content as="pre" data-slot="panel" value="bun">
<button data-copy data-slot="command" onClick={handleCopyClick}>
<span>
<span data-slot="protocol">bun add -g </span>
<span data-slot="highlight">opencode-ai</span>
</span>
<CopyStatus />
</button>
</Tabs.Content>
<Tabs.Content as="pre" data-slot="panel" value="brew">
<button data-copy data-slot="command" onClick={handleCopyClick}>
<span>
<span data-slot="protocol">brew install </span>
<span data-slot="highlight">opencode</span>
</span>
<CopyStatus />
</button>
</Tabs.Content>
<Tabs.Content as="pre" data-slot="panel" value="paru">
<button data-copy data-slot="command" onClick={handleCopyClick}>
<span>
<span data-slot="protocol">paru -S </span>
<span data-slot="highlight">opencode</span>
</span>
<CopyStatus />
</button>
</Tabs.Content>
</div>
</Tabs>
</div>
</section>
<section data-component="cta">
<div data-slot="left">
<a href="/docs">Get Started</a>
</div>
<div data-slot="center">
<a href="/auth">opencode zen</a>
</div>
<div data-slot="right">
<button data-copy data-slot="command">
<span>
<span>curl -fsSL </span>
<span data-slot="protocol">https://</span>
<span data-slot="highlight">opencode.ai/install</span>
<span> | bash</span>
</span>
<CopyStatus />
</button>
</div>
</section>
<section data-component="video">
<video src={video} autoplay playsinline loop muted preload="auto" poster={videoPoster}>
Your browser does not support the video tag.
</video>
</section>
<section data-component="features">
<ul data-slot="list">
<li>
<strong>Native TUI</strong> A responsive, native, themeable terminal UI
</li>
<li>
<strong>LSP enabled</strong> Automatically loads the right LSPs for the LLM
</li>
<li>
<strong>opencode zen</strong> A <a href="/docs/zen">curated list of models</a> provided by opencode{" "}
<label>New</label>
</li>
<li>
<strong>Multi-session</strong> Start multiple agents in parallel on the same project
</li>
<li>
<strong>Shareable links</strong> Share a link to any sessions for reference or to debug
</li>
<li>
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
</li>
<li>
<strong>Use any model</strong> Supports 75+ LLM providers through{" "}
<a href="https://models.dev">Models.dev</a>, including local models
</li>
</ul>
</section>
<section data-component="what">
<div data-slot="section-title">
<h3>What is OpenCode?</h3>
<p>OpenCode is an open source agent that helps you write and run code directly from the terminal.</p>
</div>
<ul>
<li>
<span>[*]</span>
<div>
<strong>Native TUI</strong> A responsive, native, themeable terminal UI
</div>
</li>
<li>
<span>[*]</span>
<div>
<strong>LSP enabled</strong> Automatically loads the right LSPs for the LLM
</div>
</li>
<li>
<span>[*]</span>
<div>
<strong>Multi-session</strong> Start multiple agents in parallel on the same project
</div>
</li>
<li>
<span>[*]</span>
<div>
<strong>Share links</strong> Share a link to any session for reference or to debug
</div>
</li>
<li>
<span>[*]</span>
<div>
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
</div>
</li>
<li>
<span>[*]</span>
<div>
<strong>Any model</strong> 75+ LLM providers through Models.dev, including local models
</div>
</li>
<li>
<span>[*]</span>
<div>
<strong>Any editor</strong> OpenCode runs in your terminal, pair it with any IDE
</div>
</li>
</ul>
</section>
<section data-component="install">
<div data-component="method">
<h3 data-component="title">npm</h3>
<button data-copy data-slot="button">
<span>
npm install -g <strong>opencode-ai</strong>
</span>
<CopyStatus />
</button>
</div>
<div data-component="method">
<h3 data-component="title">bun</h3>
<button data-copy data-slot="button">
<span>
bun install -g <strong>opencode-ai</strong>
</span>
<CopyStatus />
</button>
</div>
<div data-component="method">
<h3 data-component="title">homebrew</h3>
<button data-copy data-slot="button">
<span>
brew install <strong>sst/tap/opencode</strong>
</span>
<CopyStatus />
</button>
</div>
<div data-component="method">
<h3 data-component="title">paru</h3>
<button data-copy data-slot="button">
<span>
paru -S <strong>opencode-bin</strong>
</span>
<CopyStatus />
</button>
</div>
</section>
<section data-component="growth">
<div data-slot="section-title">
<h3>The open source AI coding agent</h3>
<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.
</p>
</div>
<section data-component="screenshots">
<figure>
<figcaption>opencode TUI with the tokyonight theme</figcaption>
<a href="/docs/cli">
<img src={IMG_SPLASH} alt="opencode TUI with tokyonight theme" />
</a>
</figure>
</section>
<div data-component="growth-stats">
<div data-component="growth-stat">
<div data-component="stat-illustration">
<svg width="205" height="264" viewBox="0 0 205 264" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.5" clip-path="url(#clip0_236_15902)">
<mask
id="mask0_236_15902"
style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="205"
height="264"
>
<path
d="M27.2119 253.122L0 264H205V0L192.109 17.8482L175.297 43.8089L152.877 59.95L137.902 77.6701L126.989 87.3251L118.603 106.449L103.114 123.643L93.359 141.714L84.2883 160.311L78.7262 177.329L67.773 193.997L62.8098 212.068L57.3332 231.191L42.5292 243.824L27.2119 253.122Z"
fill="url(#paint0_linear_236_15902)"
/>
</mask>
<g mask="url(#mask0_236_15902)">
<path
d="M150.932 -135.014L-251.766 267.684M154.115 -131.832L-248.582 270.865M157.295 -128.65L-245.402 274.047M160.479 -125.469L-242.219 277.229M163.662 -122.287L-239.035 280.41M166.842 -119.105L-235.855 283.592M170.025 -115.924L-232.672 286.773M173.205 -112.742L-229.492 289.955M176.385 -109.561L-226.312 293.137M179.568 -106.377L-223.129 296.32M182.752 -103.193L-219.945 299.504M185.936 -100.012L-216.762 302.686M189.119 -96.8301L-213.578 305.867M192.295 -93.6484L-210.402 309.049M195.479 -90.4668L-207.219 312.23M198.662 -87.2852L-204.035 315.412M201.842 -84.1035L-200.855 318.594M205.025 -80.9219L-197.672 321.775M208.209 -77.7383L-194.488 324.959M211.389 -74.5586L-191.309 328.139M214.568 -71.375L-188.129 331.322M217.752 -68.1934L-184.945 334.504M220.936 -65.0117L-181.762 337.686M224.119 -61.8281L-178.578 340.869M227.303 -58.6465L-175.395 344.051M230.482 -55.4668L-172.215 347.23M233.662 -52.2832L-169.035 350.414M236.846 -49.0996L-165.852 353.598M240.025 -45.9199L-162.672 356.777M243.209 -42.7383L-159.488 359.959M246.393 -39.5547L-156.305 363.143M249.572 -36.375L-153.125 366.322M252.756 -33.1934L-149.941 369.504M255.936 -30.0098L-146.762 372.688M259.119 -26.8281L-143.578 375.869M262.303 -23.6465L-140.395 379.051M265.486 -20.4609L-137.211 382.236M268.666 -17.2812L-134.031 385.416M271.85 -14.0996L-130.848 388.598M275.029 -10.918L-127.668 391.779M278.209 -7.73633L-124.488 394.961M281.393 -4.55469L-121.305 398.143M284.576 -1.37305L-118.121 401.324M287.756 1.80859L-114.941 404.506M290.94 4.99023L-111.758 407.688M294.119 8.17383L-108.578 410.871M297.303 11.3574L-105.395 414.055M300.486 14.5391L-102.211 417.236M303.67 17.7207L-99.0273 420.418M306.85 20.9023L-95.8477 423.6M310.033 24.084L-92.6641 426.781M313.213 27.2656L-89.4844 429.963M316.393 30.4473L-86.3047 433.145M319.576 33.6289L-83.1211 436.326M322.76 36.8125L-79.9375 439.51M325.94 39.9941L-76.7578 442.691M329.123 43.1758L-73.5742 445.873M332.307 46.3574L-70.3906 449.055M335.486 49.541L-67.2109 452.238M338.67 52.7227L-64.0273 455.42M341.854 55.9043L-60.8438 458.602M345.033 59.0859L-57.6641 461.783M348.217 62.2676L-54.4805 464.965M351.397 65.4512L-51.3008 468.148M354.576 68.6328L-48.1211 471.33M357.76 71.8145L-44.9375 474.512M360.943 74.9961L-41.7539 477.693M364.123 78.1777L-38.5742 480.875M367.307 81.3594L-35.3906 484.057M370.49 84.541L-32.207 487.238M373.67 87.7246L-29.0273 490.422M376.854 90.9062L-25.8438 493.604M380.033 94.0859L-22.6641 496.783M383.217 97.2695L-19.4805 499.967M386.4 100.453L-16.2969 503.15M389.58 103.633L-13.1172 506.33M392.76 106.816L-9.9375 509.514"
stroke="#8E8B8B"
/>
</g>
<path
d="M0 264L27.2119 253.122L42.5292 243.824L57.3332 231.191L62.8098 212.068L67.773 193.997L78.7262 177.329L84.2883 160.311L93.359 141.714L103.114 123.643L118.603 106.449L126.989 87.3251L137.902 77.6701L152.877 59.95L175.297 43.8089L192.109 17.8482L205 0"
stroke="#BCBBBB"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_236_15902"
x1="102.5"
y1="-34.8571"
x2="102.5"
y2="264"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#565656" />
<stop offset="1" stop-color="#F1F0F0" stop-opacity="0" />
</linearGradient>
<clipPath id="clip0_236_15902">
<rect width="205" height="264" fill="white" />
</clipPath>
</defs>
</svg>
</div>
<span>
<figure>Fig 1.</figure> <strong>26K</strong> GitHub Stars
</span>
</div>
<footer data-component="footer">
<div data-slot="cell">
<a href="https://x.com/opencode">X.com</a>
</div>
<div data-slot="cell">
<a href="https://github.com/sst/opencode">GitHub</a>
</div>
<div data-slot="cell">
<a href="https://opencode.ai/discord">Discord</a>
</div>
</footer>
</div>
<div data-component="legal">
<span>
©2025 <a href="https://anoma.ly">Anomaly Innovations</a>
</span>
<div data-component="growth-stat">
<div data-component="stat-illustration">
<svg width="205" height="264" viewBox="0 0 205 264" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.5" clip-path="url(#clip0_236_15557)">
<g clip-path="url(#clip1_236_15557)">
<rect opacity="0.81" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.46" x="14" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.86" x="28" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.08" x="42" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.23" x="56" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.9" x="70" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.59" x="84" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.8" x="98" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.21" x="112" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.22" x="126" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.62" x="140" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.41" x="154" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.22" x="168" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.25" x="182" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.34" x="196" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.84" y="14" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.79" x="14" y="14" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.49" x="28" y="14" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.49" x="42" y="14" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.05" x="56" y="14" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.59" x="70" y="14" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.44" x="84" y="14" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.21" x="98" y="14" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.53" x="112" y="14" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.81" x="126" y="14" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.24" x="140" y="14" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.61" x="154" y="14" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.14" x="168" y="14" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.26" x="182" y="14" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.8" x="196" y="14" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.02" y="28" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.69" x="14" y="28" width="6" height="6" fill="#CFCECD" />
<rect x="28" y="28" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.4" x="42" y="28" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.88" x="56" y="28" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.38" x="70" y="28" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.38" x="84" y="28" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.78" x="98" y="28" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.49" x="112" y="28" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.13" x="126" y="28" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.76" x="140" y="28" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.35" x="154" y="28" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.59" x="168" y="28" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.34" x="182" y="28" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.3" x="196" y="28" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.6" y="42" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.3" x="14" y="42" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.65" x="28" y="42" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.41" x="42" y="42" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.84" x="56" y="42" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.33" x="70" y="42" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.81" x="84" y="42" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.78" x="98" y="42" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.72" x="112" y="42" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.71" x="126" y="42" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.46" x="140" y="42" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.06" x="154" y="42" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.05" x="168" y="42" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.44" x="182" y="42" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.09" x="196" y="42" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.03" y="56" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.58" x="14" y="56" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.24" x="28" y="56" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.1" x="42" y="56" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.09" x="56" y="56" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.3" x="70" y="56" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.6" x="84" y="56" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.39" x="98" y="56" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.53" x="112" y="56" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.83" x="126" y="56" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.25" x="140" y="56" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.87" x="154" y="56" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.38" x="168" y="56" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.19" x="182" y="56" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.89" x="196" y="56" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.98" y="70" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.26" x="14" y="70" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.79" x="28" y="70" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.67" x="56" y="70" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.48" x="70" y="70" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.76" x="84" y="70" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.72" x="98" y="70" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.01" x="112" y="70" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.46" x="126" y="70" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.27" x="140" y="70" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.78" x="154" y="70" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.16" x="168" y="70" width="6" height="6" fill="#CFCECD" />
<rect x="182" y="70" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.86" x="196" y="70" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.18" y="84" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.04" x="14" y="84" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.61" x="28" y="84" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.47" x="42" y="84" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.81" x="56" y="84" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.98" x="70" y="84" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.3" x="84" y="84" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.1" x="98" y="84" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.42" x="112" y="84" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.66" x="126" y="84" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.68" x="140" y="84" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.35" x="154" y="84" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.6" x="168" y="84" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.95" x="182" y="84" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.05" x="196" y="84" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.77" y="98" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.06" x="14" y="98" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.45" x="28" y="98" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.73" x="42" y="98" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.21" x="70" y="98" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.18" x="84" y="98" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.92" x="98" y="98" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.26" x="112" y="98" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.21" x="126" y="98" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.27" x="140" y="98" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.84" x="154" y="98" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.74" x="168" y="98" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.53" x="182" y="98" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.9" x="196" y="98" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.32" y="112" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.75" x="14" y="112" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.69" x="28" y="112" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.66" x="42" y="112" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.93" x="56" y="112" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.32" x="70" y="112" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.52" x="84" y="112" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.02" x="98" y="112" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.88" x="126" y="112" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.12" x="140" y="112" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.93" x="154" y="112" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.79" x="168" y="112" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.24" x="182" y="112" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.64" x="196" y="112" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.57" y="126" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.6" x="14" y="126" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.05" x="28" y="126" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.28" x="42" y="126" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.21" x="56" y="126" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.93" x="70" y="126" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.63" x="84" y="126" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.58" x="98" y="126" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.64" x="112" y="126" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.74" x="126" y="126" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.74" x="140" y="126" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.1" x="154" y="126" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.93" x="168" y="126" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.43" x="182" y="126" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.45" x="196" y="126" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.77" y="140" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.78" x="14" y="140" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.18" x="28" y="140" width="6" height="6" fill="#DAD9D9" />
<rect x="42" y="140" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.39" x="56" y="140" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.53" x="70" y="140" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.06" x="84" y="140" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.81" x="98" y="140" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.49" x="112" y="140" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.45" x="126" y="140" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.37" x="140" y="140" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.58" x="154" y="140" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.8" x="168" y="140" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.35" x="182" y="140" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.73" x="196" y="140" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.92" y="154" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.32" x="14" y="154" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.3" x="28" y="154" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.03" x="42" y="154" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.65" x="56" y="154" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.66" x="70" y="154" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.83" x="84" y="154" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.52" x="98" y="154" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.82" x="112" y="154" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.95" x="126" y="154" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.89" x="140" y="154" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.2" x="154" y="154" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.61" x="168" y="154" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.34" x="196" y="154" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.9" y="168" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.99" x="14" y="168" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.49" x="28" y="168" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.84" x="42" y="168" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.67" x="56" y="168" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.92" x="70" y="168" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.79" x="84" y="168" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.8" x="98" y="168" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.74" x="112" y="168" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.38" x="126" y="168" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.56" x="140" y="168" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.7" x="154" y="168" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.47" x="168" y="168" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.92" x="182" y="168" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.19" x="196" y="168" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.12" y="182" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.16" x="14" y="182" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.98" x="28" y="182" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.6" x="42" y="182" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.15" x="56" y="182" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.17" x="70" y="182" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.26" x="84" y="182" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.3" x="98" y="182" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.12" x="112" y="182" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.31" x="126" y="182" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.62" x="140" y="182" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.74" x="154" y="182" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.8" x="168" y="182" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.89" x="182" y="182" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.75" x="196" y="182" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.1" y="196" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.11" x="14" y="196" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.79" x="28" y="196" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.69" x="42" y="196" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.39" x="56" y="196" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.31" x="70" y="196" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.33" x="84" y="196" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.2" x="98" y="196" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.21" x="112" y="196" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.02" x="126" y="196" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.82" x="140" y="196" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.28" x="154" y="196" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.19" x="168" y="196" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.97" x="182" y="196" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.45" x="196" y="196" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.88" y="210" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.58" x="14" y="210" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.53" x="28" y="210" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.89" x="42" y="210" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.38" x="56" y="210" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.73" x="70" y="210" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.87" x="84" y="210" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.35" x="98" y="210" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.61" x="112" y="210" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.8" x="126" y="210" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.87" x="140" y="210" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.77" x="154" y="210" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.94" x="168" y="210" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.59" x="182" y="210" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.37" x="196" y="210" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.7" y="224" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.72" x="14" y="224" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.95" x="28" y="224" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.26" x="42" y="224" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.68" x="56" y="224" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.55" x="70" y="224" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.2" x="84" y="224" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.63" x="98" y="224" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.5" x="112" y="224" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.79" x="126" y="224" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.02" x="140" y="224" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.17" x="154" y="224" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.99" x="168" y="224" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.82" x="182" y="224" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.28" x="196" y="224" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.76" y="238" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.39" x="14" y="238" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.14" x="28" y="238" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.17" x="42" y="238" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.37" x="56" y="238" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.13" x="70" y="238" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.35" x="84" y="238" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.13" x="98" y="238" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.55" x="112" y="238" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.83" x="126" y="238" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.86" x="140" y="238" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.63" x="154" y="238" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.38" x="168" y="238" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.57" x="182" y="238" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.13" x="196" y="238" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.9" y="252" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.63" x="14" y="252" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.23" x="28" y="252" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.56" x="42" y="252" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.38" x="56" y="252" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.19" x="70" y="252" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.29" x="84" y="252" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.78" x="98" y="252" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.14" x="112" y="252" width="6" height="6" fill="#BCBBBB" />
<rect opacity="0.64" x="126" y="252" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.27" x="140" y="252" width="6" height="6" fill="#CFCECD" />
<rect opacity="0.85" x="154" y="252" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.02" x="168" y="252" width="6" height="6" fill="#DAD9D9" />
<rect opacity="0.29" x="182" y="252" width="6" height="6" fill="#8E8B8B" />
<rect opacity="0.4" x="196" y="252" width="6" height="6" fill="#8E8B8B" />
</g>
</g>
<defs>
<clipPath id="clip0_236_15557">
<rect width="205" height="264" fill="white" />
</clipPath>
<clipPath id="clip1_236_15557">
<rect width="236" height="264" fill="white" transform="translate(-0.164062)" />
</clipPath>
</defs>
</svg>
</div>
<span>
<figure>Fig 2.</figure> <strong>188</strong> Contributors
</span>
</div>
<div data-component="growth-stat">
<div data-component="stat-illustration">
<svg width="205" height="264" viewBox="0 0 205 264" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.5">
<path d="M205 0H203.985V264H205V0Z" fill="#8E8B8B" />
<path d="M197.896 34H196.881V264H197.896V34Z" fill="#8E8B8B" />
<path d="M189.777 26H188.762V264H189.777V26Z" fill="#8E8B8B" />
<path d="M183.688 52H182.673V264H183.688V52Z" fill="#8E8B8B" />
<path d="M176.584 0H175.569V264H176.584V0Z" fill="#8E8B8B" />
<path d="M169.48 29H168.465V264H169.48V29Z" fill="#8E8B8B" />
<path d="M162.376 44H161.361V264H162.376V44Z" fill="#8E8B8B" />
<path d="M155.272 65H154.257V264H155.272V65Z" fill="#8E8B8B" />
<path d="M149.183 29H148.168V264H149.183V29Z" fill="#8E8B8B" />
<path d="M142.079 36H141.064V264H142.079V36Z" fill="#8E8B8B" />
<path d="M134.975 48H133.96V264H134.975V48Z" fill="#8E8B8B" />
<path d="M127.871 7H126.856V264H127.871V7Z" fill="#8E8B8B" />
<path d="M120.767 0H119.752V264H120.767V0Z" fill="#8E8B8B" />
<path d="M113.663 14H112.649V264H113.663V14Z" fill="#8E8B8B" />
<path d="M106.559 27H105.545V264H106.559V27Z" fill="#8E8B8B" />
<path d="M99.4554 70H98.4406V264H99.4554V70Z" fill="#8E8B8B" />
<path d="M92.3515 32H91.3366V264H92.3515V32Z" fill="#8E8B8B" />
<path d="M85.2475 35H84.2327V264H85.2475V35Z" fill="#8E8B8B" />
<path d="M78.1436 36H77.1287V264H78.1436V36Z" fill="#8E8B8B" />
<path d="M71.0396 10H70.0248V264H71.0396V10Z" fill="#8E8B8B" />
<path d="M63.9356 42H62.9208V264H63.9356V42Z" fill="#8E8B8B" />
<path d="M56.8317 43H55.8168V264H56.8317V43Z" fill="#8E8B8B" />
<path d="M49.7277 38H48.7129V264H49.7277V38Z" fill="#8E8B8B" />
<path d="M42.6238 56H41.6089V264H42.6238V56Z" fill="#8E8B8B" />
<path d="M36.5347 36H35.5198V264H36.5347V36Z" fill="#8E8B8B" />
<path d="M29.4307 8H28.4158V264H29.4307V8Z" fill="#8E8B8B" />
<path d="M22.3267 20H21.3119V264H22.3267V20Z" fill="#8E8B8B" />
<path d="M15.2228 1H14.2079V264H15.2228V1Z" fill="#8E8B8B" />
<path d="M8.11881 9H7.10396V264H8.11881V9Z" fill="#8E8B8B" />
<path d="M1.01485 31H0V264H1.01485V31Z" fill="#8E8B8B" />
</g>
</svg>
</div>
<span>
<figure>Fig 3.</figure> <strong>200K</strong> Monthly Devs
</span>
</div>
</div>
</div>
</section>
<section data-component="privacy">
<div data-slot="privacy-title">
<h3>Built for privacy first</h3>
<div>
<span>[*]</span>
<p>
OpenCode does not store any of your code or context data, so that it can operate in privacy sensitive
environments. Learn more about <a href="/docs/enterprise/ ">privacy</a>.
</p>
</div>
</div>
</section>
<section data-component="faq">
<div data-slot="section-title">
<h3>FAQ</h3>
</div>
<ul>
<li>
<Faq question="What is OpenCode?">
OpenCode is an open source agent that helps you write and run code directly from the terminal. You can
pair OpenCode with any AI model, and because its terminal-based you can pair it with your preferred
code editor.
</Faq>
</li>
<li>
<Faq question="How do I use OpenCode?">
The easiest way to get started is to read the <a href="/docs">intro</a>.
</Faq>
</li>
<li>
<Faq question="Do I need extra AI subscriptions to use OpenCode?">
Not necessarily, but probably. Youll need an AI subscription if you want to connect OpenCode to a
paid provider, although you can work with{" "}
<a href="/docs/providers/#lm-studio" target="_blank">
local models
</a>{" "}
for free. While we encourage users to use <A href="/zen">Zen</A>, OpenCode works with all popular
providers such as OpenAI, Anthropic, xAI etc.
</Faq>
</li>
<li>
<Faq question="Can I only use OpenCode in the terminal?">
Yes, for now. We are actively working on a desktop app. Join the waitlist for early access.
</Faq>
</li>
<li>
<Faq question="How much does OpenCode cost?">
OpenCode is 100% free to use. Any additional costs will come from your subscription to a model
provider. While OpenCode works with any model provider, we recommend using <A href="/zen">Zen</A>.
</Faq>
</li>
<li>
<Faq question="What about data and privacy?">
Your data and information is only stored when you create sharable links in OpenCode. Learn more about{" "}
<a href="/docs/share/#privacy">share pages</a>.
</Faq>
</li>
<li>
<Faq question="Is OpenCode open source?">
Yes, OpenCode is fully open source. The source code is public on{" "}
<a href="https://github.com/sst/opencode" target="_blank">
GitHub
</a>{" "}
under the{" "}
<a href="https://github.com/sst/opencode?tab=MIT-1-ov-file#readme" target="_blank">
MIT License
</a>
, meaning anyone can use, modify, or contribute to its development. Anyone from the community can file
issues, submit pull requests, and extend functionality.
</Faq>
</li>
</ul>
</section>
<section data-component="zen-cta">
<div data-slot="zen-cta-copy">
<strong>Access reliable optimized models for coding agents</strong>
<p>
Zen gives you access to a handpicked set of AI models that OpenCode has tested and benchmarked
specifically for coding agents. No need to worry about inconsistent performance and quality across
providers, use validated models that work.
</p>
<div data-slot="model-logos">
<div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask
id="mask0_79_128586"
style="mask-type:luminance"
maskUnits="userSpaceOnUse"
x="1"
y="1"
width="22"
height="22"
>
<path d="M23 1.5H1V22.2952H23V1.5Z" fill="white" />
</mask>
<g mask="url(#mask0_79_128586)">
<path
d="M9.43799 9.06943V7.09387C9.43799 6.92749 9.50347 6.80267 9.65601 6.71959L13.8206 4.43211C14.3875 4.1202 15.0635 3.9747 15.7611 3.9747C18.3775 3.9747 20.0347 5.9087 20.0347 7.96734C20.0347 8.11288 20.0347 8.27926 20.0128 8.44564L15.6956 6.03335C15.434 5.88785 15.1723 5.88785 14.9107 6.03335L9.43799 9.06943ZM19.1624 16.7637V12.0431C19.1624 11.7519 19.0315 11.544 18.7699 11.3984L13.2972 8.36234L15.0851 7.3849C15.2377 7.30182 15.3686 7.30182 15.5212 7.3849L19.6858 9.67238C20.8851 10.3379 21.6917 11.7519 21.6917 13.1243C21.6917 14.7047 20.7106 16.1604 19.1624 16.7636V16.7637ZM8.15158 12.6047L6.36369 11.6066C6.21114 11.5235 6.14566 11.3986 6.14566 11.2323V6.65735C6.14566 4.43233 7.93355 2.7478 10.3538 2.7478C11.2697 2.7478 12.1199 3.039 12.8396 3.55886L8.54424 5.92959C8.28268 6.07508 8.15181 6.28303 8.15181 6.57427V12.6049L8.15158 12.6047ZM12 14.7258L9.43799 13.3533V10.4421L12 9.06965L14.5618 10.4421V13.3533L12 14.7258ZM13.6461 21.0476C12.7303 21.0476 11.8801 20.7564 11.1604 20.2366L15.4557 17.8658C15.7173 17.7203 15.8482 17.5124 15.8482 17.2211V11.1905L17.658 12.1886C17.8105 12.2717 17.876 12.3965 17.876 12.563V17.1379C17.876 19.3629 16.0662 21.0474 13.6461 21.0474V21.0476ZM8.47863 16.4103L4.314 14.1229C3.11471 13.4573 2.30808 12.0433 2.30808 10.6709C2.30808 9.06965 3.31106 7.6348 4.85903 7.03168V11.773C4.85903 12.0642 4.98995 12.2721 5.25151 12.4177L10.7025 15.4328L8.91464 16.4103C8.76209 16.4934 8.63117 16.4934 8.47863 16.4103ZM8.23892 19.8207C5.77508 19.8207 3.96533 18.0531 3.96533 15.8696C3.96533 15.7032 3.98719 15.5368 4.00886 15.3704L8.30418 17.7412C8.56574 17.8867 8.82752 17.8867 9.08909 17.7412L14.5618 14.726V16.7015C14.5618 16.8679 14.4964 16.9927 14.3438 17.0758L10.1792 19.3633C9.61225 19.6752 8.93631 19.8207 8.23869 19.8207H8.23892ZM13.6461 22.2952C16.2844 22.2952 18.4865 20.5069 18.9882 18.1362C21.4301 17.5331 23 15.3495 23 13.1245C23 11.6688 22.346 10.2548 21.1685 9.23581C21.2775 8.79908 21.343 8.36234 21.343 7.92582C21.343 4.95215 18.8137 2.72691 15.892 2.72691C15.3034 2.72691 14.7365 2.80999 14.1695 2.99726C13.1882 2.08223 11.8364 1.5 10.3538 1.5C7.71557 1.5 5.51352 3.28829 5.01185 5.65902C2.56987 6.26214 1 8.44564 1 10.6707C1 12.1264 1.65404 13.5404 2.83147 14.5594C2.72246 14.9961 2.65702 15.4328 2.65702 15.8694C2.65702 18.8431 5.1863 21.0683 8.108 21.0683C8.69661 21.0683 9.26354 20.9852 9.83046 20.7979C10.8115 21.713 12.1634 22.2952 13.6461 22.2952Z"
fill="currentColor"
/>
</g>
</svg>
</div>
<div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.7891 3.93164L20.2223 20.0677H23.7502L17.317 3.93164H13.7891Z" fill="currentColor" />
<path
d="M6.32538 13.6824L8.52662 8.01177L10.7279 13.6824H6.32538ZM6.68225 3.93164L0.25 20.0677H3.84652L5.16202 16.6791H11.8914L13.2067 20.0677H16.8033L10.371 3.93164H6.68225Z"
fill="currentColor"
/>
</svg>
</div>
<div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M9.16861 16.0529L17.2018 9.85156C17.5957 9.54755 18.1586 9.66612 18.3463 10.1384C19.3339 12.6288 18.8926 15.6217 16.9276 17.6766C14.9626 19.7314 12.2285 20.1821 9.72948 19.1557L6.9995 20.4775C10.9151 23.2763 15.6699 22.5841 18.6411 19.4749C20.9979 17.0103 21.7278 13.6508 21.0453 10.6214L21.0515 10.6278C20.0617 6.17736 21.2948 4.39847 23.8207 0.760904C23.8804 0.674655 23.9402 0.588405 24 0.5L20.6762 3.97585V3.96506L9.16658 16.0551"
fill="currentColor"
/>
<path
d="M7.37742 16.7017C4.67579 14.0395 5.14158 9.91963 7.44676 7.54383C9.15135 5.78544 11.9442 5.06779 14.3821 6.12281L17.0005 4.87559C16.5288 4.52392 15.9242 4.14566 15.2305 3.87986C12.0948 2.54882 8.34069 3.21127 5.79171 5.8386C3.33985 8.36779 2.56881 12.2567 3.89286 15.5751C4.88192 18.0552 3.26056 19.8094 1.62731 21.5801C1.04853 22.2078 0.467774 22.8355 0 23.5L7.3754 16.7037"
fill="currentColor"
/>
</svg>
</div>
<div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12.6043 1.34016C12.9973 2.03016 13.3883 2.72215 13.7783 3.41514C13.7941 3.44286 13.8169 3.46589 13.8445 3.48187C13.8721 3.49786 13.9034 3.50624 13.9353 3.50614H19.4873C19.6612 3.50614 19.8092 3.61614 19.9332 3.83314L21.3872 6.40311C21.5772 6.74011 21.6272 6.88111 21.4112 7.24011C21.1512 7.6701 20.8982 8.1041 20.6512 8.54009L20.2842 9.19809C20.1782 9.39409 20.0612 9.47809 20.2442 9.71008L22.8962 14.347C23.0682 14.648 23.0072 14.841 22.8532 15.117C22.4162 15.902 21.9712 16.681 21.5182 17.457C21.3592 17.729 21.1662 17.832 20.8382 17.827C20.0612 17.811 19.2863 17.817 18.5113 17.843C18.4946 17.8439 18.4785 17.8489 18.4644 17.8576C18.4502 17.8664 18.4385 17.8785 18.4303 17.893C17.5361 19.4773 16.6344 21.0573 15.7253 22.633C15.5563 22.926 15.3453 22.996 15.0003 22.997C14.0033 23 12.9983 23.001 11.9833 22.999C11.8889 22.9987 11.7961 22.9735 11.7145 22.9259C11.6328 22.8783 11.5652 22.8101 11.5184 22.728L10.1834 20.405C10.1756 20.3898 10.1637 20.3771 10.149 20.3684C10.1343 20.3598 10.1174 20.3554 10.1004 20.356H4.98244C4.69744 20.386 4.42944 20.355 4.17745 20.264L2.57447 17.494C2.52706 17.412 2.50193 17.319 2.50158 17.2243C2.50123 17.1296 2.52567 17.0364 2.57247 16.954L3.77945 14.834C3.79665 14.8041 3.80569 14.7701 3.80569 14.7355C3.80569 14.701 3.79665 14.667 3.77945 14.637C3.15073 13.5485 2.52573 12.4579 1.90448 11.3651L1.11449 9.97008C0.954488 9.66008 0.941489 9.47409 1.20949 9.00509C1.67448 8.1921 2.13647 7.38011 2.59647 6.56911C2.72847 6.33512 2.90046 6.23512 3.18046 6.23412C4.04344 6.23048 4.90644 6.23015 5.76943 6.23312C5.79123 6.23295 5.81259 6.22704 5.83138 6.21597C5.85016 6.20491 5.8657 6.1891 5.87643 6.17012L8.68239 1.27516C8.72491 1.2007 8.78631 1.13875 8.86039 1.09556C8.93448 1.05238 9.01863 1.02948 9.10439 1.02917C9.62838 1.02817 10.1574 1.02917 10.6874 1.02317L11.7044 1.00017C12.0453 0.997165 12.4283 1.03217 12.6043 1.34016ZM9.17238 1.74316C9.16185 1.74315 9.15149 1.74592 9.14236 1.75119C9.13323 1.75645 9.12565 1.76403 9.12038 1.77316L6.25442 6.78811C6.24066 6.81174 6.22097 6.83137 6.19729 6.84505C6.17361 6.85873 6.14677 6.86599 6.11942 6.86611H3.25346C3.19746 6.86611 3.18346 6.89111 3.21246 6.94011L9.02239 17.096C9.04739 17.138 9.03539 17.158 8.98839 17.159L6.19342 17.174C6.15256 17.1727 6.11214 17.1828 6.07678 17.2033C6.04141 17.2238 6.01253 17.2539 5.99342 17.29L4.67344 19.6C4.62944 19.678 4.65244 19.718 4.74144 19.718L10.4574 19.726C10.5034 19.726 10.5374 19.746 10.5614 19.787L11.9643 22.241C12.0103 22.322 12.0563 22.323 12.1033 22.241L17.1093 13.481L17.8923 12.0991C17.897 12.0905 17.904 12.0834 17.9125 12.0785C17.9209 12.0735 17.9305 12.0709 17.9403 12.0709C17.9501 12.0709 17.9597 12.0735 17.9681 12.0785C17.9765 12.0834 17.9835 12.0905 17.9883 12.0991L19.4123 14.629C19.4229 14.648 19.4385 14.6637 19.4573 14.6746C19.4761 14.6855 19.4975 14.6912 19.5193 14.691L22.2822 14.671C22.2893 14.6711 22.2963 14.6693 22.3024 14.6658C22.3086 14.6623 22.3137 14.6572 22.3172 14.651C22.3206 14.6449 22.3224 14.638 22.3224 14.631C22.3224 14.624 22.3206 14.6172 22.3172 14.611L19.4173 9.52508C19.4068 9.50809 19.4013 9.48853 19.4013 9.46859C19.4013 9.44864 19.4068 9.42908 19.4173 9.41209L19.7102 8.90509L20.8302 6.92811C20.8542 6.88711 20.8422 6.86611 20.7952 6.86611H9.20038C9.14138 6.86611 9.12738 6.84011 9.15738 6.78911L10.5914 4.28413C10.6021 4.26706 10.6078 4.24731 10.6078 4.22714C10.6078 4.20697 10.6021 4.18721 10.5914 4.17014L9.22538 1.77416C9.22016 1.7647 9.21248 1.75682 9.20315 1.75137C9.19382 1.74591 9.18319 1.74307 9.17238 1.74316ZM15.4623 9.76308C15.5083 9.76308 15.5203 9.78308 15.4963 9.82308L14.6643 11.2881L12.0513 15.873C12.0464 15.8819 12.0392 15.8894 12.0304 15.8945C12.0216 15.8996 12.0115 15.9022 12.0013 15.902C11.9912 15.902 11.9813 15.8993 11.9725 15.8942C11.9637 15.8891 11.9564 15.8818 11.9513 15.873L8.49839 9.84108C8.47839 9.80708 8.48839 9.78908 8.52639 9.78708L8.74239 9.77508L15.4643 9.76308H15.4623Z"
fill="currentColor"
/>
</svg>
</div>
<div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.6241 11.346L20.3848 3.44816C20.5309 3.29931 20.4487 3 20.2601 3H16.0842C16.0388 3 15.9949 3.01897 15.9594 3.05541L7.59764 11.5629C7.46721 11.6944 7.27446 11.5771 7.27446 11.3666V3.25183C7.27446 3.11242 7.18515 3 7.07594 3H4.19843C4.08932 3 4 3.11242 4 3.25183V20.7482C4 20.8876 4.08932 21 4.19843 21H7.07594C7.18515 21 7.27446 20.8876 7.27446 20.7482V17.1834C7.27446 17.1073 7.30136 17.0344 7.34815 16.987L9.94075 14.3486C10.0031 14.2853 10.0895 14.2757 10.159 14.3232L17.0934 19.5573C18.2289 20.3412 19.4975 20.8226 20.786 20.9652C20.9008 20.9778 21 20.8606 21 20.7133V17.3559C21 17.2276 20.9249 17.1232 20.8243 17.1073C20.0659 16.9853 19.326 16.6845 18.6569 16.222L12.6538 11.764C12.5291 11.6785 12.5135 11.4584 12.6241 11.346Z"
fill="currentColor"
/>
</svg>
</div>
</div>
<A href="/zen">
<span>Learn about Zen </span>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.5 12L17 12M13 16.5L17.5 12L13 7.5"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="square"
/>
</svg>
</A>
</div>
</section>
<EmailSignup />
<Footer />
</div>
</div>
<Legal />
</main>
)
}
@@ -1,11 +1,11 @@
import { Billing } from "@opencode/console-core/billing.js"
import { Billing } from "@opencode-ai/console-core/billing.js"
import type { APIEvent } from "@solidjs/start/server"
import { Database, eq, sql } from "@opencode/console-core/drizzle/index.js"
import { BillingTable, PaymentTable } from "@opencode/console-core/schema/billing.sql.js"
import { Identifier } from "@opencode/console-core/identifier.js"
import { centsToMicroCents } from "@opencode/console-core/util/price.js"
import { Actor } from "@opencode/console-core/actor.js"
import { Resource } from "@opencode/console-resource"
import { and, Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js"
import { BillingTable, PaymentTable } from "@opencode-ai/console-core/schema/billing.sql.js"
import { Identifier } from "@opencode-ai/console-core/identifier.js"
import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
import { Actor } from "@opencode-ai/console-core/actor.js"
import { Resource } from "@opencode-ai/console-resource"
export async function POST(input: APIEvent) {
const body = await Billing.stripe().webhooks.constructEventAsync(
@@ -41,12 +41,14 @@ export async function POST(input: APIEvent) {
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 (!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()
@@ -86,11 +88,45 @@ export async function POST(input: APIEvent) {
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")
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))
})
}
console.log("finished handling")
+169
View File
@@ -0,0 +1,169 @@
import "./index.css"
import { Title } from "@solidjs/meta"
import { onCleanup, onMount } from "solid-js"
import logoLight from "../asset/logo-ornate-light.svg"
import logoDark from "../asset/logo-ornate-dark.svg"
import IMG_SPLASH from "../asset/lander/screenshot-splash.png"
import { IconCopy, IconCheck } from "../component/icon"
function CopyStatus() {
return (
<div data-component="copy-status">
<IconCopy data-slot="copy" />
<IconCheck data-slot="check" />
</div>
)
}
export default function Home() {
onMount(() => {
const commands = document.querySelectorAll("[data-copy]")
for (const button of commands) {
const callback = () => {
const text = button.textContent
if (text) {
navigator.clipboard.writeText(text)
button.setAttribute("data-copied", "")
setTimeout(() => {
button.removeAttribute("data-copied")
}, 1500)
}
}
button.addEventListener("click", callback)
onCleanup(() => {
button.removeEventListener("click", callback)
})
}
})
return (
<main data-page="home">
<Title>opencode | AI coding agent built for the terminal</Title>
<div data-component="content">
<section data-component="top">
<img data-slot="logo light" src={logoLight} alt="opencode logo light" />
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
<h1 data-slot="title">The AI coding agent built for the terminal</h1>
<div data-slot="login">
<a href="/auth">opencode zen</a>
</div>
</section>
<section data-component="cta">
<div data-slot="left">
<a href="/docs">Get Started</a>
</div>
<div data-slot="center">
<a href="/auth">opencode zen</a>
</div>
<div data-slot="right">
<button data-copy data-slot="command">
<span>
<span>curl -fsSL </span>
<span data-slot="protocol">https://</span>
<span data-slot="highlight">opencode.ai/install</span>
<span> | bash</span>
</span>
<CopyStatus />
</button>
</div>
</section>
<section data-component="features">
<ul data-slot="list">
<li>
<strong>Native TUI</strong> A responsive, native, themeable terminal UI
</li>
<li>
<strong>LSP enabled</strong> Automatically loads the right LSPs for the LLM
</li>
<li>
<strong>opencode zen</strong> A <a href="/docs/zen">curated list of models</a> provided by opencode{" "}
<label>New</label>
</li>
<li>
<strong>Multi-session</strong> Start multiple agents in parallel on the same project
</li>
<li>
<strong>Shareable links</strong> Share a link to any sessions for reference or to debug
</li>
<li>
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
</li>
<li>
<strong>Use any model</strong> Supports 75+ LLM providers through{" "}
<a href="https://models.dev">Models.dev</a>, including local models
</li>
</ul>
</section>
<section data-component="install">
<div data-component="method">
<h3 data-component="title">npm</h3>
<button data-copy data-slot="button">
<span>
npm install -g <strong>opencode-ai</strong>
</span>
<CopyStatus />
</button>
</div>
<div data-component="method">
<h3 data-component="title">bun</h3>
<button data-copy data-slot="button">
<span>
bun install -g <strong>opencode-ai</strong>
</span>
<CopyStatus />
</button>
</div>
<div data-component="method">
<h3 data-component="title">homebrew</h3>
<button data-copy data-slot="button">
<span>
brew install <strong>sst/tap/opencode</strong>
</span>
<CopyStatus />
</button>
</div>
<div data-component="method">
<h3 data-component="title">paru</h3>
<button data-copy data-slot="button">
<span>
paru -S <strong>opencode-bin</strong>
</span>
<CopyStatus />
</button>
</div>
</section>
<section data-component="screenshots">
<figure>
<figcaption>opencode TUI with the tokyonight theme</figcaption>
<a href="/docs/cli">
<img src={IMG_SPLASH} alt="opencode TUI with tokyonight theme" />
</a>
</figure>
</section>
<footer data-component="footer">
<div data-slot="cell">
<a href="https://x.com/opencode">X.com</a>
</div>
<div data-slot="cell">
<a href="https://github.com/sst/opencode">GitHub</a>
</div>
<div data-slot="cell">
<a href="https://opencode.ai/discord">Discord</a>
</div>
</footer>
</div>
<div data-component="legal">
<span>
©2025 <a href="https://anoma.ly">Anomaly</a>
</span>
</div>
</main>
)
}
@@ -0,0 +1,17 @@
[data-component="user-menu"] {
[data-component="dropdown"] {
[data-slot="trigger"] span {
color: var(--color-text-muted);
}
[data-slot="dropdown"] {
form {
width: 100%;
}
}
[data-slot="item"] {
color: var(--color-danger);
}
}
}
@@ -0,0 +1,35 @@
import { action, redirect } from "@solidjs/router"
import { getRequestEvent } from "solid-js/web"
import { useAuthSession } from "~/context/auth.session"
import { Dropdown } from "~/component/dropdown"
import "./user-menu.css"
const logout = action(async () => {
"use server"
const auth = await useAuthSession()
const event = getRequestEvent()
const current = auth.data.current
if (current)
await auth.update((val) => {
delete val.account?.[current]
const first = Object.keys(val.account ?? {})[0]
val.current = first
event!.locals.actor = undefined
return val
})
throw redirect("/zen")
})
export function UserMenu(props: { email: string | null | undefined }) {
return (
<div data-component="user-menu">
<Dropdown trigger={props.email ?? ""} align="right">
<form action={logout} method="post">
<button type="submit" formaction={logout} data-slot="item">
Logout
</button>
</form>
</Dropdown>
</div>
)
}
@@ -0,0 +1,74 @@
[data-component="workspace-picker"] {
[data-component="dropdown"] {
[data-slot="trigger"] {
/* Override blue accent colors with neutral colors for dropdown trigger */
--color-accent: var(--color-border);
--color-accent-hover: var(--color-border);
--color-accent-active: var(--color-border);
--color-primary: var(--color-border);
--color-primary-hover: var(--color-border);
--color-primary-active: var(--color-border);
--color-primary-alpha-20: transparent;
}
[data-slot="dropdown"] {
max-height: 240px;
overflow-y: auto;
min-width: 200px;
}
}
[data-slot="create-item"] {
width: 100%;
padding: var(--space-2-5) var(--space-3);
border: none;
background: none;
color: var(--color-text);
font-size: var(--font-size-sm);
font-family: var(--font-sans);
text-align: left;
cursor: pointer;
transition: background-color 0.15s ease;
&:hover {
background-color: var(--color-bg-surface);
}
}
[data-slot="create-form"] {
width: 100%;
}
[data-slot="create-input-group"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
[data-slot="button-group"] {
display: flex;
gap: var(--space-2);
justify-content: flex-end;
}
[data-slot="create-input"] {
flex: 1;
padding: var(--space-2-5) 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);
font-family: var(--font-sans);
&:focus {
outline: none;
border-color: var(--color-border);
box-shadow: none;
}
&::placeholder {
color: var(--color-text-muted);
}
}
}
@@ -0,0 +1,116 @@
import { query, useParams, action, createAsync, redirect, useSubmission } from "@solidjs/router"
import { For, Show, createEffect } from "solid-js"
import { createStore } from "solid-js/store"
import { withActor } from "~/context/auth.withActor"
import { Actor } from "@opencode-ai/console-core/actor.js"
import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js"
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
import { Workspace } from "@opencode-ai/console-core/workspace.js"
import { Dropdown, DropdownItem } from "~/component/dropdown"
import { Modal } from "~/component/modal"
import "./workspace-picker.css"
const getWorkspaces = query(async () => {
"use server"
return withActor(async () => {
return Database.transaction((tx) =>
tx
.select({
id: WorkspaceTable.id,
name: WorkspaceTable.name,
slug: WorkspaceTable.slug,
})
.from(UserTable)
.innerJoin(WorkspaceTable, eq(UserTable.workspaceID, WorkspaceTable.id))
.where(and(eq(UserTable.accountID, Actor.account()), isNull(WorkspaceTable.timeDeleted))),
)
})
}, "workspaces")
const createWorkspace = action(async (form: FormData) => {
"use server"
const name = form.get("workspaceName") as string
if (name?.trim()) {
return withActor(async () => {
const workspaceID = await Workspace.create({ name: name.trim() })
return redirect(`/workspace/${workspaceID}`)
})
}
}, "createWorkspace")
export function WorkspacePicker() {
const params = useParams()
const workspaces = createAsync(() => getWorkspaces())
const submission = useSubmission(createWorkspace)
const [store, setStore] = createStore({
showForm: false,
})
let inputRef: HTMLInputElement | undefined
const currentWorkspace = () => {
const ws = workspaces()?.find((w) => w.id === params.id)
return ws ? ws.name : "Select workspace"
}
const handleWorkspaceNew = () => {
setStore("showForm", true)
}
createEffect(() => {
if (store.showForm && inputRef) {
setTimeout(() => inputRef?.focus(), 0)
}
})
const handleSelectWorkspace = (workspaceID: string) => {
if (workspaceID === params.id) return
window.location.href = `/workspace/${workspaceID}`
}
// Reset signals when workspace ID changes
createEffect(() => {
params.id
setStore("showForm", false)
})
return (
<div data-component="workspace-picker">
<Dropdown trigger={currentWorkspace()} align="left">
<For each={workspaces()}>
{(workspace) => (
<DropdownItem selected={workspace.id === params.id} onClick={() => handleSelectWorkspace(workspace.id)}>
{workspace.name || workspace.slug}
</DropdownItem>
)}
</For>
<button data-slot="create-item" type="button" onClick={() => handleWorkspaceNew()}>
+ Create New Workspace
</button>
</Dropdown>
<Modal open={store.showForm} onClose={() => setStore("showForm", false)} title="Create New Workspace">
<form data-slot="create-form" action={createWorkspace} method="post">
<div data-slot="create-input-group">
<input
ref={inputRef}
data-slot="create-input"
type="text"
name="workspaceName"
placeholder="Enter workspace name"
required
/>
<div data-slot="button-group">
<button type="button" data-color="ghost" onClick={() => setStore("showForm", false)}>
Cancel
</button>
<button type="submit" data-color="primary" disabled={submission.pending}>
{submission.pending ? "Creating..." : "Create"}
</button>
</div>
</div>
</form>
</Modal>
</div>
)
}
+4 -24
View File
@@ -11,7 +11,6 @@
font-size: var(--font-size-sm);
font-family: var(--font-sans);
font-weight: 500;
text-transform: uppercase;
cursor: pointer;
transition: all 0.15s ease;
@@ -55,9 +54,6 @@
a {
color: var(--color-text);
text-decoration: underline;
text-underline-offset: var(--space-0-75);
text-decoration-thickness: 1px;
}
/* Workspace Header */
@@ -80,16 +76,14 @@
[data-slot="header-brand"] {
flex: 0 0 auto;
padding-top: 4px;
svg {
width: 138px;
}
display: flex;
align-items: center;
gap: var(--space-4);
[data-component="site-title"] {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--color-text);
text-decoration: none;
letter-spacing: -0.02em;
}
}
@@ -109,19 +103,5 @@
display: none;
}
}
a,
button {
appearance: none;
background: none;
border: none;
cursor: pointer;
padding: 0;
color: var(--color-text);
text-decoration: underline;
text-underline-offset: var(--space-0-75);
text-decoration-thickness: 1px;
text-transform: uppercase;
}
}
}
}
+16 -43
View File
@@ -1,64 +1,37 @@
import { query, createAsync, RouteSectionProps, useParams, A } from "@solidjs/router"
import "./workspace.css"
import { useAuthSession } from "~/context/auth.session"
import { IconLogo } from "../component/icon"
import { IconWorkspaceLogo } from "../component/icon"
import { WorkspacePicker } from "./workspace-picker"
import { UserMenu } from "./user-menu"
import { withActor } from "~/context/auth.withActor"
import {
query,
action,
redirect,
createAsync,
RouteSectionProps,
Navigate,
useNavigate,
useParams,
A,
} from "@solidjs/router"
import { User } from "@opencode/console-core/user.js"
import { Actor } from "@opencode/console-core/actor.js"
import { getRequestEvent } from "solid-js/web"
import { User } from "@opencode-ai/console-core/user.js"
import { Actor } from "@opencode-ai/console-core/actor.js"
import { Link } from "@solidjs/meta"
const getUserInfo = query(async (workspaceID: string) => {
const getUserEmail = query(async (workspaceID: string) => {
"use server"
return withActor(async () => {
const actor = Actor.assert("user")
return await User.fromID(actor.properties.userID)
const email = await User.getAccountEmail(actor.properties.userID)
return email
}, workspaceID)
}, "userInfo")
const logout = action(async () => {
"use server"
const auth = await useAuthSession()
const event = getRequestEvent()
const current = auth.data.current
if (current)
await auth.update((val) => {
delete val.account?.[current]
const first = Object.keys(val.account ?? {})[0]
val.current = first
event!.locals.actor = undefined
return val
})
throw redirect("/")
})
}, "userEmail")
export default function WorkspaceLayout(props: RouteSectionProps) {
const params = useParams()
const userInfo = createAsync(() => getUserInfo(params.id))
const userEmail = createAsync(() => getUserEmail(params.id))
return (
<main data-page="workspace">
<Link rel="icon" type="image/svg+xml" href="/favicon-zen.svg" />
<header data-component="workspace-header">
<div data-slot="header-brand">
<A href="/" data-component="site-title">
<IconLogo />
<IconWorkspaceLogo />
</A>
<WorkspacePicker />
</div>
<div data-slot="header-actions">
<span data-slot="user">{userInfo()?.email}</span>
<form action={logout} method="post">
<button type="submit" formaction={logout}>
Logout
</button>
</form>
<UserMenu email={userEmail()} />
</div>
</header>
<div>{props.children}</div>
@@ -1,7 +1,72 @@
[data-page="workspace"] {
line-height: 1;
}
/* Workspace Layout */
[data-component="workspace-container"] {
display: flex;
height: calc(100vh - 73px);
}
[data-component="workspace-nav"] {
width: 240px;
flex-shrink: 0;
padding: var(--space-6) var(--space-4);
display: flex;
justify-content: flex-end;
}
[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;
}
}
}
}
[data-component="workspace-content"] {
flex: 1;
padding: var(--space-6) var(--space-8);
overflow-y: auto;
@media (max-width: 48rem) {
padding: var(--space-6) var(--space-4);
}
}
[data-page="workspace-[id]"] {
max-width: 64rem;
padding: var(--space-10) var(--space-4);
margin: 0 auto;
padding: var(--space-2) var(--space-4);
margin: 0;
width: 100%;
display: flex;
flex-direction: column;
@@ -32,7 +97,6 @@
gap: var(--space-6);
}
/* Section titles */
[data-slot="section-title"] {
display: flex;
flex-direction: column;
@@ -44,8 +108,7 @@
line-height: 1.2;
letter-spacing: -0.03125rem;
margin: 0;
color: var(--color-text-secondary);
text-transform: uppercase;
color: var(--color-text);
@media (max-width: 30rem) {
font-size: var(--font-size-md);
@@ -66,7 +129,15 @@
}
}
}
[data-slot="section-content"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
margin-top: var(--space-8);
}
}
section:not(:last-child) {
border-bottom: 1px solid var(--color-border);
padding-bottom: var(--space-16);
@@ -78,7 +149,7 @@
}
/* Title section */
[data-component="title-section"] {
[data-component="header-section"] {
display: flex;
flex-direction: column;
gap: var(--space-2);
@@ -105,11 +176,50 @@
p {
line-height: 1.5;
font-size: var(--font-size-md);
color: var(--color-text-muted);
color: var(--color-text);
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
@media (max-width: 48rem) {
flex-direction: column;
align-items: flex-start;
gap: var(--space-3);
}
a {
color: var(--color-text-muted);
}
[data-slot="billing-info"] {
flex-shrink: 0;
margin-left: auto;
}
[data-slot="balance"] {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
b {
font-weight: 600;
color: var(--color-text);
}
}
}
}
}
@media (max-width: 48rem) {
[data-component="workspace-container"] {
flex-direction: column;
}
[data-component="workspace-nav"] {
width: 100%;
flex-direction: row;
border-right: none;
border-bottom: 1px solid var(--color-border);
padding: var(--space-4);
}
}
@@ -1,50 +1,37 @@
import "./[id].css"
import { Billing } from "@opencode/console-core/billing.js"
import { query, useParams, createAsync } from "@solidjs/router"
import { Show } from "solid-js"
import { withActor } from "~/context/auth.withActor"
import { MonthlyLimitSection } from "~/component/workspace/monthly-limit-section"
import { NewUserSection } from "~/component/workspace/new-user-section"
import { BillingSection } from "~/component/workspace/billing-section"
import { PaymentSection } from "~/component/workspace/payment-section"
import { UsageSection } from "~/component/workspace/usage-section"
import { KeySection } from "~/component/workspace/key-section"
import { createAsync, RouteSectionProps, useParams, A } from "@solidjs/router"
import { querySessionInfo } from "./common"
import "./[id].css"
const getBillingInfo = query(async (workspaceID: string) => {
"use server"
return withActor(async () => {
return await Billing.get()
}, workspaceID)
}, "billing.get")
export default function () {
export default function WorkspaceLayout(props: RouteSectionProps) {
const params = useParams()
const balanceInfo = createAsync(() => getBillingInfo(params.id))
const userInfo = createAsync(() => querySessionInfo(params.id))
return (
<div data-page="workspace-[id]">
<section data-component="title-section">
<h1>Zen</h1>
<p>
Curated list of models provided by opencode.{" "}
<a target="_blank" href="/docs/zen">
Learn more
</a>
.
</p>
</section>
<div data-slot="sections">
<NewUserSection />
<KeySection />
<BillingSection />
<Show when={true}>
{/*<Show when={balanceInfo()?.reload}>*/}
<MonthlyLimitSection />
</Show>
<UsageSection />
<PaymentSection />
<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
</A>
<A href={`/workspace/${params.id}/settings`} activeClass="active" data-nav-button>
Settings
</A>
</Show>
</div>
</nav>
<div data-component="workspace-content">{props.children}</div>
</div>
</div>
</main>
)
}
@@ -1,10 +1,4 @@
.root {
[data-slot="section-content"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
[data-slot="reload-error"] {
display: flex;
align-items: center;
@@ -29,6 +23,7 @@
flex-shrink: 0;
}
}
[data-slot="payment"] {
display: flex;
flex-direction: column;
@@ -86,7 +81,7 @@
@media (max-width: 30rem) {
flex-direction: column;
> button {
>button {
width: 100%;
}
}
@@ -96,19 +91,21 @@
}
/* Make Enable Billing button full width when it's the only button */
> button {
>button {
flex: 1;
}
}
}
[data-slot="usage"] {
p {
font-size: var(--font-size-sm);
line-height: 1.5;
color: var(--color-text-secondary);
b {
font-weight: 600;
}
}
}
}
}
@@ -1,14 +1,12 @@
import { json, query, action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router"
import { createMemo, Show } from "solid-js"
import { Billing } from "@opencode/console-core/billing.js"
import { Billing } from "@opencode-ai/console-core/billing.js"
import { withActor } from "~/context/auth.withActor"
import { IconCreditCard } from "~/component/icon"
import styles from "./billing-section.module.css"
const createCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => {
"use server"
return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID)
}, "checkoutUrl")
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"
@@ -17,12 +15,23 @@ const reload = action(async (form: FormData) => {
return json(await withActor(() => Billing.reload(), workspaceID), { revalidate: getBillingInfo.key })
}, "billing.reload")
const disableReload = action(async (form: FormData) => {
const setReload = 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.disableReload(), workspaceID), { revalidate: getBillingInfo.key })
}, "billing.disableReload")
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")
const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
"use server"
@@ -44,7 +53,7 @@ export function BillingSection() {
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
const createSessionUrlAction = useAction(createSessionUrl)
const createSessionUrlSubmission = useSubmission(createSessionUrl)
const disableReloadSubmission = useSubmission(disableReload)
const setReloadSubmission = useSubmission(setReload)
const reloadSubmission = useSubmission(reload)
// DUMMY DATA FOR TESTING - UNCOMMENT ONE OF THE SCENARIOS BELOW
@@ -89,6 +98,10 @@ export function BillingSection() {
return ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
})
const hasBalance = createMemo(() => {
return (balanceInfo()?.balance ?? 0) > 0 && balanceAmount() !== "0.00"
})
return (
<section class={styles.root}>
<div data-slot="section-title">
@@ -136,19 +149,32 @@ export function BillingSection() {
<Show
when={balanceInfo()?.reload}
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
}
}}
<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>
}
>
{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
@@ -164,21 +190,31 @@ export function BillingSection() {
>
{createSessionUrlSubmission.pending ? "Loading..." : "Manage Payment Methods"}
</button>
<form action={disableReload} method="post" data-slot="create-form">
<form action={setReload} method="post" data-slot="create-form">
<input type="hidden" name="workspaceID" value={params.id} />
<button data-color="ghost" type="submit" disabled={disableReloadSubmission.pending}>
{disableReloadSubmission.pending ? "Disabling..." : "Disable"}
<input type="hidden" name="reload" value="false" />
<button data-color="ghost" type="submit" disabled={setReloadSubmission.pending}>
{setReloadSubmission.pending ? "Disabling..." : "Disable"}
</button>
</form>
</Show>
</div>
</div>
<div data-slot="usage">
<Show when={!balanceInfo()?.reload && !(balanceAmount() === "0.00" || balanceAmount() === "-0.00")}>
<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 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>
@@ -0,0 +1,23 @@
import { MonthlyLimitSection } from "./monthly-limit-section"
import { BillingSection } from "./billing-section"
import { PaymentSection } from "./payment-section"
import { Show } from "solid-js"
import { createAsync, useParams } from "@solidjs/router"
import { querySessionInfo } from "../../common"
export default function () {
const params = useParams()
const userInfo = createAsync(() => querySessionInfo(params.id))
return (
<div data-page="workspace-[id]">
<div data-slot="sections">
<Show when={userInfo()?.isAdmin}>
<BillingSection />
<MonthlyLimitSection />
<PaymentSection />
</Show>
</div>
</div>
)
}
@@ -1,10 +1,4 @@
.root {
[data-slot="section-content"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
[data-slot="balance"] {
display: flex;
flex-direction: column;
@@ -99,4 +93,4 @@
margin: 0;
line-height: 1.4;
}
}
}
@@ -2,7 +2,7 @@ import { json, query, action, useParams, createAsync, useSubmission } from "@sol
import { createEffect, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { withActor } from "~/context/auth.withActor"
import { Billing } from "@opencode/console-core/billing.js"
import { Billing } from "@opencode-ai/console-core/billing.js"
import styles from "./monthly-limit-section.module.css"
const getBillingInfo = query(async (workspaceID: string) => {

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