Compare commits

..

223 Commits

Author SHA1 Message Date
Aiden Cline
090f638318 ignore 2025-12-09 16:29:55 -06:00
Aiden Cline
8f3f87943a fmt 2025-12-09 16:29:33 -06:00
Aiden Cline
7ac3f9182b fixes 2025-12-09 16:23:18 -06:00
Aiden Cline
cda2138f24 fixes 2025-12-09 16:22:36 -06:00
Aiden Cline
c4f8c6c005 fix 2025-12-09 15:59:03 -06:00
Aiden Cline
8304bd418f Merge branch 'dev' into interleaved-thinking 2025-12-09 15:54:28 -06:00
Aiden Cline
8a9c7a4ef3 add OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT 2025-12-09 15:44:56 -06:00
Adam
2dad56c9a2 wip(desktop): progress 2025-12-09 15:39:44 -06:00
Aiden Cline
366b6e4fbb wip 2025-12-09 15:36:53 -06:00
Adam
41d78c1ecc wip(desktop): progress 2025-12-09 15:32:36 -06:00
ry2009
16c4b02b69 feat: add Biome LSP support (#5290) 2025-12-09 15:31:13 -06:00
Adam
35c04d9283 wip(desktop): progress 2025-12-09 15:24:11 -06:00
Adam
1fbd7a7f9a wip(desktop): progress 2025-12-09 15:21:47 -06:00
Adam
d7563d1694 wip(desktop): progress 2025-12-09 15:21:47 -06:00
Adam
b9fa7d9163 wip(desktop): progress 2025-12-09 15:21:47 -06:00
Adam
f736751ab2 wip(desktop): progress 2025-12-09 15:21:45 -06:00
Aiden Cline
dbcc779f0b ci: fix sdk workflow 2025-12-09 15:08:30 -06:00
Nick
c33a90320c fix: resolve 'latest' to actual version when caching plugins (#5292) 2025-12-09 15:07:59 -06:00
Dax Raad
802b862aae format on write 2025-12-09 16:06:57 -05:00
GitHub Action
b0cd171c1b chore: format code 2025-12-09 21:05:09 +00:00
GitHub Action
13755f4680 chore: regen sdk 2025-12-09 21:04:37 +00:00
Dax Raad
b242659cc3 fix types 2025-12-09 16:03:58 -05:00
Dax Raad
5f6b2fdc6f fix tests 2025-12-09 16:03:33 -05:00
GitHub Action
e34f18991e chore: format code 2025-12-09 20:53:06 +00:00
GitHub Action
209b0a06f7 chore: regen sdk 2025-12-09 20:52:31 +00:00
Dax Raad
a2e460bc4b discover logic 2025-12-09 15:51:55 -05:00
GitHub Action
fc9081afe4 chore: format code 2025-12-09 20:49:25 +00:00
GitHub Action
1a3f7c3d84 chore: regen sdk 2025-12-09 20:48:49 +00:00
Dax Raad
06aa1f49b8 sync 2025-12-09 15:48:22 -05:00
GitHub Action
dfd67cd922 chore: format code 2025-12-09 20:44:09 +00:00
GitHub Action
70f7287ca1 chore: regen sdk 2025-12-09 20:43:35 +00:00
Dax Raad
f1955b4d05 core: fix project event emission to include updated worktree data 2025-12-09 15:41:38 -05:00
Dax Raad
c5e5627cbd test fixes 2025-12-09 15:41:38 -05:00
GitHub Action
93378526b9 chore: format code 2025-12-09 20:26:02 +00:00
GitHub Action
abf176a335 chore: regen sdk 2025-12-09 20:25:21 +00:00
Aiden Cline
84a0868e66 fix: read when file is svg 2025-12-09 14:24:30 -06:00
GitHub Action
75a9c42789 chore: format code 2025-12-09 20:20:03 +00:00
GitHub Action
204fa54625 chore: regen sdk 2025-12-09 20:19:27 +00:00
Dax Raad
365584048f core: fix project creation to include updated timestamp 2025-12-09 15:18:55 -05:00
Dax Raad
edffcc32cf core: make project updated timestamp optional to support legacy project data 2025-12-09 15:18:55 -05:00
GitHub Action
238f441bcb chore: format code 2025-12-09 20:17:37 +00:00
GitHub Action
0571a8302c chore: regen sdk 2025-12-09 20:16:59 +00:00
Aiden Cline
8c07382382 ci: fix sdk gen 2025-12-09 14:16:22 -06:00
GitHub Action
fa32fbd187 chore: format code 2025-12-09 20:12:07 +00:00
GitHub Action
0fd2ecd0ba chore: regen sdk 2025-12-09 20:11:32 +00:00
Dax Raad
7439a40b00 core: fix project icon update to preserve existing icon properties 2025-12-09 15:11:00 -05:00
GitHub Action
2ad99713f3 chore: format code 2025-12-09 20:07:32 +00:00
GitHub Action
19ec970701 chore: regen sdk 2025-12-09 20:06:57 +00:00
Dax Raad
b48caec218 core: add automatic project icon discovery from favicon/logo files 2025-12-09 15:06:24 -05:00
GitHub Action
380c34af53 chore: format code 2025-12-09 19:54:54 +00:00
GitHub Action
553d9013eb chore: regen sdk 2025-12-09 19:54:19 +00:00
Dax Raad
8bff3cdae8 fix ci 2025-12-09 14:53:47 -05:00
Dax Raad
0b40c3d37d rework project loading 2025-12-09 14:41:14 -05:00
Dax Raad
1e3bdcc71c rename bus 2025-12-09 14:32:09 -05:00
GitHub Action
de577e17da chore: format code 2025-12-09 19:31:07 +00:00
GitHub Action
8a9e258ad7 chore: regen sdk 2025-12-09 19:30:32 +00:00
Adam
9a34965432 feat: add color to project 2025-12-09 13:29:59 -06:00
Adam
c944d19c3b wip(desktop): progress 2025-12-09 13:24:37 -06:00
Dax Raad
fb1b6c5e6b add project.name/icon 2025-12-09 13:57:18 -05:00
GitHub Action
ad0c4c5d89 chore: format code 2025-12-09 18:37:14 +00:00
GitHub Action
a54b663a39 chore: regen sdk 2025-12-09 18:36:38 +00:00
Adam
ae4993f39a wip(desktop): progress 2025-12-09 12:36:06 -06:00
GitHub Action
aa638cec48 chore: format code 2025-12-09 18:10:31 +00:00
GitHub Action
4db4a90559 chore: regen sdk 2025-12-09 18:07:43 +00:00
Aiden Cline
e23a81097c core: add test to prevent MCP headers regression when OAuth is enabled
Custom headers configured for remote MCP servers were being silently
dropped when OAuth was enabled (the default). This test ensures headers
are always sent to MCP servers regardless of OAuth configuration.
2025-12-09 12:06:40 -06:00
opencode
76f4803d8d release: v1.0.138 2025-12-09 18:05:21 +00:00
GitHub Action
22e4649318 chore: format code 2025-12-09 17:53:53 +00:00
GitHub Action
0ac70ff261 chore: regen sdk 2025-12-09 17:53:14 +00:00
Adam
1bc1e56da3 wip(desktop): progress 2025-12-09 11:52:43 -06:00
GitHub Action
0d0c20e673 chore: format code 2025-12-09 17:45:35 +00:00
Jay
a964824b22 docs: Modify documentation for SDK ecosystem references
Updated link text to refer to community-built projects.
2025-12-09 12:44:33 -05:00
GitHub Action
2cf0d578fe chore: regen sdk 2025-12-09 17:43:58 +00:00
Jay
13e8fb382f docs: Update community plugins reference in documentation 2025-12-09 12:43:29 -05:00
GitHub Action
4090bc9dea chore: format code 2025-12-09 17:39:25 +00:00
Aiden Cline
cec1caf99e ci: sdk stuff 2025-12-09 11:38:22 -06:00
GitHub Action
c74da97d52 chore: regen sdk 2025-12-09 17:38:01 +00:00
Jay
1f2497ce69 docs: Add submission note for OpenCode projects
Added a note about submitting projects to the list.
2025-12-09 12:37:34 -05:00
GitHub Action
986f14cb15 chore: format code 2025-12-09 17:37:32 +00:00
GitHub Action
34f639d510 chore: regen sdk 2025-12-09 17:34:45 +00:00
Aiden Cline
defe51c825 docs: fix name 2025-12-09 11:34:15 -06:00
GitHub Action
5a16acef8c chore: format code 2025-12-09 17:26:28 +00:00
Aiden Cline
2ce249dbc0 docs: OpenCode ecosystem (#5287)
Co-authored-by: GitHub Action <action@github.com>
2025-12-09 11:25:28 -06:00
GitHub Action
7ba6b18945 chore: format code 2025-12-09 17:10:13 +00:00
GitHub Action
b8c0b393bf chore: regen sdk 2025-12-09 17:09:35 +00:00
Adam
5442adb517 wip(desktop): progress 2025-12-09 11:09:00 -06:00
Adam
6b2ac20abc wip(desktop): progress 2025-12-09 11:09:00 -06:00
GitHub Action
3efc95b157 chore: format code 2025-12-09 16:53:58 +00:00
GitHub Action
cd9db8a81d chore: regen sdk 2025-12-09 16:53:24 +00:00
Dax Raad
036f5d4eef core: add project update timestamps to track when projects were last modified
Projects now track when they were last updated, making it easier for users
to see recent activity and identify stale projects in their workspace.
2025-12-09 11:52:53 -05:00
GitHub Action
c4401290db chore: format code 2025-12-09 15:53:48 +00:00
GitHub Action
4a6deb6420 chore: regen sdk 2025-12-09 15:53:12 +00:00
André Cruz
87a03e1e30 fix(mcp): send custom headers regardless of OAuth settings (#5273)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-09 09:52:39 -06:00
GitHub Action
01dc9d7ec6 chore: format code 2025-12-09 15:47:54 +00:00
GitHub Action
e78e0f9841 chore: regen sdk 2025-12-09 15:47:19 +00:00
Ravi Kumar
8326640670 feat(telemetry): Add userId and sessionId metadata to experimental_telemetry (#5279) 2025-12-09 09:46:48 -06:00
GitHub Action
d079af4be2 chore: format code 2025-12-09 12:16:05 +00:00
GitHub Action
82c9584382 chore: regen sdk 2025-12-09 12:15:30 +00:00
Adam
d3b6de855b chore: cleanup 2025-12-09 06:14:58 -06:00
Adam
5ad000fd99 chore: cleanup 2025-12-09 06:14:22 -06:00
Adam
fe196da430 fix(tui): order 2025-12-09 06:13:36 -06:00
Adam
20662e2101 wip(desktop): progress 2025-12-09 06:12:09 -06:00
Adam
0a357be160 wip(desktop): progress 2025-12-09 06:12:09 -06:00
Adam
d29205e677 fix: diff scroll gutter 2025-12-09 06:12:09 -06:00
GitHub Action
9d0630f094 ignore: update download stats 2025-12-09 2025-12-09 12:04:56 +00:00
Github Action
b6844565e8 Update Nix flake.lock and hashes 2025-12-09 11:15:34 +00:00
GitHub Action
17d1b24def chore: format code 2025-12-09 11:15:02 +00:00
GitHub Action
3d279edf44 chore: regen sdk 2025-12-09 11:14:23 +00:00
Brendan Allan
0a47a3cea0 fix: use ts project references for desktop and tauri 2025-12-09 19:13:51 +08:00
Github Action
306d57fcde Update Nix flake.lock and hashes 2025-12-09 09:17:58 +00:00
GitHub Action
ff6f1abf61 chore: format code 2025-12-09 09:17:36 +00:00
GitHub Action
331278a5be chore: regen sdk 2025-12-09 09:17:00 +00:00
Brendan Allan
78547f3c59 desktop: move updater logic to js 2025-12-09 17:16:24 +08:00
GitHub Action
d32671224f chore: format code 2025-12-09 07:26:55 +00:00
GitHub Action
9ade416ad4 chore: regen sdk 2025-12-09 07:26:16 +00:00
Aiden Cline
f8bd4ff705 core: refactor providerOptions function to accept Provider.Model for cleaner API 2025-12-09 01:25:36 -06:00
GitHub Action
2206e10d92 chore: format code 2025-12-09 06:42:56 +00:00
Aiden Cline
e282d5dc42 ci: run format workflow after sdk workflow completion 2025-12-09 00:41:40 -06:00
GitHub Action
2b4a5aede1 chore: regen sdk 2025-12-09 06:37:35 +00:00
Aiden Cline
654a2cd6a4 core: remove unused fzf dependency to address CVE
- Eliminates fzf binary dependency that was no longer used after file search overhaul
- Removes fzf from Nix package configuration and Arch Linux PKGBUILD dependencies
2025-12-09 00:36:33 -06:00
GitHub Action
2252b5ca1b chore: format code 2025-12-09 03:18:47 +00:00
terakael
c78dad8db5 tui: add permission indicator to footer (#4813)
Co-authored-by: terakael <terakael@gmail.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-08 21:18:17 -06:00
opencode
0569e8652c release: v1.0.137 2025-12-09 03:00:50 +00:00
GitHub Action
f2d5b32e52 chore: regen sdk 2025-12-09 02:17:38 +00:00
Matt Silverlock
b2f5ea7c30 themes: add new orng theme (#5267)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2025-12-08 20:17:10 -06:00
GitHub Action
8eb8f1a16c chore: format code 2025-12-09 02:07:00 +00:00
Aiden Cline
ad93d50ab7 ci: ignore 2025-12-08 20:06:27 -06:00
GitHub Action
73c5c328a8 chore: regen sdk 2025-12-09 01:07:23 +00:00
Dax Raad
72a13212e2 ci 2025-12-08 20:06:36 -05:00
Github Action
78bcf35f37 Update Nix flake.lock and hashes 2025-12-09 00:52:48 +00:00
GitHub Action
3a3ee3ecfd chore: format code 2025-12-09 00:52:05 +00:00
Sebastian Herrlinger
34e045c275 bump opentui to v0.1.59, allowing shift+space insert as char again 2025-12-09 01:51:27 +01:00
GitHub Action
e187918ce0 chore: regen sdk 2025-12-08 23:46:24 +00:00
Aiden Cline
7ac17c7833 ci: ignore 2025-12-08 17:45:50 -06:00
GitHub Action
b05d5b30fb chore: format code 2025-12-08 22:44:57 +00:00
Aiden Cline
57c08b01b7 ignore: mark ripgrep as an sst/tap/opencode depends_on 2025-12-08 16:42:32 -06:00
GitHub Action
08efb9cdf6 chore: regen sdk 2025-12-08 22:16:30 +00:00
Ariane Emory
8b51da768c fix: improve sidebar working directory colour contrast with modified files (resolves #5184 (#5188) 2025-12-08 16:15:40 -06:00
Github Action
862407c674 Update Nix flake.lock and hashes 2025-12-08 22:11:38 +00:00
GitHub Action
a381aa7e0d chore: format code 2025-12-08 22:10:46 +00:00
Aiden Cline
4dcda3d53d fix: parcel watcher musl support 2025-12-08 16:08:53 -06:00
GitHub Action
479cca29a5 chore: regen sdk 2025-12-08 21:29:05 +00:00
Ravi Kumar
e38814c597 fix: apply provider filtering to /connect endpoint (#5233) 2025-12-08 15:28:32 -06:00
GitHub Action
4d42daa9a3 chore: format code 2025-12-08 21:05:09 +00:00
Ariane Emory
130345bd5d fix: sort sessions by updated time (resolves #5074) (#5080) 2025-12-08 15:04:39 -06:00
Github Action
0baee00be4 Update Nix flake.lock and hashes 2025-12-08 21:00:43 +00:00
GitHub Action
e0acd5d361 chore: regen sdk 2025-12-08 20:59:55 +00:00
Sebastian Herrlinger
1e7d78a215 bump opentui to v0.1.58, fixing diff color issues in light themes 2025-12-08 21:59:09 +01:00
GitHub Action
7a6cb85617 chore: format code 2025-12-08 20:34:25 +00:00
Shoubhit Dash
20530104ce add ocaml lsp support (#5230) 2025-12-08 14:33:45 -06:00
GitHub Action
d22754dd68 chore: regen sdk 2025-12-08 20:06:51 +00:00
Sebastian Herrlinger
db0e1ebb80 fix colors for dialog prompt (like session rename prompt in light theme) 2025-12-08 21:06:03 +01:00
Sebastian Herrlinger
e83a47debe set theme text color for no matching items in auto complete 2025-12-08 20:59:00 +01:00
GitHub Action
eed48e76de chore: format code 2025-12-08 18:06:48 +00:00
Aiden Cline
ac70c1e813 docs: lsp & formatters 2025-12-08 12:05:25 -06:00
GitHub Action
ac0bed16a2 chore: regen sdk 2025-12-08 18:02:54 +00:00
rari404
fab8ab2840 feat: add terraform-ls language server and formatter (#5243) 2025-12-08 12:02:25 -06:00
GitHub Action
09ff8eba00 chore: format code 2025-12-08 17:40:24 +00:00
rari404
9bd2ea5e5f feat: add bash-language-server LSP (#5246) 2025-12-08 11:39:49 -06:00
GitHub Action
aa525482ae chore: regen sdk 2025-12-08 17:37:58 +00:00
Shoubhit Dash
285605737d add ocamlformat support (#5229) 2025-12-08 11:37:30 -06:00
GitHub Action
4201fe6e01 chore: format code 2025-12-08 17:31:56 +00:00
Daniel Polito
203f3312ee feat: ability to toggle MCP Servers in TUI (#4509) 2025-12-08 11:31:22 -06:00
GitHub Action
0c77c46dc7 chore: regen sdk 2025-12-08 17:28:14 +00:00
Jérôme Benoit
52bb43eebd fix: SAP AI Core Vercel AI SDK v2 support (#5180)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
2025-12-08 11:27:47 -06:00
GitHub Action
e020f5355e chore: format code 2025-12-08 17:07:18 +00:00
rari404
9b86d4e595 feat: add theme support for thinking text opacity (#5240) 2025-12-08 11:06:46 -06:00
GitHub Action
f3d3b41a3f chore: regen sdk 2025-12-08 16:27:32 +00:00
Bishal Roy
a959199f09 feat: add catppuccin-macchiato theme (#5225) 2025-12-08 10:27:03 -06:00
Adam
5e3a59d5a2 feat: resize handle 2025-12-08 10:24:26 -06:00
Adam
9f23d85e20 wip(desktop): progress 2025-12-08 10:24:26 -06:00
GitHub Action
54e15b38ea chore: regen sdk 2025-12-08 15:48:52 +00:00
Adam
d66b903e7f fix: opencode web command 2025-12-08 09:48:21 -06:00
Adam
4425c66732 revert: opencode web command changes
This reverts commit c347056246.
2025-12-08 09:47:43 -06:00
GitHub Action
0dd0250285 chore: format code 2025-12-08 15:40:18 +00:00
Adam
c347056246 fix: opencode web command 2025-12-08 09:39:41 -06:00
GitHub Action
2a5255ac8f chore: regen sdk 2025-12-08 15:35:44 +00:00
Adam
d9175be989 fix: opencode web command 2025-12-08 09:35:12 -06:00
GitHub Action
cebbfcfbaa chore: format code 2025-12-08 15:19:26 +00:00
Adam
de415be4f6 fix: opencode web command 2025-12-08 09:18:46 -06:00
Adam
e8ce113b7f fix: bump localstorage 2025-12-08 09:05:26 -06:00
GitHub Action
8ca02b7664 chore: regen sdk 2025-12-08 12:48:30 +00:00
Adam
3f5bb21f16 chore: identity cleanup, vscode ext images 2025-12-08 06:47:57 -06:00
Github Action
fe114c41b5 Update Nix flake.lock and hashes 2025-12-08 12:25:44 +00:00
GitHub Action
d90fd8a5d7 chore: format code 2025-12-08 12:24:56 +00:00
Adam
9363c15b4a feat: better code and diff perf 2025-12-08 06:24:24 -06:00
GitHub Action
3325823f23 ignore: update download stats 2025-12-08 2025-12-08 12:04:36 +00:00
GitHub Action
4b4d8da1ad chore: regen sdk 2025-12-08 05:46:02 +00:00
Brendan Allan
d531dff8d3 Export DesktopInterface from desktop and add PlatformContext 2025-12-08 13:43:36 +08:00
GitHub Action
923bf36593 chore: format code 2025-12-08 04:54:08 +00:00
Justin Vogt
d6e499dd48 docs: Update "model" inheritance for agents (#4894)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-07 22:53:41 -06:00
GitHub Action
aec1497c6e chore: regen sdk 2025-12-08 04:43:13 +00:00
Aiden Cline
f5a77c8cd8 bump plugin versions 2025-12-07 22:42:35 -06:00
GitHub Action
bc524eeb44 chore: format code 2025-12-08 04:22:50 +00:00
ry2009
725f658260 fix: ensure Auth.all returns valid objs (#5128) 2025-12-07 22:22:21 -06:00
GitHub Action
af1080dd42 chore: regen sdk 2025-12-08 04:21:06 +00:00
Ariane Emory
63e54541fe fix: Sort themes in the /theme modal alphabetically (resolves #5217) (#5219)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-07 22:20:40 -06:00
GitHub Action
c3f7a88c1c chore: format code 2025-12-08 04:20:07 +00:00
opencode-agent[bot]
586a8b7b31 docs: azure content filter note (#5212)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2025-12-07 22:19:39 -06:00
GitHub Action
981744f802 chore: regen sdk 2025-12-08 03:54:26 +00:00
Aiden Cline
a3bb4a3c85 pdf support in read tool (#5222)
Co-authored-by: ammi1378 <ammi1378@users.noreply.github.com>
2025-12-07 21:54:00 -06:00
GitHub Action
06ba1f76dc chore: format code 2025-12-08 03:49:13 +00:00
Aiden Cline
076c8b2ca1 ci: fix fmt 2025-12-07 21:48:41 -06:00
Adam
9a90939ac4 fix: use diff context instead of prop drilling 2025-12-07 21:21:18 -06:00
GitHub Action
123a136093 chore: regen sdk 2025-12-08 02:56:09 +00:00
Dax Raad
4126fedbd4 openapi 2025-12-07 21:55:36 -05:00
Dax Raad
145d185e6f ci 2025-12-07 21:51:22 -05:00
GitHub Action
6b32667c7d chore: regen sdk 2025-12-08 02:29:26 +00:00
Aiden Cline
d8401e1937 ci: fix fmt 2025-12-07 20:28:50 -06:00
GitHub Action
55d6fcc350 chore: format code 2025-12-08 02:11:26 +00:00
Aiden Cline
9ff39503e9 tweak: additional error msg parsing case 2025-12-07 20:10:54 -06:00
GitHub Action
f96c181afd chore: regen sdk 2025-12-08 01:48:52 +00:00
Aiden Cline
f9b75a09df ignore: update description 2025-12-07 19:48:23 -06:00
GitHub Action
6111ed79b1 chore: format code 2025-12-08 01:47:31 +00:00
Ariane Emory
4cf2322b7f fix: toggle timestamps now properly hides/shows timestamps when toggled (resolves #5142) (#5145) 2025-12-07 19:47:02 -06:00
GitHub Action
2c6fcc5dc1 chore: regen sdk 2025-12-08 01:44:32 +00:00
franlol
81ee8541ab feat: add experimental.text.complete plugin hook (#4962) 2025-12-07 19:44:04 -06:00
GitHub Action
5b3550ab9f chore: format code 2025-12-08 01:13:22 +00:00
Patrick Erichsen
4fabce58d8 fix(cli): prevent help text wrapping (#5185) 2025-12-07 19:12:54 -06:00
GitHub Action
da7edb5f5c chore: regen sdk 2025-12-08 00:58:40 +00:00
Dax Raad
bf0f85e37f playing with sdk docs 2025-12-07 19:58:04 -05:00
Github Action
7b52160bff Update Nix flake.lock and hashes 2025-12-08 00:05:25 +00:00
GitHub Action
fde97ec4a7 chore: format code 2025-12-08 00:04:40 +00:00
Dax
ea7ec60f51 v2 SDK (#5216)
Co-authored-by: GitHub Action <action@github.com>
2025-12-07 19:04:14 -05:00
193 changed files with 7098 additions and 4722 deletions

View File

@@ -8,9 +8,14 @@ on:
branches-ignore:
- production
workflow_dispatch:
workflow_run:
workflows: ["sdk"]
types:
- completed
jobs:
format:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.event.workflow_run.conclusion == 'success'
permissions:
contents: write
steps:
@@ -18,6 +23,8 @@ jobs:
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}
- name: Setup Bun
uses: ./.github/actions/setup-bun
@@ -27,5 +34,4 @@ jobs:
./script/format.ts
env:
CI: true
GITHUB_HEAD_REF: ${{ github.head_ref }}
GITHUB_REF_NAME: ${{ github.ref_name }}
PUSH_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}

View File

@@ -82,7 +82,9 @@ jobs:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
publish-tauri:
continue-on-error: true
strategy:
fail-fast: false
matrix:
settings:
- host: macos-latest

View File

@@ -18,6 +18,8 @@ jobs:
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}
- name: Setup Bun
uses: ./.github/actions/setup-bun
@@ -26,6 +28,7 @@ jobs:
run: |
bun ./packages/sdk/js/script/build.ts
(cd packages/opencode && bun dev generate > ../sdk/openapi.json)
bun x prettier --write packages/sdk/js/src/openapi.ts
if [ -z "$(git status --porcelain)" ]; then
echo "No changes to commit"
exit 0
@@ -34,6 +37,7 @@ jobs:
git config --local user.name "GitHub Action"
git add -A
git commit -m "chore: regen sdk"
git push --no-verify
git push origin HEAD:${PUSH_BRANCH} --no-verify
env:
CI: true
PUSH_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@ node_modules
playground
tmp
dist
ts-dist
.turbo
**/.serena
.serena/

View File

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

View File

@@ -7,9 +7,7 @@
"instructions": ["STYLE_GUIDE.md"],
"provider": {
"opencode": {
"options": {
// "baseURL": "http://localhost:8080",
},
"options": {},
},
},
"mcp": {

328
STATS.md
View File

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

View File

@@ -20,7 +20,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -48,7 +48,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -75,7 +75,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -99,7 +99,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -123,7 +123,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -168,7 +168,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -196,7 +196,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@@ -212,7 +212,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.0.134",
"version": "1.0.138",
"bin": {
"opencode": "./bin/opencode",
},
@@ -242,8 +242,8 @@
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.2.8",
"@opentui/core": "0.1.56",
"@opentui/solid": "0.1.56",
"@opentui/core": "0.1.59",
"@opentui/solid": "0.1.59",
"@parcel/watcher": "2.5.1",
"@pierre/precision-diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
@@ -285,7 +285,9 @@
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1",
"@standard-schema/spec": "1.0.0",
"@tsconfig/bun": "catalog:",
@@ -302,7 +304,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -322,7 +324,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.0.134",
"version": "1.0.138",
"devDependencies": {
"@hey-api/openapi-ts": "0.88.1",
"@tsconfig/node22": "catalog:",
@@ -333,7 +335,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -346,12 +348,13 @@
},
"packages/tauri": {
"name": "@opencode-ai/tauri",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"@opencode-ai/desktop": "workspace:*",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "~2",
"@tauri-apps/plugin-opener": "^2",
"@tauri-apps/plugin-process": "~2",
"@tauri-apps/plugin-shell": "~2",
"@tauri-apps/plugin-updater": "~2",
"solid-js": "catalog:",
@@ -360,13 +363,14 @@
"@actions/artifact": "4.0.0",
"@tauri-apps/cli": "^2",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "~5.6.2",
"vite": "catalog:",
},
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -398,7 +402,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"zod": "catalog:",
},
@@ -409,7 +413,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.0.134",
"version": "1.0.138",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -458,7 +462,7 @@
"@hono/zod-validator": "0.4.2",
"@kobalte/core": "0.13.11",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/precision-diffs": "0.6.0-beta.3",
"@pierre/precision-diffs": "0.6.0-beta.10",
"@solidjs/meta": "0.29.4",
"@solidjs/router": "0.15.4",
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
@@ -1143,21 +1147,21 @@
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@opentui/core": ["@opentui/core@0.1.56", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.56", "@opentui/core-darwin-x64": "0.1.56", "@opentui/core-linux-arm64": "0.1.56", "@opentui/core-linux-x64": "0.1.56", "@opentui/core-win32-arm64": "0.1.56", "@opentui/core-win32-x64": "0.1.56", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-TI5cSCPYythHIQYpAEdXyZhewGACn2TfnfC1qZmrSyEq33zFo4W7zpQ4EZNpy9xZJFCI+elAUVJFARwhudp9EQ=="],
"@opentui/core": ["@opentui/core@0.1.59", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.59", "@opentui/core-darwin-x64": "0.1.59", "@opentui/core-linux-arm64": "0.1.59", "@opentui/core-linux-x64": "0.1.59", "@opentui/core-win32-arm64": "0.1.59", "@opentui/core-win32-x64": "0.1.59", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-vOtEvIulvfCOWJy0EfKAPzAMtDTmC+S0boGYrefjLqIp7tp+bbVJuXVh/8bz6GQTPmbQC6MIk6bv/ij3pdUVkA=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.56", "", { "os": "darwin", "cpu": "arm64" }, "sha512-x5U9J2k1Fmbb9Mdh1nOd/yZVpg4ARCrV5pFngpaeKrIWDhs8RLpQW3ap+r7uyFLGFkSn4h5wBR0jj6Dg+Tyw+A=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.59", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JQWq7W/wkmTujW/2/Ig0d7S+701rul87LSW5txQ+GM4o6EWchqHrELwo6jcZpczsyOEj4fXxI2O8l4OVYyMa9A=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.56", "", { "os": "darwin", "cpu": "x64" }, "sha512-7swq9rV/SaNVBWoUbC7mlP1VNyKBl7SSwmyVMkcaBP71lkm95zWuh4pgGj82fLgZ9gITRBD95TJVDmTovOyW0A=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.59", "", { "os": "darwin", "cpu": "x64" }, "sha512-GzafWzMP9Lt4AzUwQAk02lxgITgfvvo33OLCN265LtQBO8w23u0eB7Fjs9W+nmtcvzXtB9q6HuA0PvP9a3OioA=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.56", "", { "os": "linux", "cpu": "arm64" }, "sha512-v8b+kiTlynAJzR0hFeVpGFzVi5PGqXAe3Zql9iTiQqTExkm/sR34sfC/P6rBOUhuAnos8ovPDKWtDb6eCTSm9g=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.59", "", { "os": "linux", "cpu": "arm64" }, "sha512-QMMFg3dr2v43g3jICgzNFYQyU4YL3zHw733MVJINC+c882+qiQ8l0utTFoVEx/iRYeBzFvMVrKZ4f6G8fFrtrw=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.56", "", { "os": "linux", "cpu": "x64" }, "sha512-lbxgvAi5SBswK/2hoMPtLhPvJxASgquPUwvGTRHqzDkCvrOChP/loTjBQpL09/nAFc3jbM3SAbZtnEgA2SGYVw=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.59", "", { "os": "linux", "cpu": "x64" }, "sha512-XSblVjhW/7+Xs+/o+xJHwHn74nw9j69mnPAFiNdH0d8ilP4j09nUYHZOvQ89sHZaMYeSIuJEciHnh/qP0n5QXQ=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.56", "", { "os": "win32", "cpu": "arm64" }, "sha512-RoCAbvDo+59OevX+6GrEGbaueERiBVnTaWJkrS41hRAD2fFS3CZpW7UuS5jIg7zn5clHmOGyfvCiBkTRXmgkhw=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.59", "", { "os": "win32", "cpu": "arm64" }, "sha512-GU5pPUcTpYmeOUYKpQgAPx0VKBMrfz5LNZlK8gm/jlo2CbLrIW7QLMWCoxncVZmNYqYJeG+KUZkmXYe5KLPXCQ=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.56", "", { "os": "win32", "cpu": "x64" }, "sha512-i6N5TjZU5gRkJsKmH8e/qY9vwSk0rh6A5t37mHDGlzN4E5yO/MbBrYH4ppLp5stps9Zfi1Re51ofJX1s2hZY/Q=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.59", "", { "os": "win32", "cpu": "x64" }, "sha512-InIawEI0TOG8MBBpavMq31WBRBjJ6XPuqFcsDnjqDJcXrRbNkguRW3PNXEwlyaU4tXHfYOsdlPpRtsysS8X/bQ=="],
"@opentui/solid": ["@opentui/solid@0.1.56", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.56", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-3R7AfxsYHUyehwJK98rt5dI9u2WCT/uH/CYvddZIgXPHyfFm1SHJekMdy3DUoiQTCUllt68eFGKMv9zRi6Laww=="],
"@opentui/solid": ["@opentui/solid@0.1.59", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.59", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-O88a/+YHkHlDC4IxbrfWD2ZWlpkpu4oXC2FCLTK8taaUAnLYoybxdrMpv1+o8u8KoWXOoZmEHdntdO9O4abHnQ=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
@@ -1273,7 +1277,7 @@
"@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.6.0-beta.3", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-1FBm9jhLWZvs7BqN3yG2Wh9SpGuO1us2QsKZlQqSwyCctMr9DRGzYQJ9lF6yR03LHzXs3fuIzO++d9sCObYzrQ=="],
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.6.0-beta.10", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-2rdd1Q1xJbB0Z4oUbm0Ybrr2gLFEdvNetZLadJboZSFL7Q4gFujdQZfXfV3vB9X+esjt++v0nzb3mioW25BOTA=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
@@ -1653,6 +1657,8 @@
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew=="],
"@tauri-apps/plugin-process": ["@tauri-apps/plugin-process@2.3.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA=="],
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Xod+pRcFxmOWFWEnqH5yZcA7qwAMuaaDkMR1Sply+F8VfBj++CGnj2xf5UoialmjZ2Cvd8qrvSCbU+7GgNVsKQ=="],
"@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.9.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg=="],
@@ -2819,6 +2825,8 @@
"lru.min": ["lru.min@1.1.3", "", {}, "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q=="],
"lru_map": ["lru_map@0.4.1", "", {}, "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg=="],
"luxon": ["luxon@3.6.1", "", {}, "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],

View File

@@ -1,3 +1,3 @@
{
"nodeModules": "sha256-7ItLfqYrXzC6LO2iXZ8m+ZfQH1D7NWtcAcgRMO5NXZI="
"nodeModules": "sha256-lM/7mkrPHz5E6YOMjWspfRhKjwav9ANrLt9kYlpPkEI="
}

View File

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

View File

@@ -30,7 +30,7 @@
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/precision-diffs": "0.6.0-beta.3",
"@pierre/precision-diffs": "0.6.0-beta.10",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
"ai": "5.0.97",

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,6 +23,6 @@
</script>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="/src/index.tsx" type="module"></script>
<script src="/src/entry.tsx" type="module"></script>
</body>
</html>

View File

@@ -1,10 +1,10 @@
{
"name": "@opencode-ai/desktop",
"version": "1.0.134",
"version": "1.0.138",
"description": "",
"type": "module",
"exports": {
".": "./src/index.tsx",
".": "./src/index.ts",
"./vite": "./vite.js"
},
"scripts": {

View File

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

View File

@@ -19,7 +19,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
sdk.global.event().then(async (events) => {
for await (const event of events.stream) {
// console.log("event", event)
emitter.emit(event.directory, event.payload)
emitter.emit(event.directory ?? "global", event.payload)
}
})

View File

@@ -22,7 +22,7 @@ type State = {
ready: boolean
provider: Provider[]
agent: Agent[]
project: Project
project: string
config: Config
path: Path
session: Session[]
@@ -51,7 +51,6 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
init: () => {
const [globalStore, setGlobalStore] = createStore<{
ready: boolean
defaultProject?: Project // TODO: remove this when we can select projects
projects: Project[]
children: Record<string, State>
}>({
@@ -61,11 +60,10 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
})
const children: Record<string, ReturnType<typeof createStore<State>>> = {}
function child(directory: string) {
if (!children[directory]) {
setGlobalStore("children", directory, {
project: { id: "", worktree: "", time: { created: 0, initialized: 0 } },
project: "",
config: {},
path: { state: "", config: "", worktree: "", directory: "" },
ready: false,
@@ -75,7 +73,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
session_status: {},
session_diff: {},
todo: {},
limit: 10,
limit: 5,
message: {},
part: {},
node: [],
@@ -89,9 +87,29 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
const sdk = useGlobalSDK()
sdk.event.listen((e) => {
const directory = e.name
const [store, setStore] = child(directory)
const event = e.details
if (directory === "global") {
switch (event.type) {
case "project.updated": {
const result = Binary.search(globalStore.projects, event.properties.id, (s) => s.id)
if (result.found) {
setGlobalStore("projects", result.index, reconcile(event.properties))
break
}
setGlobalStore(
"projects",
produce((draft) => {
draft.splice(result.index, 0, event.properties)
}),
)
break
}
}
return
}
const [store, setStore] = child(directory)
switch (event.type) {
case "session.updated": {
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
@@ -165,11 +183,11 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
sdk.client.project.list().then((x) =>
setGlobalStore(
"projects",
x.data!.filter((x) => !x.worktree.includes("opencode-test")),
x
.data!.filter((x) => !x.worktree.includes("opencode-test") && x.vcs)
.sort((a, b) => a.id.localeCompare(b.id)),
),
),
// TODO: remove this when we can select projects
sdk.client.project.current().then((x) => setGlobalStore("defaultProject", x.data)),
]).then(() => setGlobalStore("ready", true))
return {

View File

@@ -1,18 +1,20 @@
import { createStore } from "solid-js/store"
import { createMemo } from "solid-js"
import { createMemo, onMount } from "solid-js"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { makePersisted } from "@solid-primitives/storage"
import { useGlobalSync } from "./global-sync"
import { useGlobalSDK } from "./global-sdk"
export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
name: "Layout",
init: () => {
const globalSdk = useGlobalSDK()
const globalSync = useGlobalSync()
const [store, setStore] = makePersisted(
createStore({
projects: [] as { directory: string; expanded: boolean }[],
projects: [] as { worktree: string; expanded: boolean }[],
sidebar: {
opened: true,
opened: false,
width: 280,
},
terminal: {
@@ -24,29 +26,66 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
}),
{
name: "____default-layout",
name: "default-layout.v6",
},
)
async function loadProjectSessions(directory: string) {
const [, setStore] = globalSync.child(directory)
globalSdk.client.session.list({ directory }).then((x) => {
const sessions = (x.data ?? [])
.slice()
.sort((a, b) => a.id.localeCompare(b.id))
.slice(0, 5)
setStore("session", sessions)
})
}
onMount(() => {
Promise.all(
store.projects.map(({ worktree }) => {
return loadProjectSessions(worktree)
}),
)
})
function enrich(project: { worktree: string; expanded: boolean }) {
const metadata = globalSync.data.projects.find((x) => x.worktree === project.worktree)
if (!metadata) return []
return [
{
...project,
...metadata,
},
]
}
return {
projects: {
list: createMemo(() =>
globalSync.data.defaultProject
? [{ directory: globalSync.data.defaultProject!.worktree, expanded: true }, ...store.projects]
: store.projects,
),
list: createMemo(() => store.projects.flatMap(enrich)),
open(directory: string) {
if (store.projects.find((x) => x.directory === directory)) return
setStore("projects", (x) => [...x, { directory, expanded: true }])
if (store.projects.find((x) => x.worktree === directory)) return
loadProjectSessions(directory)
setStore("projects", (x) => [...x, { worktree: directory, expanded: true }])
},
close(directory: string) {
setStore("projects", (x) => x.filter((x) => x.directory !== directory))
setStore("projects", (x) => x.filter((x) => x.worktree !== directory))
},
expand(directory: string) {
setStore("projects", (x) => x.map((x) => (x.directory === directory ? { ...x, expanded: true } : x)))
setStore("projects", (x) => x.map((x) => (x.worktree === directory ? { ...x, expanded: true } : x)))
},
collapse(directory: string) {
setStore("projects", (x) => x.map((x) => (x.directory === directory ? { ...x, expanded: false } : x)))
setStore("projects", (x) => x.map((x) => (x.worktree === directory ? { ...x, expanded: false } : x)))
},
move(directory: string, toIndex: number) {
setStore("projects", (projects) => {
const fromIndex = projects.findIndex((x) => x.worktree === directory)
if (fromIndex === -1 || fromIndex === toIndex) return projects
const result = [...projects]
const [item] = result.splice(fromIndex, 1)
result.splice(toIndex, 0, item)
return result
})
},
},
sidebar: {

View File

@@ -5,7 +5,7 @@ import type { FileContent, FileNode, Model, Provider, File as FileStatus } from
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useSDK } from "./sdk"
import { useSync } from "./sync"
import { base64Encode } from "@/utils"
import { base64Encode } from "@opencode-ai/util/encode"
export type LocalFile = FileNode &
Partial<{
@@ -257,7 +257,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const load = async (path: string) => {
const relativePath = relative(path)
sdk.client.file.read({ path: relativePath }).then((x) => {
await sdk.client.file.read({ path: relativePath }).then((x) => {
setStore(
"node",
relativePath,
@@ -335,7 +335,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
return {
node: async (path: string) => {
if (!store.node[path]) {
if (!store.node[path] || !store.node[path].loaded) {
await init(path)
}
return store.node[path]

View File

@@ -0,0 +1,25 @@
import { createSimpleContext } from "@opencode-ai/ui/context"
export type Platform = {
/** Platform discriminator */
platform: "web" | "tauri"
/** Open native directory picker dialog (Tauri only) */
openDirectoryPickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise<string | string[] | null>
/** Open native file picker dialog (Tauri only) */
openFilePickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise<string | string[] | null>
/** Save file picker dialog (Tauri only) */
saveFilePickerDialog?(opts?: { title?: string; defaultPath?: string }): Promise<string | null>
/** Open a URL in the default browser */
openLink(url: string): void
}
export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({
name: "Platform",
init: (props: { value: Platform }) => {
return props.value
},
})

View File

@@ -7,7 +7,6 @@ import { TextSelection } from "./local"
import { pipe, sumBy } from "remeda"
import { AssistantMessage, UserMessage } from "@opencode-ai/sdk/v2"
import { useParams } from "@solidjs/router"
import { base64Encode } from "@/utils"
import { useSDK } from "./sdk"
export type LocalPTY = {
@@ -25,9 +24,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
const sdk = useSDK()
const params = useParams()
const sync = useSync()
const name = createMemo(
() => `${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}.v1`,
)
const name = createMemo(() => `${params.dir}/session${params.id ? "/" + params.id : ""}.v3`)
const [store, setStore] = makePersisted(
createStore<{

View File

@@ -13,7 +13,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
const [store, setStore] = globalSync.child(sdk.directory)
const load = {
project: () => sdk.client.project.current().then((x) => setStore("project", x.data!)),
project: () => sdk.client.project.current().then((x) => setStore("project", x.data!.id)),
provider: () => sdk.client.config.providers().then((x) => setStore("provider", x.data!.providers)),
path: () => sdk.client.path.get().then((x) => setStore("path", x.data!)),
agent: () => sdk.client.app.agents().then((x) => setStore("agent", x.data ?? [])),
@@ -41,6 +41,11 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
get ready() {
return store.ready
},
get project() {
const match = Binary.search(globalSync.data.projects, store.project, (p) => p.id)
if (match.found) return globalSync.data.projects[match.index]
return undefined
},
session: {
get(sessionID: string) {
const match = Binary.search(store.session, sessionID, (s) => s.id)

View File

@@ -0,0 +1,27 @@
// @refresh reload
import { render } from "solid-js/web"
import { App } from "@/app"
import { Platform, PlatformProvider } from "@/context/platform"
const root = document.getElementById("root")
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
throw new Error(
"Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?",
)
}
const platform: Platform = {
platform: "web",
openLink(url: string) {
window.open(url, "_blank")
},
}
render(
() => (
<PlatformProvider value={platform}>
<App />
</PlatformProvider>
),
root!,
)

View File

@@ -0,0 +1,2 @@
export { PlatformProvider, type Platform } from "./context/platform"
export { App } from "./app"

View File

@@ -1,73 +0,0 @@
/* @refresh reload */
import "@/index.css"
import { render } from "solid-js/web"
import { Router, Route, Navigate } from "@solidjs/router"
import { MetaProvider } from "@solidjs/meta"
import { Font } from "@opencode-ai/ui/font"
import { Favicon } from "@opencode-ai/ui/favicon"
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
import { GlobalSyncProvider, useGlobalSync } from "./context/global-sync"
import Layout from "@/pages/layout"
import DirectoryLayout from "@/pages/directory-layout"
import Session from "@/pages/session"
import { LayoutProvider } from "./context/layout"
import { GlobalSDKProvider } from "./context/global-sdk"
import { SessionProvider } from "./context/session"
import { base64Encode } from "./utils"
import { createMemo, Show } from "solid-js"
const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
const url =
new URLSearchParams(document.location.search).get("url") ||
(location.hostname.includes("opencode.ai") || location.hostname.includes("localhost")
? `http://${host}:${port}`
: "/")
const root = document.getElementById("root")
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
throw new Error(
"Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?",
)
}
render(
() => (
<MarkedProvider>
<GlobalSDKProvider url={url}>
<GlobalSyncProvider>
<LayoutProvider>
<MetaProvider>
<Font />
<Router root={Layout}>
<Route
path="/"
component={() => {
const globalSync = useGlobalSync()
const slug = createMemo(() => base64Encode(globalSync.data.defaultProject!.worktree))
return <Navigate href={`${slug()}/session`} />
}}
/>
<Route path="/:dir" component={DirectoryLayout}>
<Route path="/" component={() => <Navigate href="session" />} />
<Route
path="/session/:id?"
component={(p) => (
<Show when={p.params.id || true} keyed>
<SessionProvider>
<Session />
</SessionProvider>
</Show>
)}
/>
</Route>
</Router>
</MetaProvider>
</LayoutProvider>
</GlobalSyncProvider>
</GlobalSDKProvider>
</MarkedProvider>
),
root!,
)

View File

@@ -1,32 +1,31 @@
import { createMemo, type ParentProps } from "solid-js"
import { createMemo, Show, type ParentProps } from "solid-js"
import { useParams } from "@solidjs/router"
import { SDKProvider } from "@/context/sdk"
import { SyncProvider, useSync } from "@/context/sync"
import { LocalProvider } from "@/context/local"
import { useGlobalSync } from "@/context/global-sync"
import { base64Decode } from "@/utils"
import { base64Decode } from "@opencode-ai/util/encode"
import { DataProvider } from "@opencode-ai/ui/context"
import { iife } from "@opencode-ai/util/iife"
export default function Layout(props: ParentProps) {
const params = useParams()
const sync = useGlobalSync()
const directory = createMemo(() => {
const decoded = base64Decode(params.dir!)
return sync.data.projects.find((x) => x.worktree === decoded)?.worktree ?? "/"
return base64Decode(params.dir!)
})
return (
<SDKProvider directory={directory()}>
<SyncProvider>
{iife(() => {
const sync = useSync()
return (
<DataProvider data={sync.data} directory={directory()}>
<LocalProvider>{props.children}</LocalProvider>
</DataProvider>
)
})}
</SyncProvider>
</SDKProvider>
<Show when={params.dir} keyed>
<SDKProvider directory={directory()}>
<SyncProvider>
{iife(() => {
const sync = useSync()
return (
<DataProvider data={sync.data} directory={directory()}>
<LocalProvider>{props.children}</LocalProvider>
</DataProvider>
)
})}
</SyncProvider>
</SDKProvider>
</Show>
)
}

View File

@@ -1,21 +1,92 @@
import { useGlobalSync } from "@/context/global-sync"
import { base64Encode } from "@/utils"
import { For } from "solid-js"
import { A } from "@solidjs/router"
import { For, Match, Show, Switch } from "solid-js"
import { Button } from "@opencode-ai/ui/button"
import { getFilename } from "@opencode-ai/util/path"
import { Logo } from "@opencode-ai/ui/logo"
import { useLayout } from "@/context/layout"
import { useNavigate } from "@solidjs/router"
import { base64Encode } from "@opencode-ai/util/encode"
import { Icon } from "@opencode-ai/ui/icon"
import { usePlatform } from "@/context/platform"
import { DateTime } from "luxon"
export default function Home() {
const sync = useGlobalSync()
const layout = useLayout()
const platform = usePlatform()
const navigate = useNavigate()
function openProject(directory: string) {
layout.projects.open(directory)
navigate(`/${base64Encode(directory)}`)
}
async function chooseProject() {
const result = await platform.openDirectoryPickerDialog?.({
title: "Open project",
multiple: true,
})
if (Array.isArray(result)) {
for (const directory of result) {
openProject(directory)
}
} else if (result) {
openProject(result)
}
}
return (
<div class="flex flex-col gap-3">
<For each={sync.data.projects}>
{(project) => (
<Button as={A} href={base64Encode(project.worktree)}>
{getFilename(project.worktree)}
</Button>
)}
</For>
<div class="mx-auto mt-55">
<Logo class="w-xl opacity-12" />
<Switch>
<Match when={sync.data.projects.length > 0}>
<div class="mt-20 w-full flex flex-col gap-4">
<div class="flex gap-2 items-center justify-between pl-3">
<div class="text-14-medium text-text-strong">Recent projects</div>
<Show when={platform.openDirectoryPickerDialog}>
<Button icon="folder-add-left" size="normal" class="pl-2 pr-3" onClick={chooseProject}>
Open project
</Button>
</Show>
</div>
<ul class="flex flex-col gap-2">
<For
each={sync.data.projects
.toSorted((a, b) => (b.time.updated ?? b.time.created) - (a.time.updated ?? a.time.created))
.slice(0, 5)}
>
{(project) => (
<Button
size="large"
variant="ghost"
class="text-14-mono text-left justify-between px-3"
onClick={() => openProject(project.worktree)}
>
{project.worktree}
<div class="text-14-regular text-text-weak">
{DateTime.fromMillis(project.time.updated ?? project.time.created).toRelative()}
</div>
</Button>
)}
</For>
</ul>
</div>
</Match>
<Match when={true}>
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
<Icon name="folder-add-left" size="large" />
<div class="flex flex-col gap-1 items-center justify-center">
<div class="text-14-medium text-text-strong">No recent projects</div>
<div class="text-12-regular text-text-weak">Get started by opening a local project</div>
</div>
<div />
<Show when={platform.openDirectoryPickerDialog}>
<Button class="px-3" onClick={chooseProject}>
Open project
</Button>
</Show>
</div>
</Match>
</Switch>
</div>
)
}

View File

@@ -1,10 +1,12 @@
import { createMemo, For, ParentProps, Show } from "solid-js"
import { createEffect, createMemo, For, Match, ParentProps, Show, Switch, type JSX } from "solid-js"
import { DateTime } from "luxon"
import { A, useNavigate, useParams } from "@solidjs/router"
import { useLayout } from "@/context/layout"
import { useGlobalSync } from "@/context/global-sync"
import { base64Decode, base64Encode } from "@/utils"
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
import { Mark } from "@opencode-ai/ui/logo"
import { Avatar } from "@opencode-ai/ui/avatar"
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
@@ -13,24 +15,296 @@ import { Collapsible } from "@opencode-ai/ui/collapsible"
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
import { getFilename } from "@opencode-ai/util/path"
import { Select } from "@opencode-ai/ui/select"
import { Session } from "@opencode-ai/sdk/v2/client"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
import { Session, Project } from "@opencode-ai/sdk/v2/client"
import { usePlatform } from "@/context/platform"
import { createStore } from "solid-js/store"
import {
DragDropProvider,
DragDropSensors,
DragOverlay,
SortableProvider,
closestCenter,
createSortable,
useDragDropContext,
} from "@thisbeyond/solid-dnd"
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
export default function Layout(props: ParentProps) {
const navigate = useNavigate()
const [store, setStore] = createStore({
lastSession: {} as { [directory: string]: string },
activeDraggable: undefined as string | undefined,
})
const params = useParams()
const globalSync = useGlobalSync()
const layout = useLayout()
const platform = usePlatform()
const navigate = useNavigate()
const currentDirectory = createMemo(() => base64Decode(params.dir ?? ""))
const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? [])
const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
function navigateToProject(directory: string | undefined) {
if (!directory) return
const lastSession = store.lastSession[directory]
navigate(`/${base64Encode(directory)}${lastSession ? `/session/${lastSession}` : ""}`)
}
function navigateToSession(session: Session | undefined) {
if (!session) return
navigate(`/${params.dir}/session/${session?.id}`)
}
const handleOpenProject = async () => {
// layout.projects.open(dir.)
function openProject(directory: string, navigate = true) {
layout.projects.open(directory)
if (navigate) navigateToProject(directory)
}
function closeProject(directory: string) {
layout.projects.close(directory)
// TODO: more intelligent navigation
navigate("/")
}
async function chooseProject() {
const result = await platform.openDirectoryPickerDialog?.({
title: "Open project",
multiple: true,
})
if (Array.isArray(result)) {
for (const directory of result) {
openProject(directory, false)
}
navigateToProject(result[0])
} else if (result) {
openProject(result)
}
}
createEffect(() => {
if (!params.dir || !params.id) return
const directory = base64Decode(params.dir)
setStore("lastSession", directory, params.id)
})
function getDraggableId(event: unknown): string | undefined {
if (typeof event !== "object" || event === null) return undefined
if (!("draggable" in event)) return undefined
const draggable = (event as { draggable?: { id?: unknown } }).draggable
if (!draggable) return undefined
return typeof draggable.id === "string" ? draggable.id : undefined
}
function handleDragStart(event: unknown) {
const id = getDraggableId(event)
if (!id) return
setStore("activeDraggable", id)
}
function handleDragOver(event: DragEvent) {
const { draggable, droppable } = event
if (draggable && droppable) {
const projects = layout.projects.list()
const fromIndex = projects.findIndex((p) => p.worktree === draggable.id.toString())
const toIndex = projects.findIndex((p) => p.worktree === droppable.id.toString())
if (fromIndex !== toIndex && toIndex !== -1) {
layout.projects.move(draggable.id.toString(), toIndex)
}
}
}
function handleDragEnd() {
setStore("activeDraggable", undefined)
}
const ConstrainDragXAxis = (): JSX.Element => {
const context = useDragDropContext()
if (!context) return <></>
const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context
const transformer: Transformer = {
id: "constrain-x-axis",
order: 100,
callback: (transform) => ({ ...transform, x: 0 }),
}
onDragStart((event) => {
const id = getDraggableId(event)
if (!id) return
addTransformer("draggables", id, transformer)
})
onDragEnd((event) => {
const id = getDraggableId(event)
if (!id) return
removeTransformer("draggables", id, transformer.id)
})
return <></>
}
const ProjectVisual = (props: { project: Project & { expanded: boolean }; class?: string }): JSX.Element => {
const name = createMemo(() => getFilename(props.project.worktree))
return (
<Switch>
<Match when={layout.sidebar.opened()}>
<Button
as={"div"}
variant="ghost"
data-active
class="flex items-center justify-between gap-3 w-full px-1 self-stretch h-8 border-none rounded-lg"
>
<div class="flex items-center gap-3 p-0 text-left min-w-0 grow">
<div class="size-6 shrink-0">
<Avatar
fallback={name()}
src={props.project.icon?.url}
background={props.project.icon?.color ?? "var(--surface-info-base)"}
class="size-full"
/>
</div>
<span class="truncate text-14-medium text-text-strong">{name()}</span>
</div>
</Button>
</Match>
<Match when={true}>
<Button
variant="ghost"
size="large"
class="flex items-center justify-center p-0 aspect-square border-none rounded-lg"
data-selected={props.project.worktree === currentDirectory()}
onClick={() => navigateToProject(props.project.worktree)}
>
<div class="size-6 shrink-0">
<Avatar
fallback={name()}
src={props.project.icon?.url}
background={props.project.icon?.color ?? "var(--surface-info-base)"}
class="size-full"
/>
</div>
</Button>
</Match>
</Switch>
)
}
const SortableProject = (props: { project: Project & { expanded: boolean } }): JSX.Element => {
const sortable = createSortable(props.project.worktree)
const [projectStore] = globalSync.child(props.project.worktree)
const slug = createMemo(() => base64Encode(props.project.worktree))
const name = createMemo(() => getFilename(props.project.worktree))
return (
// @ts-ignore
<div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
<Switch>
<Match when={layout.sidebar.opened()}>
<Collapsible variant="ghost" defaultOpen class="gap-2 shrink-0">
<Button
as={"div"}
variant="ghost"
class="group/session flex items-center justify-between gap-3 w-full px-1 self-stretch h-auto border-none rounded-lg"
>
<Collapsible.Trigger class="group/trigger flex items-center gap-3 p-0 text-left min-w-0 grow border-none">
<div class="size-6 shrink-0">
<Avatar
fallback={name()}
src={props.project.icon?.url}
background={props.project.icon?.color ?? "var(--surface-info-base)"}
class="size-full group-hover/session:hidden"
/>
<Icon
name="chevron-right"
size="large"
class="hidden size-full items-center justify-center text-text-subtle group-hover/session:flex group-data-[expanded]/trigger:rotate-90 transition-transform duration-50"
/>
</div>
<span class="truncate text-14-medium text-text-strong">{name()}</span>
</Collapsible.Trigger>
<div class="flex invisible gap-1 items-center group-hover/session:visible has-[[data-expanded]]:visible">
<DropdownMenu>
<DropdownMenu.Trigger as={IconButton} icon="dot-grid" variant="ghost" />
<DropdownMenu.Portal>
<DropdownMenu.Content>
<DropdownMenu.Item onSelect={() => closeProject(props.project.worktree)}>
<DropdownMenu.ItemLabel>Close Project</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
<Tooltip placement="top" value="New session">
<IconButton as={A} href={`${slug()}/session`} icon="plus-small" variant="ghost" />
</Tooltip>
</div>
</Button>
<Collapsible.Content>
<nav class="hidden @[4rem]:flex w-full flex-col gap-1.5">
<For each={projectStore.session}>
{(session) => {
const updated = createMemo(() => DateTime.fromMillis(session.time.updated))
return (
<A
data-active={session.id === params.id}
href={`${slug()}/session/${session.id}`}
class="group/session focus:outline-none cursor-default"
>
<Tooltip placement="right" value={session.title}>
<div
class="w-full pl-4 pr-2 py-1 rounded-md
group-data-[active=true]/session:bg-surface-raised-base-hover
group-hover/session:bg-surface-raised-base-hover
group-focus/session:bg-surface-raised-base-hover"
>
<div class="flex items-center self-stretch gap-6 justify-between">
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
{session.title}
</span>
<span class="text-12-regular text-text-weak text-right whitespace-nowrap">
{Math.abs(updated().diffNow().as("seconds")) < 60
? "Now"
: updated()
.toRelative({
style: "short",
unit: ["days", "hours", "minutes"],
})
?.replace(" ago", "")
?.replace(/ days?/, "d")
?.replace(" min.", "m")
?.replace(" hr.", "h")}
</span>
</div>
<div class="hidden _flex justify-between items-center self-stretch">
<span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span>
<Show when={session.summary}>{(summary) => <DiffChanges changes={summary()} />}</Show>
</div>
</div>
</Tooltip>
</A>
)
}}
</For>
</nav>
</Collapsible.Content>
</Collapsible>
</Match>
<Match when={true}>
<Tooltip placement="right" value={props.project.worktree}>
<ProjectVisual project={props.project} />
</Tooltip>
</Match>
</Switch>
</div>
)
}
const ProjectDragOverlay = (): JSX.Element => {
const project = createMemo(() => layout.projects.list().find((p) => p.worktree === store.activeDraggable))
return (
<Show when={project()}>
{(p) => (
<div class="bg-background-base rounded-md">
<ProjectVisual project={p()} />
</div>
)}
</Show>
)
}
return (
@@ -49,61 +323,75 @@ export default function Layout(props: ParentProps) {
<Mark class="shrink-0" />
</A>
<div class="pl-4 px-6 flex items-center justify-between gap-4 w-full">
<div class="flex items-center gap-3">
<div class="flex items-center gap-2">
<Select
options={layout.projects.list().map((project) => getFilename(project.directory))}
current={getFilename(currentDirectory())}
class="text-14-regular text-text-base"
variant="ghost"
/>
<div class="text-text-weaker">/</div>
<Select
options={sessions()}
current={currentSession()}
placeholder="Select session"
label={(x) => x.title}
value={(x) => x.id}
onSelect={navigateToSession}
class="text-14-regular text-text-base max-w-3xs"
variant="ghost"
/>
<Show when={params.dir && layout.projects.list().length > 0}>
<div class="flex items-center gap-3">
<div class="flex items-center gap-2">
<Select
options={layout.projects.list().map((project) => project.worktree)}
current={currentDirectory()}
label={(x) => getFilename(x)}
onSelect={(x) => (x ? navigateToProject(x) : undefined)}
class="text-14-regular text-text-base"
variant="ghost"
>
{/* @ts-ignore */}
{(i) => (
<div class="flex items-center gap-2">
<Icon name="folder" size="small" />
<div class="text-text-strong">{getFilename(i)}</div>
</div>
)}
</Select>
<div class="text-text-weaker">/</div>
<Select
options={sessions()}
current={currentSession()}
placeholder="New session"
label={(x) => x.title}
value={(x) => x.id}
onSelect={navigateToSession}
class="text-14-regular text-text-base max-w-md"
variant="ghost"
/>
</div>
<Show when={currentSession()}>
<Button as={A} href={`/${params.dir}/session`} icon="plus-small">
New session
</Button>
</Show>
</div>
<Button as={A} href={`/${params.dir}/session`} icon="plus-small">
New session
</Button>
</div>
<div class="flex items-center gap-4">
<Tooltip
class="shrink-0"
value={
<div class="flex items-center gap-2">
<span>Toggle terminal</span>
<span class="text-icon-base text-12-medium">Ctrl `</span>
</div>
}
>
<Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
size="small"
name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
class="group-hover/terminal-toggle:hidden"
/>
<Icon
size="small"
name="layout-bottom-partial"
class="hidden group-hover/terminal-toggle:inline-block"
/>
<Icon
size="small"
name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
class="hidden group-active/terminal-toggle:inline-block"
/>
</div>
</Button>
</Tooltip>
</div>
<div class="flex items-center gap-4">
<Tooltip
class="shrink-0"
value={
<div class="flex items-center gap-2">
<span>Toggle terminal</span>
<span class="text-icon-base text-12-medium">Ctrl `</span>
</div>
}
>
<Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
size="small"
name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
class="group-hover/terminal-toggle:hidden"
/>
<Icon
size="small"
name="layout-bottom-partial"
class="hidden group-hover/terminal-toggle:inline-block"
/>
<Icon
size="small"
name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
class="hidden group-active/terminal-toggle:inline-block"
/>
</div>
</Button>
</Tooltip>
</div>
</Show>
</div>
</header>
<div class="h-[calc(100vh-3rem)] flex">
@@ -116,49 +404,22 @@ export default function Layout(props: ParentProps) {
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
>
<Show when={layout.sidebar.opened()}>
<div
class="absolute inset-y-0 right-0 z-10 w-2 translate-x-1/2 cursor-ew-resize"
onMouseDown={(e) => {
e.preventDefault()
const startX = e.clientX
const startWidth = layout.sidebar.width()
const maxWidth = window.innerWidth * 0.3
const minWidth = 150
const collapseThreshold = 80
let currentWidth = startWidth
document.body.style.userSelect = "none"
document.body.style.overflow = "hidden"
const onMouseMove = (moveEvent: MouseEvent) => {
const deltaX = moveEvent.clientX - startX
currentWidth = startWidth + deltaX
const clampedWidth = Math.min(maxWidth, Math.max(minWidth, currentWidth))
layout.sidebar.resize(clampedWidth)
}
const onMouseUp = () => {
document.body.style.userSelect = ""
document.body.style.overflow = ""
document.removeEventListener("mousemove", onMouseMove)
document.removeEventListener("mouseup", onMouseUp)
if (currentWidth < collapseThreshold) {
layout.sidebar.close()
}
}
document.addEventListener("mousemove", onMouseMove)
document.addEventListener("mouseup", onMouseUp)
}}
<ResizeHandle
direction="horizontal"
size={layout.sidebar.width()}
min={150}
max={window.innerWidth * 0.3}
collapseThreshold={80}
onResize={layout.sidebar.resize}
onCollapse={layout.sidebar.close}
/>
</Show>
<div class="grow flex flex-col items-start self-stretch gap-4 p-2 min-h-0">
<div class="flex flex-col items-start self-stretch gap-4 p-2 min-h-0 overflow-hidden">
<Tooltip class="shrink-0" placement="right" value="Toggle sidebar" inactive={layout.sidebar.opened()}>
<Button
variant="ghost"
size="large"
class="group/sidebar-toggle shrink-0 w-full text-left justify-start"
class="group/sidebar-toggle shrink-0 w-full text-left justify-start rounded-lg"
onClick={layout.sidebar.toggle}
>
<div class="relative -ml-px flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
@@ -185,103 +446,42 @@ export default function Layout(props: ParentProps) {
</Show>
</Button>
</Tooltip>
<div class="flex flex-col justify-center items-start gap-4 self-stretch min-h-0">
<div class="hidden @[4rem]:flex size-full flex-col grow overflow-y-auto no-scrollbar">
<For each={layout.projects.list()}>
{(project) => {
const [store] = globalSync.child(project.directory)
const slug = createMemo(() => base64Encode(project.directory))
return (
<Collapsible variant="ghost" defaultOpen class="gap-2">
<Button
as={"div"}
variant="ghost"
class="flex items-center justify-between gap-3 w-full h-8 pl-2 pr-2.25 self-stretch"
>
<Collapsible.Trigger class="p-0 text-left text-14-medium text-text-strong grow min-w-0 truncate">
{getFilename(project.directory)}
</Collapsible.Trigger>
<IconButton as={A} href={`${slug()}/session`} icon="plus-small" size="normal" />
</Button>
<Collapsible.Content>
<nav class="w-full flex flex-col gap-1.5">
<For each={store.session}>
{(session) => {
const updated = createMemo(() => DateTime.fromMillis(session.time.updated))
return (
<A
data-active={session.id === params.id}
href={`${slug()}/session/${session.id}`}
class="group/session focus:outline-none cursor-default"
>
<Tooltip placement="right" value={session.title}>
<div
class="w-full px-2 py-1 rounded-md
group-data-[active=true]/session:bg-surface-raised-base-hover
group-hover/session:bg-surface-raised-base-hover
group-focus/session:bg-surface-raised-base-hover"
>
<div class="flex items-center self-stretch gap-6 justify-between">
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
{session.title}
</span>
<span class="text-12-regular text-text-weak text-right whitespace-nowrap">
{Math.abs(updated().diffNow().as("seconds")) < 60
? "Now"
: updated()
.toRelative({ style: "short", unit: ["days", "hours", "minutes"] })
?.replace(" ago", "")
?.replace(/ days?/, "d")
?.replace(" min.", "m")
?.replace(" hr.", "h")}
</span>
</div>
<div class="hidden _flex justify-between items-center self-stretch">
<span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span>
<Show when={session.summary}>
{(summary) => <DiffChanges changes={summary()} />}
</Show>
</div>
</div>
</Tooltip>
</A>
)
}}
</For>
</nav>
{/* <Show when={sync.session.more()}> */}
{/* <button */}
{/* class="shrink-0 self-start p-3 text-12-medium text-text-weak hover:text-text-strong" */}
{/* onClick={() => sync.session.fetch()} */}
{/* > */}
{/* Show more */}
{/* </button> */}
{/* </Show> */}
</Collapsible.Content>
</Collapsible>
)
}}
</For>
<DragDropProvider
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
collisionDetector={closestCenter}
>
<DragDropSensors />
<ConstrainDragXAxis />
<div class="w-full min-w-8 flex flex-col gap-2 min-h-0 overflow-y-auto no-scrollbar">
<SortableProvider ids={layout.projects.list().map((p) => p.worktree)}>
<For each={layout.projects.list()}>{(project) => <SortableProject project={project} />}</For>
</SortableProvider>
</div>
</div>
<DragOverlay>
<ProjectDragOverlay />
</DragOverlay>
</DragDropProvider>
</div>
<div class="flex flex-col gap-1.5 self-stretch items-start shrink-0 px-2 py-3">
<Tooltip placement="right" value="Open project" inactive={layout.sidebar.opened()}>
<Button
disabled
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px]"
variant="ghost"
size="large"
icon="folder-add-left"
onClick={handleOpenProject}
>
<Show when={layout.sidebar.opened()}>Open project</Show>
</Button>
</Tooltip>
<Show when={platform.openDirectoryPickerDialog}>
<Tooltip placement="right" value="Open project" inactive={layout.sidebar.opened()}>
<Button
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px] rounded-lg"
variant="ghost"
size="large"
icon="folder-add-left"
onClick={chooseProject}
>
<Show when={layout.sidebar.opened()}>Open project</Show>
</Button>
</Tooltip>
</Show>
<Tooltip placement="right" value="Settings" inactive={layout.sidebar.opened()}>
<Button
disabled
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px]"
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px] rounded-lg"
variant="ghost"
size="large"
icon="settings-gear"
@@ -294,7 +494,7 @@ export default function Layout(props: ParentProps) {
as={"a"}
href="https://opencode.ai/desktop-feedback"
target="_blank"
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px]"
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px] rounded-lg"
variant="ghost"
size="large"
icon="bubble-5"

View File

@@ -9,6 +9,7 @@ import { Icon } from "@opencode-ai/ui/icon"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { Tabs } from "@opencode-ai/ui/tabs"
import { Code } from "@opencode-ai/ui/code"
import { SessionTurn } from "@opencode-ai/ui/session-turn"
@@ -27,11 +28,11 @@ import {
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
import type { JSX } from "solid-js"
import { useSync } from "@/context/sync"
import { useSession } from "@/context/session"
import { useSession, type LocalPTY } from "@/context/session"
import { useLayout } from "@/context/layout"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { Diff } from "@opencode-ai/ui/diff"
import { Terminal } from "@/components/terminal"
import { checksum } from "@opencode-ai/util/encode"
export default function Page() {
const layout = useLayout()
@@ -42,6 +43,7 @@ export default function Page() {
clickTimer: undefined as number | undefined,
fileSelectOpen: false,
activeDraggable: undefined as string | undefined,
activeTerminalDraggable: undefined as string | undefined,
})
let inputRef!: HTMLDivElement
@@ -177,6 +179,49 @@ export default function Page() {
setStore("activeDraggable", undefined)
}
const handleTerminalDragStart = (event: unknown) => {
const id = getDraggableId(event)
if (!id) return
setStore("activeTerminalDraggable", id)
}
const handleTerminalDragOver = (event: DragEvent) => {
const { draggable, droppable } = event
if (draggable && droppable) {
const terminals = session.terminal.all()
const fromIndex = terminals.findIndex((t) => t.id === draggable.id.toString())
const toIndex = terminals.findIndex((t) => t.id === droppable.id.toString())
if (fromIndex !== -1 && toIndex !== -1 && fromIndex !== toIndex) {
session.terminal.move(draggable.id.toString(), toIndex)
}
}
}
const handleTerminalDragEnd = () => {
setStore("activeTerminalDraggable", undefined)
}
const SortableTerminalTab = (props: { terminal: LocalPTY }): JSX.Element => {
const sortable = createSortable(props.terminal.id)
return (
// @ts-ignore
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
<div class="relative h-full">
<Tabs.Trigger
value={props.terminal.id}
closeButton={
session.terminal.all().length > 1 && (
<IconButton icon="close" variant="ghost" onClick={() => session.terminal.close(props.terminal.id)} />
)
}
>
{props.terminal.title}
</Tabs.Trigger>
</div>
</div>
)
}
const FileVisual = (props: { file: LocalFile; active?: boolean }): JSX.Element => {
return (
<div class="flex items-center gap-x-1.5">
@@ -219,7 +264,6 @@ export default function Page() {
onTabClose: (tab: string) => void
}): JSX.Element => {
const sortable = createSortable(props.tab)
const [file] = createResource(
() => props.tab,
async (tab) => {
@@ -229,7 +273,6 @@ export default function Page() {
return undefined
},
)
return (
// @ts-ignore
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
@@ -282,7 +325,7 @@ export default function Page() {
const wide = createMemo(() => layout.review.state() === "tab" || !session.diffs().length)
return (
<div class="relative bg-background-base size-full overflow-x-hidden flex flex-col items-start">
<div class="relative bg-background-base size-full overflow-x-hidden flex flex-col">
<div class="min-h-0 grow w-full">
<DragDropProvider
onDragStart={handleDragStart}
@@ -389,7 +432,6 @@ export default function Page() {
? "pr-6 pl-18"
: "px-6"),
}}
diffComponent={Diff}
/>
</div>
</Match>
@@ -403,15 +445,19 @@ export default function Page() {
<span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
</div>
</div>
<div class="flex justify-center items-center gap-3">
<Icon name="pencil-line" size="small" />
<div class="text-12-medium text-text-weak">
Last modified&nbsp;
<span class="text-text-strong">
{DateTime.fromMillis(sync.data.project.time.created).toRelative()}
</span>
</div>
</div>
<Show when={sync.project}>
{(project) => (
<div class="flex justify-center items-center gap-3">
<Icon name="pencil-line" size="small" />
<div class="text-12-medium text-text-weak">
Last modified&nbsp;
<span class="text-text-strong">
{DateTime.fromMillis(project().time.updated ?? project().time.created).toRelative()}
</span>
</div>
</div>
)}
</Show>
</div>
</Match>
</Switch>
@@ -438,7 +484,6 @@ export default function Page() {
container: "px-6",
}}
diffs={session.diffs()}
diffComponent={Diff}
actions={
<Tooltip value="Open in tab">
<IconButton
@@ -470,7 +515,6 @@ export default function Page() {
container: "px-6",
}}
diffs={session.diffs()}
diffComponent={Diff}
split
/>
</div>
@@ -493,7 +537,11 @@ export default function Page() {
<Match when={file()}>
{(f) => (
<Code
file={{ name: f().path, contents: f().content?.content ?? "" }}
file={{
name: f().path,
contents: f().content?.content ?? "",
cacheKey: checksum(f().content?.content ?? ""),
}}
overflow="scroll"
class="pb-40"
/>
@@ -574,7 +622,6 @@ export default function Page() {
onOpenChange={(open) => setStore("fileSelectOpen", open)}
onSelect={(x) => {
if (x) {
local.file.open(x)
return session.layout.openTab("file://" + x)
}
return undefined
@@ -606,76 +653,63 @@ export default function Page() {
class="relative w-full flex flex-col shrink-0 border-t border-border-weak-base"
style={{ height: `${layout.terminal.height()}px` }}
>
<div
class="absolute inset-x-0 top-0 z-10 h-2 -translate-y-1/2 cursor-ns-resize"
onMouseDown={(e) => {
e.preventDefault()
const startY = e.clientY
const startHeight = layout.terminal.height()
const maxHeight = window.innerHeight * 0.6
const minHeight = 100
const collapseThreshold = 50
let currentHeight = startHeight
document.body.style.userSelect = "none"
document.body.style.overflow = "hidden"
const onMouseMove = (moveEvent: MouseEvent) => {
const deltaY = startY - moveEvent.clientY
currentHeight = startHeight + deltaY
const clampedHeight = Math.min(maxHeight, Math.max(minHeight, currentHeight))
layout.terminal.resize(clampedHeight)
}
const onMouseUp = () => {
document.body.style.userSelect = ""
document.body.style.overflow = ""
document.removeEventListener("mousemove", onMouseMove)
document.removeEventListener("mouseup", onMouseUp)
if (currentHeight < collapseThreshold) {
layout.terminal.close()
}
}
document.addEventListener("mousemove", onMouseMove)
document.addEventListener("mouseup", onMouseUp)
}}
<ResizeHandle
direction="vertical"
size={layout.terminal.height()}
min={100}
max={window.innerHeight * 0.6}
collapseThreshold={50}
onResize={layout.terminal.resize}
onCollapse={layout.terminal.close}
/>
<Tabs variant="alt" value={session.terminal.active()} onChange={session.terminal.open}>
<Tabs.List class="h-10">
<DragDropProvider
onDragStart={handleTerminalDragStart}
onDragEnd={handleTerminalDragEnd}
onDragOver={handleTerminalDragOver}
collisionDetector={closestCenter}
>
<DragDropSensors />
<ConstrainDragYAxis />
<Tabs variant="alt" value={session.terminal.active()} onChange={session.terminal.open}>
<Tabs.List class="h-10">
<SortableProvider ids={session.terminal.all().map((t) => t.id)}>
<For each={session.terminal.all()}>{(terminal) => <SortableTerminalTab terminal={terminal} />}</For>
</SortableProvider>
<div class="h-full flex items-center justify-center">
<Tooltip value="Open file" class="flex items-center">
<IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={session.terminal.new} />
</Tooltip>
</div>
</Tabs.List>
<For each={session.terminal.all()}>
{(terminal) => (
<Tabs.Trigger
value={terminal.id}
closeButton={
session.terminal.all().length > 1 && (
<IconButton icon="close" variant="ghost" onClick={() => session.terminal.close(terminal.id)} />
)
}
>
{terminal.title}
</Tabs.Trigger>
<Tabs.Content value={terminal.id}>
<Terminal
pty={terminal}
onCleanup={session.terminal.update}
onConnectError={() => session.terminal.clone(terminal.id)}
/>
</Tabs.Content>
)}
</For>
<div class="h-full flex items-center justify-center">
<Tooltip value="Open file" class="flex items-center">
<IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={session.terminal.new} />
</Tooltip>
</div>
</Tabs.List>
<For each={session.terminal.all()}>
{(terminal) => (
<Tabs.Content value={terminal.id}>
<Terminal
pty={terminal}
onCleanup={session.terminal.update}
onConnectError={() => session.terminal.clone(terminal.id)}
/>
</Tabs.Content>
)}
</For>
</Tabs>
</Tabs>
<DragOverlay>
<Show when={store.activeTerminalDraggable}>
{(draggedId) => {
const terminal = createMemo(() => session.terminal.all().find((t) => t.id === draggedId()))
return (
<Show when={terminal()}>
{(t) => (
<div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular">
{t().title}
</div>
)}
</Show>
)
}}
</Show>
</DragOverlay>
</DragDropProvider>
</div>
</Show>
</div>

View File

@@ -1,7 +0,0 @@
export function base64Encode(value: string) {
return btoa(value).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
}
export function base64Decode(value: string) {
return atob(value.replace(/-/g, "+").replace(/_/g, "/"))
}

View File

@@ -1,2 +1 @@
export * from "./dom"
export * from "./encode"

View File

@@ -1,6 +1,7 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"composite": true,
"target": "ESNext",
"module": "ESNext",
"skipLibCheck": true,
@@ -11,7 +12,9 @@
"jsxImportSource": "solid-js",
"allowJs": true,
"strict": true,
"noEmit": true,
"noEmit": false,
"emitDeclarationOnly": true,
"outDir": "ts-dist",
"isolatedModules": true,
"paths": {
"@/*": ["./src/*"]

View File

@@ -8,53 +8,20 @@
"dark": "#15803D"
},
"favicon": "/favicon.svg",
"servers": [
{
"url": "https://opencode.ai/openapi.json"
}
],
"navigation": {
"tabs": [
{
"tab": "Guides",
"tab": "SDK",
"groups": [
{
"group": "Getting started",
"pages": ["index", "quickstart", "development"]
},
{
"group": "Customization",
"pages": ["essentials/settings", "essentials/navigation"]
},
{
"group": "Writing content",
"pages": ["essentials/markdown", "essentials/code", "essentials/images", "essentials/reusable-snippets"]
},
{
"group": "AI tools",
"pages": ["ai-tools/cursor", "ai-tools/claude-code", "ai-tools/windsurf"]
"pages": ["index", "quickstart", "development"],
"openapi": "https://opencode.ai/openapi.json"
}
]
},
{
"tab": "API Reference",
"openapi": "https://opencode.ai/openapi.json"
}
],
"global": {
"anchors": [
{
"anchor": "Documentation",
"href": "https://mintlify.com/docs",
"icon": "book-open-cover"
},
{
"anchor": "Blog",
"href": "https://mintlify.com/blog",
"icon": "newspaper"
}
]
}
"global": {}
},
"logo": {
"light": "/logo/light.svg",

1
packages/docs/openapi.json Symbolic link
View File

@@ -0,0 +1 @@
../sdk/openapi.json

View File

@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.0.134",
"version": "1.0.138",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,4 +1,4 @@
import { FileDiff, Message, Model, Part, Session, SessionStatus } from "@opencode-ai/sdk"
import { FileDiff, Message, Model, Part, Session } from "@opencode-ai/sdk/v2"
import { fn } from "@opencode-ai/util/fn"
import { iife } from "@opencode-ai/util/iife"
import { Identifier } from "@opencode-ai/util/identifier"

View File

@@ -1,7 +1,8 @@
import { FileDiff, Message, Model, Part, Session, SessionStatus, UserMessage } from "@opencode-ai/sdk"
import { FileDiff, Message, Model, Part, Session, SessionStatus, UserMessage } from "@opencode-ai/sdk/v2"
import { SessionTurn } from "@opencode-ai/ui/session-turn"
import { SessionReview } from "@opencode-ai/ui/session-review"
import { DataProvider } from "@opencode-ai/ui/context"
import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
import { createAsync, query, useParams } from "@solidjs/router"
import { createEffect, createMemo, ErrorBoundary, For, Match, Show, Switch } from "solid-js"
import { Share } from "~/core/share"
@@ -18,7 +19,7 @@ import z from "zod"
import NotFound from "../[...404]"
import { Tabs } from "@opencode-ai/ui/tabs"
import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
import { Diff } from "@opencode-ai/ui/diff-ssr"
import { Diff as SSRDiff } from "@opencode-ai/ui/diff-ssr"
import { clientOnly } from "@solidjs/start"
const ClientOnlyDiff = clientOnly(() => import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff })))
@@ -157,234 +158,240 @@ export default function () {
const info = createMemo(() => data().session[match().index])
return (
<DataProvider data={data()} directory={info().directory}>
{iife(() => {
const [store, setStore] = createStore({
messageId: undefined as string | undefined,
})
const messages = createMemo(() =>
data().sessionID
? (data().message[data().sessionID]?.filter((m) => m.role === "user") ?? []).sort(
(a, b) => b.time.created - a.time.created,
)
: [],
)
const firstUserMessage = createMemo(() => messages().at(0))
const activeMessage = createMemo(
() => messages().find((m) => m.id === store.messageId) ?? firstUserMessage(),
)
function setActiveMessage(message: UserMessage | undefined) {
if (message) {
setStore("messageId", message.id)
} else {
setStore("messageId", undefined)
<DiffComponentProvider component={ClientOnlyDiff}>
<DataProvider data={data()} directory={info().directory}>
{iife(() => {
const [store, setStore] = createStore({
messageId: undefined as string | undefined,
})
const messages = createMemo(() =>
data().sessionID
? (data().message[data().sessionID]?.filter((m) => m.role === "user") ?? []).sort(
(a, b) => b.time.created - a.time.created,
)
: [],
)
const firstUserMessage = createMemo(() => messages().at(0))
const activeMessage = createMemo(
() => messages().find((m) => m.id === store.messageId) ?? firstUserMessage(),
)
function setActiveMessage(message: UserMessage | undefined) {
if (message) {
setStore("messageId", message.id)
} else {
setStore("messageId", undefined)
}
}
}
const provider = createMemo(() => activeMessage()?.model?.providerID)
const modelID = createMemo(() => activeMessage()?.model?.modelID)
const model = createMemo(() => data().model[data().sessionID]?.find((m) => m.id === modelID()))
const diffs = createMemo(() => {
const diffs = data().session_diff[data().sessionID] ?? []
const preloaded = data().session_diff_preload[data().sessionID] ?? []
return diffs.map((diff) => ({
...diff,
preloaded: preloaded.find((d) => d.newFile.name === diff.file),
}))
})
const splitDiffs = createMemo(() => {
const diffs = data().session_diff[data().sessionID] ?? []
const preloaded = data().session_diff_preload_split[data().sessionID] ?? []
return diffs.map((diff) => ({
...diff,
preloaded: preloaded.find((d) => d.newFile.name === diff.file),
}))
})
const provider = createMemo(() => activeMessage()?.model?.providerID)
const modelID = createMemo(() => activeMessage()?.model?.modelID)
const model = createMemo(() => data().model[data().sessionID]?.find((m) => m.id === modelID()))
const diffs = createMemo(() => {
const diffs = data().session_diff[data().sessionID] ?? []
const preloaded = data().session_diff_preload[data().sessionID] ?? []
return diffs.map((diff) => ({
...diff,
preloaded: preloaded.find((d) => d.newFile.name === diff.file),
}))
})
const splitDiffs = createMemo(() => {
const diffs = data().session_diff[data().sessionID] ?? []
const preloaded = data().session_diff_preload_split[data().sessionID] ?? []
return diffs.map((diff) => ({
...diff,
preloaded: preloaded.find((d) => d.newFile.name === diff.file),
}))
})
const title = () => (
<div class="flex flex-col gap-4">
<div class="h-8 flex gap-4 items-center justify-start self-stretch">
<div class="pl-[2.5px] pr-2 flex items-center gap-1.75 bg-surface-strong shadow-xs-border-base">
<Mark class="shrink-0 w-3 my-0.5" />
<div class="text-12-mono text-text-base">v{info().version}</div>
</div>
<div class="flex gap-2 items-center">
<img src={`https://models.dev/logos/${provider()}.svg`} class="size-3.5 shrink-0 dark:invert" />
<div class="text-12-regular text-text-base">{model()?.name ?? modelID()}</div>
</div>
<div class="text-12-regular text-text-weaker">
{DateTime.fromMillis(info().time.created).toFormat("dd MMM yyyy, HH:mm")}
</div>
</div>
<div class="text-left text-16-medium text-text-strong">{info().title}</div>
</div>
)
const turns = () => (
<div class="relative mt-2 pt-6 pb-8 min-w-0 w-full h-full overflow-y-auto no-scrollbar">
<div class="px-4">{title()}</div>
<div class="flex flex-col gap-15 items-start justify-start mt-4">
<For each={messages()}>
{(message) => (
<SessionTurn
sessionID={data().sessionID}
messageID={message.id}
classes={{
root: "min-w-0 w-full relative",
content:
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
container: "px-4",
}}
diffComponent={ClientOnlyDiff}
const title = () => (
<div class="flex flex-col gap-4">
<div class="h-8 flex gap-4 items-center justify-start self-stretch">
<div class="pl-[2.5px] pr-2 flex items-center gap-1.75 bg-surface-strong shadow-xs-border-base">
<Mark class="shrink-0 w-3 my-0.5" />
<div class="text-12-mono text-text-base">v{info().version}</div>
</div>
<div class="flex gap-2 items-center">
<img
src={`https://models.dev/logos/${provider()}.svg`}
class="size-3.5 shrink-0 dark:invert"
/>
)}
</For>
</div>
<div class="px-4 flex items-center justify-center pt-20 pb-8 shrink-0">
<Logo class="w-58.5 opacity-12" />
</div>
</div>
)
const wide = createMemo(() => diffs().length === 0)
return (
<div class="relative bg-background-stronger w-screen h-screen overflow-hidden flex flex-col">
<header class="h-12 px-6 py-2 flex items-center justify-between self-stretch bg-background-base border-b border-border-weak-base">
<div class="">
<a href="https://opencode.ai">
<Mark />
</a>
<div class="text-12-regular text-text-base">{model()?.name ?? modelID()}</div>
</div>
<div class="text-12-regular text-text-weaker">
{DateTime.fromMillis(info().time.created).toFormat("dd MMM yyyy, HH:mm")}
</div>
</div>
<div class="flex gap-3 items-center">
<IconButton
as={"a"}
href="https://github.com/sst/opencode"
target="_blank"
icon="github"
variant="ghost"
/>
<IconButton
as={"a"}
href="https://opencode.ai/discord"
target="_blank"
icon="discord"
variant="ghost"
/>
<div class="text-left text-16-medium text-text-strong">{info().title}</div>
</div>
)
const turns = () => (
<div class="relative mt-2 pt-6 pb-8 min-w-0 w-full h-full overflow-y-auto no-scrollbar">
<div class="px-4">{title()}</div>
<div class="flex flex-col gap-15 items-start justify-start mt-4">
<For each={messages()}>
{(message) => (
<SessionTurn
sessionID={data().sessionID}
messageID={message.id}
classes={{
root: "min-w-0 w-full relative",
content:
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
container: "px-4",
}}
/>
)}
</For>
</div>
</header>
<div class="select-text flex flex-col flex-1 min-h-0">
<div classList={{ "hidden w-full flex-1 min-h-0": true, "md:flex": wide(), "lg:flex": !wide() }}>
<div class="px-4 flex items-center justify-center pt-20 pb-8 shrink-0">
<Logo class="w-58.5 opacity-12" />
</div>
</div>
)
const wide = createMemo(() => diffs().length === 0)
return (
<div class="relative bg-background-stronger w-screen h-screen overflow-hidden flex flex-col">
<header class="h-12 px-6 py-2 flex items-center justify-between self-stretch bg-background-base border-b border-border-weak-base">
<div class="">
<a href="https://opencode.ai">
<Mark />
</a>
</div>
<div class="flex gap-3 items-center">
<IconButton
as={"a"}
href="https://github.com/sst/opencode"
target="_blank"
icon="github"
variant="ghost"
/>
<IconButton
as={"a"}
href="https://opencode.ai/discord"
target="_blank"
icon="discord"
variant="ghost"
/>
</div>
</header>
<div class="select-text flex flex-col flex-1 min-h-0">
<div
classList={{
"@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full": true,
"mx-auto max-w-146": !wide(),
}}
classList={{ "hidden w-full flex-1 min-h-0": true, "md:flex": wide(), "lg:flex": !wide() }}
>
<div
classList={{
"w-full flex justify-start items-start min-w-0": true,
"max-w-146 mx-auto px-6": wide(),
"pr-6 pl-18": !wide() && messages().length > 1,
"px-6": !wide() && messages().length === 1,
"@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full": true,
"mx-auto max-w-146": !wide(),
}}
>
{title()}
</div>
<div class="flex items-start justify-start h-full min-h-0">
<SessionMessageRail
messages={messages()}
current={activeMessage()}
onMessageSelect={setActiveMessage}
wide={wide()}
/>
<SessionTurn
sessionID={data().sessionID}
messageID={store.messageId ?? firstUserMessage()!.id!}
classes={{
root: "grow",
content: "flex flex-col justify-between items-start",
container:
"w-full pb-20 " +
(wide() ? "max-w-146 mx-auto px-6" : messages().length > 1 ? "pr-6 pl-18" : "px-6"),
<div
classList={{
"w-full flex justify-start items-start min-w-0": true,
"max-w-146 mx-auto px-6": wide(),
"pr-6 pl-18": !wide() && messages().length > 1,
"px-6": !wide() && messages().length === 1,
}}
diffComponent={ClientOnlyDiff}
>
<div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}>
<Logo class="w-58.5 opacity-12" />
</div>
</SessionTurn>
{title()}
</div>
<div class="flex items-start justify-start h-full min-h-0">
<SessionMessageRail
messages={messages()}
current={activeMessage()}
onMessageSelect={setActiveMessage}
wide={wide()}
/>
<SessionTurn
sessionID={data().sessionID}
messageID={store.messageId ?? firstUserMessage()!.id!}
classes={{
root: "grow",
content: "flex flex-col justify-between items-start",
container:
"w-full pb-20 " +
(wide() ? "max-w-146 mx-auto px-6" : messages().length > 1 ? "pr-6 pl-18" : "px-6"),
}}
>
<div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}>
<Logo class="w-58.5 opacity-12" />
</div>
</SessionTurn>
</div>
</div>
</div>
<Show when={diffs().length > 0}>
<div class="@container relative grow pt-14 flex-1 min-h-0 border-l border-border-weak-base">
<SessionReview
class="@4xl:hidden"
diffs={diffs()}
diffComponent={Diff}
classes={{
root: "pb-20",
header: "px-6",
container: "px-6",
}}
/>
<SessionReview
split
class="hidden @4xl:flex"
diffs={splitDiffs()}
diffComponent={Diff}
classes={{
root: "pb-20",
header: "px-6",
container: "px-6",
}}
/>
</div>
</Show>
</div>
<Switch>
<Match when={diffs().length > 0}>
<Tabs classList={{ "md:hidden": wide(), "lg:hidden": !wide() }}>
<Tabs.List>
<Tabs.Trigger value="session" class="w-1/2" classes={{ button: "w-full" }}>
Session
</Tabs.Trigger>
<Tabs.Trigger value="review" class="w-1/2 !border-r-0" classes={{ button: "w-full" }}>
5 Files Changed
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="session" class="!overflow-hidden">
{turns()}
</Tabs.Content>
<Tabs.Content
forceMount
value="review"
class="!overflow-hidden hidden data-[selected]:block"
>
<div class="relative h-full pt-8 overflow-y-auto no-scrollbar">
<Show when={diffs().length > 0}>
<DiffComponentProvider component={SSRDiff}>
<div class="@container relative grow pt-14 flex-1 min-h-0 border-l border-border-weak-base">
<SessionReview
class="@4xl:hidden"
diffs={diffs()}
diffComponent={Diff}
classes={{
root: "pb-20",
header: "px-4",
container: "px-4",
header: "px-6",
container: "px-6",
}}
/>
<SessionReview
split
class="hidden @4xl:flex"
diffs={splitDiffs()}
classes={{
root: "pb-20",
header: "px-6",
container: "px-6",
}}
/>
</div>
</Tabs.Content>
</Tabs>
</Match>
<Match when={true}>
<div classList={{ "!overflow-hidden": true, "md:hidden": wide(), "lg:hidden": !wide() }}>
{turns()}
</div>
</Match>
</Switch>
</DiffComponentProvider>
</Show>
</div>
<Switch>
<Match when={diffs().length > 0}>
<Tabs classList={{ "md:hidden": wide(), "lg:hidden": !wide() }}>
<Tabs.List>
<Tabs.Trigger value="session" class="w-1/2" classes={{ button: "w-full" }}>
Session
</Tabs.Trigger>
<Tabs.Trigger value="review" class="w-1/2 !border-r-0" classes={{ button: "w-full" }}>
{diffs().length} Files Changed
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="session" class="!overflow-hidden">
{turns()}
</Tabs.Content>
<Tabs.Content
forceMount
value="review"
class="!overflow-hidden hidden data-[selected]:block"
>
<div class="relative h-full pt-8 overflow-y-auto no-scrollbar">
<DiffComponentProvider component={SSRDiff}>
<SessionReview
diffs={diffs()}
classes={{
root: "pb-20",
header: "px-4",
container: "px-4",
}}
/>
</DiffComponentProvider>
</div>
</Tabs.Content>
</Tabs>
</Match>
<Match when={true}>
<div classList={{ "!overflow-hidden": true, "md:hidden": wide(), "lg:hidden": !wide() }}>
{turns()}
</div>
</Match>
</Switch>
</div>
</div>
</div>
)
})}
</DataProvider>
)
})}
</DataProvider>
</DiffComponentProvider>
)
}}
</Show>

View File

@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The AI coding agent built for the terminal"
version = "1.0.134"
description = "The open source coding agent."
version = "1.0.138"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/sst/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-darwin-arm64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-darwin-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-linux-arm64.tar.gz"
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-linux-x64.tar.gz"
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-windows-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,12 +0,0 @@
<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="white"/>
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="white"/>
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="white"/>
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="white"/>
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="white"/>
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="white"/>
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 981 B

View File

@@ -1,12 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 981 B

View File

@@ -1,18 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,18 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,4 +0,0 @@
<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 13H35V58H0V13ZM26.25 22.1957H8.75V48.701H26.25V22.1957Z" fill="white"/>
<path d="M43.75 13H70V22.1957H52.5V48.701H70V57.8967H43.75V13Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 306 B

View File

@@ -1,4 +0,0 @@
<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 13H35V58H0V13ZM26.25 22.1957H8.75V48.701H26.25V22.1957Z" fill="black"/>
<path d="M43.75 13H70V22.1957H52.5V48.701H70V57.8967H43.75V13Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 306 B

View File

@@ -1,4 +0,0 @@
<svg width="64" height="42" viewBox="0 0 64 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="white"/>
<path d="M40 0H64V8.5H48V33H64V41.5H40V0Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 267 B

View File

@@ -1,4 +0,0 @@
<svg width="64" height="42" viewBox="0 0 64 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="black"/>
<path d="M40 0H64V8.5H48V33H64V41.5H40V0Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 B

View File

@@ -0,0 +1,5 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" fill="#FDFCFC"/>
<path d="M320 224V352H192V224H320Z" fill="#E6E5E6"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M384 416H128V96H384V416ZM320 160H192V352H320V160Z" fill="#17181C"/>
</svg>

After

Width:  |  Height:  |  Size: 325 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512"><svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" fill="#131010"></rect>
<path d="M320 224V352H192V224H320Z" fill="#5A5858"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M384 416H128V96H384V416ZM320 160H192V352H320V160Z" fill="white"></path>
</svg><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
@media (prefers-color-scheme: dark) { :root { filter: none; } }
</style></svg>

After

Width:  |  Height:  |  Size: 612 B

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.0.134",
"version": "1.0.138",
"name": "opencode",
"type": "module",
"private": true,
@@ -23,7 +23,9 @@
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1",
"@standard-schema/spec": "1.0.0",
"@tsconfig/bun": "catalog:",
@@ -64,8 +66,8 @@
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.2.8",
"@opentui/core": "0.1.56",
"@opentui/solid": "0.1.56",
"@opentui/core": "0.1.59",
"@opentui/solid": "0.1.59",
"@parcel/watcher": "2.5.1",
"@pierre/precision-diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",

View File

@@ -128,6 +128,7 @@ for (const item of targets) {
OTUI_TREE_SITTER_WORKER_PATH: bunfsRoot + workerRelativePath,
OPENCODE_WORKER_PATH: workerPath,
OPENCODE_CHANNEL: `'${Script.channel}'`,
OPENCODE_LIBC: item.os === "linux" ? `'${item.abi ?? "glibc"}'` : "",
},
})

View File

@@ -90,7 +90,7 @@ if (!Script.preview) {
"license=('MIT')",
"provides=('opencode')",
"conflicts=('opencode')",
"depends=('fzf' 'ripgrep')",
"depends=('ripgrep')",
"",
`source_aarch64=("\${pkgname}_\${pkgver}_aarch64.tar.gz::https://github.com/sst/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-arm64.tar.gz")`,
`sha256sums_aarch64=('${arm64Sha}')`,
@@ -120,7 +120,7 @@ if (!Script.preview) {
"license=('MIT')",
"provides=('opencode')",
"conflicts=('opencode-bin')",
"depends=('fzf' 'ripgrep')",
"depends=('ripgrep')",
"makedepends=('git' 'bun-bin' 'go')",
"",
`source=("opencode-\${pkgver}.tar.gz::https://github.com/sst/opencode/archive/v\${pkgver}\${_subver}.tar.gz")`,
@@ -199,6 +199,8 @@ if (!Script.preview) {
` homepage "https://github.com/sst/opencode"`,
` version "${Script.version.split("-")[0]}"`,
"",
` depends_on "ripgrep"`,
"",
" on_macos do",
" if Hardware::CPU.intel?",
` url "https://github.com/sst/opencode/releases/download/v${Script.version}/opencode-darwin-x64.zip"`,

View File

@@ -25,7 +25,6 @@ import { Provider } from "../provider/provider"
import { Installation } from "@/installation"
import { MessageV2 } from "@/session/message-v2"
import { Config } from "@/config/config"
import { MCP } from "@/mcp"
import { Todo } from "@/session/todo"
import { z } from "zod"
import { LoadAPIKeyError } from "ai"

View File

@@ -245,7 +245,12 @@ export namespace Agent {
system.push(PROMPT_GENERATE)
const existing = await list()
const result = await generateObject({
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
experimental_telemetry: {
isEnabled: cfg.experimental?.openTelemetry,
metadata: {
userId: cfg.username ?? "unknown",
},
},
temperature: 0.3,
prompt: [
...system.map(

View File

@@ -35,16 +35,22 @@ export namespace Auth {
const filepath = path.join(Global.Path.data, "auth.json")
export async function get(providerID: string) {
const file = Bun.file(filepath)
return file
.json()
.catch(() => ({}))
.then((x) => x[providerID] as Info | undefined)
const auth = await all()
return auth[providerID]
}
export async function all(): Promise<Record<string, Info>> {
const file = Bun.file(filepath)
return file.json().catch(() => ({}))
const data = await file.json().catch(() => ({}) as Record<string, unknown>)
return Object.entries(data).reduce(
(acc, [key, value]) => {
const parsed = Info.safeParse(value)
if (!parsed.success) return acc
acc[key] = parsed.data
return acc
},
{} as Record<string, Info>,
)
}
export async function set(key: string, info: Info) {

View File

@@ -127,7 +127,18 @@ export namespace BunProc {
await runInstall()
parsed.dependencies[pkg] = version
// Resolve actual version from installed package when using "latest"
// This ensures subsequent starts use the cached version until explicitly updated
let resolvedVersion = version
if (version === "latest") {
const installedPkgJson = Bun.file(path.join(mod, "package.json"))
const installedPkg = await installedPkgJson.json().catch(() => null)
if (installedPkg?.version) {
resolvedVersion = installedPkg.version
}
}
parsed.dependencies[pkg] = resolvedVersion
await Bun.write(pkgjson.name!, JSON.stringify(parsed, null, 2))
return mod
}

View File

@@ -0,0 +1,43 @@
import z from "zod"
import type { ZodType } from "zod"
import { Log } from "../util/log"
export namespace BusEvent {
const log = Log.create({ service: "event" })
export type Definition = ReturnType<typeof define>
const registry = new Map<string, Definition>()
export function define<Type extends string, Properties extends ZodType>(type: Type, properties: Properties) {
const result = {
type,
properties,
}
registry.set(type, result)
return result
}
export function payloads() {
return z
.discriminatedUnion(
"type",
registry
.entries()
.map(([type, def]) => {
return z
.object({
type: z.literal(type),
properties: def.properties,
})
.meta({
ref: "Event" + "." + def.type,
})
})
.toArray() as any,
)
.meta({
ref: "Event",
})
}
}

View File

@@ -3,7 +3,7 @@ import { EventEmitter } from "events"
export const GlobalBus = new EventEmitter<{
event: [
{
directory: string
directory?: string
payload: any
},
]

View File

@@ -1,7 +1,7 @@
import z from "zod"
import type { ZodType } from "zod"
import { Log } from "../util/log"
import { Instance } from "../project/instance"
import { BusEvent } from "./bus-event"
import { GlobalBus } from "./global"
export namespace Bus {
@@ -9,10 +9,6 @@ export namespace Bus {
type Subscription = (event: any) => void
const disposedEventType = "server.instance.disposed"
export type EventDefinition = ReturnType<typeof event>
const registry = new Map<string, EventDefinition>()
const state = Instance.state(
() => {
const subscriptions = new Map<any, Subscription[]>()
@@ -36,46 +32,14 @@ export namespace Bus {
},
)
export function event<Type extends string, Properties extends ZodType>(type: Type, properties: Properties) {
const result = {
type,
properties,
}
registry.set(type, result)
return result
}
export const InstanceDisposed = event(
export const InstanceDisposed = BusEvent.define(
disposedEventType,
z.object({
directory: z.string(),
}),
)
export function payloads() {
return z
.discriminatedUnion(
"type",
registry
.entries()
.map(([type, def]) => {
return z
.object({
type: z.literal(type),
properties: def.properties,
})
.meta({
ref: "Event" + "." + def.type,
})
})
.toArray() as any,
)
.meta({
ref: "Event",
})
}
export async function publish<Definition extends EventDefinition>(
export async function publish<Definition extends BusEvent.Definition>(
def: Definition,
properties: z.output<Definition["properties"]>,
) {
@@ -100,14 +64,14 @@ export namespace Bus {
return Promise.all(pending)
}
export function subscribe<Definition extends EventDefinition>(
export function subscribe<Definition extends BusEvent.Definition>(
def: Definition,
callback: (event: { type: Definition["type"]; properties: z.infer<Definition["properties"]> }) => void,
) {
return raw(def.type, callback)
}
export function once<Definition extends EventDefinition>(
export function once<Definition extends BusEvent.Definition>(
def: Definition,
callback: (event: {
type: Definition["type"]

View File

@@ -29,7 +29,7 @@ export const AuthListCommand = cmd({
const homedir = os.homedir()
const displayPath = authPath.startsWith(homedir) ? authPath.replace(homedir, "~") : authPath
prompts.intro(`Credentials ${UI.Style.TEXT_DIM}${displayPath}`)
const results = await Auth.all().then((x) => Object.entries(x))
const results = Object.entries(await Auth.all())
const database = await ModelsDev.get()
for (const [providerID, result] of results) {
@@ -143,7 +143,10 @@ export const AuthLoginCommand = cmd({
map((x) => ({
label: x.name,
value: x.id,
hint: priority[x.id] <= 1 ? "recommended" : undefined,
hint: {
opencode: "recommended",
anthropic: "Claude Max or API key",
}[x.id],
})),
),
{

View File

@@ -5,6 +5,26 @@ export const GenerateCommand = {
command: "generate",
handler: async () => {
const specs = await Server.openapi()
for (const item of Object.values(specs.paths)) {
for (const method of ["get", "post", "put", "delete", "patch"] as const) {
const operation = item[method]
if (!operation?.operationId) continue
// @ts-expect-error
operation["x-codeSamples"] = [
{
lang: "js",
source: [
`import { createOpencodeClient } from "@opencode-ai/sdk`,
``,
`const client = createOpencodeClient()`,
`await client.${operation.operationId}({`,
` ...`,
`})`,
].join("\n"),
},
]
}
}
const json = JSON.stringify(specs, null, 2)
// Wait for stdout to finish writing before process.exit() is called

View File

@@ -12,6 +12,7 @@ import { SDKProvider, useSDK } from "@tui/context/sdk"
import { SyncProvider, useSync } from "@tui/context/sync"
import { LocalProvider, useLocal } from "@tui/context/local"
import { DialogModel, useConnected } from "@tui/component/dialog-model"
import { DialogMcp } from "@tui/component/dialog-mcp"
import { DialogStatus } from "@tui/component/dialog-status"
import { DialogThemeList } from "@tui/component/dialog-theme-list"
import { DialogHelp } from "./ui/dialog-help"
@@ -301,6 +302,14 @@ function App() {
dialog.replace(() => <DialogAgent />)
},
},
{
title: "Toggle MCPs",
value: "mcp.list",
category: "Agent",
onSelect: () => {
dialog.replace(() => <DialogMcp />)
},
},
{
title: "Agent cycle",
value: "agent.cycle",

View File

@@ -0,0 +1,86 @@
import { createMemo, createSignal } from "solid-js"
import { useLocal } from "@tui/context/local"
import { useSync } from "@tui/context/sync"
import { map, pipe, entries, sortBy } from "remeda"
import { DialogSelect, type DialogSelectRef, type DialogSelectOption } from "@tui/ui/dialog-select"
import { useTheme } from "../context/theme"
import { Keybind } from "@/util/keybind"
import { TextAttributes } from "@opentui/core"
import { useSDK } from "@tui/context/sdk"
function Status(props: { enabled: boolean; loading: boolean }) {
const { theme } = useTheme()
if (props.loading) {
return <span style={{ fg: theme.textMuted }}> Loading</span>
}
if (props.enabled) {
return <span style={{ fg: theme.success, attributes: TextAttributes.BOLD }}> Enabled</span>
}
return <span style={{ fg: theme.textMuted }}> Disabled</span>
}
export function DialogMcp() {
const local = useLocal()
const sync = useSync()
const sdk = useSDK()
const [, setRef] = createSignal<DialogSelectRef<unknown>>()
const [loading, setLoading] = createSignal<string | null>(null)
const options = createMemo(() => {
// Track sync data and loading state to trigger re-render when they change
const mcpData = sync.data.mcp
const loadingMcp = loading()
return pipe(
mcpData ?? {},
entries(),
sortBy(([name]) => name),
map(([name, status]) => ({
value: name,
title: name,
description: status.status === "failed" ? "failed" : status.status,
footer: <Status enabled={local.mcp.isEnabled(name)} loading={loadingMcp === name} />,
category: undefined,
})),
)
})
const keybinds = createMemo(() => [
{
keybind: Keybind.parse("space")[0],
title: "toggle",
onTrigger: async (option: DialogSelectOption<string>) => {
// Prevent toggling while an operation is already in progress
if (loading() !== null) return
setLoading(option.value)
try {
await local.mcp.toggle(option.value)
// Refresh MCP status from server
const status = await sdk.client.mcp.status()
if (status.data) {
sync.set("mcp", status.data)
} else {
console.error("Failed to refresh MCP status: no data returned")
}
} catch (error) {
console.error("Failed to toggle MCP:", error)
} finally {
setLoading(null)
}
},
},
])
return (
<DialogSelect
ref={setRef}
title="MCPs"
options={options()}
keybind={keybinds()}
onSelect={(option) => {
// Don't close on select, only on escape
}}
/>
)
}

View File

@@ -26,6 +26,7 @@ export function DialogSessionList() {
const today = new Date().toDateString()
return sync.data.session
.filter((x) => x.parentID === undefined)
.toSorted((a, b) => b.time.updated - a.time.updated)
.map((x) => {
const date = new Date(x.time.updated)
let category = date.toDateString()

View File

@@ -5,10 +5,12 @@ import { onCleanup, onMount } from "solid-js"
export function DialogThemeList() {
const theme = useTheme()
const options = Object.keys(theme.all()).map((value) => ({
title: value,
value: value,
}))
const options = Object.keys(theme.all())
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
.map((value) => ({
title: value,
value: value,
}))
const dialog = useDialog()
let confirmed = false
let ref: DialogSelectRef<string>

View File

@@ -307,10 +307,14 @@ export function Autocomplete(props: {
},
{
display: "/status",
aliases: ["/mcp"],
description: "show status",
onSelect: () => command.trigger("opencode.status"),
},
{
display: "/mcp",
description: "toggle MCPs",
onSelect: () => command.trigger("mcp.list"),
},
{
display: "/theme",
description: "toggle theme",
@@ -493,7 +497,7 @@ export function Autocomplete(props: {
each={options()}
fallback={
<box paddingLeft={1} paddingRight={1}>
<text>No matching items</text>
<text fg={theme.textMuted}>No matching items</text>
</box>
}
>

View File

@@ -813,10 +813,10 @@ export function Prompt(props: PromptProps) {
</text>
<Show when={store.mode === "normal"}>
<box flexDirection="row" gap={1}>
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
<text flexShrink={0} fg={theme.text}>
{local.model.parsed().model}
</text>
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
</box>
</Show>
</box>

View File

@@ -10,12 +10,14 @@ import { createSimpleContext } from "./helper"
import { useToast } from "../ui/toast"
import { Provider } from "@/provider/provider"
import { useArgs } from "./args"
import { useSDK } from "./sdk"
import { RGBA } from "@opentui/core"
export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
name: "Local",
init: () => {
const sync = useSync()
const sdk = useSDK()
const toast = useToast()
function isModelValid(model: { providerID: string; modelID: string }) {
@@ -310,9 +312,27 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}
})
const mcp = {
isEnabled(name: string) {
const status = sync.data.mcp[name]
return status?.status === "connected"
},
async toggle(name: string) {
const status = sync.data.mcp[name]
if (status?.status === "connected") {
// Disable: disconnect the MCP
await sdk.client.mcp.disconnect({ name })
} else {
// Enable/Retry: connect the MCP (handles disabled, failed, and other states)
await sdk.client.mcp.connect({ name })
}
},
}
const result = {
model,
agent,
mcp,
}
return result
},

View File

@@ -6,6 +6,7 @@ import { createSimpleContext } from "./helper"
import aura from "./theme/aura.json" with { type: "json" }
import ayu from "./theme/ayu.json" with { type: "json" }
import catppuccin from "./theme/catppuccin.json" with { type: "json" }
import catppuccinMacchiato from "./theme/catppuccin-macchiato.json" with { type: "json" }
import cobalt2 from "./theme/cobalt2.json" with { type: "json" }
import dracula from "./theme/dracula.json" with { type: "json" }
import everforest from "./theme/everforest.json" with { type: "json" }
@@ -21,6 +22,7 @@ import nightowl from "./theme/nightowl.json" with { type: "json" }
import nord from "./theme/nord.json" with { type: "json" }
import onedark from "./theme/one-dark.json" with { type: "json" }
import opencode from "./theme/opencode.json" with { type: "json" }
import orng from "./theme/orng.json" with { type: "json" }
import palenight from "./theme/palenight.json" with { type: "json" }
import rosepine from "./theme/rosepine.json" with { type: "json" }
import solarized from "./theme/solarized.json" with { type: "json" }
@@ -92,6 +94,7 @@ type ThemeColors = {
type Theme = ThemeColors & {
_hasSelectedListItemText: boolean
thinkingOpacity: number
}
export function selectedForeground(theme: Theme): RGBA {
@@ -124,6 +127,7 @@ type ThemeJson = {
theme: Omit<Record<keyof ThemeColors, ColorValue>, "selectedListItemText" | "backgroundMenu"> & {
selectedListItemText?: ColorValue
backgroundMenu?: ColorValue
thinkingOpacity?: number
}
}
@@ -131,6 +135,7 @@ export const DEFAULT_THEMES: Record<string, ThemeJson> = {
aura,
ayu,
catppuccin,
["catppuccin-macchiato"]: catppuccinMacchiato,
cobalt2,
dracula,
everforest,
@@ -146,6 +151,7 @@ export const DEFAULT_THEMES: Record<string, ThemeJson> = {
nord,
["one-dark"]: onedark,
opencode,
orng,
palenight,
rosepine,
solarized,
@@ -181,9 +187,9 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
const resolved = Object.fromEntries(
Object.entries(theme.theme)
.filter(([key]) => key !== "selectedListItemText" && key !== "backgroundMenu")
.filter(([key]) => key !== "selectedListItemText" && key !== "backgroundMenu" && key !== "thinkingOpacity")
.map(([key, value]) => {
return [key, resolveColor(value)]
return [key, resolveColor(value as ColorValue)]
}),
) as Partial<ThemeColors>
@@ -204,9 +210,13 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
resolved.backgroundMenu = resolved.backgroundElement
}
// Handle thinkingOpacity - optional with default of 0.6
const thinkingOpacity = theme.theme.thinkingOpacity ?? 0.6
return {
...resolved,
_hasSelectedListItemText: hasSelectedListItemText,
thinkingOpacity,
} as Theme
}
@@ -552,7 +562,7 @@ function generateSubtleSyntax(theme: Theme) {
Math.round(fg.r * 255),
Math.round(fg.g * 255),
Math.round(fg.b * 255),
Math.round(0.6 * 255),
Math.round(theme.thinkingOpacity * 255),
),
},
}
@@ -564,6 +574,12 @@ function generateSubtleSyntax(theme: Theme) {
function getSyntaxRules(theme: Theme) {
return [
{
scope: ["default"],
style: {
foreground: theme.text,
},
},
{
scope: ["prompt"],
style: {

View File

@@ -0,0 +1,233 @@
{
"$schema": "https://opencode.ai/theme.json",
"defs": {
"macRosewater": "#f4dbd6",
"macFlamingo": "#f0c6c6",
"macPink": "#f5bde6",
"macMauve": "#c6a0f6",
"macRed": "#ed8796",
"macMaroon": "#ee99a0",
"macPeach": "#f5a97f",
"macYellow": "#eed49f",
"macGreen": "#a6da95",
"macTeal": "#8bd5ca",
"macSky": "#91d7e3",
"macSapphire": "#7dc4e4",
"macBlue": "#8aadf4",
"macLavender": "#b7bdf8",
"macText": "#cad3f5",
"macSubtext1": "#b8c0e0",
"macSubtext0": "#a5adcb",
"macOverlay2": "#939ab7",
"macOverlay1": "#8087a2",
"macOverlay0": "#6e738d",
"macSurface2": "#5b6078",
"macSurface1": "#494d64",
"macSurface0": "#363a4f",
"macBase": "#24273a",
"macMantle": "#1e2030",
"macCrust": "#181926"
},
"theme": {
"primary": {
"dark": "macBlue",
"light": "macBlue"
},
"secondary": {
"dark": "macMauve",
"light": "macMauve"
},
"accent": {
"dark": "macPink",
"light": "macPink"
},
"error": {
"dark": "macRed",
"light": "macRed"
},
"warning": {
"dark": "macYellow",
"light": "macYellow"
},
"success": {
"dark": "macGreen",
"light": "macGreen"
},
"info": {
"dark": "macTeal",
"light": "macTeal"
},
"text": {
"dark": "macText",
"light": "macText"
},
"textMuted": {
"dark": "macSubtext1",
"light": "macSubtext1"
},
"background": {
"dark": "macBase",
"light": "macBase"
},
"backgroundPanel": {
"dark": "macMantle",
"light": "macMantle"
},
"backgroundElement": {
"dark": "macCrust",
"light": "macCrust"
},
"border": {
"dark": "macSurface0",
"light": "macSurface0"
},
"borderActive": {
"dark": "macSurface1",
"light": "macSurface1"
},
"borderSubtle": {
"dark": "macSurface2",
"light": "macSurface2"
},
"diffAdded": {
"dark": "macGreen",
"light": "macGreen"
},
"diffRemoved": {
"dark": "macRed",
"light": "macRed"
},
"diffContext": {
"dark": "macOverlay2",
"light": "macOverlay2"
},
"diffHunkHeader": {
"dark": "macPeach",
"light": "macPeach"
},
"diffHighlightAdded": {
"dark": "macGreen",
"light": "macGreen"
},
"diffHighlightRemoved": {
"dark": "macRed",
"light": "macRed"
},
"diffAddedBg": {
"dark": "#29342b",
"light": "#29342b"
},
"diffRemovedBg": {
"dark": "#3a2a31",
"light": "#3a2a31"
},
"diffContextBg": {
"dark": "macMantle",
"light": "macMantle"
},
"diffLineNumber": {
"dark": "macSurface1",
"light": "macSurface1"
},
"diffAddedLineNumberBg": {
"dark": "#223025",
"light": "#223025"
},
"diffRemovedLineNumberBg": {
"dark": "#2f242b",
"light": "#2f242b"
},
"markdownText": {
"dark": "macText",
"light": "macText"
},
"markdownHeading": {
"dark": "macMauve",
"light": "macMauve"
},
"markdownLink": {
"dark": "macBlue",
"light": "macBlue"
},
"markdownLinkText": {
"dark": "macSky",
"light": "macSky"
},
"markdownCode": {
"dark": "macGreen",
"light": "macGreen"
},
"markdownBlockQuote": {
"dark": "macYellow",
"light": "macYellow"
},
"markdownEmph": {
"dark": "macYellow",
"light": "macYellow"
},
"markdownStrong": {
"dark": "macPeach",
"light": "macPeach"
},
"markdownHorizontalRule": {
"dark": "macSubtext0",
"light": "macSubtext0"
},
"markdownListItem": {
"dark": "macBlue",
"light": "macBlue"
},
"markdownListEnumeration": {
"dark": "macSky",
"light": "macSky"
},
"markdownImage": {
"dark": "macBlue",
"light": "macBlue"
},
"markdownImageText": {
"dark": "macSky",
"light": "macSky"
},
"markdownCodeBlock": {
"dark": "macText",
"light": "macText"
},
"syntaxComment": {
"dark": "macOverlay2",
"light": "macOverlay2"
},
"syntaxKeyword": {
"dark": "macMauve",
"light": "macMauve"
},
"syntaxFunction": {
"dark": "macBlue",
"light": "macBlue"
},
"syntaxVariable": {
"dark": "macRed",
"light": "macRed"
},
"syntaxString": {
"dark": "macGreen",
"light": "macGreen"
},
"syntaxNumber": {
"dark": "macPeach",
"light": "macPeach"
},
"syntaxType": {
"dark": "macYellow",
"light": "macYellow"
},
"syntaxOperator": {
"dark": "macSky",
"light": "macSky"
},
"syntaxPunctuation": {
"dark": "macText",
"light": "macText"
}
}
}

View File

@@ -0,0 +1,245 @@
{
"$schema": "https://opencode.ai/theme.json",
"defs": {
"darkStep1": "#0a0a0a",
"darkStep2": "#141414",
"darkStep3": "#1e1e1e",
"darkStep4": "#282828",
"darkStep5": "#323232",
"darkStep6": "#3c3c3c",
"darkStep7": "#484848",
"darkStep8": "#606060",
"darkStep9": "#EC5B2B",
"darkStep10": "#EE7948",
"darkStep11": "#808080",
"darkStep12": "#eeeeee",
"darkSecondary": "#EE7948",
"darkAccent": "#FFF7F1",
"darkRed": "#e06c75",
"darkOrange": "#EC5B2B",
"darkGreen": "#7fd88f",
"darkCyan": "#56b6c2",
"darkYellow": "#e5c07b",
"lightStep1": "#ffffff",
"lightStep2": "#FFF7F1",
"lightStep3": "#f5f0eb",
"lightStep4": "#ebebeb",
"lightStep5": "#e1e1e1",
"lightStep6": "#d4d4d4",
"lightStep7": "#b8b8b8",
"lightStep8": "#a0a0a0",
"lightStep9": "#EC5B2B",
"lightStep10": "#c94d24",
"lightStep11": "#8a8a8a",
"lightStep12": "#1a1a1a",
"lightSecondary": "#EE7948",
"lightAccent": "#c94d24",
"lightRed": "#d1383d",
"lightOrange": "#EC5B2B",
"lightGreen": "#3d9a57",
"lightCyan": "#318795",
"lightYellow": "#b0851f"
},
"theme": {
"primary": {
"dark": "darkStep9",
"light": "lightStep9"
},
"secondary": {
"dark": "darkSecondary",
"light": "lightSecondary"
},
"accent": {
"dark": "darkAccent",
"light": "lightAccent"
},
"error": {
"dark": "darkRed",
"light": "lightRed"
},
"warning": {
"dark": "darkOrange",
"light": "lightOrange"
},
"success": {
"dark": "darkGreen",
"light": "lightGreen"
},
"info": {
"dark": "darkCyan",
"light": "lightCyan"
},
"text": {
"dark": "darkStep12",
"light": "lightStep12"
},
"textMuted": {
"dark": "darkStep11",
"light": "lightStep11"
},
"background": {
"dark": "darkStep1",
"light": "lightStep1"
},
"backgroundPanel": {
"dark": "darkStep2",
"light": "lightStep2"
},
"backgroundElement": {
"dark": "darkStep3",
"light": "lightStep3"
},
"border": {
"dark": "#EC5B2B",
"light": "#EC5B2B"
},
"borderActive": {
"dark": "#EE7948",
"light": "#c94d24"
},
"borderSubtle": {
"dark": "darkStep6",
"light": "lightStep6"
},
"diffAdded": {
"dark": "#4fd6be",
"light": "#1e725c"
},
"diffRemoved": {
"dark": "#c53b53",
"light": "#c53b53"
},
"diffContext": {
"dark": "#828bb8",
"light": "#7086b5"
},
"diffHunkHeader": {
"dark": "#828bb8",
"light": "#7086b5"
},
"diffHighlightAdded": {
"dark": "#b8db87",
"light": "#4db380"
},
"diffHighlightRemoved": {
"dark": "#e26a75",
"light": "#f52a65"
},
"diffAddedBg": {
"dark": "#20303b",
"light": "#d5e5d5"
},
"diffRemovedBg": {
"dark": "#37222c",
"light": "#f7d8db"
},
"diffContextBg": {
"dark": "darkStep2",
"light": "lightStep2"
},
"diffLineNumber": {
"dark": "darkStep3",
"light": "lightStep3"
},
"diffAddedLineNumberBg": {
"dark": "#1b2b34",
"light": "#c5d5c5"
},
"diffRemovedLineNumberBg": {
"dark": "#2d1f26",
"light": "#e7c8cb"
},
"markdownText": {
"dark": "darkStep12",
"light": "lightStep12"
},
"markdownHeading": {
"dark": "#EC5B2B",
"light": "#EC5B2B"
},
"markdownLink": {
"dark": "darkStep9",
"light": "lightStep9"
},
"markdownLinkText": {
"dark": "darkCyan",
"light": "lightCyan"
},
"markdownCode": {
"dark": "darkGreen",
"light": "lightGreen"
},
"markdownBlockQuote": {
"dark": "#FFF7F1",
"light": "lightYellow"
},
"markdownEmph": {
"dark": "darkYellow",
"light": "lightYellow"
},
"markdownStrong": {
"dark": "#EE7948",
"light": "#EC5B2B"
},
"markdownHorizontalRule": {
"dark": "darkStep11",
"light": "lightStep11"
},
"markdownListItem": {
"dark": "darkStep9",
"light": "lightStep9"
},
"markdownListEnumeration": {
"dark": "darkCyan",
"light": "lightCyan"
},
"markdownImage": {
"dark": "darkStep9",
"light": "lightStep9"
},
"markdownImageText": {
"dark": "darkCyan",
"light": "lightCyan"
},
"markdownCodeBlock": {
"dark": "darkStep12",
"light": "lightStep12"
},
"syntaxComment": {
"dark": "darkStep11",
"light": "lightStep11"
},
"syntaxKeyword": {
"dark": "#EC5B2B",
"light": "#EC5B2B"
},
"syntaxFunction": {
"dark": "#EE7948",
"light": "#c94d24"
},
"syntaxVariable": {
"dark": "darkRed",
"light": "lightRed"
},
"syntaxString": {
"dark": "darkGreen",
"light": "lightGreen"
},
"syntaxNumber": {
"dark": "#FFF7F1",
"light": "#EC5B2B"
},
"syntaxType": {
"dark": "darkYellow",
"light": "lightYellow"
},
"syntaxOperator": {
"dark": "darkCyan",
"light": "lightCyan"
},
"syntaxPunctuation": {
"dark": "darkStep12",
"light": "lightStep12"
}
}
}

View File

@@ -1,9 +1,10 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import z from "zod"
export const TuiEvent = {
PromptAppend: Bus.event("tui.prompt.append", z.object({ text: z.string() })),
CommandExecute: Bus.event(
PromptAppend: BusEvent.define("tui.prompt.append", z.object({ text: z.string() })),
CommandExecute: BusEvent.define(
"tui.command.execute",
z.object({
command: z.union([
@@ -27,7 +28,7 @@ export const TuiEvent = {
]),
}),
),
ToastShow: Bus.event(
ToastShow: BusEvent.define(
"tui.toast.show",
z.object({
title: z.string().optional(),

View File

@@ -24,8 +24,12 @@ export function Home() {
return Object.values(sync.data.mcp).some((x) => x.status === "failed")
})
const connectedMcpCount = createMemo(() => {
return Object.values(sync.data.mcp).filter((x) => x.status === "connected").length
})
const Hint = (
<Show when={Object.keys(sync.data.mcp).length > 0}>
<Show when={connectedMcpCount() > 0}>
<box flexShrink={0} flexDirection="row" gap={1}>
<text fg={theme.text}>
<Switch>
@@ -35,7 +39,7 @@ export function Home() {
</Match>
<Match when={true}>
<span style={{ fg: theme.success }}></span>{" "}
{Locale.pluralize(Object.values(sync.data.mcp).length, "{} mcp server", "{} mcp servers")}
{Locale.pluralize(connectedMcpCount(), "{} mcp server", "{} mcp servers")}
</Match>
</Switch>
</text>
@@ -85,7 +89,7 @@ export function Home() {
<span style={{ fg: theme.success }}> </span>
</Match>
</Switch>
{Object.keys(sync.data.mcp).length} MCP
{connectedMcpCount()} MCP
</text>
<text fg={theme.textMuted}>/status</text>
</Show>

View File

@@ -4,13 +4,19 @@ import { useSync } from "../../context/sync"
import { useDirectory } from "../../context/directory"
import { useConnected } from "../../component/dialog-model"
import { createStore } from "solid-js/store"
import { useRoute } from "../../context/route"
export function Footer() {
const { theme } = useTheme()
const sync = useSync()
const route = useRoute()
const mcp = createMemo(() => Object.keys(sync.data.mcp))
const mcpError = createMemo(() => Object.values(sync.data.mcp).some((x) => x.status === "failed"))
const lsp = createMemo(() => Object.keys(sync.data.lsp))
const permissions = createMemo(() => {
if (route.data.type !== "session") return []
return sync.data.permission[route.data.sessionID] ?? []
})
const directory = useDirectory()
const connected = useConnected()
@@ -51,6 +57,12 @@ export function Footer() {
</text>
</Match>
<Match when={connected()}>
<Show when={permissions().length > 0}>
<text fg={theme.warning}>
<span style={{ fg: theme.warning }}></span> {permissions().length} Permission
{permissions().length > 1 ? "s" : ""}
</text>
</Show>
<text fg={theme.text}>
<span style={{ fg: theme.success }}></span> {lsp().length} LSP
</text>

View File

@@ -1049,12 +1049,12 @@ function UserMessage(props: {
<Show
when={queued()}
fallback={
<span style={{ fg: theme.textMuted }}>
{ctx.usernameVisible() ? " · " : " "}
{ctx.showTimestamps()
? Locale.todayTimeOrDateTime(props.message.time.created)
: Locale.time(props.message.time.created)}
</span>
<Show when={ctx.showTimestamps()}>
<span style={{ fg: theme.textMuted }}>
{ctx.usernameVisible() ? " · " : " "}
{Locale.todayTimeOrDateTime(props.message.time.created)}
</span>
</Show>
}
>
<span style={{ bg: theme.accent, fg: theme.backgroundPanel, bold: true }}> QUEUED </span>
@@ -1600,6 +1600,7 @@ ToolRegistry.register<typeof EditTool>({
showLineNumbers={true}
width="100%"
wrapMode={ctx.diffWrapMode()}
fg={theme.text}
addedBg={theme.diffAddedBg}
removedBg={theme.diffRemovedBg}
contextBg={theme.diffContextBg}

View File

@@ -248,7 +248,7 @@ export function Sidebar(props: { sessionID: string }) {
</box>
</scrollbox>
<box flexShrink={0} gap={1}>
<box flexShrink={0} gap={1} paddingTop={1}>
<Show when={!hasProviders()}>
<box
backgroundColor={theme.backgroundElement}
@@ -275,7 +275,7 @@ export function Sidebar(props: { sessionID: string }) {
</box>
</box>
</Show>
<text fg={theme.textMuted}>{directory()}</text>
<text fg={theme.text}>{directory()}</text>
<text fg={theme.textMuted}>
<span style={{ fg: theme.success }}></span> <b>Open</b>
<span style={{ fg: theme.text }}>

View File

@@ -60,7 +60,6 @@ export const TuiThreadCommand = cmd({
const cwd = args.project ? path.resolve(baseCwd, args.project) : process.cwd()
const localWorker = new URL("./worker.ts", import.meta.url)
const distWorker = new URL("./cli/cmd/tui/worker.js", import.meta.url)
const execDir = path.dirname(process.execPath)
const workerPath = await iife(async () => {
if (typeof OPENCODE_WORKER_PATH !== "undefined") return OPENCODE_WORKER_PATH
if (await Bun.file(distWorker).exists()) return distWorker

View File

@@ -49,6 +49,9 @@ export function DialogPrompt(props: DialogPromptProps) {
ref={(val: TextareaRenderable) => (textarea = val)}
initialValue={props.value}
placeholder={props.placeholder ?? "Enter text"}
textColor={theme.text}
focusedTextColor={theme.text}
cursorColor={theme.text}
/>
</box>
<box paddingBottom={1} gap={1} flexDirection="row">

View File

@@ -1,14 +1,15 @@
import { Bus } from "@/bus"
import { BusEvent } from "@/bus/bus-event"
import z from "zod"
import { Config } from "../config/config"
import { Instance } from "../project/instance"
import { Bus } from "../bus"
import { Identifier } from "../id/id"
import PROMPT_INITIALIZE from "./template/initialize.txt"
import PROMPT_REVIEW from "./template/review.txt"
export namespace Command {
export const Event = {
Executed: Bus.event(
Executed: BusEvent.define(
"command.executed",
z.object({
name: z.string(),

View File

@@ -2,7 +2,7 @@ import { Instance } from "../project/instance"
export namespace Env {
const state = Instance.state(() => {
return { ...process.env } as Record<string, string | undefined>
return process.env as Record<string, string | undefined>
})
export function get(key: string) {

View File

@@ -1,124 +0,0 @@
import path from "path"
import { Global } from "../global"
import fs from "fs/promises"
import z from "zod"
import { NamedError } from "@opencode-ai/util/error"
import { lazy } from "../util/lazy"
import { Log } from "../util/log"
import { ZipReader, BlobReader, BlobWriter } from "@zip.js/zip.js"
export namespace Fzf {
const log = Log.create({ service: "fzf" })
const VERSION = "0.62.0"
const PLATFORM = {
darwin: { extension: "tar.gz" },
linux: { extension: "tar.gz" },
win32: { extension: "zip" },
} as const
export const ExtractionFailedError = NamedError.create(
"FzfExtractionFailedError",
z.object({
filepath: z.string(),
stderr: z.string(),
}),
)
export const UnsupportedPlatformError = NamedError.create(
"FzfUnsupportedPlatformError",
z.object({
platform: z.string(),
}),
)
export const DownloadFailedError = NamedError.create(
"FzfDownloadFailedError",
z.object({
url: z.string(),
status: z.number(),
}),
)
const state = lazy(async () => {
let filepath = Bun.which("fzf")
if (filepath) {
log.info("found", { filepath })
return { filepath }
}
filepath = path.join(Global.Path.bin, "fzf" + (process.platform === "win32" ? ".exe" : ""))
const file = Bun.file(filepath)
if (!(await file.exists())) {
const archMap = { x64: "amd64", arm64: "arm64" } as const
const arch = archMap[process.arch as keyof typeof archMap] ?? "amd64"
const config = PLATFORM[process.platform as keyof typeof PLATFORM]
if (!config) throw new UnsupportedPlatformError({ platform: process.platform })
const version = VERSION
const platformName = process.platform === "win32" ? "windows" : process.platform
const filename = `fzf-${version}-${platformName}_${arch}.${config.extension}`
const url = `https://github.com/junegunn/fzf/releases/download/v${version}/${filename}`
const response = await fetch(url)
if (!response.ok) throw new DownloadFailedError({ url, status: response.status })
const buffer = await response.arrayBuffer()
const archivePath = path.join(Global.Path.bin, filename)
await Bun.write(archivePath, buffer)
if (config.extension === "tar.gz") {
const proc = Bun.spawn(["tar", "-xzf", archivePath, "fzf"], {
cwd: Global.Path.bin,
stderr: "pipe",
stdout: "pipe",
})
await proc.exited
if (proc.exitCode !== 0)
throw new ExtractionFailedError({
filepath,
stderr: await Bun.readableStreamToText(proc.stderr),
})
}
if (config.extension === "zip") {
const zipFileReader = new ZipReader(new BlobReader(new Blob([await Bun.file(archivePath).arrayBuffer()])))
const entries = await zipFileReader.getEntries()
let fzfEntry: any
for (const entry of entries) {
if (entry.filename === "fzf.exe") {
fzfEntry = entry
break
}
}
if (!fzfEntry) {
throw new ExtractionFailedError({
filepath: archivePath,
stderr: "fzf.exe not found in zip archive",
})
}
const fzfBlob = await fzfEntry.getData(new BlobWriter())
if (!fzfBlob) {
throw new ExtractionFailedError({
filepath: archivePath,
stderr: "Failed to extract fzf.exe from zip archive",
})
}
await Bun.write(filepath, await fzfBlob.arrayBuffer())
await zipFileReader.close()
}
await fs.unlink(archivePath)
if (process.platform !== "win32") await fs.chmod(filepath, 0o755)
}
return {
filepath,
}
})
export async function filepath() {
const { filepath } = await state()
return filepath
}
}

View File

@@ -1,5 +1,6 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import z from "zod"
import { Bus } from "../bus"
import { $ } from "bun"
import type { BunFile } from "bun"
import { formatPatch, structuredPatch } from "diff"
@@ -111,7 +112,7 @@ export namespace File {
}
export const Event = {
Edited: Bus.event(
Edited: BusEvent.define(
"file.edited",
z.object({
file: z.string(),

View File

@@ -1,5 +1,6 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import z from "zod"
import { Bus } from "../bus"
import { Instance } from "../project/instance"
import { Log } from "../util/log"
import { FileIgnore } from "./ignore"
@@ -8,12 +9,15 @@ import { Config } from "../config/config"
import { createWrapper } from "@parcel/watcher/wrapper"
import { lazy } from "@/util/lazy"
import type ParcelWatcher from "@parcel/watcher"
import { $ } from "bun"
declare const OPENCODE_LIBC: string | undefined
export namespace FileWatcher {
const log = Log.create({ service: "file.watcher" })
export const Event = {
Updated: Bus.event(
Updated: BusEvent.define(
"file.watcher.updated",
z.object({
file: z.string(),
@@ -24,7 +28,7 @@ export namespace FileWatcher {
const watcher = lazy(() => {
const binding = require(
`@parcel/watcher-${process.platform}-${process.arch}${process.platform === "linux" ? "-glibc" : ""}`,
`@parcel/watcher-${process.platform}-${process.arch}${process.platform === "linux" ? `-${OPENCODE_LIBC || "glibc"}` : ""}`,
)
return createWrapper(binding) as typeof import("@parcel/watcher")
})
@@ -63,7 +67,7 @@ export namespace FileWatcher {
}),
)
const vcsDir = Instance.project.vcsDir
const vcsDir = await $`git rev-parse --git-dir`.quiet().nothrow().cwd(Instance.worktree).text()
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
subs.push(
await watcher().subscribe(vcsDir, subscribe, {

View File

@@ -11,8 +11,8 @@ export namespace Flag {
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS")
export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT")
export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"]
export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH =
process.env["OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH"]
export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH = number("OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH")
export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT = number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT")
// Experimental
export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL")
@@ -25,4 +25,11 @@ export namespace Flag {
const value = process.env[key]?.toLowerCase()
return value === "true" || value === "1"
}
function number(key: string) {
const value = process.env[key]
if (!value) return undefined
const parsed = Number(value)
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined
}
}

View File

@@ -255,3 +255,23 @@ export const dart: Info = {
return Bun.which("dart") !== null
},
}
export const ocamlformat: Info = {
name: "ocamlformat",
command: ["ocamlformat", "-i", "$FILE"],
extensions: [".ml", ".mli"],
async enabled() {
if (!Bun.which("ocamlformat")) return false
const items = await Filesystem.findUp(".ocamlformat", Instance.directory, Instance.worktree)
return items.length > 0
},
}
export const terraform: Info = {
name: "terraform",
command: ["terraform", "fmt", "$FILE"],
extensions: [".tf", ".tfvars"],
async enabled() {
return Bun.which("terraform") !== null
},
}

View File

@@ -30,7 +30,7 @@ await Promise.all([
fs.mkdir(Global.Path.bin, { recursive: true }),
])
const CACHE_VERSION = "13"
const CACHE_VERSION = "14"
const version = await Bun.file(path.join(Global.Path.cache, "version"))
.text()

View File

@@ -1,8 +1,9 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { spawn } from "bun"
import z from "zod"
import { NamedError } from "@opencode-ai/util/error"
import { Log } from "../util/log"
import { Bus } from "../bus"
const SUPPORTED_IDES = [
{ name: "Windsurf" as const, cmd: "windsurf" },
@@ -16,7 +17,7 @@ export namespace Ide {
const log = Log.create({ service: "ide" })
export const Event = {
Installed: Bus.event(
Installed: BusEvent.define(
"ide.installed",
z.object({
ide: z.string(),

View File

@@ -43,6 +43,7 @@ process.on("uncaughtException", (e) => {
const cli = yargs(hideBin(process.argv))
.parserConfiguration({ "populate--": true })
.scriptName("opencode")
.wrap(100)
.help("help", "show help")
.alias("help", "h")
.version("version", "show version number", Installation.VERSION)

View File

@@ -1,8 +1,9 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import path from "path"
import { $ } from "bun"
import z from "zod"
import { NamedError } from "@opencode-ai/util/error"
import { Bus } from "../bus"
import { Log } from "../util/log"
import { iife } from "@/util/iife"
@@ -17,13 +18,13 @@ export namespace Installation {
export type Method = Awaited<ReturnType<typeof method>>
export const Event = {
Updated: Bus.event(
Updated: BusEvent.define(
"installation.updated",
z.object({
version: z.string(),
}),
),
UpdateAvailable: Bus.event(
UpdateAvailable: BusEvent.define(
"installation.update-available",
z.object({
version: z.string(),

View File

@@ -1,9 +1,10 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import path from "path"
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node"
import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types"
import { Log } from "../util/log"
import { LANGUAGE_EXTENSIONS } from "./language"
import { Bus } from "../bus"
import z from "zod"
import type { LSPServer } from "./server"
import { NamedError } from "@opencode-ai/util/error"
@@ -25,7 +26,7 @@ export namespace LSPClient {
)
export const Event = {
Diagnostics: Bus.event(
Diagnostics: BusEvent.define(
"lsp.client.diagnostics",
z.object({
serverID: z.string(),

View File

@@ -1,3 +1,5 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { Log } from "../util/log"
import { LSPClient } from "./client"
import path from "path"
@@ -6,13 +8,12 @@ import z from "zod"
import { Config } from "../config/config"
import { spawn } from "child_process"
import { Instance } from "../project/instance"
import { Bus } from "../bus"
export namespace LSP {
const log = Log.create({ service: "lsp" })
export const Event = {
Updated: Bus.event("lsp.updated", z.object({})),
Updated: BusEvent.define("lsp.updated", z.object({})),
}
export const Range = z

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