Compare commits

..
355 Commits
Author SHA1 Message Date
opencode 28e1d67ea4 release: v0.11.0 2025-09-21 08:04:21 +00:00
GitHub Action c1940d1d2c chore: format code 2025-09-21 04:23:40 +00:00
Frank 869f629c14 wip: zen 2025-09-21 00:23:05 -04:00
Frank a55943e469 wip: zen 2025-09-21 00:06:50 -04:00
Aiden ClineandGitHub 84d95a0d2a ignore: lsp log (#2715) 2025-09-20 23:02:06 -05:00
opencode 7dfed8ca35 release: v0.10.4 2025-09-20 23:00:32 +00:00
Dax Raad 38ea0fc051 turn back on compaction summaries 2025-09-20 18:52:30 -04:00
9223b6ed8f Enhance theme documentation with links (#2707)
Co-authored-by: rekram1-node <aidenpcline@gmail.com>
2025-09-20 11:25:12 -05:00
GitHub Action f8528c52d9 ignore: update download stats 2025-09-20 2025-09-20 12:04:03 +00:00
Aiden ClineandGitHub d63ce40af2 fix: no payment method (#2706) 2025-09-20 04:15:44 -04:00
Jay V 5acdd70587 docs: zen 2025-09-19 20:13:57 -04:00
opencode b04df6c0d2 release: v0.10.3 2025-09-19 21:22:00 +00:00
GitHub Action f1cbdf441c chore: format code 2025-09-19 18:18:56 +00:00
Frank 9420d80b73 zen: data share 2025-09-19 14:16:53 -04:00
Aiden ClineandGitHub c21161b75e docs: fix bad docs (#2691) 2025-09-19 12:40:40 -05:00
GitHub Action aaff066457 chore: format code 2025-09-19 17:29:44 +00:00
Jay V c7fbf9de44 ignore: zen 2025-09-19 13:29:04 -04:00
Adam d88c17dad0 wip: desktop progress 2025-09-19 10:53:49 -05:00
opencode f57c3f7cf6 release: v0.10.2 2025-09-19 15:18:11 +00:00
Adam 2460108223 fix: remove grok msg 2025-09-19 09:59:47 -05:00
Adam 84e8eea52e wip: desktop cleanup 2025-09-19 09:55:46 -05:00
Adam 9efc2eaf2e wip: desktop cleanup 2025-09-19 09:42:39 -05:00
Adam 37e2644452 wip: desktop visual tweaks 2025-09-19 09:38:10 -05:00
neriousyandAdam 22a78cf13f chore: opencode favicon 2025-09-19 09:33:28 -05:00
GitHub Action 2e9806b320 chore: format code 2025-09-19 14:32:55 +00:00
Adam ba839d4446 chore: normalize theme hex 2025-09-19 09:32:19 -05:00
GitHub Action 2bec21d81d chore: format code 2025-09-19 12:19:13 +00:00
Adam e5271f3d1a wip: desktop work 2025-09-19 07:18:39 -05:00
Adam 1edb23c2c7 wip: desktop work 2025-09-19 07:18:39 -05:00
Adam b1e6b9c7c9 wip: desktop work 2025-09-19 07:18:39 -05:00
Adam 20cb5a7c56 wip: desktop starting state 2025-09-19 07:18:38 -05:00
GitHub Action b1d44482bc ignore: update download stats 2025-09-19 2025-09-19 12:04:16 +00:00
GitHub Action e11102c9df chore: format code 2025-09-19 10:14:30 +00:00
Dax Raad 7be9dc8e49 ignore: fix 2025-09-19 06:13:45 -04:00
Dax Raad 824e035815 ci: stuff 2025-09-19 06:10:27 -04:00
GitHub Action d652b94a14 chore: format code 2025-09-19 09:29:24 +00:00
Dax Raad ebef2ea2d0 ci: stuff 2025-09-19 05:28:46 -04:00
GitHub Action b5b8a0555d chore: format code 2025-09-19 09:12:20 +00:00
Dax Raad ae6154e1c3 ignore: rework bootstrap so server lazy starts it 2025-09-19 05:11:29 -04:00
opencode 0e19ca21ed release: v0.10.1 2025-09-19 05:15:32 +00:00
Dax Raad baaff81a06 fix task tool ui disappearing once done 2025-09-19 01:09:52 -04:00
Aiden ClineandGitHub ffa5689885 docs: subtask config option (#2682) 2025-09-18 17:52:23 -05:00
Aiden ClineandGitHub 0e409842e8 docs: rm incorrect lsp mention (#2677) 2025-09-18 16:08:50 -05:00
opencode 5a7a725787 release: v0.10.0 2025-09-18 20:51:03 +00:00
GitHub Action f277512938 chore: format code 2025-09-18 14:59:48 +00:00
Frank 4ceabdffa0 wip: zen 2025-09-18 10:59:01 -04:00
GitHub Action c87480cf93 ignore: update download stats 2025-09-18 2025-09-18 12:04:18 +00:00
Timo ClasenandGitHub 0df6fc1226 fix(config): keybinds should not be required in config schema (#2669) 2025-09-18 06:30:44 -05:00
GitHub Action 32ba2e02aa chore: format code 2025-09-18 09:43:35 +00:00
Dax Raad 1ffc8be2b6 rework custom tools 2025-09-18 05:42:59 -04:00
Dax Raad 5f2945ae71 docs: add custom tools section to plugins documentation 2025-09-18 04:29:08 -04:00
DaxandGitHub 65baf76df6 Plugin tool updates (#2670) 2025-09-18 04:26:57 -04:00
DaxandGitHub 3b6c0ec0b3 support custom tools (#2668) 2025-09-18 03:58:21 -04:00
Frank e9d902d844 wip: zen 2025-09-18 01:36:54 -04:00
GitHub Action e8b4f593a6 chore: format code 2025-09-18 05:33:32 +00:00
Frank fc4f281408 wip: zen 2025-09-18 01:32:40 -04:00
GitHub Action f8c4f713a5 chore: format code 2025-09-18 03:39:57 +00:00
Jason QuenseandGitHub 63c8874d2d fix: type exports in package.json for SDK package (#2654) 2025-09-17 23:39:26 -04:00
71076d5c68 fix: add synthetic user prompt after session compaction (#2659)
Co-authored-by: Julián Díaz <git@jdiaz.io>
2025-09-17 23:27:37 -04:00
Frank 0319043b49 Support GLM coding plan 2025-09-17 16:54:00 -04:00
Frank e0334d5569 wip: zen 2025-09-17 16:03:47 -04:00
Aiden ClineandGitHub ff6a93f355 fix: only keep aborted messages if they have sufficient parts (#2651) 2025-09-17 14:24:53 -05:00
opencode 733b21e22b release: v0.9.11 2025-09-17 16:29:48 +00:00
Aiden ClineandGitHub 3c3d6b65c2 Revert "fix: type 'reasoning' was provided without its required follo… (#2648) 2025-09-17 11:17:26 -05:00
opencode 9ca48d3a39 release: v0.9.10 2025-09-17 14:54:25 +00:00
Timo ClasenandGitHub 16f9edc1a0 fix(TUI): display correct branch name in git worktree (#2626) 2025-09-17 09:46:18 -05:00
Aiden ClineandGitHub 8c2aec43b8 fix: type 'reasoning' was provided without its required following item (#2633) 2025-09-17 09:45:13 -05:00
Aiden ClineandGitHub 2564801bde tweak: adjust title gen when using models like gpt-5-nano on non open… (#2646) 2025-09-17 08:39:34 -05:00
GitHub Action 7c99a03493 ignore: update download stats 2025-09-17 2025-09-17 12:04:20 +00:00
opencode 0e0460f6c0 release: v0.9.9 2025-09-17 07:40:40 +00:00
Dax Raad 8acd537d1d ci: turborepo typecheck 2025-09-17 03:33:54 -04:00
Dax Raad 40c206c2f9 add opencode attach command to connect to a remote opencode server 2025-09-17 03:30:25 -04:00
Dax Raad 259c722208 only prune messages from more than 2 turns ago 2025-09-17 03:30:09 -04:00
opencode e618cbc447 release: v0.9.8 2025-09-17 07:14:33 +00:00
Dax Raad abd99aeb7d ignore: fix event type gen 2025-09-17 01:17:56 -04:00
opencode ad5fc76b11 release: v0.9.7 2025-09-17 05:09:08 +00:00
Dax Raad ff1f4d6bf9 disable reading .env file automatically 2025-09-17 01:02:23 -04:00
GitHub Action 170ea9c32b chore: format code 2025-09-16 23:53:59 +00:00
Jay V 65ced67432 ignore: zen 2025-09-16 19:53:18 -04:00
Jay V 9f46068c57 ignore: mobile styles zen 2025-09-16 19:39:36 -04:00
Jay V 479cf2fa4f ignore: zen 2025-09-16 19:39:36 -04:00
Frank 39c54f367f wip: zen 2025-09-16 18:13:05 -04:00
Frank 8c71107a93 wip: zen 2025-09-16 17:49:39 -04:00
GitHub Action ef10097329 chore: format code 2025-09-16 21:17:56 +00:00
Jay V 36ee4b5ede ignore: zen 2025-09-16 17:17:17 -04:00
Jay V ae84d5a734 ignore: zen 2025-09-16 17:17:17 -04:00
GitHub Action cd53770734 chore: format code 2025-09-16 20:17:16 +00:00
Jay V 4b1eca73eb ignore: zen 2025-09-16 16:16:30 -04:00
opencode fffcf69cd4 release: v0.9.6 2025-09-16 17:56:45 +00:00
Dax Raad d4c01f858b disable thinking for opencode zen and gpt-5 temporarily 2025-09-16 13:49:37 -04:00
GitHub Action 8e17570c53 chore: format code 2025-09-16 17:35:32 +00:00
Jay V 7f9d08b556 docs: zen 2025-09-16 13:26:49 -04:00
Frank 32a045f60b wip: zen 2025-09-16 09:01:13 -04:00
Aiden ClineandGitHub 91adc3cd41 docs: remove dup section (#2629) 2025-09-16 07:30:29 -05:00
GitHub Action 2bb9b4212f ignore: update download stats 2025-09-16 2025-09-16 12:04:17 +00:00
opencode 3472a50928 release: v0.9.5 2025-09-16 08:58:35 +00:00
Dax Raad 3aeac02bf1 enable session pruning and allow disabling with OPENCODE_DISABLE_PRUNE 2025-09-16 04:53:17 -04:00
opencode 52fcdcc37b release: v0.9.4 2025-09-16 08:35:55 +00:00
Dax Raad 78d6b3a963 fix crash when todo content is empty fixes #2622 2025-09-16 04:28:35 -04:00
15df2710fa fix(windows): force npm cmd shim generation and update install docs (#2558)
Co-authored-by: Dax <mail@thdxr.com>
Co-authored-by: GitHub Action <action@github.com>
2025-09-16 03:40:19 -04:00
opencode 02e492f6eb release: v0.9.3 2025-09-16 07:16:25 +00:00
Dax Raad 2d5bd26a59 feat: enhance provider model mapping and reasoning capabilities
- Add npm package tracking to provider model mapping
- Implement special handling for opencode provider with reasoning
- Update provider options mapping to use npm package names
2025-09-16 03:10:17 -04:00
opencode 8f58fef5ad release: v0.9.2 2025-09-16 04:29:00 +00:00
14cb2d2af6 feat: improve file watcher with chokidar and better ignore patterns (#2621)
Co-authored-by: GitHub Action <action@github.com>
2025-09-16 00:17:10 -04:00
Stephen MurrayandGitHub 52fb571739 fix: restore chat.message plugin hook (#2619) 2025-09-15 21:44:07 -05:00
GitHub Action 51c647ca89 chore: format code 2025-09-16 01:06:51 +00:00
Jay V 52fa7840c2 docs: zen 2025-09-15 21:05:47 -04:00
Frank 2c61b39088 wip: zen 2025-09-15 19:37:55 -04:00
Frank 6c02d4ce66 wip: zen 2025-09-15 19:26:46 -04:00
GitHub Action 11154ba697 chore: format code 2025-09-15 23:19:50 +00:00
Jay V f8ca524bf7 ignore: zen 2025-09-15 19:19:10 -04:00
GitHub Action 56222fff3c chore: format code 2025-09-15 23:03:41 +00:00
Jay V 74f9fcea88 ignore: zen 2025-09-15 19:02:51 -04:00
Frank bc213e1a61 wip: zen 2025-09-15 18:47:04 -04:00
Frank d795a38fc7 wip: zen 2025-09-15 18:44:21 -04:00
opencode 96698ea070 release: v0.9.1 2025-09-15 22:31:44 +00:00
Dax Raad 6fff10b670 docs: zen 2025-09-15 18:21:03 -04:00
Frank 194aea8e54 wip: zen 2025-09-15 18:14:07 -04:00
Frank b6d2046b0e wip: zen 2025-09-15 17:37:07 -04:00
Frank 910ea84360 wip: zen 2025-09-15 17:07:48 -04:00
Frank bc2e4e23c9 wip: zen 2025-09-15 15:53:17 -04:00
GitHub Action f5e75606e3 chore: format code 2025-09-15 19:39:00 +00:00
Jay V 0707890359 docs: zen 2025-09-15 15:38:23 -04:00
Frank 6dbba8e326 wip: zen 2025-09-15 15:21:06 -04:00
GitHub Action 413c9d9ad1 chore: format code 2025-09-15 18:50:30 +00:00
Frank 5e6dd312eb wip: zen 2025-09-15 14:48:02 -04:00
Frank 7218a662ab wip: zen 2025-09-15 14:48:02 -04:00
GitHub Action d947df3069 ignore: update download stats 2025-09-15 2025-09-15 12:04:17 +00:00
GitHub Action 5bb1f5f0a0 chore: format code 2025-09-15 07:54:04 +00:00
Dax Raad d38594d34a ci: sync 2025-09-15 03:53:27 -04:00
Dax Raad 925284c6c1 ci: sync 2025-09-15 03:53:21 -04:00
GitHub Action e716271466 chore: format code 2025-09-15 07:31:02 +00:00
Dax Raad df046e5e04 ci: typecheck 2025-09-15 03:30:26 -04:00
Dax Raad e0bfbcb663 ci: format 2025-09-15 03:28:53 -04:00
GitHub Action c1c6aca31e chore: format code 2025-09-15 07:28:34 +00:00
725104572e feat: add desktop/web app package (#2606)
Co-authored-by: adamdotdevin <2363879+adamdottv@users.noreply.github.com>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2025-09-15 03:28:08 -04:00
opencode 4954edf8ae release: v0.9.0 2025-09-15 07:18:49 +00:00
c1b4e1f19d Upgrade to Zod v4 (#2605)
Co-authored-by: GitHub Action <action@github.com>
2025-09-15 03:12:07 -04:00
Aiden ClineandGitHub 89d820b1c4 fix: visual token bug (#2603) 2025-09-14 21:23:52 -05:00
Aiden ClineandGitHub e3e459fc50 fix: reasoning metadata persistence (#2602) 2025-09-14 16:28:06 -05:00
Tommy D. RossiandGitHub 4bf0541bd6 log bash output when using opencode run (#2595) 2025-09-14 09:03:40 -05:00
Aiden ClineandGitHub c81624aef7 tweak: make bash permissions key off of command pattern (#2592) 2025-09-14 09:01:57 -05:00
Kenn CostalesandGitHub df61aa801b fix: fix wrong tool references LS and Agent (#2466) 2025-09-14 08:53:50 -05:00
GitHub Action 6af0c2ec21 ignore: update download stats 2025-09-14 2025-09-14 12:03:54 +00:00
Dax Raad ce9d2ee04f ci: script 2025-09-14 02:07:58 -04:00
opencode 4b30705c42 release: v0.8.0 2025-09-14 06:07:43 +00:00
Mani SundararajanandGitHub 1f8d396b76 fix(dev): build tui with correct file ext for windows (#2590) 2025-09-14 01:59:25 -04:00
Aiden ClineandGitHub 3752bb9717 fix: token counting visual bug (#2587) 2025-09-13 19:46:24 -05:00
Aiden ClineandGitHub 16d66c209d respect subagent in command, add subtask flag (#2569) 2025-09-13 12:47:18 -05:00
Aiden ClineandGitHub 6506e48c54 tweak: keep aborted msgs in context (#2583) 2025-09-13 12:25:30 -05:00
GitHub Action f0e8b7c29b ignore: update download stats 2025-09-13 2025-09-13 12:03:56 +00:00
Dax Raad a00b49d65b disable autocompact if context is 0 2025-09-13 05:59:18 -04:00
Dax Raad b1589be4ba add disable OPENCODE_DISABLE_AUTOCOMPACT 2025-09-13 05:55:04 -04:00
Dax Raad eb24d2f847 ignore: fix 2025-09-13 05:53:03 -04:00
9bb25a9260 Session management and prompt handling improvements (#2577)
Co-authored-by: GitHub Action <action@github.com>
2025-09-13 05:46:14 -04:00
opencode 535230dce4 release: v0.7.9 2025-09-13 05:29:37 +00:00
Dax Raad 555fb53505 nudge llm to continue properly after compaction 2025-09-13 01:23:54 -04:00
b1e0a23351 fix: ShellError: exit code 1 errors (#2568)
Co-authored-by: rekram1-node <aidenpcline@gmail.com>
2025-09-13 00:06:07 -05:00
Nicholas HamiltonandGitHub 2b69bcccdf docs: typo in web agents.mdx (#2574) 2025-09-12 23:26:52 -05:00
e03f27381f docs: add tip block for finding available models (#2501)
Co-authored-by: GitHub Action <action@github.com>
2025-09-12 21:22:54 -04:00
Aiden ClineandGitHub aebd50da7e fix: make permission always behavior match expectation (#2573) 2025-09-12 18:59:38 -05:00
Stephen MurrayandGitHub c02f58c2af fix: await cleanupRevert() to prevent dupe msgs after undo (#2572) 2025-09-12 18:42:39 -05:00
Dax Raad c8f4d54f7f wip: zen 2025-09-12 14:53:00 -04:00
GitHub Action 4983d255dd chore: format code 2025-09-12 18:46:43 +00:00
Dax Raad f2b4891ff0 wip: zen 2025-09-12 14:46:08 -04:00
GitHub Action efcb5abbf7 chore: format code 2025-09-12 18:33:14 +00:00
Jay V d37e58719e ignore: zen 2025-09-12 14:32:43 -04:00
Frank c6c153de95 wip: zen 2025-09-12 14:22:42 -04:00
opencode 417e8f619c release: v0.7.8 2025-09-12 18:09:55 +00:00
Dax Raad f2094b7bb3 temporarily disable midstream compaction 2025-09-12 14:00:54 -04:00
Dax Raad 176dc51b2e ci: exclude production branch from format workflow 2025-09-12 13:41:38 -04:00
opencode f7d9a031e6 release: v0.7.7
Format / format (push) Has been cancelled
2025-09-12 17:28:35 +00:00
Dax Raad 3e2478ebf9 undo session pruning 2025-09-12 13:20:13 -04:00
GitHub Action 1f4e8b4954 chore: format code 2025-09-12 16:19:09 +00:00
Frank 9a346a00fb wip: zen 2025-09-12 12:18:32 -04:00
Frank 0a13820927 Merge branch 'production' into dev 2025-09-12 12:04:27 -04:00
GitHub Action c5fa3ee9f8 chore: format code 2025-09-12 15:57:50 +00:00
Frank c294a18155 wip: zen 2025-09-12 11:57:14 -04:00
Frank c3dc6d6df6 wip: zen 2025-09-12 11:57:14 -04:00
GitHub Action ef3425a177 ignore: update download stats 2025-09-12 2025-09-12 12:04:14 +00:00
Dax Raadandopencode 0290b4aaf0 ignore: internal 2025-09-12 10:45:44 +00:00
opencode 4ceee53480 release: v0.7.6
Format / format (push) Has been cancelled
2025-09-12 10:45:44 +00:00
Dax Raad 469dc9095f add microcompact 2025-09-12 06:38:47 -04:00
opencode 661d50f95f release: v0.7.5
Format / format (push) Has been cancelled
2025-09-12 10:25:57 +00:00
opencode 3978a8e636 release: v0.7.4
Format / format (push) Has been cancelled
2025-09-12 10:08:33 +00:00
Dax Raad 983e3b2ee3 fix compaction issues 2025-09-12 06:01:11 -04:00
GitHub Action 1bd198eb34 chore: format code 2025-09-11 22:34:21 +00:00
GitHub Action 3c502861a7 chore: format code 2025-09-11 22:30:52 +00:00
Frank a52b352b24 wip: zen 2025-09-11 18:30:13 -04:00
Dax Raad 79c73267cf wip: zen 2025-09-11 18:20:37 -04:00
opencode 54f7fb5019 release: v0.7.3
Format / format (push) Has been cancelled
2025-09-11 21:38:17 +00:00
Frank dd97d784b6 Merge branch 'production' into dev 2025-09-11 17:28:51 -04:00
GitHub Action 91832bd5d7 chore: format code 2025-09-11 21:22:56 +00:00
Frank 3abca8fd4b wip: zen 2025-09-11 17:22:05 -04:00
Dax Raad f5b3992479 properly support model level npm definition 2025-09-11 16:22:44 -04:00
53f1f16122 feat: Add an experimental option to disable paste summaries (#2552)
Co-authored-by: rekram1-node <aidenpcline@gmail.com>
2025-09-11 14:21:08 -05:00
Aiden ClineandGitHub 4614e4983e fix: command being passed as arg when no args present (#2553) 2025-09-11 13:03:12 -05:00
opencode 84f0c63fa1 release: v0.7.2
Format / format (push) Has been cancelled
2025-09-11 17:02:59 +00:00
Dax Raad 3e9b451fb4 reduce LSP verbosity 2025-09-11 12:54:12 -04:00
Dax Raad 4ccf683527 remove block anchor edit 2025-09-11 12:53:10 -04:00
GitHub Action b236ca9047 ignore: update download stats 2025-09-11 2025-09-11 12:04:26 +00:00
Dax Raad aa9ebe5d7c ignore: compacting 2025-09-11 02:31:28 -04:00
Dax Raad 4c94753eda compaction improvements 2025-09-11 02:22:51 -04:00
GitHub Action c3a55c35bb chore: format code 2025-09-11 05:33:59 +00:00
Frank d5275010d5 wip: zen 2025-09-11 01:33:23 -04:00
Frank dedfa563c2 wip: zen 2025-09-11 01:32:06 -04:00
GitHub Action 37b6a55eb1 chore: format code 2025-09-11 04:00:17 +00:00
GitHub Action 7aa57accf5 chore: format code 2025-09-11 03:59:39 +00:00
Jay V c2fa28c1be ignore: zen 2025-09-10 17:59:03 -10:00
Jay V 30aae66320 docs: lander 2025-09-10 16:42:32 -10:00
Jay V 7b95190df3 docs: add twitter 2025-09-10 11:47:41 -10:00
Frank fa3e7bb9b0 wip: zen 2025-09-10 17:39:28 -04:00
Emmanuel LOUISY-GABRIELandGitHub 5b56848c3d Update providers.mdx because of small typo (#2539) 2025-09-10 15:49:25 -05:00
Aiden ClineandGitHub 780e532094 resolve nested commands (#2537) 2025-09-10 14:05:26 -05:00
Aiden ClineandGitHub 29310957c8 fix: handle @dir in command (#2533) 2025-09-10 13:27:44 -05:00
opencode 2b0577c725 release: v0.7.1
Format / format (push) Has been cancelled
2025-09-10 15:40:31 +00:00
Dax Raad bcd656ffae fix issue with flags being parsed incorrectly 2025-09-10 11:34:39 -04:00
GitHub Action 0e0c5a9b68 ignore: update download stats 2025-09-10 2025-09-10 12:04:18 +00:00
opencode d36fcc4f8e release: v0.7.0
Format / format (push) Has been cancelled
2025-09-10 08:42:45 +00:00
Dax Raad ea82b60d7d ci: stuff 2025-09-10 04:35:49 -04:00
Dax Raad ea0285a96c ci: stuff 2025-09-10 04:27:23 -04:00
Dax Raad 6960408ca2 ci: bump version 2025-09-10 04:23:57 -04:00
GitHub Action fa36195492 chore: format code 2025-09-10 07:40:01 +00:00
Dax Raad a6265ea3d2 upgrade to latest bun 2025-09-10 03:36:42 -04:00
Dax Raad bb3f02b8bb wip: ignore 2025-09-10 03:13:42 -04:00
Aiden ClineandGitHub bdc0f7c86d tweak: wrap build-switch w/ system-reminder (#2525) 2025-09-09 23:57:13 -05:00
GitHub Action c8ca036834 chore: format code 2025-09-10 03:49:07 +00:00
Dax Raad 8c7fee7840 ci: fix 2025-09-09 23:48:35 -04:00
Dax Raad e53fb7f8ed ci: format 2025-09-09 23:47:47 -04:00
Dax Raad b05cbc9101 ci: format 2025-09-09 23:44:04 -04:00
Dax Raad 38e8c42cf0 ci: format 2025-09-09 23:44:04 -04:00
opencode 58fe884327 release: v0.6.10 2025-09-10 03:32:47 +00:00
Dax Raad e69d10b6c9 repair tool calls when casing is wrong 2025-09-09 23:25:27 -04:00
opencode 10aee9755c release: v0.6.9 2025-09-09 21:17:41 +00:00
Frank 63384bc214 wip: zen 2025-09-09 16:40:12 -04:00
Frank 2508e06c58 wip: zen 2025-09-09 16:32:56 -04:00
Frank 6487d0607b wip: zen 2025-09-09 16:15:35 -04:00
Frank a3513244f1 wip: zen 2025-09-09 15:47:28 -04:00
madflowandGitHub 32b47fcc1e feat: svelte lsp (#2508) 2025-09-09 13:59:58 -05:00
Aiden ClineandGitHub fde03d3c93 fix: exit code being non zero when using run cmd (#2523) 2025-09-09 12:00:55 -05:00
GitHub Action 9045f13acc ignore: update download stats 2025-09-09 2025-09-09 12:04:32 +00:00
Frank 74f0edc7a8 wip: zen 2025-09-09 05:42:15 -04:00
opencode dcabafcdce release: v0.6.8 2025-09-09 07:40:23 +00:00
Frank 02e8242c3b Remove debug logging 2025-09-09 03:35:09 -04:00
opencode 57e26bd2fe release: v0.6.7 2025-09-09 07:23:01 +00:00
Frank 0f263bfefe Hide experimental models 2025-09-09 03:16:44 -04:00
Frank 34a33dfc16 wip: zen 2025-09-09 02:44:36 -04:00
Aiden ClineandGitHub 162a789fa2 remove edit tool from plan agent (#2505) 2025-09-08 22:00:14 -05:00
Frank 198a753b62 Merge branch 'production' into dev 2025-09-08 16:37:38 -04:00
Zack JacksonandGitHub ab3c22b77a feat: add dynamic tool registration for plugins and external services (#2420) 2025-09-08 16:25:04 -04:00
opencode f0f6e9cad7 release: v0.6.6 2025-09-08 20:20:35 +00:00
Mani SundararajanandGitHub bbaae459c6 feat: make npm package install work on windows (#2419) 2025-09-08 16:14:18 -04:00
Frank eb3c820fb8 wip: zen 2025-09-08 15:57:29 -04:00
Frank 3468808fc6 wip: zen 2025-09-08 15:51:01 -04:00
Frank cd42503e2c Zen: telemetry 2025-09-08 15:46:59 -04:00
Aiden ClineandGitHub 1cea8b9e77 tweak: reenable todowrite & todoread for qwen models (#2499) 2025-09-08 13:21:16 -05:00
Douglas DennisandGitHub d8fd7b155f fix: aws bedrock add check for govcloud (#2495) 2025-09-08 11:54:06 -05:00
GitHub Action 248a644fb0 ignore: update download stats 2025-09-08 2025-09-08 12:04:58 +00:00
Aiden ClineandGitHub c8ff81bae4 fix: silent error if bad flag was passed (#2486) 2025-09-07 23:14:38 -05:00
Aiden ClineandGitHub 74469a0d3d fix: shell invocations are dropped if last interaction was revert (#2485) 2025-09-07 21:45:13 -05:00
Aiden ClineandGitHub 4d481dea7e fix: dont paste collapse if in bash mode (#2482) 2025-09-07 20:24:49 -05:00
opencode 7df32eac2a release: v0.6.5 2025-09-07 19:44:44 +00:00
4654fb88de fix: max output tokens when setting budget thinking tokens (#2056)
Co-authored-by: rekram1-node <aidenpcline@gmail.com>
2025-09-07 13:15:53 -05:00
GitHub Action e915a3720e ignore: update download stats 2025-09-07 2025-09-07 12:03:51 +00:00
Aiden ClineandGitHub 93c2f5060e fix: title gen w/ gpt-5-nano (#2473) 2025-09-06 22:50:16 -05:00
Aiden ClineandGitHub 564143071e fix: title not generated if first msg is shell invocation (#2451) 2025-09-06 09:47:42 -05:00
GitHub Action 3cdfc529a0 ignore: update download stats 2025-09-06 2025-09-06 12:03:41 +00:00
Aiden ClineandGitHub bffe547417 fix: command model selection (#2448) 2025-09-05 20:54:39 -05:00
Aiden ClineandGitHub dc99005e65 fix: default to last used model (#2443) 2025-09-05 17:25:25 -05:00
Adam 8ffedbe157 fix: file read response 2025-09-05 15:58:56 -05:00
spoons-and-mirrorsandGitHub 900fe5ca04 tweak(edit): separate edit tool error message with clearer guidance to avoid llm doom editing loop (#2051) 2025-09-05 12:36:13 -04:00
GitHub Action 66a5d58221 ignore: update download stats 2025-09-05 2025-09-05 12:04:16 +00:00
Jay V 48328bec6e docs: fix lander 2025-09-04 17:44:32 -10:00
Michał OlenderandGitHub 8426a0d595 Update index.tsx (#2423) 2025-09-04 17:44:26 -10:00
Aiden ClineandGitHub 9186c3feae fix: webfetch prompt mistake (#2424) 2025-09-04 13:35:25 -05:00
Adam f171250033 fix: better file/content return 2025-09-04 12:39:49 -05:00
GitHub Action d440ba32ab ignore: update download stats 2025-09-04 2025-09-04 12:04:06 +00:00
Adam f7ab6beaf3 fix: worktree file/content never includes patch 2025-09-04 06:10:07 -05:00
Jay V 85ac243752 ignore: 404 2025-09-04 01:39:54 -07:00
Jay V 03522471a1 docs: fix 2025-09-04 01:03:23 -07:00
Jay V 42b440be0c docs: handle base path 2025-09-04 00:53:45 -07:00
Jay V 133ae42c55 ignore: zen 2025-09-04 00:17:06 -07:00
Zack JacksonandGitHub e001af2709 feat: add createOpencodeTui() function to SDK for programmatic TUI launching (#2410) 2025-09-04 02:49:44 -04:00
Aiden ClineandGitHub a97612287f fix: file fuzzy search (#2409) 2025-09-03 23:20:16 -05:00
Jay V d13467d869 ignore: mobile styles 2025-09-03 19:33:01 -07:00
Jay V 368bd97952 ignore: lander 2025-09-03 19:05:49 -07:00
Jay V d0104278fa docs: lander 2025-09-03 18:30:04 -07:00
Jay V 222244719b ignore: cloud 2025-09-03 17:21:46 -07:00
Jay V 21008d733f docs: link 2025-09-03 17:12:51 -07:00
Jay V 2808e95ac7 ignore: zen 2025-09-03 15:53:31 -07:00
Frank 70e0d71ac2 wip console 2025-09-03 15:57:41 -04:00
Frank 93f507d330 wip console 2025-09-03 15:35:46 -04:00
Dax Raad 7119ace940 wip: cloud 2025-09-03 14:57:02 -04:00
Dax Raad 4e24e04aec ignore: opencode auth stuff 2025-09-03 14:43:50 -04:00
Dax Raad 469a83a55b wip: email 2025-09-03 14:32:05 -04:00
Dax Raad e8f54b9b38 wip: fix logout 2025-09-03 14:24:37 -04:00
Frank 01b18456a3 wip console 2025-09-03 14:09:39 -04:00
Frank 1acd5445b5 wip console 2025-09-03 14:07:57 -04:00
Dax Raad ff89305ebe wip: logout 2025-09-03 14:07:23 -04:00
Dax Raad 605f78944d wip: css 2025-09-03 14:05:10 -04:00
Dax Raad 9f7e14dc7e wip: css hack 2025-09-03 13:58:08 -04:00
Dax Raad cb7f3cf2f1 wip: cloud 2025-09-03 13:58:08 -04:00
Dax Raad 4406096974 wip: cloud 2025-09-03 13:58:08 -04:00
Frank fefaad6226 wip cloud smart 2025-09-03 13:52:01 -04:00
Frank 3f9b569575 script package 2025-09-03 13:43:32 -04:00
Dax Raad 1e4f5710aa wip: cloud 2025-09-03 13:17:32 -04:00
Dax Raad 48a79b1173 wip: make less shit 2025-09-03 13:10:57 -04:00
Dax Raad 59e550271d wip: cloud 2025-09-03 12:32:03 -04:00
Frank afebe920b2 wip console 2025-09-03 11:50:18 -04:00
Frank 6921702605 wip console 2025-09-03 11:40:59 -04:00
Frank 9c6783e88e wip console 2025-09-03 11:31:16 -04:00
Frank f1a60a0a93 wip: generate config.json 2025-09-03 11:24:13 -04:00
Jay V 4b9cae82e6 ignore: zen 2025-09-03 11:16:49 -04:00
Jay V fdf08ecfab ignore: zen 2025-09-03 11:10:07 -04:00
Jay V 22f5c26eec docs: edits 2025-09-03 11:05:43 -04:00
opencode b6de122ddc release: v0.6.4 2025-09-03 13:31:11 +00:00
Frank 0f8cb69bff wip console 2025-09-03 09:24:23 -04:00
GitHub Action fca2bddc3b ignore: update download stats 2025-09-03 2025-09-03 12:04:06 +00:00
Frank f65e20b8ce wip console 2025-09-03 06:53:30 -04:00
Frank 93f2805bc2 wip: console 2025-09-03 06:37:40 -04:00
Frank 9ad4dc9296 wip: console 2025-09-03 06:27:53 -04:00
Frank 23af974bd3 wip: console 2025-09-03 06:22:44 -04:00
Frank 36ea46ee67 wip: console 2025-09-03 06:15:08 -04:00
Frank 4d2cc9d858 wip: console 2025-09-03 06:12:17 -04:00
Dax Raad 610ffbdd61 wip: console 2025-09-03 01:33:49 -04:00
Brendan AllanandGitHub 854f9227a2 Patch Start to preload route css in SSR (#2389) 2025-09-03 01:28:34 -04:00
Dax Raad 8d368fdfd2 wip: zen 2025-09-02 23:56:10 -04:00
Dax Raad 1c31c2dd97 wip: zen 2025-09-02 23:30:48 -04:00
Frank c1d754bec9 wip cloud 2025-09-02 23:28:54 -04:00
Dax Raad c67b721787 docs: remove remaining directory query param mentions from SDK docs 2025-09-02 22:25:32 -04:00
Dax Raad 11e41e7564 docs: remove directory query param mentions from SDK docs 2025-09-02 22:25:32 -04:00
Dax Raad afd42bf46d docs: fix SDK usage to use path/query/body, correct return types, and update examples 2025-09-02 22:25:32 -04:00
Aiden ClineandGitHub f740663ded fix: more durable @ references for commands (#2386) 2025-09-02 21:24:56 -05:00
Jay V 751b81af34 docs: zen 2025-09-02 21:29:03 -04:00
Jay V b725bcd2cd ignore: adding public files 2025-09-02 21:25:09 -04:00
Frank c278e16e4e generate api key 2025-09-02 20:38:36 -04:00
Frank 4e629c5b64 wip: cloud 2025-09-02 20:01:13 -04:00
Dax Raad 4624f0a260 ci: ignore 2025-09-02 19:32:21 -04:00
Dax Raad 2e16d685eb wip: zen 2025-09-02 18:00:48 -04:00
Jay V e544cccc70 ignore: zen 2025-09-02 17:30:51 -04:00
Jay V c141b88087 ignore: zen 2025-09-02 17:28:35 -04:00
Jay V 023c4532c1 ignore: cloud lander 2025-09-02 17:28:35 -04:00
Dax Raad 042802848d wip: zen 2025-09-02 16:38:50 -04:00
Dax Raad a8aa44bd3f docs: simplify config example to show only model 2025-09-02 16:38:50 -04:00
Dax Raad db2a3a171e docs: clarify config behavior and remove theme example 2025-09-02 16:38:50 -04:00
Dax Raad 38a4bee1be docs: add config example to SDK server creation 2025-09-02 16:38:50 -04:00
Dax Raad 8952b3d246 support OPENCODE_CONFIG_CONTENT 2025-09-02 16:38:50 -04:00
Aiden ClineandGitHub d6350a7fa6 tweak: update ls tool to use rg (#2367) 2025-09-02 10:40:20 -05:00
Yuta URANOandGitHub ae83138832 docs: update log level configuration in troubleshooting guide (#2374) 2025-09-02 10:31:04 -05:00
OpeOginniandGitHub 3ee4280dfa fix: local subdirectory subagents not being picked up (#2376) 2025-09-02 09:46:00 -05:00
GitHub Action 26fbf9e647 ignore: update download stats 2025-09-02 2025-09-02 12:04:59 +00:00
Adam 97a41062c9 fix: file.list relative to root 2025-09-02 06:20:08 -05:00
Dax Raad 4a76224268 wip: typechecking 2025-09-02 03:18:30 -04:00
Dax Raad 810c9cff1d wip: cloud 2025-09-02 03:18:30 -04:00
47d4c87bdd make @file references in custom slash commands more robust (#2203)
Co-authored-by: Adam Spiers <opencode@adamspiers.org>
Co-authored-by: rekram1-node <aidenpcline@gmail.com>
2025-09-01 21:14:27 -05:00
1513 changed files with 46151 additions and 12208 deletions
+3 -1
View File
@@ -17,11 +17,13 @@ jobs:
- uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.19
bun-version: 1.2.21
- run: bun install
- run: bun sst deploy --stage=${{ github.ref_name }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }}
+32
View File
@@ -0,0 +1,32 @@
name: format
on:
push:
branches-ignore:
- production
pull_request:
branches-ignore:
- production
workflow_dispatch:
jobs:
format:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.21
- name: run
run: |
bun install
./script/format.ts
env:
CI: true
+1 -1
View File
@@ -2,7 +2,7 @@ name: discord
on:
release:
types: [published] # fires only when a release is published
types: [published] # fires only when a release is published
jobs:
notify:
+1 -1
View File
@@ -24,4 +24,4 @@ jobs:
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
with:
model: opencode/sonic
model: opencode/sonic
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.19
bun-version: 1.2.21
- run: git fetch --force --tags
- run: bun install -g @vscode/vsce
+13 -12
View File
@@ -1,17 +1,17 @@
name: publish
run-name: "${{ format('v{0}', inputs.version) }}"
run-name: "${{ format('release {0}', inputs.bump) }}"
on:
workflow_dispatch:
inputs:
version:
description: "Version to publish"
bump:
description: "Bump major, minor, or patch"
required: true
type: string
title:
description: "Custom title for this run"
required: false
type: string
type: choice
options:
- major
- minor
- patch
concurrency: ${{ github.workflow }}-${{ github.ref }}
@@ -37,16 +37,16 @@ jobs:
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.19
bun-version: 1.2.21
- name: Cache ~/.bun
id: cache-bun
uses: actions/cache@v3
with:
path: ~/.bun
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
key: ${{ runner.os }}-bun-1-2-21-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
${{ runner.os }}-bun-1-2-21-
- name: Install makepkg
run: |
@@ -65,8 +65,9 @@ jobs:
- name: Publish
run: |
OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
./script/publish.ts
env:
OPENCODE_BUMP: ${{ inputs.bump }}
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }}
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
+2 -2
View File
@@ -1,4 +1,4 @@
name: Typecheck
name: typecheck
on:
pull_request:
@@ -15,7 +15,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.19
bun-version: 1.2.21
- name: Install dependencies
run: bun install
+2
View File
@@ -8,3 +8,5 @@ node_modules
openapi.json
playground
tmp
dist
.turbo
+11
View File
@@ -0,0 +1,11 @@
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "call this tool when you want to give up",
args: {
message: tool.schema.string().describe("give up message"),
},
async execute(args) {
return "Hey fuck you!"
},
})
+1 -1
View File
@@ -107,4 +107,4 @@ The other confusingly named repo has no relation to this one. You can [read the
---
**Join our community** [Discord](https://discord.gg/opencode) | [YouTube](https://www.youtube.com/c/sst-dev) | [X.com](https://x.com/anomaly_inv)
**Join our community** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
+19
View File
@@ -66,3 +66,22 @@
| 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) |
+635 -596
View File
File diff suppressed because it is too large Load Diff
-39
View File
@@ -1,39 +0,0 @@
import { JSX } from "solid-js"
export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="currentColor" />
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="currentColor" />
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="currentColor" />
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="currentColor" />
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="currentColor" />
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="currentColor" />
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="currentColor" />
</svg>
);
}
export function IconCopy(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg
{...props}
viewBox="0 0 512 512" >
<rect width="336" height="336" x="128" y="128" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" rx="57" ry="57"></rect>
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"></path>
</svg>
)
}
export function IconCheck(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg
{...props}
viewBox="0 0 24 24" >
<path fill="currentColor" d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"></path>
</svg>
)
}
-79
View File
@@ -1,79 +0,0 @@
import { getRequestEvent } from "solid-js/web"
import { and, Database, eq, inArray } from "@opencode/cloud-core/drizzle/index.js"
import { WorkspaceTable } from "@opencode/cloud-core/schema/workspace.sql.js"
import { UserTable } from "@opencode/cloud-core/schema/user.sql.js"
import { query, redirect } from "@solidjs/router"
import { AccountTable } from "@opencode/cloud-core/schema/account.sql.js"
import { Actor } from "@opencode/cloud-core/actor.js"
import { createClient } from "@openauthjs/openauth/client"
import { useAuthSession } from "./auth.session"
export const AuthClient = createClient({
clientID: "app",
issuer: import.meta.env.VITE_AUTH_URL,
})
export const getActor = query(async (): Promise<Actor.Info> => {
"use server"
const evt = getRequestEvent()
if (!evt) throw new Error("No request event")
const url = new URL(evt.request.headers.has("x-server-id") ? evt.request.headers.get("referer")! : evt.request.url)
const auth = await useAuthSession()
const splits = url.pathname.split("/").filter(Boolean)
if (splits[0] !== "workspace") {
if (auth.data.current) {
const current = auth.data.account[auth.data.current]
return {
type: "account",
properties: {
email: current.email,
accountID: current.id,
},
}
}
if (Object.keys(auth.data.account ?? {}).length > 0) {
const current = Object.values(auth.data.account)[0]
await auth.update((val) => ({
...val,
current: current.id,
}))
return {
type: "account",
properties: {
email: current.email,
accountID: current.id,
},
}
}
return {
type: "public",
properties: {},
}
}
const workspaceHint = splits[1]
const accounts = Object.keys(auth.data.account ?? {})
const result = await Database.transaction(async (tx) => {
return await tx
.select({
user: UserTable,
})
.from(AccountTable)
.innerJoin(UserTable, and(eq(UserTable.email, AccountTable.email)))
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
.where(and(inArray(AccountTable.id, accounts), eq(WorkspaceTable.id, workspaceHint)))
.limit(1)
.execute()
.then((x) => x[0])
})
if (result) {
return {
type: "user",
properties: {
userID: result.user.id,
workspaceID: result.user.workspaceID,
},
}
}
throw redirect("/auth/authorize")
}, "actor")
-16
View File
@@ -1,16 +0,0 @@
import { Actor } from "@opencode/cloud-core/actor.js"
import { getActor } from "./auth"
import { query } from "@solidjs/router"
export async function withActor<T>(fn: () => T) {
const actor = await getActor()
return Actor.provide(actor.type, actor.properties, fn)
}
export function actorQuery<T>(cb: () => T, name: string) {
"use server"
return query(async () => {
const actor = await getActor()
return withActor(cb)
}, name)
}
-4
View File
@@ -1,4 +0,0 @@
// @refresh reload
import { mount, StartClient } from "@solidjs/start/client";
mount(() => <StartClient />, document.getElementById("app")!);
-25
View File
@@ -1,25 +0,0 @@
// @refresh reload
import { createHandler, StartServer } from "@solidjs/start/server"
export default createHandler(() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.svg" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
), {
mode: "sync",
})
-19
View File
@@ -1,19 +0,0 @@
import { Title } from "@solidjs/meta";
import { HttpStatusCode } from "@solidjs/start";
export default function NotFound() {
return (
<main>
<Title>Not Found</Title>
<HttpStatusCode code={404} />
<h1>Page Not Found</h1>
<p>
Visit{" "}
<a href="https://start.solidjs.com" target="_blank">
start.solidjs.com
</a>{" "}
to learn how to build SolidStart apps.
</p>
</main>
);
}
-7
View File
@@ -1,7 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { AuthClient } from "~/context/auth"
export async function GET(input: APIEvent) {
const result = await AuthClient.authorize(new URL("./callback", input.request.url).toString(), "code")
return Response.redirect(result.url, 302)
}
@@ -1,576 +0,0 @@
import { Resource } from "@opencode/cloud-resource"
import { Billing } from "@opencode/cloud-core/billing.js"
import type { APIEvent } from "@solidjs/start/server"
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
import { BillingTable, PaymentTable } from "@opencode/cloud-core/schema/billing.sql.js"
import { Identifier } from "@opencode/cloud-core/identifier.js"
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
import { Actor } from "@opencode/cloud-core/actor.js"
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
const SUPPORTED_MODELS = {
// "anthropic/claude-sonnet-4": {
// input: 0.0000015,
// output: 0.000006,
// reasoning: 0.0000015,
// cacheRead: 0.0000001,
// cacheWrite: 0.0000001,
// model: () =>
// createAnthropic({
// apiKey: Resource.ANTHROPIC_API_KEY.value,
// })("claude-sonnet-4-20250514"),
// },
// "openai/gpt-4.1": {
// input: 0.0000015,
// output: 0.000006,
// reasoning: 0.0000015,
// cacheRead: 0.0000001,
// cacheWrite: 0.0000001,
// model: () =>
// createOpenAI({
// apiKey: Resource.OPENAI_API_KEY.value,
// })("gpt-4.1"),
// },
// "zhipuai/glm-4.5-flash": {
// input: 0,
// output: 0,
// reasoning: 0,
// cacheRead: 0,
// cacheWrite: 0,
// model: () =>
// createOpenAICompatible({
// name: "Zhipu AI",
// baseURL: "https://api.z.ai/api/paas/v4",
// apiKey: Resource.ZHIPU_API_KEY.value,
// })("glm-4.5-flash"),
// },
}
export async function POST(input: APIEvent) {
// Check auth header
const authHeader = input.request.headers.get("authorization")
if (!authHeader || !authHeader.startsWith("Bearer "))
return Response.json(
{
error: {
message: "Missing API key.",
type: "invalid_request_error",
param: null,
code: "unauthorized",
},
},
{ status: 401 },
)
const apiKey = authHeader.split(" ")[1]
// Check against KeyTable
const keyRecord = await Database.use((tx) =>
tx
.select({
id: KeyTable.id,
workspaceID: KeyTable.workspaceID,
})
.from(KeyTable)
.where(eq(KeyTable.key, apiKey))
.then((rows) => rows[0]),
)
if (!keyRecord)
return Response.json(
{
error: {
message: "Invalid API key.",
type: "invalid_request_error",
param: null,
code: "unauthorized",
},
},
{ status: 401 },
)
/*
return await Actor.provide("system", { workspaceID: keyRecord.workspaceID }, async () => {
try {
// Check balance
const customer = await Billing.get()
if (customer.balance <= 0) {
return Response.json(
{
error: {
message: "Insufficient balance",
type: "insufficient_quota",
param: null,
code: "insufficient_quota",
},
},
{ status: 401 },
)
}
const body = await input.request.json<ChatCompletionCreateParamsBase>()
const model = SUPPORTED_MODELS[body.model as keyof typeof SUPPORTED_MODELS]?.model()
if (!model) throw new Error(`Unsupported model: ${body.model}`)
const requestBody = transformOpenAIRequestToAiSDK()
return body.stream ? await handleStream() : await handleGenerate()
async function handleStream() {
const result = await model.doStream({
...requestBody,
})
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
const id = `chatcmpl-${Date.now()}`
const created = Math.floor(Date.now() / 1000)
try {
for await (const chunk of result.stream) {
console.log("!!! CHUNK !!! : " + chunk.type)
switch (chunk.type) {
case "text-delta": {
const data = {
id,
object: "chat.completion.chunk",
created,
model: body.model,
choices: [
{
index: 0,
delta: {
content: chunk.delta,
},
finish_reason: null,
},
],
}
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
break
}
case "reasoning-delta": {
const data = {
id,
object: "chat.completion.chunk",
created,
model: body.model,
choices: [
{
index: 0,
delta: {
reasoning_content: chunk.delta,
},
finish_reason: null,
},
],
}
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
break
}
case "tool-call": {
const data = {
id,
object: "chat.completion.chunk",
created,
model: body.model,
choices: [
{
index: 0,
delta: {
tool_calls: [
{
index: 0,
id: chunk.toolCallId,
type: "function",
function: {
name: chunk.toolName,
arguments: chunk.input,
},
},
],
},
finish_reason: null,
},
],
}
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
break
}
case "error": {
const data = {
id,
object: "chat.completion.chunk",
created,
model: body.model,
choices: [
{
index: 0,
delta: {},
finish_reason: "stop",
},
],
error: {
message: typeof chunk.error === "string" ? chunk.error : chunk.error,
type: "server_error",
},
}
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
controller.enqueue(encoder.encode("data: [DONE]\n\n"))
controller.close()
break
}
case "finish": {
const data = {
id,
object: "chat.completion.chunk",
created,
model: body.model,
choices: [
{
index: 0,
delta: {},
finish_reason:
{
stop: "stop",
length: "length",
"content-filter": "content_filter",
"tool-calls": "tool_calls",
error: "stop",
other: "stop",
unknown: "stop",
}[chunk.finishReason] || "stop",
},
],
usage: {
prompt_tokens: chunk.usage.inputTokens,
completion_tokens: chunk.usage.outputTokens,
total_tokens: chunk.usage.totalTokens,
completion_tokens_details: {
reasoning_tokens: chunk.usage.reasoningTokens,
},
prompt_tokens_details: {
cached_tokens: chunk.usage.cachedInputTokens,
},
},
}
await trackUsage(body.model, chunk.usage, chunk.providerMetadata)
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
controller.enqueue(encoder.encode("data: [DONE]\n\n"))
controller.close()
break
}
//case "stream-start":
//case "response-metadata":
case "text-start":
case "text-end":
case "reasoning-start":
case "reasoning-end":
case "tool-input-start":
case "tool-input-delta":
case "tool-input-end":
case "raw":
default:
// Log unknown chunk types for debugging
console.warn(`Unknown chunk type: ${(chunk as any).type}`)
break
}
}
} catch (error) {
controller.error(error)
}
},
})
return new Response(stream, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
})
}
async function handleGenerate() {
const response = await model.doGenerate({
...requestBody,
})
await trackUsage(body.model, response.usage, response.providerMetadata)
return c.json({
id: `chatcmpl-${Date.now()}`,
object: "chat.completion" as const,
created: Math.floor(Date.now() / 1000),
model: body.model,
choices: [
{
index: 0,
message: {
role: "assistant" as const,
content: response.content?.find((c) => c.type === "text")?.text ?? "",
reasoning_content: response.content?.find((c) => c.type === "reasoning")?.text,
tool_calls: response.content
?.filter((c) => c.type === "tool-call")
.map((toolCall) => ({
id: toolCall.toolCallId,
type: "function" as const,
function: {
name: toolCall.toolName,
arguments: toolCall.input,
},
})),
},
finish_reason:
(
{
stop: "stop",
length: "length",
"content-filter": "content_filter",
"tool-calls": "tool_calls",
error: "stop",
other: "stop",
unknown: "stop",
} as const
)[response.finishReason] || "stop",
},
],
usage: {
prompt_tokens: response.usage?.inputTokens,
completion_tokens: response.usage?.outputTokens,
total_tokens: response.usage?.totalTokens,
completion_tokens_details: {
reasoning_tokens: response.usage?.reasoningTokens,
},
prompt_tokens_details: {
cached_tokens: response.usage?.cachedInputTokens,
},
},
})
}
function transformOpenAIRequestToAiSDK() {
const prompt = transformMessages()
const tools = transformTools()
return {
prompt,
maxOutputTokens: body.max_tokens ?? body.max_completion_tokens ?? undefined,
temperature: body.temperature ?? undefined,
topP: body.top_p ?? undefined,
frequencyPenalty: body.frequency_penalty ?? undefined,
presencePenalty: body.presence_penalty ?? undefined,
providerOptions: body.reasoning_effort
? {
anthropic: {
reasoningEffort: body.reasoning_effort,
},
}
: undefined,
stopSequences: (typeof body.stop === "string" ? [body.stop] : body.stop) ?? undefined,
responseFormat: (() => {
if (!body.response_format) return { type: "text" as const }
if (body.response_format.type === "json_schema")
return {
type: "json" as const,
schema: body.response_format.json_schema.schema,
name: body.response_format.json_schema.name,
description: body.response_format.json_schema.description,
}
if (body.response_format.type === "json_object") return { type: "json" as const }
throw new Error("Unsupported response format")
})(),
seed: body.seed ?? undefined,
tools: tools.tools,
toolChoice: tools.toolChoice,
}
function transformTools() {
const { tools, tool_choice } = body
if (!tools || tools.length === 0) {
return { tools: undefined, toolChoice: undefined }
}
const aiSdkTools = tools.map((tool) => {
return {
type: tool.type,
name: tool.function.name,
description: tool.function.description,
inputSchema: tool.function.parameters!,
}
})
let aiSdkToolChoice
if (tool_choice == null) {
aiSdkToolChoice = undefined
} else if (tool_choice === "auto") {
aiSdkToolChoice = { type: "auto" as const }
} else if (tool_choice === "none") {
aiSdkToolChoice = { type: "none" as const }
} else if (tool_choice === "required") {
aiSdkToolChoice = { type: "required" as const }
} else if (tool_choice.type === "function") {
aiSdkToolChoice = {
type: "tool" as const,
toolName: tool_choice.function.name,
}
}
return { tools: aiSdkTools, toolChoice: aiSdkToolChoice }
}
function transformMessages() {
const { messages } = body
const prompt: LanguageModelV2Prompt = []
for (const message of messages) {
switch (message.role) {
case "system": {
prompt.push({
role: "system",
content: message.content as string,
})
break
}
case "user": {
if (typeof message.content === "string") {
prompt.push({
role: "user",
content: [{ type: "text", text: message.content }],
})
} else {
const content = message.content.map((part) => {
switch (part.type) {
case "text":
return { type: "text" as const, text: part.text }
case "image_url":
return {
type: "file" as const,
mediaType: "image/jpeg" as const,
data: part.image_url.url,
}
default:
throw new Error(`Unsupported content part type: ${(part as any).type}`)
}
})
prompt.push({
role: "user",
content,
})
}
break
}
case "assistant": {
const content: Array<
| { type: "text"; text: string }
| {
type: "tool-call"
toolCallId: string
toolName: string
input: any
}
> = []
if (message.content) {
content.push({
type: "text",
text: message.content as string,
})
}
if (message.tool_calls) {
for (const toolCall of message.tool_calls) {
content.push({
type: "tool-call",
toolCallId: toolCall.id,
toolName: toolCall.function.name,
input: JSON.parse(toolCall.function.arguments),
})
}
}
prompt.push({
role: "assistant",
content,
})
break
}
case "tool": {
prompt.push({
role: "tool",
content: [
{
type: "tool-result",
toolName: "placeholder",
toolCallId: message.tool_call_id,
output: {
type: "text",
value: message.content as string,
},
},
],
})
break
}
default: {
throw new Error(`Unsupported message role: ${message.role}`)
}
}
}
return prompt
}
}
async function trackUsage(model: string, usage: LanguageModelUsage, providerMetadata?: ProviderMetadata) {
const modelData = SUPPORTED_MODELS[model as keyof typeof SUPPORTED_MODELS]
if (!modelData) throw new Error(`Unsupported model: ${model}`)
const inputTokens = usage.inputTokens ?? 0
const outputTokens = usage.outputTokens ?? 0
const reasoningTokens = usage.reasoningTokens ?? 0
const cacheReadTokens = usage.cachedInputTokens ?? 0
const cacheWriteTokens =
providerMetadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
// @ts-expect-error
providerMetadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
0
const inputCost = modelData.input * inputTokens
const outputCost = modelData.output * outputTokens
const reasoningCost = modelData.reasoning * reasoningTokens
const cacheReadCost = modelData.cacheRead * cacheReadTokens
const cacheWriteCost = modelData.cacheWrite * cacheWriteTokens
const costInCents = (inputCost + outputCost + reasoningCost + cacheReadCost + cacheWriteCost) * 100
await Billing.consume({
model,
inputTokens,
outputTokens,
reasoningTokens,
cacheReadTokens,
cacheWriteTokens,
costInCents,
})
await Database.use((tx) =>
tx
.update(KeyTable)
.set({ timeUsed: sql`now()` })
.where(eq(KeyTable.id, keyRecord.id)),
)
}
} catch (error: any) {
return Response.json({ error: { message: error.message } }, { status: 500 })
}
})
*/
}
-69
View File
@@ -1,69 +0,0 @@
[data-page="workspace"] {
line-height: 1;
@media (max-width: 30rem) {
padding: var(--space-4);
gap: var(--space-5);
}
/* Workspace Header */
[data-component="workspace-header"] {
position: sticky;
top: 0;
z-index: 100;
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-4) var(--space-4);
border-bottom: 1px solid var(--color-border);
background-color: var(--color-bg);
@media (max-width: 30rem) {
padding: 0 var(--space-4);
margin: calc(-1 * var(--space-4));
margin-bottom: var(--space-5);
}
}
[data-slot="header-brand"] {
flex: 0 0 auto;
padding-top: 4px;
svg {
width: 138px;
}
[data-component="site-title"] {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--color-text);
text-decoration: none;
letter-spacing: -0.02em;
}
}
[data-slot="header-actions"] {
display: flex;
gap: var(--space-4);
align-items: center;
font-size: var(--font-size-sm);
span {
color: var(--color-text-muted);
}
a,
button {
appearance: none;
background: none;
border: none;
cursor: pointer;
padding: 0;
color: var(--color-text);
text-decoration: underline;
text-underline-offset: var(--space-0-75);
text-decoration-thickness: 1px;
text-transform: uppercase;
}
}
}
-54
View File
@@ -1,54 +0,0 @@
import "./workspace.css"
import { useAuthSession } from "~/context/auth.session"
import { IconLogo } from "../component/icon"
import { withActor } from "~/context/auth.withActor"
import "./workspace.css"
import { query, action, redirect, createAsync, RouteSectionProps } from "@solidjs/router"
import { User } from "@opencode/cloud-core/user.js"
import { Actor } from "@opencode/cloud-core/actor.js"
const getUserInfo = query(async () => {
"use server"
return withActor(async () => {
const actor = Actor.assert("user")
const user = await User.fromID(actor.properties.userID)
return { user }
})
}, "userInfo")
const logout = action(async () => {
"use server"
const auth = await useAuthSession()
const current = auth.data.current
if (current)
await auth.update((val) => {
delete val.account[current]
return val
})
return redirect("/")
})
export default function WorkspaceLayout(props: RouteSectionProps) {
const userInfo = createAsync(() => getUserInfo(), {
deferStream: true,
})
return (
<main data-page="workspace">
<header data-component="workspace-header">
<div data-slot="header-brand">
<a href="/" data-component="site-title">
<IconLogo />
</a>
</div>
<div data-slot="header-actions">
<span>{userInfo()?.user.email}</span>
<form action={logout} method="post">
<button type="submit" formaction={logout}>Logout</button>
</form>
</div>
</header>
<div data-slot="content">{props.children}</div>
</main>
)
}
-474
View File
@@ -1,474 +0,0 @@
/* Root container */
[data-slot="root"] {
max-width: 64rem;
padding: var(--space-10) var(--space-4);
margin: 0 auto;
width: 100%;
display: flex;
flex-direction: column;
gap: var(--space-10);
[data-slot="sections"] {
display: flex;
flex-direction: column;
gap: var(--space-16);
section {
display: flex;
flex-direction: column;
gap: var(--space-6);
}
section:not(:last-child) {
border-bottom: 1px solid var(--color-border);
padding-bottom: var(--space-16);
}
}
/* Common elements */
button {
padding: var(--space-3) var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
font-family: var(--font-sans);
font-weight: 500;
text-transform: uppercase;
cursor: pointer;
transition: all 0.15s ease;
&:hover {
background-color: var(--color-surface-hover);
border-color: var(--color-accent);
}
&:active {
transform: translateY(1px);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
&:hover {
background-color: var(--color-bg);
border-color: var(--color-border);
transform: none;
}
}
&[data-color="primary"] {
background-color: var(--color-primary);
border-color: var(--color-primary);
color: var(--color-primary-text);
&:hover {
background-color: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
}
&[data-color="ghost"] {
background-color: transparent;
border-color: transparent;
color: var(--color-text-muted);
&:hover {
background-color: var(--color-surface-hover);
border-color: var(--color-border);
color: var(--color-text);
}
}
}
a {
color: var(--color-text);
text-decoration: underline;
text-underline-offset: var(--space-0-75);
text-decoration-thickness: 1px;
}
[data-slot="empty-state"] {
padding: var(--space-20) var(--space-6);
text-align: center;
border: 1px dashed var(--color-border);
border-radius: var(--border-radius-sm);
display: flex;
flex-direction: column;
gap: var(--space-2);
p {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
margin: 0;
}
}
/* Title section */
[data-slot="title-section"] {
display: flex;
flex-direction: column;
gap: var(--space-2);
padding-bottom: var(--space-8);
border-bottom: 1px solid var(--color-border);
h1 {
font-size: var(--font-size-2xl);
font-weight: 500;
line-height: 1.2;
letter-spacing: -0.03125rem;
margin: 0;
text-transform: uppercase;
@media (max-width: 30rem) {
font-size: var(--font-size-xl);
line-height: 1.25;
}
}
p {
font-size: var(--font-size-md);
color: var(--color-text-muted);
a {
color: var(--color-text-muted);
}
}
}
/* Section titles */
[data-slot="section-title"] {
display: flex;
flex-direction: column;
gap: var(--space-1);
h2 {
font-size: var(--font-size-md);
font-weight: 600;
line-height: 1.2;
letter-spacing: -0.03125rem;
margin: 0;
color: var(--color-text-secondary);
text-transform: uppercase;
@media (max-width: 30rem) {
font-size: var(--font-size-lg);
line-height: 1.25;
}
}
p {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
}
}
/* API Keys Section */
[data-slot="api-keys-section"] {
[data-slot="create-form"] {
display: flex;
gap: var(--space-3);
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
input {
flex: 1;
padding: var(--space-2) var(--space-3);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
font-family: var(--font-mono);
&:focus {
outline: none;
border-color: var(--color-accent);
}
&::placeholder {
color: var(--color-text-disabled);
}
}
[data-slot="form-actions"] {
display: flex;
gap: var(--space-2);
}
}
[data-slot="api-keys-table"] {
overflow-x: auto;
}
[data-slot="api-keys-table-element"] {
width: 100%;
border-collapse: collapse;
font-size: var(--font-size-sm);
thead {
border-bottom: 1px solid var(--color-border);
}
th {
padding: var(--space-3) var(--space-4);
text-align: left;
font-weight: normal;
color: var(--color-text-muted);
text-transform: uppercase;
}
td {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border-muted);
color: var(--color-text-muted);
font-family: var(--font-mono);
&[data-slot="key-name"] {
color: var(--color-text);
font-family: var(--font-sans);
font-weight: 500;
}
&[data-slot="key-value"] {
font-family: var(--font-mono);
div {
cursor: pointer;
display: flex;
align-items: center;
gap: var(--space-2);
}
}
&[data-slot="key-date"] {
color: var(--color-text);
}
&[data-slot="key-actions"] {
font-family: var(--font-sans);
}
}
tbody tr {
&:last-child td {
border-bottom: none;
}
}
@media (max-width: 40rem) {
th,
td {
padding: var(--space-2) var(--space-3);
font-size: var(--font-size-xs);
}
th {
&:nth-child(3) /* Date */ {
display: none;
}
}
td {
&:nth-child(3) /* Date */ {
display: none;
}
}
}
}
}
/* Balance Section */
[data-slot="balance-section"] {
[data-slot="balance"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
min-width: 14.5rem;
width: fit-content;
[data-slot="amount"] {
padding: var(--space-3-5) var(--space-4);
background-color: var(--color-bg-surface);
border-radius: var(--border-radius-sm);
display: flex;
align-items: baseline;
gap: var(--space-1);
justify-content: flex-end;
&.danger {
[data-slot="value"] {
color: var(--color-danger);
}
}
[data-slot="currency"] {
position: relative;
bottom: 2px;
font-size: var(--font-size-lg);
color: var(--color-text-muted);
font-weight: 400;
}
[data-slot="value"] {
font-size: var(--font-size-3xl);
font-weight: 500;
color: var(--color-text);
}
}
}
}
/* Payments Section */
[data-slot="payments-section"] {
[data-slot="payments-table"] {
overflow-x: auto;
}
[data-slot="payments-table-element"] {
width: 100%;
border-collapse: collapse;
font-size: var(--font-size-sm);
thead {
border-bottom: 1px solid var(--color-border);
}
th {
padding: var(--space-3) var(--space-4);
text-align: left;
font-weight: normal;
color: var(--color-text-muted);
text-transform: uppercase;
}
td {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border-muted);
color: var(--color-text-muted);
font-family: var(--font-mono);
&[data-slot="payment-date"] {
color: var(--color-text);
}
&[data-slot="payment-id"] {
font-family: var(--font-mono);
font-weight: 400;
color: var(--color-text-muted);
max-width: 200px;
word-break: break-word;
}
&[data-slot="payment-amount"] {
color: var(--color-text);
}
}
tbody tr {
&:last-child td {
border-bottom: none;
}
}
@media (max-width: 40rem) {
th,
td {
padding: var(--space-2) var(--space-3);
font-size: var(--font-size-xs);
}
th {
&:nth-child(2) /* Payment ID */ {
display: none;
}
}
td {
&:nth-child(2) /* Payment ID */ {
display: none;
}
}
}
}
}
/* Usage Section */
[data-slot="usage-section"] {
[data-slot="usage-table"] {
overflow-x: auto;
}
[data-slot="usage-table-element"] {
width: 100%;
border-collapse: collapse;
font-size: var(--font-size-sm);
thead {
border-bottom: 1px solid var(--color-border);
}
th {
padding: var(--space-3) var(--space-4);
text-align: left;
font-weight: normal;
color: var(--color-text-muted);
text-transform: uppercase;
}
td {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border-muted);
color: var(--color-text-muted);
font-family: var(--font-mono);
&[data-slot="usage-date"] {
color: var(--color-text);
}
&[data-slot="usage-model"] {
font-family: var(--font-sans);
font-weight: 400;
color: var(--color-text-secondary);
max-width: 200px;
word-break: break-word;
}
&[data-slot="usage-cost"] {
color: var(--color-text);
}
}
tbody tr {
&:last-child td {
border-bottom: none;
}
}
@media (max-width: 40rem) {
th,
td {
padding: var(--space-2) var(--space-3);
font-size: var(--font-size-xs);
}
th {
&:nth-child(2) /* Model */ {
display: none;
}
}
td {
&:nth-child(2) /* Model */ {
display: none;
}
}
}
}
}
}
-407
View File
@@ -1,407 +0,0 @@
import { Billing } from "@opencode/cloud-core/billing.js"
import { Key } from "@opencode/cloud-core/key.js"
import { action, createAsync, query, useAction, useSubmission, json } from "@solidjs/router"
import { createSignal, For, onMount, Show } from "solid-js"
import { getActor } from "~/context/auth"
import { withActor } from "~/context/auth.withActor"
import { IconCopy, IconCheck } from "~/component/icon"
import "./[id].css"
import { User } from "@opencode/cloud-core/user.js"
import { Actor } from "@opencode/cloud-core/actor.js"
/////////////////////////////////////
// Keys related queries and actions
/////////////////////////////////////
const listKeys = query(() => {
"use server"
return withActor(() => Key.list())
}, "key.list")
const createKey = action(async (name: string) => {
"use server"
return json(
withActor(() => Key.create({ name })),
{ revalidate: listKeys.key },
)
}, "key.create")
const removeKey = action(async (id: string) => {
"use server"
return json(
withActor(() => Key.remove({ id })),
{ revalidate: listKeys.key },
)
}, "key.remove")
/////////////////////////////////////
// Billing related queries and actions
/////////////////////////////////////
const getBillingInfo = query(async () => {
"use server"
return withActor(async () => {
const actor = Actor.assert("user")
const [user, billing, payments, usage] = await Promise.all([
User.fromID(actor.properties.userID),
Billing.get(),
Billing.payments(),
Billing.usages(),
])
return { user, billing, payments, usage }
})
}, "billingInfo")
const createCheckoutUrl = action(async (successUrl: string, cancelUrl: string) => {
"use server"
return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }))
}, "checkoutUrl")
const createPortalUrl = action(async (returnUrl: string) => {
"use server"
return withActor(() => Billing.generatePortalUrl({ returnUrl }))
}, "portalUrl")
export default function () {
/////////////////
// Keys section
/////////////////
const keys = createAsync(() => listKeys(), {
deferStream: true,
})
const createKeyAction = useAction(createKey)
const removeKeyAction = useAction(removeKey)
const createKeySubmission = useSubmission(createKey)
const [showCreateForm, setShowCreateForm] = createSignal(false)
const [keyName, setKeyName] = createSignal("")
const [copiedKeyId, setCopiedKeyId] = createSignal<string | null>(null)
const formatDate = (date: Date) => {
return date.toLocaleDateString()
}
const formatDateForTable = (date: Date) => {
const options: Intl.DateTimeFormatOptions = {
day: "numeric",
month: "short",
hour: "numeric",
minute: "2-digit",
hour12: true,
}
return date.toLocaleDateString("en-GB", options).replace(",", ",")
}
const formatDateUTC = (date: Date) => {
const options: Intl.DateTimeFormatOptions = {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
timeZoneName: "short",
timeZone: "UTC",
}
return date.toLocaleDateString("en-US", options)
}
const formatKey = (key: string) => {
if (key.length <= 11) return key
return `${key.slice(0, 7)}...${key.slice(-4)}`
}
const copyToClipboard = async (text: string) => {
try {
await navigator.clipboard.writeText(text)
} catch (error) {
console.error("Failed to copy to clipboard:", error)
}
}
const copyKeyToClipboard = async (text: string, keyId: string) => {
try {
await navigator.clipboard.writeText(text)
setCopiedKeyId(keyId)
setTimeout(() => setCopiedKeyId(null), 1500)
} catch (error) {
console.error("Failed to copy to clipboard:", error)
}
}
const handleCreateKey = async () => {
if (!keyName().trim()) return
try {
await createKeyAction(keyName().trim())
setKeyName("")
setShowCreateForm(false)
} catch (error) {
console.error("Failed to create API key:", error)
}
}
const handleDeleteKey = async (keyId: string) => {
if (!confirm("Are you sure you want to delete this API key?")) {
return
}
try {
await removeKeyAction(keyId)
} catch (error) {
console.error("Failed to delete API key:", error)
}
}
/////////////////
// Billing section
/////////////////
const billingInfo = createAsync(() => getBillingInfo(), {
deferStream: true,
})
const createCheckoutUrlAction = useAction(createCheckoutUrl)
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
const handleBuyCredits = async () => {
try {
const baseUrl = window.location.href
const checkoutUrl = await createCheckoutUrlAction(baseUrl, baseUrl)
if (checkoutUrl) {
window.location.href = checkoutUrl
}
} catch (error) {
console.error("Failed to get checkout URL:", error)
}
}
return (
<div data-slot="root">
{/* Title */}
<section data-slot="title-section">
<h1>Gateway</h1>
<p>
Coding models optimized for use with opencode. <a href="/docs">Learn more</a>.
</p>
</section>
<div data-slot="sections">
{/* API Keys Section */}
<section data-slot="api-keys-section">
<div data-slot="section-title">
<h2>API Keys</h2>
<p>Manage your API keys for accessing opencode services.</p>
</div>
<Show
when={!showCreateForm()}
fallback={
<div data-slot="create-form">
<input
data-component="input"
type="text"
placeholder="Enter key name"
value={keyName()}
onInput={(e) => setKeyName(e.currentTarget.value)}
onKeyPress={(e) => e.key === "Enter" && handleCreateKey()}
/>
<div data-slot="form-actions">
<button
data-color="ghost"
onClick={() => {
setShowCreateForm(false)
setKeyName("")
}}
>
Cancel
</button>
<button
data-color="primary"
disabled={createKeySubmission.pending || !keyName().trim()}
onClick={handleCreateKey}
>
{createKeySubmission.pending ? "Creating..." : "Create"}
</button>
</div>
</div>
}
>
<button
data-color="primary"
onClick={() => {
console.log("clicked")
setShowCreateForm(true)
}}
>
Create API Key
</button>
</Show>
<div data-slot="api-keys-table">
<Show
when={keys()?.length}
fallback={
<div data-slot="empty-state">
<p>Create an opencode Gateway API key</p>
</div>
}
>
<table data-slot="api-keys-table-element">
<thead>
<tr>
<th>Name</th>
<th>Key</th>
<th>Created</th>
<th></th>
</tr>
</thead>
<tbody>
<For each={keys()!}>
{(key) => (
<tr>
<td data-slot="key-name">{key.name}</td>
<td data-slot="key-value">
<div onClick={() => copyKeyToClipboard(key.key, key.id)} title="Click to copy API key">
<span>{formatKey(key.key)}</span>
<Show
when={copiedKeyId() === key.id}
fallback={<IconCopy style={{ width: "14px", height: "14px" }} />}
>
<IconCheck style={{ width: "14px", height: "14px" }} />
</Show>
</div>
</td>
<td data-slot="key-date" title={formatDateUTC(key.timeCreated)}>
{formatDateForTable(key.timeCreated)}
</td>
<td data-slot="key-actions">
<button data-color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key">
Delete
</button>
</td>
</tr>
)}
</For>
</tbody>
</table>
</Show>
</div>
</section>
{/* Balance Section */}
<section data-slot="balance-section">
<div data-slot="section-title">
<h2>Balance</h2>
<p>Add credits to your account.</p>
</div>
<div data-slot="balance">
<div
data-slot="amount"
classList={{
danger: (() => {
const balanceStr = ((billingInfo()?.billing?.balance ?? 0) / 100000000).toFixed(2)
return balanceStr === "0.00" || balanceStr === "-0.00"
})(),
}}
>
<span data-slot="currency">$</span>
<span data-slot="value">
{(() => {
const balanceStr = ((billingInfo()?.billing?.balance ?? 0) / 100000000).toFixed(2)
return balanceStr === "-0.00" ? "0.00" : balanceStr
})()}
</span>
</div>
<button data-color="primary" disabled={createCheckoutUrlSubmission.pending} onClick={handleBuyCredits}>
{createCheckoutUrlSubmission.pending ? "Loading..." : "Buy Credits"}
</button>
</div>
</section>
{/* Usage Section */}
<section data-slot="usage-section">
<div data-slot="section-title">
<h2>Usage History</h2>
<p>Recent API usage and costs.</p>
</div>
<div data-slot="usage-table">
<Show
when={billingInfo() && billingInfo()!.usage.length > 0}
fallback={
<div data-slot="empty-state">
<p>Make your first API call to get started.</p>
</div>
}
>
<table data-slot="usage-table-element">
<thead>
<tr>
<th>Date</th>
<th>Model</th>
<th>Tokens</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<For each={billingInfo()!.usage}>
{(usage) => {
const totalTokens = usage.inputTokens + usage.outputTokens + (usage.reasoningTokens || 0)
const date = new Date(usage.timeCreated)
return (
<tr>
<td data-slot="usage-date" title={formatDateUTC(date)}>
{formatDateForTable(date)}
</td>
<td data-slot="usage-model">{usage.model}</td>
<td data-slot="usage-tokens">{totalTokens.toLocaleString()}</td>
<td data-slot="usage-cost">${((usage.cost ?? 0) / 100000000).toFixed(4)}</td>
</tr>
)
}}
</For>
</tbody>
</table>
</Show>
</div>
</section>
{/* Payments Section */}
<Show when={billingInfo() && billingInfo()!.payments.length > 0}>
<section data-slot="payments-section">
<div data-slot="section-title">
<h2>Payments History</h2>
<p>Recent payment transactions.</p>
</div>
<div data-slot="payments-table">
<table data-slot="payments-table-element">
<thead>
<tr>
<th>Date</th>
<th>Payment ID</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<For each={billingInfo()!.payments}>
{(payment) => {
const date = new Date(payment.timeCreated)
return (
<tr>
<td data-slot="payment-date" title={formatDateUTC(date)}>
{formatDateForTable(date)}
</td>
<td data-slot="payment-id">{payment.id}</td>
<td data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</td>
</tr>
)
}}
</For>
</tbody>
</table>
</div>
</section>
</Show>
</div>
</div>
)
}
@@ -1,66 +0,0 @@
CREATE TABLE "billing" (
"id" varchar(30) NOT NULL,
"workspace_id" varchar(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"customer_id" varchar(255),
"payment_method_id" varchar(255),
"payment_method_last4" varchar(4),
"balance" bigint NOT NULL,
"reload" boolean,
CONSTRAINT "billing_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
);
--> statement-breakpoint
CREATE TABLE "payment" (
"id" varchar(30) NOT NULL,
"workspace_id" varchar(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"customer_id" varchar(255),
"payment_id" varchar(255),
"amount" bigint NOT NULL,
CONSTRAINT "payment_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
);
--> statement-breakpoint
CREATE TABLE "usage" (
"id" varchar(30) NOT NULL,
"workspace_id" varchar(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"request_id" varchar(255),
"model" varchar(255) NOT NULL,
"input_tokens" integer NOT NULL,
"output_tokens" integer NOT NULL,
"reasoning_tokens" integer,
"cache_read_tokens" integer,
"cache_write_tokens" integer,
"cost" bigint NOT NULL,
CONSTRAINT "usage_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
);
--> statement-breakpoint
CREATE TABLE "user" (
"id" varchar(30) NOT NULL,
"workspace_id" varchar(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"email" text NOT NULL,
"name" varchar(255) NOT NULL,
"time_seen" timestamp with time zone,
"color" integer,
CONSTRAINT "user_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
);
--> statement-breakpoint
CREATE TABLE "workspace" (
"id" varchar(30) PRIMARY KEY NOT NULL,
"slug" varchar(255),
"name" varchar(255),
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone
);
--> statement-breakpoint
ALTER TABLE "billing" ADD CONSTRAINT "billing_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "payment" ADD CONSTRAINT "payment_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "usage" ADD CONSTRAINT "usage_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user" ADD CONSTRAINT "user_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "user_email" ON "user" USING btree ("workspace_id","email");--> statement-breakpoint
CREATE UNIQUE INDEX "slug" ON "workspace" USING btree ("slug");
@@ -1,8 +0,0 @@
CREATE TABLE "account" (
"id" varchar(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"email" varchar(255) NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX "email" ON "account" USING btree ("email");
@@ -1,14 +0,0 @@
CREATE TABLE "key" (
"id" varchar(30) NOT NULL,
"workspace_id" varchar(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"user_id" text NOT NULL,
"name" varchar(255) NOT NULL,
"key" varchar(255) NOT NULL,
"time_used" timestamp with time zone,
CONSTRAINT "key_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
);
--> statement-breakpoint
ALTER TABLE "key" ADD CONSTRAINT "key_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "global_key" ON "key" USING btree ("key");
@@ -1 +0,0 @@
ALTER TABLE "usage" DROP COLUMN "request_id";
@@ -1,461 +0,0 @@
{
"id": "9b5cec8c-8b59-4d7a-bb5c-76ade1c83d6f",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.billing": {
"name": "billing",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"payment_method_id": {
"name": "payment_method_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"payment_method_last4": {
"name": "payment_method_last4",
"type": "varchar(4)",
"primaryKey": false,
"notNull": false
},
"balance": {
"name": "balance",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"reload": {
"name": "reload",
"type": "boolean",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"billing_workspace_id_workspace_id_fk": {
"name": "billing_workspace_id_workspace_id_fk",
"tableFrom": "billing",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.payment": {
"name": "payment",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"payment_id": {
"name": "payment_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"amount": {
"name": "amount",
"type": "bigint",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"payment_workspace_id_workspace_id_fk": {
"name": "payment_workspace_id_workspace_id_fk",
"tableFrom": "payment",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.usage": {
"name": "usage",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"request_id": {
"name": "request_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"model": {
"name": "model",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"input_tokens": {
"name": "input_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"output_tokens": {
"name": "output_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"reasoning_tokens": {
"name": "reasoning_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"cache_read_tokens": {
"name": "cache_read_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"cache_write_tokens": {
"name": "cache_write_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"cost": {
"name": "cost",
"type": "bigint",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"usage_workspace_id_workspace_id_fk": {
"name": "usage_workspace_id_workspace_id_fk",
"tableFrom": "usage",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"color": {
"name": "color",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
{
"expression": "workspace_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"user_workspace_id_workspace_id_fk": {
"name": "user_workspace_id_workspace_id_fk",
"tableFrom": "user",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.workspace": {
"name": "workspace",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": true,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
@@ -1,515 +0,0 @@
{
"id": "bf9e9084-4073-4ecb-8e56-5610816c9589",
"prevId": "9b5cec8c-8b59-4d7a-bb5c-76ade1c83d6f",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.account": {
"name": "account",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"email": {
"name": "email",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.billing": {
"name": "billing",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"payment_method_id": {
"name": "payment_method_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"payment_method_last4": {
"name": "payment_method_last4",
"type": "varchar(4)",
"primaryKey": false,
"notNull": false
},
"balance": {
"name": "balance",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"reload": {
"name": "reload",
"type": "boolean",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"billing_workspace_id_workspace_id_fk": {
"name": "billing_workspace_id_workspace_id_fk",
"tableFrom": "billing",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.payment": {
"name": "payment",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"payment_id": {
"name": "payment_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"amount": {
"name": "amount",
"type": "bigint",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"payment_workspace_id_workspace_id_fk": {
"name": "payment_workspace_id_workspace_id_fk",
"tableFrom": "payment",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.usage": {
"name": "usage",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"request_id": {
"name": "request_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"model": {
"name": "model",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"input_tokens": {
"name": "input_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"output_tokens": {
"name": "output_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"reasoning_tokens": {
"name": "reasoning_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"cache_read_tokens": {
"name": "cache_read_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"cache_write_tokens": {
"name": "cache_write_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"cost": {
"name": "cost",
"type": "bigint",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"usage_workspace_id_workspace_id_fk": {
"name": "usage_workspace_id_workspace_id_fk",
"tableFrom": "usage",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"color": {
"name": "color",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
{
"expression": "workspace_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"user_workspace_id_workspace_id_fk": {
"name": "user_workspace_id_workspace_id_fk",
"tableFrom": "user",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.workspace": {
"name": "workspace",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": true,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
-34
View File
@@ -1,34 +0,0 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1754518198186,
"tag": "0000_amused_mojo",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1754609655262,
"tag": "0001_thankful_chat",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1754627626945,
"tag": "0002_stale_jackal",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1754672464106,
"tag": "0003_tranquil_spencer_smythe",
"breakpoints": true
}
]
}
-24
View File
@@ -1,24 +0,0 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/cloud-core",
"version": "0.6.3",
"private": true,
"type": "module",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@opencode/cloud-resource": "workspace:*",
"drizzle-orm": "0.41.0",
"postgres": "3.4.7",
"stripe": "18.0.0",
"ulid": "3.0.0"
},
"exports": {
"./*": "./src/*"
},
"scripts": {
"db": "sst shell drizzle-kit"
},
"devDependencies": {
"drizzle-kit": "0.30.5"
}
}
-162
View File
@@ -1,162 +0,0 @@
import { Stripe } from "stripe"
import { Database, eq, sql } from "./drizzle"
import { BillingTable, PaymentTable, UsageTable } from "./schema/billing.sql"
import { Actor } from "./actor"
import { fn } from "./util/fn"
import { z } from "zod"
import { Identifier } from "./identifier"
import { centsToMicroCents } from "./util/price"
import { User } from "./user"
import { Resource } from "@opencode/cloud-resource"
export namespace Billing {
export const stripe = () =>
new Stripe(Resource.STRIPE_SECRET_KEY.value, {
apiVersion: "2025-03-31.basil",
})
export const get = async () => {
return Database.use(async (tx) =>
tx
.select({
customerID: BillingTable.customerID,
paymentMethodID: BillingTable.paymentMethodID,
balance: BillingTable.balance,
reload: BillingTable.reload,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, Actor.workspace()))
.then((r) => r[0]),
)
}
export const payments = async () => {
return await Database.use((tx) =>
tx
.select()
.from(PaymentTable)
.where(eq(PaymentTable.workspaceID, Actor.workspace()))
.orderBy(sql`${PaymentTable.timeCreated} DESC`)
.limit(100),
)
}
export const usages = async () => {
return await Database.use((tx) =>
tx
.select()
.from(UsageTable)
.where(eq(UsageTable.workspaceID, Actor.workspace()))
.orderBy(sql`${UsageTable.timeCreated} DESC`)
.limit(100),
)
}
export const consume = fn(
z.object({
requestID: z.string().optional(),
model: z.string(),
inputTokens: z.number(),
outputTokens: z.number(),
reasoningTokens: z.number().optional(),
cacheReadTokens: z.number().optional(),
cacheWriteTokens: z.number().optional(),
costInCents: z.number(),
}),
async (input) => {
const workspaceID = Actor.workspace()
const cost = centsToMicroCents(input.costInCents)
return await Database.transaction(async (tx) => {
await tx.insert(UsageTable).values({
workspaceID,
id: Identifier.create("usage"),
requestID: input.requestID,
model: input.model,
inputTokens: input.inputTokens,
outputTokens: input.outputTokens,
reasoningTokens: input.reasoningTokens,
cacheReadTokens: input.cacheReadTokens,
cacheWriteTokens: input.cacheWriteTokens,
cost,
})
const [updated] = await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} - ${cost}`,
})
.where(eq(BillingTable.workspaceID, workspaceID))
.returning()
return updated.balance
})
},
)
export const generateCheckoutUrl = fn(
z.object({
successUrl: z.string(),
cancelUrl: z.string(),
}),
async (input) => {
const account = Actor.assert("user")
const { successUrl, cancelUrl } = input
const user = await User.fromID(account.properties.userID)
const customer = await Billing.get()
const session = await Billing.stripe().checkout.sessions.create({
mode: "payment",
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: "opencode credits",
},
unit_amount: 2000, // $20 minimum
},
quantity: 1,
},
],
payment_intent_data: {
setup_future_usage: "on_session",
},
...(customer.customerID
? { customer: customer.customerID }
: {
customer_email: user.email,
customer_creation: "always",
}),
metadata: {
workspaceID: Actor.workspace(),
},
currency: "usd",
payment_method_types: ["card"],
success_url: successUrl,
cancel_url: cancelUrl,
})
return session.url
},
)
export const generatePortalUrl = fn(
z.object({
returnUrl: z.string(),
}),
async (input) => {
const { returnUrl } = input
const customer = await Billing.get()
if (!customer?.customerID) {
throw new Error("No stripe customer ID")
}
const session = await Billing.stripe().billingPortal.sessions.create({
customer: customer.customerID,
return_url: returnUrl,
})
return session.url
},
)
}
-79
View File
@@ -1,79 +0,0 @@
import { z } from "zod"
import { fn } from "./util/fn"
import { Actor } from "./actor"
import { and, Database, eq, sql } from "./drizzle"
import { Identifier } from "./identifier"
import { KeyTable } from "./schema/key.sql"
export namespace Key {
export const list = async () => {
const user = Actor.assert("user")
const keys = await Database.use((tx) =>
tx
.select({
id: KeyTable.id,
name: KeyTable.name,
key: KeyTable.key,
userID: KeyTable.userID,
timeCreated: KeyTable.timeCreated,
timeUsed: KeyTable.timeUsed,
})
.from(KeyTable)
.where(eq(KeyTable.workspaceID, user.properties.workspaceID))
.orderBy(sql`${KeyTable.timeCreated} DESC`),
)
return keys
}
export const create = fn(z.object({ name: z.string().min(1).max(255) }), async (input) => {
const user = Actor.assert("user")
const { name } = input
// Generate secret key: sk- + 64 random characters (upper, lower, numbers)
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
let randomPart = ""
for (let i = 0; i < 64; i++) {
randomPart += chars.charAt(Math.floor(Math.random() * chars.length))
}
const secretKey = `sk-${randomPart}`
const keyRecord = await Database.use((tx) =>
tx
.insert(KeyTable)
.values({
id: Identifier.create("key"),
workspaceID: user.properties.workspaceID,
userID: user.properties.userID,
name,
key: secretKey,
timeUsed: null,
})
.returning(),
)
return {
key: secretKey,
id: keyRecord[0].id,
name: keyRecord[0].name,
created: keyRecord[0].timeCreated,
}
})
export const remove = fn(z.object({ id: z.string() }), async (input) => {
const user = Actor.assert("user")
const { id } = input
const result = await Database.use((tx) =>
tx
.delete(KeyTable)
.where(and(eq(KeyTable.id, id), eq(KeyTable.workspaceID, user.properties.workspaceID)))
.returning({ id: KeyTable.id }),
)
if (result.length === 0) {
throw new Error("Key not found")
}
return { id: result[0].id }
})
}
-45
View File
@@ -1,45 +0,0 @@
import { bigint, boolean, integer, pgTable, varchar } from "drizzle-orm/pg-core"
import { timestamps, workspaceColumns } from "../drizzle/types"
import { workspaceIndexes } from "./workspace.sql"
export const BillingTable = pgTable(
"billing",
{
...workspaceColumns,
...timestamps,
customerID: varchar("customer_id", { length: 255 }),
paymentMethodID: varchar("payment_method_id", { length: 255 }),
paymentMethodLast4: varchar("payment_method_last4", { length: 4 }),
balance: bigint("balance", { mode: "number" }).notNull(),
reload: boolean("reload"),
},
(table) => [...workspaceIndexes(table)],
)
export const PaymentTable = pgTable(
"payment",
{
...workspaceColumns,
...timestamps,
customerID: varchar("customer_id", { length: 255 }),
paymentID: varchar("payment_id", { length: 255 }),
amount: bigint("amount", { mode: "number" }).notNull(),
},
(table) => [...workspaceIndexes(table)],
)
export const UsageTable = pgTable(
"usage",
{
...workspaceColumns,
...timestamps,
model: varchar("model", { length: 255 }).notNull(),
inputTokens: integer("input_tokens").notNull(),
outputTokens: integer("output_tokens").notNull(),
reasoningTokens: integer("reasoning_tokens"),
cacheReadTokens: integer("cache_read_tokens"),
cacheWriteTokens: integer("cache_write_tokens"),
cost: bigint("cost", { mode: "number" }).notNull(),
},
(table) => [...workspaceIndexes(table)],
)
-16
View File
@@ -1,16 +0,0 @@
import { text, pgTable, varchar, uniqueIndex } from "drizzle-orm/pg-core"
import { timestamps, utc, workspaceColumns } from "../drizzle/types"
import { workspaceIndexes } from "./workspace.sql"
export const KeyTable = pgTable(
"key",
{
...workspaceColumns,
...timestamps,
userID: text("user_id").notNull(),
name: varchar("name", { length: 255 }).notNull(),
key: varchar("key", { length: 255 }).notNull(),
timeUsed: utc("time_used"),
},
(table) => [...workspaceIndexes(table), uniqueIndex("global_key").on(table.key)],
)
-14
View File
@@ -1,14 +0,0 @@
import { z } from "zod"
export function fn<T extends z.ZodType, Result>(
schema: T,
cb: (input: z.output<T>) => Result,
) {
const result = (input: z.input<T>) => {
const parsed = schema.parse(input)
return cb(parsed)
}
result.force = (input: z.input<T>) => cb(input)
result.schema = schema
return result
}
-11
View File
@@ -1,11 +0,0 @@
export function memo<T>(fn: () => T) {
let value: T | undefined
let loaded = false
return (): T => {
if (loaded) return value as T
loaded = true
value = fn()
return value as T
}
}
-596
View File
@@ -1,596 +0,0 @@
import { Hono, MiddlewareHandler } from "hono"
import { type ProviderMetadata, type LanguageModelUsage } from "ai"
import { createAnthropic } from "@ai-sdk/anthropic"
import { createOpenAI } from "@ai-sdk/openai"
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
import type { LanguageModelV2Prompt } from "@ai-sdk/provider"
import { type ChatCompletionCreateParamsBase } from "openai/resources/chat/completions"
import { Actor } from "@opencode/cloud-core/actor.js"
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
import { Billing } from "@opencode/cloud-core/billing.js"
import { Resource } from "@opencode/cloud-resource"
type Env = {}
const SUPPORTED_MODELS = {
"anthropic/claude-sonnet-4": {
input: 0.0000015,
output: 0.000006,
reasoning: 0.0000015,
cacheRead: 0.0000001,
cacheWrite: 0.0000001,
model: () =>
createAnthropic({
apiKey: Resource.ANTHROPIC_API_KEY.value,
})("claude-sonnet-4-20250514"),
},
"openai/gpt-4.1": {
input: 0.0000015,
output: 0.000006,
reasoning: 0.0000015,
cacheRead: 0.0000001,
cacheWrite: 0.0000001,
model: () =>
createOpenAI({
apiKey: Resource.OPENAI_API_KEY.value,
})("gpt-4.1"),
},
"zhipuai/glm-4.5-flash": {
input: 0,
output: 0,
reasoning: 0,
cacheRead: 0,
cacheWrite: 0,
model: () =>
createOpenAICompatible({
name: "Zhipu AI",
baseURL: "https://api.z.ai/api/paas/v4",
apiKey: Resource.ZHIPU_API_KEY.value,
})("glm-4.5-flash"),
},
}
const GatewayAuth: MiddlewareHandler = async (c, next) => {
const authHeader = c.req.header("authorization")
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return c.json(
{
error: {
message: "Missing API key.",
type: "invalid_request_error",
param: null,
code: "unauthorized",
},
},
401,
)
}
const apiKey = authHeader.split(" ")[1]
// Check against KeyTable
const keyRecord = await Database.use((tx) =>
tx
.select({
id: KeyTable.id,
workspaceID: KeyTable.workspaceID,
})
.from(KeyTable)
.where(eq(KeyTable.key, apiKey))
.then((rows) => rows[0]),
)
if (!keyRecord) {
return c.json(
{
error: {
message: "Invalid API key.",
type: "invalid_request_error",
param: null,
code: "unauthorized",
},
},
401,
)
}
c.set("keyRecord", keyRecord)
await next()
}
const app = new Hono<{ Bindings: Env; Variables: { keyRecord?: { id: string; workspaceID: string } } }>()
.get("/", (c) => c.text("Hello, world!"))
.post("/v1/chat/completions", GatewayAuth, async (c) => {
const keyRecord = c.get("keyRecord")!
return await Actor.provide("system", { workspaceID: keyRecord.workspaceID }, async () => {
try {
// Check balance
const customer = await Billing.get()
if (customer.balance <= 0) {
return c.json(
{
error: {
message: "Insufficient balance",
type: "insufficient_quota",
param: null,
code: "insufficient_quota",
},
},
401,
)
}
const body = await c.req.json<ChatCompletionCreateParamsBase>()
const model = SUPPORTED_MODELS[body.model as keyof typeof SUPPORTED_MODELS]?.model()
if (!model) throw new Error(`Unsupported model: ${body.model}`)
const requestBody = transformOpenAIRequestToAiSDK()
return body.stream ? await handleStream() : await handleGenerate()
async function handleStream() {
const result = await model.doStream({
...requestBody,
})
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
const id = `chatcmpl-${Date.now()}`
const created = Math.floor(Date.now() / 1000)
try {
for await (const chunk of result.stream) {
console.log("!!! CHUNK !!! : " + chunk.type)
switch (chunk.type) {
case "text-delta": {
const data = {
id,
object: "chat.completion.chunk",
created,
model: body.model,
choices: [
{
index: 0,
delta: {
content: chunk.delta,
},
finish_reason: null,
},
],
}
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
break
}
case "reasoning-delta": {
const data = {
id,
object: "chat.completion.chunk",
created,
model: body.model,
choices: [
{
index: 0,
delta: {
reasoning_content: chunk.delta,
},
finish_reason: null,
},
],
}
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
break
}
case "tool-call": {
const data = {
id,
object: "chat.completion.chunk",
created,
model: body.model,
choices: [
{
index: 0,
delta: {
tool_calls: [
{
index: 0,
id: chunk.toolCallId,
type: "function",
function: {
name: chunk.toolName,
arguments: chunk.input,
},
},
],
},
finish_reason: null,
},
],
}
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
break
}
case "error": {
const data = {
id,
object: "chat.completion.chunk",
created,
model: body.model,
choices: [
{
index: 0,
delta: {},
finish_reason: "stop",
},
],
error: {
message: typeof chunk.error === "string" ? chunk.error : chunk.error,
type: "server_error",
},
}
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
controller.enqueue(encoder.encode("data: [DONE]\n\n"))
controller.close()
break
}
case "finish": {
const data = {
id,
object: "chat.completion.chunk",
created,
model: body.model,
choices: [
{
index: 0,
delta: {},
finish_reason:
{
stop: "stop",
length: "length",
"content-filter": "content_filter",
"tool-calls": "tool_calls",
error: "stop",
other: "stop",
unknown: "stop",
}[chunk.finishReason] || "stop",
},
],
usage: {
prompt_tokens: chunk.usage.inputTokens,
completion_tokens: chunk.usage.outputTokens,
total_tokens: chunk.usage.totalTokens,
completion_tokens_details: {
reasoning_tokens: chunk.usage.reasoningTokens,
},
prompt_tokens_details: {
cached_tokens: chunk.usage.cachedInputTokens,
},
},
}
await trackUsage(body.model, chunk.usage, chunk.providerMetadata)
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
controller.enqueue(encoder.encode("data: [DONE]\n\n"))
controller.close()
break
}
//case "stream-start":
//case "response-metadata":
case "text-start":
case "text-end":
case "reasoning-start":
case "reasoning-end":
case "tool-input-start":
case "tool-input-delta":
case "tool-input-end":
case "raw":
default:
// Log unknown chunk types for debugging
console.warn(`Unknown chunk type: ${(chunk as any).type}`)
break
}
}
} catch (error) {
controller.error(error)
}
},
})
return new Response(stream, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
})
}
async function handleGenerate() {
const response = await model.doGenerate({
...requestBody,
})
await trackUsage(body.model, response.usage, response.providerMetadata)
return c.json({
id: `chatcmpl-${Date.now()}`,
object: "chat.completion" as const,
created: Math.floor(Date.now() / 1000),
model: body.model,
choices: [
{
index: 0,
message: {
role: "assistant" as const,
content: response.content?.find((c) => c.type === "text")?.text ?? "",
reasoning_content: response.content?.find((c) => c.type === "reasoning")?.text,
tool_calls: response.content
?.filter((c) => c.type === "tool-call")
.map((toolCall) => ({
id: toolCall.toolCallId,
type: "function" as const,
function: {
name: toolCall.toolName,
arguments: toolCall.input,
},
})),
},
finish_reason:
(
{
stop: "stop",
length: "length",
"content-filter": "content_filter",
"tool-calls": "tool_calls",
error: "stop",
other: "stop",
unknown: "stop",
} as const
)[response.finishReason] || "stop",
},
],
usage: {
prompt_tokens: response.usage?.inputTokens,
completion_tokens: response.usage?.outputTokens,
total_tokens: response.usage?.totalTokens,
completion_tokens_details: {
reasoning_tokens: response.usage?.reasoningTokens,
},
prompt_tokens_details: {
cached_tokens: response.usage?.cachedInputTokens,
},
},
})
}
function transformOpenAIRequestToAiSDK() {
const prompt = transformMessages()
const tools = transformTools()
return {
prompt,
maxOutputTokens: body.max_tokens ?? body.max_completion_tokens ?? undefined,
temperature: body.temperature ?? undefined,
topP: body.top_p ?? undefined,
frequencyPenalty: body.frequency_penalty ?? undefined,
presencePenalty: body.presence_penalty ?? undefined,
providerOptions: body.reasoning_effort
? {
anthropic: {
reasoningEffort: body.reasoning_effort,
},
}
: undefined,
stopSequences: (typeof body.stop === "string" ? [body.stop] : body.stop) ?? undefined,
responseFormat: (() => {
if (!body.response_format) return { type: "text" as const }
if (body.response_format.type === "json_schema")
return {
type: "json" as const,
schema: body.response_format.json_schema.schema,
name: body.response_format.json_schema.name,
description: body.response_format.json_schema.description,
}
if (body.response_format.type === "json_object") return { type: "json" as const }
throw new Error("Unsupported response format")
})(),
seed: body.seed ?? undefined,
tools: tools.tools,
toolChoice: tools.toolChoice,
}
function transformTools() {
const { tools, tool_choice } = body
if (!tools || tools.length === 0) {
return { tools: undefined, toolChoice: undefined }
}
const aiSdkTools = tools.map((tool) => {
return {
type: tool.type,
name: tool.function.name,
description: tool.function.description,
inputSchema: tool.function.parameters!,
}
})
let aiSdkToolChoice
if (tool_choice == null) {
aiSdkToolChoice = undefined
} else if (tool_choice === "auto") {
aiSdkToolChoice = { type: "auto" as const }
} else if (tool_choice === "none") {
aiSdkToolChoice = { type: "none" as const }
} else if (tool_choice === "required") {
aiSdkToolChoice = { type: "required" as const }
} else if (tool_choice.type === "function") {
aiSdkToolChoice = {
type: "tool" as const,
toolName: tool_choice.function.name,
}
}
return { tools: aiSdkTools, toolChoice: aiSdkToolChoice }
}
function transformMessages() {
const { messages } = body
const prompt: LanguageModelV2Prompt = []
for (const message of messages) {
switch (message.role) {
case "system": {
prompt.push({
role: "system",
content: message.content as string,
})
break
}
case "user": {
if (typeof message.content === "string") {
prompt.push({
role: "user",
content: [{ type: "text", text: message.content }],
})
} else {
const content = message.content.map((part) => {
switch (part.type) {
case "text":
return { type: "text" as const, text: part.text }
case "image_url":
return {
type: "file" as const,
mediaType: "image/jpeg" as const,
data: part.image_url.url,
}
default:
throw new Error(`Unsupported content part type: ${(part as any).type}`)
}
})
prompt.push({
role: "user",
content,
})
}
break
}
case "assistant": {
const content: Array<
| { type: "text"; text: string }
| {
type: "tool-call"
toolCallId: string
toolName: string
input: any
}
> = []
if (message.content) {
content.push({
type: "text",
text: message.content as string,
})
}
if (message.tool_calls) {
for (const toolCall of message.tool_calls) {
content.push({
type: "tool-call",
toolCallId: toolCall.id,
toolName: toolCall.function.name,
input: JSON.parse(toolCall.function.arguments),
})
}
}
prompt.push({
role: "assistant",
content,
})
break
}
case "tool": {
prompt.push({
role: "tool",
content: [
{
type: "tool-result",
toolName: "placeholder",
toolCallId: message.tool_call_id,
output: {
type: "text",
value: message.content as string,
},
},
],
})
break
}
default: {
throw new Error(`Unsupported message role: ${message.role}`)
}
}
}
return prompt
}
}
async function trackUsage(model: string, usage: LanguageModelUsage, providerMetadata?: ProviderMetadata) {
const modelData = SUPPORTED_MODELS[model as keyof typeof SUPPORTED_MODELS]
if (!modelData) throw new Error(`Unsupported model: ${model}`)
const inputTokens = usage.inputTokens ?? 0
const outputTokens = usage.outputTokens ?? 0
const reasoningTokens = usage.reasoningTokens ?? 0
const cacheReadTokens = usage.cachedInputTokens ?? 0
const cacheWriteTokens =
providerMetadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
// @ts-expect-error
providerMetadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
0
const inputCost = modelData.input * inputTokens
const outputCost = modelData.output * outputTokens
const reasoningCost = modelData.reasoning * reasoningTokens
const cacheReadCost = modelData.cacheRead * cacheReadTokens
const cacheWriteCost = modelData.cacheWrite * cacheWriteTokens
const costInCents = (inputCost + outputCost + reasoningCost + cacheReadCost + cacheWriteCost) * 100
await Billing.consume({
model,
inputTokens,
outputTokens,
reasoningTokens,
cacheReadTokens,
cacheWriteTokens,
costInCents,
})
await Database.use((tx) =>
tx
.update(KeyTable)
.set({ timeUsed: sql`now()` })
.where(eq(KeyTable.id, keyRecord.id)),
)
}
} catch (error: any) {
return c.json({ error: { message: error.message } }, 500)
}
})
})
.all("*", (c) => c.text("Not Found"))
export type ApiType = typeof app
export default app
-92
View File
@@ -1,92 +0,0 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
import "sst"
declare module "sst" {
export interface Resource {
"ANTHROPIC_API_KEY": {
"type": "sst.sst.Secret"
"value": string
}
"AUTH_API_URL": {
"type": "sst.sst.Linkable"
"value": string
}
"Console": {
"type": "sst.cloudflare.SolidStart"
"url": string
}
"DATABASE_PASSWORD": {
"type": "sst.sst.Secret"
"value": string
}
"DATABASE_USERNAME": {
"type": "sst.sst.Secret"
"value": string
}
"Database": {
"database": string
"host": string
"password": string
"port": number
"type": "sst.sst.Linkable"
"username": string
}
"GITHUB_APP_ID": {
"type": "sst.sst.Secret"
"value": string
}
"GITHUB_APP_PRIVATE_KEY": {
"type": "sst.sst.Secret"
"value": string
}
"GITHUB_CLIENT_ID_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
}
"GITHUB_CLIENT_SECRET_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
}
"GOOGLE_CLIENT_ID": {
"type": "sst.sst.Secret"
"value": string
}
"OPENAI_API_KEY": {
"type": "sst.sst.Secret"
"value": string
}
"STRIPE_SECRET_KEY": {
"type": "sst.sst.Secret"
"value": string
}
"STRIPE_WEBHOOK_SECRET": {
"type": "sst.sst.Linkable"
"value": string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
}
"ZHIPU_API_KEY": {
"type": "sst.sst.Secret"
"value": string
}
}
}
// cloudflare
import * as cloudflare from "@cloudflare/workers-types";
declare module "sst" {
export interface Resource {
"Api": cloudflare.Service
"AuthApi": cloudflare.Service
"AuthStorage": cloudflare.KVNamespace
"Bucket": cloudflare.R2Bucket
"GatewayApi": cloudflare.Service
}
}
import "sst"
export {}
-92
View File
@@ -1,92 +0,0 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
import "sst"
declare module "sst" {
export interface Resource {
"ANTHROPIC_API_KEY": {
"type": "sst.sst.Secret"
"value": string
}
"AUTH_API_URL": {
"type": "sst.sst.Linkable"
"value": string
}
"Console": {
"type": "sst.cloudflare.SolidStart"
"url": string
}
"DATABASE_PASSWORD": {
"type": "sst.sst.Secret"
"value": string
}
"DATABASE_USERNAME": {
"type": "sst.sst.Secret"
"value": string
}
"Database": {
"database": string
"host": string
"password": string
"port": number
"type": "sst.sst.Linkable"
"username": string
}
"GITHUB_APP_ID": {
"type": "sst.sst.Secret"
"value": string
}
"GITHUB_APP_PRIVATE_KEY": {
"type": "sst.sst.Secret"
"value": string
}
"GITHUB_CLIENT_ID_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
}
"GITHUB_CLIENT_SECRET_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
}
"GOOGLE_CLIENT_ID": {
"type": "sst.sst.Secret"
"value": string
}
"OPENAI_API_KEY": {
"type": "sst.sst.Secret"
"value": string
}
"STRIPE_SECRET_KEY": {
"type": "sst.sst.Secret"
"value": string
}
"STRIPE_WEBHOOK_SECRET": {
"type": "sst.sst.Linkable"
"value": string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
}
"ZHIPU_API_KEY": {
"type": "sst.sst.Secret"
"value": string
}
}
}
// cloudflare
import * as cloudflare from "@cloudflare/workers-types";
declare module "sst" {
export interface Resource {
"Api": cloudflare.Service
"AuthApi": cloudflare.Service
"AuthStorage": cloudflare.KVNamespace
"Bucket": cloudflare.R2Bucket
"GatewayApi": cloudflare.Service
}
}
import "sst"
export {}
+1 -1
View File
@@ -4,7 +4,7 @@
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest"
"@types/bun": "catalog:"
},
"peerDependencies": {
"typescript": "^5"
+1 -1
View File
@@ -6,4 +6,4 @@
/// <reference path="../sst-env.d.ts" />
import "sst"
export {}
export {}
+2 -2
View File
@@ -33,8 +33,8 @@ export const api = new sst.cloudflare.Worker("Api", {
},
})
export const web = new sst.cloudflare.x.Astro("Web", {
domain,
new sst.cloudflare.x.Astro("Web", {
domain: "docs." + domain,
path: "packages/web",
environment: {
// For astro config
+58 -40
View File
@@ -5,15 +5,38 @@ import { domain } from "./stage"
// DATABASE
////////////////
const DATABASE_USERNAME = new sst.Secret("DATABASE_USERNAME")
const DATABASE_PASSWORD = new sst.Secret("DATABASE_PASSWORD")
const cluster = planetscale.getDatabaseOutput({
name: "opencode",
organization: "anomalyco",
})
const branch =
$app.stage === "production"
? planetscale.getBranchOutput({
name: "production",
organization: cluster.organization,
database: cluster.name,
})
: new planetscale.Branch("DatabaseBranch", {
database: cluster.name,
organization: cluster.organization,
name: $app.stage,
parentBranch: "production",
})
const password = new planetscale.Password("DatabasePassword", {
name: $app.stage,
database: cluster.name,
organization: cluster.organization,
branch: branch.name,
})
export const database = new sst.Linkable("Database", {
properties: {
host: `aws-us-east-2-${$app.stage === "thdxr" ? "2" : "1"}.pg.psdb.cloud`,
database: "postgres",
username: DATABASE_USERNAME.value,
password: DATABASE_PASSWORD.value,
port: 6432,
host: password.accessHostUrl,
database: cluster.name,
username: password.username,
password: password.plaintext,
port: 3306,
},
})
@@ -21,7 +44,7 @@ new sst.x.DevCommand("Studio", {
link: [database],
dev: {
command: "bun db studio",
directory: "cloud/core",
directory: "packages/console/core",
autostart: true,
},
})
@@ -36,7 +59,7 @@ const GOOGLE_CLIENT_ID = new sst.Secret("GOOGLE_CLIENT_ID")
const authStorage = new sst.cloudflare.Kv("AuthStorage")
export const auth = new sst.cloudflare.Worker("AuthApi", {
domain: `auth.${domain}`,
handler: "cloud/function/src/auth.ts",
handler: "packages/console/function/src/auth.ts",
url: true,
link: [database, authStorage, GITHUB_CLIENT_ID_CONSOLE, GITHUB_CLIENT_SECRET_CONSOLE, GOOGLE_CLIENT_ID],
})
@@ -45,8 +68,8 @@ export const auth = new sst.cloudflare.Worker("AuthApi", {
// GATEWAY
////////////////
export const stripeWebhook = new WebhookEndpoint("StripeWebhook", {
url: $interpolate`https://console.${domain}/stripe/webhook`,
export const stripeWebhook = new WebhookEndpoint("StripeWebhookEndpoint", {
url: $interpolate`https://${domain}/stripe/webhook`,
enabledEvents: [
"checkout.session.async_payment_failed",
"checkout.session.async_payment_succeeded",
@@ -76,9 +99,7 @@ export const stripeWebhook = new WebhookEndpoint("StripeWebhook", {
],
})
const ANTHROPIC_API_KEY = new sst.Secret("ANTHROPIC_API_KEY")
const OPENAI_API_KEY = new sst.Secret("OPENAI_API_KEY")
const ZHIPU_API_KEY = new sst.Secret("ZHIPU_API_KEY")
const ZEN_MODELS = new sst.Secret("ZEN_MODELS")
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
properties: { value: auth.url.apply((url) => url!) },
@@ -86,40 +107,37 @@ const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
properties: { value: stripeWebhook.secret },
})
export const gateway = new sst.cloudflare.Worker("GatewayApi", {
domain: `api.gateway.${domain}`,
handler: "cloud/function/src/gateway.ts",
url: true,
link: [
database,
AUTH_API_URL,
STRIPE_WEBHOOK_SECRET,
STRIPE_SECRET_KEY,
ANTHROPIC_API_KEY,
OPENAI_API_KEY,
ZHIPU_API_KEY,
],
})
////////////////
// CONSOLE
////////////////
export const console = new sst.cloudflare.x.SolidStart("Console", {
domain: `console.${domain}`,
path: "cloud/app",
link: [
database,
AUTH_API_URL,
STRIPE_WEBHOOK_SECRET,
STRIPE_SECRET_KEY,
ANTHROPIC_API_KEY,
OPENAI_API_KEY,
ZHIPU_API_KEY,
],
let logProcessor
if ($app.stage === "production" || $app.stage === "frank") {
const HONEYCOMB_API_KEY = new sst.Secret("HONEYCOMB_API_KEY")
logProcessor = new sst.cloudflare.Worker("LogProcessor", {
handler: "packages/console/function/src/log-processor.ts",
link: [HONEYCOMB_API_KEY],
})
}
new sst.cloudflare.x.SolidStart("Console", {
domain,
path: "packages/console/app",
link: [database, AUTH_API_URL, STRIPE_WEBHOOK_SECRET, STRIPE_SECRET_KEY, ZEN_MODELS],
environment: {
//VITE_DOCS_URL: web.url.apply((url) => url!),
//VITE_API_URL: gateway.url.apply((url) => url!),
VITE_AUTH_URL: auth.url.apply((url) => url!),
},
transform: {
server: {
transform: {
worker: {
placement: { mode: "smart" },
tailConsumers: logProcessor ? [{ service: logProcessor.nodes.worker.scriptName }] : [],
},
},
},
},
})
+10
View File
@@ -0,0 +1,10 @@
import { domain } from "./stage"
new sst.cloudflare.StaticSite("Desktop", {
domain: "desktop." + domain,
path: "packages/app",
build: {
command: "bun turbo build",
output: "./dist",
},
})
+1 -8
View File
@@ -1,10 +1,3 @@
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"weather": {
"type": "local",
"command": ["opencode", "x", "@h1deya/mcp-server-weather"]
}
}
"$schema": "https://opencode.ai/config.json"
}
+13 -10
View File
@@ -3,27 +3,30 @@
"name": "opencode",
"private": true,
"type": "module",
"packageManager": "bun@1.2.19",
"packageManager": "bun@1.2.21",
"scripts": {
"dev": "bun run --conditions=development packages/opencode/src/index.ts",
"typecheck": "bun run --filter='*' typecheck",
"typecheck": "bun turbo typecheck",
"generate": "(cd packages/sdk && ./js/script/generate.ts) && (cd packages/sdk/stainless && ./generate.ts)",
"postinstall": "./script/hooks"
},
"workspaces": {
"packages": [
"cloud/*",
"packages/*",
"packages/console/*",
"packages/sdk/js"
],
"catalog": {
"@types/bun": "1.2.21",
"@hono/zod-validator": "0.4.2",
"@types/node": "22.13.9",
"@tsconfig/node22": "22.0.2",
"ai": "5.0.8",
"hono": "4.7.10",
"fuzzysort": "3.1.0",
"luxon": "3.6.1",
"typescript": "5.8.2",
"zod": "3.25.76",
"zod": "4.1.8",
"remeda": "2.26.0",
"solid-js": "1.9.9"
}
@@ -32,8 +35,9 @@
"pulumi-stripe": "0.0.24"
},
"devDependencies": {
"prettier": "3.5.3",
"sst": "3.17.12"
"prettier": "3.6.2",
"sst": "3.17.13",
"turbo": "2.5.6"
},
"repository": {
"type": "git",
@@ -52,8 +56,7 @@
"tree-sitter-bash",
"web-tree-sitter"
],
"overrides": {
"zod": "3.25.76"
},
"patchedDependencies": {}
"patchedDependencies": {
"@solidjs/start@1.1.7": "patches/@solidjs%2Fstart@1.1.7.patch"
}
}
+28
View File
@@ -0,0 +1,28 @@
# Agent Guidelines for @opencode/app
## Build/Test Commands
- **Development**: `bun run dev` (starts Vite dev server on port 3000)
- **Build**: `bun run build` (production build)
- **Preview**: `bun run serve` (preview production build)
- **Validation**: Use `bun run typecheck` only - do not build or run project for validation
- **Testing**: Do not create or run automated tests
## Code Style
- **Framework**: SolidJS with TypeScript
- **Imports**: Use `@/` alias for src/ directory (e.g., `import Button from "@/ui/button"`)
- **Formatting**: Prettier configured with semicolons disabled, 120 character line width
- **Components**: Use function declarations, splitProps for component props
- **Types**: Define interfaces for component props, avoid `any` type
- **CSS**: TailwindCSS with custom CSS variables theme system
- **Naming**: PascalCase for components, camelCase for variables/functions, snake_case for file names
- **File Structure**: UI primitives in `/ui/`, higher-level components in `/components/`, pages in `/pages/`, providers in `/providers/`
## Key Dependencies
- SolidJS, @solidjs/router, @kobalte/core (UI primitives)
- TailwindCSS 4.x with @tailwindcss/vite
- Custom theme system with CSS variables
No special rules files found.
+34
View File
@@ -0,0 +1,34 @@
## Usage
Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.
This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template.
```bash
$ npm install # or pnpm install or yarn install
```
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
## Available Scripts
In the project directory, you can run:
### `npm run dev` or `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
### `npm run build`
Builds the app for production to the `dist` folder.<br>
It correctly bundles Solid in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
## Deployment
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)
+24
View File
@@ -0,0 +1,24 @@
<!doctype html>
<html lang="en" class="h-full bg-background">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.svg" />
<link rel="stylesheet" href="/src/assets/theme.css" />
<title>opencode</title>
</head>
<body class="h-full overscroll-none select-none">
<script>
;(function () {
const savedTheme = localStorage.getItem("theme") || "opencode"
const savedDarkMode = localStorage.getItem("darkMode") !== "false"
document.documentElement.setAttribute("data-theme", savedTheme)
document.documentElement.setAttribute("data-dark", savedDarkMode.toString())
})()
</script>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="/src/index.tsx" type="module"></script>
</body>
</html>
+47
View File
@@ -0,0 +1,47 @@
{
"name": "@opencode/app",
"version": "0.11.0",
"description": "",
"type": "module",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
"typecheck": "tsc --noEmit"
},
"license": "MIT",
"devDependencies": {
"@tailwindcss/vite": "4.1.11",
"@types/luxon": "3.7.1",
"@types/node": "catalog:",
"typescript": "catalog:",
"vite": "^6.0.0",
"vite-plugin-icons-spritesheet": "3.0.1",
"vite-plugin-solid": "^2.11.6"
},
"dependencies": {
"@kobalte/core": "0.13.11",
"@opencode-ai/sdk": "workspace:*",
"@shikijs/transformers": "3.9.2",
"@solid-primitives/resize-observer": "2.1.3",
"@solid-primitives/scroll": "2.1.3",
"@solidjs/router": "0.15.3",
"@thisbeyond/solid-dnd": "0.7.5",
"diff": "8.0.2",
"fuzzysort": "catalog:",
"luxon": "catalog:",
"marked": "16.2.0",
"marked-shiki": "1.2.1",
"remeda": "catalog:",
"shiki": "3.9.2",
"solid-js": "catalog:",
"solid-list": "0.3.0",
"tailwindcss": "4.1.11",
"virtua": "0.42.3"
},
"prettier": {
"semi": false,
"printWidth": 120
}
}
+163
View File
@@ -0,0 +1,163 @@
import type { Plugin } from "vite"
import { readdir, readFile, writeFile } from "fs/promises"
import { join, resolve } from "path"
interface ThemeDefinition {
$schema?: string
defs?: Record<string, string>
theme: Record<string, any>
}
interface ResolvedThemeColor {
dark: string
light: string
}
class ColorResolver {
private colors: Map<string, any> = new Map()
private visited: Set<string> = new Set()
constructor(defs: Record<string, string> = {}, theme: Record<string, any> = {}) {
Object.entries(defs).forEach(([key, value]) => {
this.colors.set(key, value)
})
Object.entries(theme).forEach(([key, value]) => {
this.colors.set(key, value)
})
}
resolveColor(key: string, value: any): ResolvedThemeColor {
if (this.visited.has(key)) {
throw new Error(`Circular reference detected for color ${key}`)
}
this.visited.add(key)
try {
if (typeof value === "string") {
if (value === "none") return { dark: value, light: value }
if (value.startsWith("#")) {
return { dark: value.toUpperCase(), light: value.toUpperCase() }
}
const resolved = this.resolveReference(value)
return { dark: resolved, light: resolved }
}
if (typeof value === "object" && value !== null) {
const dark = this.resolveColorValue(value.dark || value.light || "#000000")
const light = this.resolveColorValue(value.light || value.dark || "#FFFFFF")
return { dark, light }
}
return { dark: "#000000", light: "#FFFFFF" }
} finally {
this.visited.delete(key)
}
}
private resolveColorValue(value: any): string {
if (typeof value === "string") {
if (value === "none") return value
if (value.startsWith("#")) {
return value.toUpperCase()
}
return this.resolveReference(value)
}
return value
}
private resolveReference(ref: string): string {
const colorValue = this.colors.get(ref)
if (colorValue === undefined) {
throw new Error(`Color reference '${ref}' not found`)
}
if (typeof colorValue === "string") {
if (colorValue === "none") return colorValue
if (colorValue.startsWith("#")) {
return colorValue.toUpperCase()
}
return this.resolveReference(colorValue)
}
return colorValue
}
}
function kebabCase(str: string): string {
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()
}
function parseTheme(themeData: ThemeDefinition): Record<string, ResolvedThemeColor> {
const resolver = new ColorResolver(themeData.defs, themeData.theme)
const colors: Record<string, ResolvedThemeColor> = {}
Object.entries(themeData.theme).forEach(([key, value]) => {
colors[key] = resolver.resolveColor(key, value)
})
return colors
}
async function loadThemes(): Promise<Record<string, Record<string, ResolvedThemeColor>>> {
const themesDir = resolve(__dirname, "../../tui/internal/theme/themes")
const files = await readdir(themesDir)
const themes: Record<string, Record<string, ResolvedThemeColor>> = {}
for (const file of files) {
if (!file.endsWith(".json")) continue
const themeName = file.replace(".json", "")
const themeData: ThemeDefinition = JSON.parse(await readFile(join(themesDir, file), "utf-8"))
themes[themeName] = parseTheme(themeData)
}
return themes
}
function generateCSS(themes: Record<string, Record<string, ResolvedThemeColor>>): string {
let css = `/* Auto-generated theme CSS - Do not edit manually */\n:root {\n`
const defaultTheme = themes["opencode"] || Object.values(themes)[0]
if (defaultTheme) {
Object.entries(defaultTheme).forEach(([key, color]) => {
const cssVar = `--theme-${kebabCase(key)}`
css += ` ${cssVar}: ${color.light};\n`
})
}
css += `}\n\n`
Object.entries(themes).forEach(([themeName, colors]) => {
css += `[data-theme="${themeName}"][data-dark="false"] {\n`
Object.entries(colors).forEach(([key, color]) => {
const cssVar = `--theme-${kebabCase(key)}`
css += ` ${cssVar}: ${color.light};\n`
})
css += `}\n\n`
css += `[data-theme="${themeName}"][data-dark="true"] {\n`
Object.entries(colors).forEach(([key, color]) => {
const cssVar = `--theme-${kebabCase(key)}`
css += ` ${cssVar}: ${color.dark};\n`
})
css += `}\n\n`
})
return css
}
export function generateThemeCSS(): Plugin {
return {
name: "generate-theme-css",
async buildStart() {
try {
console.log("Generating theme CSS...")
const themes = await loadThemes()
const css = generateCSS(themes)
const outputPath = resolve(__dirname, "../src/assets/theme.css")
await writeFile(outputPath, css)
console.log(`✅ Generated theme CSS with ${Object.keys(themes).length} themes`)
console.log(` Output: ${outputPath}`)
} catch (error) {
throw new Error(`Theme CSS generation failed: ${error}`)
}
},
}
}

Before

Width:  |  Height:  |  Size: 377 B

After

Width:  |  Height:  |  Size: 377 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#29b6f6" d="M21 16.5c0 .38-.21.71-.53.88l-7.9 4.44c-.16.12-.36.18-.57.18s-.41-.06-.57-.18l-7.9-4.44A.99.99 0 0 1 3 16.5v-9c0-.38.21-.71.53-.88l7.9-4.44c.16-.12.36-.18.57-.18s.41.06.57.18l7.9 4.44c.32.17.53.5.53.88zM12 4.15 6.04 7.5 12 10.85l5.96-3.35zM5 15.91l6 3.38v-6.71L5 9.21zm14 0v-6.7l-6 3.37v6.71z"/></svg>

After

Width:  |  Height:  |  Size: 385 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#0288d1" d="M2 10v12h14l14-12"/></svg>

After

Width:  |  Height:  |  Size: 110 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#ff5722" d="M13.295 11.033V7.65l2.126-2.136c.774-.763.919-1.981.377-2.929a2.38 2.38 0 0 0-2.068-1.217c-.203 0-.435.029-.619.087-1.044.28-1.749 1.246-1.749 2.33v3.13L8.327 9.98a5.75 5.75 0 0 0-1.208 6.214 5.62 5.62 0 0 0 4.243 3.432v.59a.5.5 0 0 1-.483.482h-1.45v1.934h1.45a2.43 2.43 0 0 0 2.416-2.417v-.483c1.962 0 4.02-1.856 4.02-4.591 0-2.223-1.855-4.108-4.02-4.108m0-7.249c0-.222.106-.396.31-.454a.47.47 0 0 1 .54.222.48.48 0 0 1-.077.59l-.773.83V3.785m-1.933 7.732c-.938.619-1.643 1.682-1.894 2.668l1.894.503v2.948a3.73 3.73 0 0 1-2.484-2.185 3.8 3.8 0 0 1 .802-4.098l1.682-1.769zm1.933 6.283v-4.89c1.13 0 2.107 1.062 2.107 2.232 0 1.691-1.227 2.658-2.107 2.658"/></svg>

After

Width:  |  Height:  |  Size: 746 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="#f44336" d="M560-160v-80h120q17 0 28.5-11.5T720-280v-80q0-38 22-69t58-44v-14q-36-13-58-44t-22-69v-80q0-17-11.5-28.5T680-720H560v-80h120q50 0 85 35t35 85v80q0 17 11.5 28.5T840-560h40v160h-40q-17 0-28.5 11.5T800-360v80q0 50-35 85t-85 35zm-280 0q-50 0-85-35t-35-85v-80q0-17-11.5-28.5T120-400H80v-160h40q17 0 28.5-11.5T160-600v-80q0-50 35-85t85-35h120v80H280q-17 0-28.5 11.5T240-680v80q0 38-22 69t-58 44v14q36 13 58 44t22 69v80q0 17 11.5 28.5T280-240h120v80z"/><path fill="#f44336" d="M360-600h80v40h-80zm80 240h40v-200h-40v80h-80v-80h-40v200h40v-80h80zm200-200v-40H530a10 10 0 0 0-10 10v100a10 10 0 0 0 10 10h70v80h-80v40h110a10 10 0 0 0 10-10v-140a10 10 0 0 0-10-10h-70v-40z"/></svg>

After

Width:  |  Height:  |  Size: 758 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#0277bd" d="m2 12 2.9-1.07c.25-1.1.87-1.73.87-1.73a3.996 3.996 0 0 1 5.65 0l1.41 1.41 6.31-6.7c.95 3.81 0 7.62-2.33 10.69L22 19.62s-8.47 1.9-13.4-1.95c-2.63-2.06-3.22-3.26-3.59-4.52zm5.04.21c.37.37.98.37 1.35 0s.37-.97 0-1.34a.96.96 0 0 0-1.35 0c-.37.37-.37.97 0 1.34"/></svg>

After

Width:  |  Height:  |  Size: 348 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="28" height="28" x="2" y="2" fill="#5d4037" rx="4"/><path fill="#ffb74d" d="M20.988 9.999a.96.96 0 0 1-.687-.269 1 1 0 0 1-.263-.704.9.9 0 0 1 .278-.681 1 1 0 0 1 .687-.268.93.93 0 0 1 .703.268 1.046 1.046 0 0 1-.015 1.385.9.9 0 0 1-.703.268M20 12h2v10h-2zm-5.63-1.98-.01-.02h-2.08a.12.12 0 0 0-.1.13 4.5 4.5 0 0 1-.06.74c-.05.13-.08.26-.12.37l-.27.78L8 22h2.14l.75-2h5.24l.79 2h2.16zM11.64 18l1.8-4.84.01.04.02.04L14.95 17l.39 1z"/></svg>

After

Width:  |  Height:  |  Size: 511 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="28" height="28" x="2" y="2" fill="#795548" rx="4"/><path fill="#ffb74d" d="M20.988 9.999a.96.96 0 0 1-.687-.269 1 1 0 0 1-.263-.704.9.9 0 0 1 .278-.681 1 1 0 0 1 .687-.268.93.93 0 0 1 .703.268 1.046 1.046 0 0 1-.015 1.385.9.9 0 0 1-.703.268M20 12h2v10h-2zm-5.63-1.98-.01-.02h-2.08a.12.12 0 0 0-.1.13 4.5 4.5 0 0 1-.06.74c-.05.13-.08.26-.12.37l-.27.78L8 22h2.14l.75-2h5.24l.79 2h2.16zM11.64 18l1.8-4.84.01.04.02.04L14.95 17l.39 1z"/></svg>

After

Width:  |  Height:  |  Size: 511 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="28" height="28" x="2" y="2" fill="#37474f" rx="4"/><path fill="#64b5f6" d="M23.744 14.716a3.7 3.7 0 0 0-1.066-.408 5.4 5.4 0 0 0-1.245-.157 2.1 2.1 0 0 0-.666.085.57.57 0 0 0-.345.24.7.7 0 0 0-.089.324.56.56 0 0 0 .111.313 1.3 1.3 0 0 0 .378.324q.386.217.79.397a7.8 7.8 0 0 1 1.71.877 2.7 2.7 0 0 1 .878.998 2.8 2.8 0 0 1 .256 1.238 2.96 2.96 0 0 1-.434 1.599 2.83 2.83 0 0 1-1.244 1.07 4.75 4.75 0 0 1-2.011.384 7 7 0 0 1-1.511-.156 4.2 4.2 0 0 1-1.134-.385.24.24 0 0 1-.122-.228v-2.092a.14.14 0 0 1 .044-.108c.034-.024.067-.012.1.012a4.6 4.6 0 0 0 1.378.59 4.8 4.8 0 0 0 1.311.18 2 2 0 0 0 .923-.169.56.56 0 0 0 .3-.505.65.65 0 0 0-.267-.48 4.6 4.6 0 0 0-1.089-.565 6.6 6.6 0 0 1-1.578-.866 3 3 0 0 1-.844-1.021 2.76 2.76 0 0 1-.256-1.226 3 3 0 0 1 .378-1.455 2.8 2.8 0 0 1 1.167-1.105A4 4 0 0 1 21.533 12a9 9 0 0 1 1.378.108 3.7 3.7 0 0 1 .956.277.2.2 0 0 1 .11.108.7.7 0 0 1 .023.144v1.96a.15.15 0 0 1-.056.12.28.28 0 0 1-.2 0M12.38 10H9.99v-.03h-2v12h2V18h2.39A3.62 3.62 0 0 0 16 14.38v-.76A3.62 3.62 0 0 0 12.38 10M14 14.38A1.626 1.626 0 0 1 12.38 16H9.99v-4h2.39A1.626 1.626 0 0 1 14 13.62Z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="28" height="28" x="2" y="2" fill="#455a64" rx="4"/><path fill="#64b5f6" d="M23.744 14.716a3.7 3.7 0 0 0-1.066-.408 5.4 5.4 0 0 0-1.245-.157 2.1 2.1 0 0 0-.666.085.57.57 0 0 0-.345.24.7.7 0 0 0-.089.324.56.56 0 0 0 .111.313 1.3 1.3 0 0 0 .378.324q.386.217.79.397a7.8 7.8 0 0 1 1.71.877 2.7 2.7 0 0 1 .878.998 2.8 2.8 0 0 1 .256 1.238 2.96 2.96 0 0 1-.434 1.599 2.83 2.83 0 0 1-1.244 1.07 4.75 4.75 0 0 1-2.011.384 7 7 0 0 1-1.511-.156 4.2 4.2 0 0 1-1.134-.385.24.24 0 0 1-.122-.228v-2.092a.14.14 0 0 1 .044-.108c.034-.024.067-.012.1.012a4.6 4.6 0 0 0 1.378.59 4.8 4.8 0 0 0 1.311.18 2 2 0 0 0 .923-.169.56.56 0 0 0 .3-.505.65.65 0 0 0-.267-.48 4.6 4.6 0 0 0-1.089-.565 6.6 6.6 0 0 1-1.578-.866 3 3 0 0 1-.844-1.021 2.76 2.76 0 0 1-.256-1.226 3 3 0 0 1 .378-1.455 2.8 2.8 0 0 1 1.167-1.105A4 4 0 0 1 21.533 12a9 9 0 0 1 1.378.108 3.7 3.7 0 0 1 .956.277.2.2 0 0 1 .11.108.7.7 0 0 1 .023.144v1.96a.15.15 0 0 1-.056.12.28.28 0 0 1-.2 0M12.38 10H9.99v-.03h-2v12h2V18h2.39A3.62 3.62 0 0 0 16 14.38v-.76A3.62 3.62 0 0 0 12.38 10M14 14.38A1.626 1.626 0 0 1 12.38 16H9.99v-4h2.39A1.626 1.626 0 0 1 14 13.62Z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#e53935" d="M4 5v22a1 1 0 0 0 1 1h22a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1m20 7c-2.926 0-4.21.722-5.012 2H22v4h-4.582C16.34 20.857 14.393 24 8 24v-4c4.559 0 5.14-1.744 6.103-4.632C15.139 12.258 16.559 8 24 8Z"/></svg>

After

Width:  |  Height:  |  Size: 297 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path fill="#7c4dff" d="m79.579 25.741-66.481 115.15h63.305l11.218-19.433H47.613L79.804 65.7l20.005 34.649 11.423-19.783zm42.118 50.221-45.203 78.297h90.408z" paint-order="fill markers stroke"/></svg>

After

Width:  |  Height:  |  Size: 262 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#7986cb" fill-rule="evenodd" d="M6.752 1.158C2.234 1.96-.271 6.943 1.758 11.09c2.537 5.185 10.047 5.142 12.511-.07C16.69 5.9 12.321.17 6.752 1.159m.587 2.335c2.576.517 5.233 1.323 5.326 1.615.26.808.256 4.849-.004 5.34-.066.125-1.209-.012-2.08-.247l-.351-.094-.001-.437c-.005-1.308-.138-2.547-.29-2.7-.176-.176-1.312-.545-3.052-.99L5.78 5.697l-.014-.267c-.033-.6.117-1.95.232-2.093.063-.079.315-.05 1.34.157M4.029 5.39c.5.066 1.083.178 1.492.289l.178.048.03.984c.058 1.844.117 2.13.475 2.29.448.2 2.083.679 3.62 1.061l.34.084-.01.653c-.012.735-.083 1.393-.175 1.617l-.062.15-.261-.03c-.976-.113-4.175-.896-5.567-1.362-.611-.205-.759-.284-.811-.435-.23-.66-.23-4.905 0-5.337.054-.1.08-.1.75-.012"/></svg>

After

Width:  |  Height:  |  Size: 775 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#ff9800" d="M14 10 5 28h12l-2-4h-4l3-6 5 10h4zm1-2 2-4 12 24h-4l-8-16z"/></svg>

After

Width:  |  Height:  |  Size: 151 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="4" height="10" x="2" y="12" fill="#8bc34a" rx="2"/><rect width="4" height="10" x="26" y="12" fill="#8bc34a" rx="2"/><path fill="#8bc34a" d="M8 12h16v12H8zm2 12h4v4a2 2 0 0 1-2 2 2 2 0 0 1-2-2zm8 0h4v4a2 2 0 0 1-2 2 2 2 0 0 1-2-2zm3.545-19.759 2.12-2.12A1 1 0 0 0 22.251.707l-2.326 2.326a7.97 7.97 0 0 0-7.85 0L9.75.707a1 1 0 1 0-1.414 1.414l2.12 2.12A7.97 7.97 0 0 0 8 10h16a7.97 7.97 0 0 0-2.455-5.759M14 8h-2V6h2Zm6 0h-2V6h2Z"/></svg>

After

Width:  |  Height:  |  Size: 509 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 24 24"><path fill="#e53935" d="M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z"/></svg>

After

Width:  |  Height:  |  Size: 263 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 24 24"><path fill="#f44336" d="M10.355 1.614a10.469 10.483 0 0 1 11.813 7.792 10.327 10.34 0 0 1-1.565 8.673 10.583 10.597 0 0 1-14.819 2.428 10.416 10.43 0 0 1-4.222-7.14 10.641 10.656 0 0 1 .999-5.994 10.498 10.512 0 0 1 7.795-5.76m.27 3.825c-.949 2.08-1.9 4.16-2.83 6.25-.479 1.345-1.127 2.615-1.716 3.915-.174.408-.468.853-.287 1.312a1.088 1.09 0 0 0 1.575.556c.458-.261.566-.828.778-1.272.952-2.405 2.13-4.708 3.11-7.104a7.356 7.366 0 0 1 .776-1.6c.568 1.406 1.186 2.791 1.773 4.19a14.819 14.839 0 0 1 .969 2.197c-1.51-.015-3.02-.004-4.531-.01 2.073 1.233 4.202 2.379 6.305 3.562a1.094 1.094 0 0 0 1.698-1.036c-.425-1.15-1.014-2.237-1.5-3.364-.917-2.393-2.076-4.685-3.097-7.036a2.685 2.689 0 0 0-.738-1.163 1.564 1.566 0 0 0-2.285.602z"/></svg>

After

Width:  |  Height:  |  Size: 823 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="12" height="12" x="10" y="2" fill="#42a5f5" rx="6"/><rect width="12" height="12" x="18" y="18" fill="#42a5f5" rx="6"/><rect width="12" height="12" x="2" y="18" fill="#42a5f5" rx="6"/><path fill="none" stroke="#42a5f5" stroke-miterlimit="10" stroke-width="3" d="m16 8 8 16M16 8 8 24"/></svg>

After

Width:  |  Height:  |  Size: 363 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#7e57c2" d="M31.93 14.457a.51.51 0 0 0-.506-.457h-2.01a.497.497 0 0 0-.491.559l.014.134c.616 6.284-4.097 12.817-10.29 14.044A13.009 13.009 0 1 1 24.3 6h4.19a16.013 16.013 0 1 0 3.44 8.457"/><circle cx="24.533" cy="4.267" r="4.267" fill="#7e57c2"/><path fill="#7e57c2" d="M17 8h-3L8 24h3z"/><path fill="#7e57c2" d="M15 8h3l6 16h-3zm2.88 13H12v-3h4.75z"/></svg>

After

Width:  |  Height:  |  Size: 431 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#78909c" d="M25.425 26.498c-1.162 1.736-2.394 3.43-4.27 3.458-1.875.042-2.477-1.106-4.605-1.106-2.142 0-2.8 1.078-4.578 1.148-1.834.07-3.22-1.848-4.396-3.542C5.183 23 3.35 16.63 5.813 12.346a6.84 6.84 0 0 1 5.767-3.514c1.792-.028 3.5 1.217 4.606 1.217 1.092 0 3.164-1.497 5.334-1.273a6.5 6.5 0 0 1 5.095 2.771 6.38 6.38 0 0 0-3.01 5.334 6.18 6.18 0 0 0 3.752 5.656 15.5 15.5 0 0 1-1.932 3.961M17.432 4.1A6.36 6.36 0 0 1 21.548 2a6.13 6.13 0 0 1-1.456 4.466 5.11 5.11 0 0 1-4.13 1.988 5.98 5.98 0 0 1 1.47-4.354"/></svg>

After

Width:  |  Height:  |  Size: 591 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#f44336" d="M6.053 20.055H21.21a3.01 3.01 0 0 1 3.049 2.966 3.01 3.01 0 0 1-3.049 2.966H6.053a3.01 3.01 0 0 1-3.049-2.966 3.01 3.01 0 0 1 3.049-2.966"/><path fill="#ffc107" d="M19.44 25.433 7.179 16.765a2.914 2.914 0 0 1-.674-4.143 3.104 3.104 0 0 1 4.258-.656l12.263 8.668a2.914 2.914 0 0 1 .674 4.143 3.104 3.104 0 0 1-4.258.656Z"/><path fill="#43a047" d="m19.489 8.05 4.683 14.026a2.95 2.95 0 0 1-1.957 3.737 3.067 3.067 0 0 1-3.841-1.904L13.69 9.884a2.95 2.95 0 0 1 1.957-3.738 3.067 3.067 0 0 1 3.842 1.905Z"/><path fill="#448aff" d="M18.363 22.076 23.047 8.05a3.067 3.067 0 0 1 3.841-1.904 2.95 2.95 0 0 1 1.958 3.737L24.162 23.91a3.067 3.067 0 0 1-3.842 1.904 2.95 2.95 0 0 1-1.957-3.737Z"/></svg>

After

Width:  |  Height:  |  Size: 776 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" viewBox="0 0 256 256"><path fill="#00b8d4" fill-rule="evenodd" d="M127.646 17.356c61.588 0 110.999 49.414 110.999 110.29a110.64 110.64 0 0 1-110.999 110.999c-60.873 0-110.29-49.414-110.29-110.999 0-60.873 49.414-110.29 110.29-110.29m27.213 131.77c-12.174 15.756-34.375 18.62-49.414 6.446-15.039-11.459-17.187-33.66-5.013-49.414 12.891-15.039 35.091-17.904 50.131-6.445 15.039 12.174 17.187 34.375 4.297 49.414zm-58.723 72.331 42.252-40.82c-15.756 3.58-32.227.716-45.117-10.026-15.039-11.459-21.484-30.795-19.336-48.699L35.98 163.45s-5.013-9.31-6.446-26.498l66.602-52.278c20.052-14.323 47.266-15.04 66.602 0 21.484 17.187 25.781 48.698 10.027 72.33l-48.699 69.466c-7.161 0-21.484-2.149-27.93-5.013"/></svg>

After

Width:  |  Height:  |  Size: 776 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#66bb6a" d="M6.278 22 6 19.556l3.167-8.723a4.37 4.37 0 0 0 1.944 1.056l-3.055 8.389zm11.666 0-1.777-1.722-3.056-8.39q.556-.138 1.042-.402a4.4 4.4 0 0 0 .903-.653l3.166 8.723zm-5.833-11.111q-1.389 0-2.361-.972-.972-.973-.972-2.361 0-1.084.624-1.932.626-.846 1.598-1.18V2h2.222v2.444a3.27 3.27 0 0 1 1.598 1.18q.624.849.624 1.932 0 1.389-.972 2.36-.972.973-2.36.973Zm0-2.222q.473 0 .792-.32t.32-.791q0-.473-.32-.793a1.08 1.08 0 0 0-.792-.319q-.472 0-.791.32t-.32.792.32.79q.319.32.791.32Z"/></svg>

After

Width:  |  Height:  |  Size: 579 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#0097a7" d="M2 14h10v2H2zm22-4h2v10h-2z"/><path fill="#0097a7" d="M20 14h10v2H20z"/><path fill="none" stroke="#0097a7" stroke-width="2" d="M2 5h4a10 10 0 0 1 10 10 10 10 0 0 0 10 10h4"/><path fill="#0097a7" d="M11.644 22A8.95 8.95 0 0 1 6 24H2v2h4a10.98 10.98 0 0 0 8.479-4ZM26 4a10.98 10.98 0 0 0-8.479 4h2.835A8.95 8.95 0 0 1 26 6h4V4Z"/></svg>

After

Width:  |  Height:  |  Size: 418 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#0097a7" d="M4 18V8l5.39 10Zm0 4v3.67A2.33 2.33 0 0 0 6.33 28h8.9l-3.496-6Zm12.444 0 3.177 5.444A11.88 11.88 0 0 0 26.448 22Zm11.419-4A15 15 0 0 0 28 16 12 12 0 0 0 16 4L6 3.995q-.08 0-.158.005L14 18Z"/></svg>

After

Width:  |  Height:  |  Size: 281 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#ff6e40" d="M8 6V2H4a2 2 0 0 0-2 2v24a2 2 0 0 0 2 2h4v-4H4V6Zm16-4v4h4v20h-4v4h4a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2Zm-4 4h-2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2m-2 6V8h2v4Zm-4 6h-2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2m-2 6v-4h2v4Zm0-18c0 2 0 2-2 2v2h2v4h2V6Zm8 12c0 2 0 2-2 2v2h2v4h2v-8Z"/></svg>

After

Width:  |  Height:  |  Size: 415 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#757575" d="M15 2H6a2.006 2.006 0 0 0-2 2v22a2.006 2.006 0 0 0 2 2h6v-4H6v-2h6v-2H6v-2h6v-2H6v-2h6v-2h2V4l8 8h2v-1Z"/><path fill="#7c4dff" d="M12 12v18h18V12Zm10 16c-.9 0-2.025-1.267-2.025-3.005-.914 0-.975.464-.975 1.005-.881-.213-1-1.15-1-2h6c0 1.919-2 1.787-2 4m2.542-6a2.5 2.5 0 0 1-2.308-1.641l-.946-2.42a.305.305 0 0 0-.576 0l-.946 2.42A2.5 2.5 0 0 1 17.458 22H16l2.965-7.59a.63.63 0 0 1 .577-.41h2.916a.63.63 0 0 1 .577.41L26 22Z"/></svg>

After

Width:  |  Height:  |  Size: 517 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#7c4dff" d="M12.106 25.849c-1.262-1.156-1.63-3.586-1.105-5.346a5.18 5.18 0 0 0 3.484 1.66 9.68 9.68 0 0 0 5.882-.734c.215-.106.413-.247.648-.39a3.5 3.5 0 0 1 .16 1.555 4.26 4.26 0 0 1-1.798 3.021c-.404.3-.832.569-1.25.852a2.613 2.613 0 0 0-1.15 3.372l.048.161a3.4 3.4 0 0 1-1.5-1.285 3.6 3.6 0 0 1-.578-1.962 9 9 0 0 0-.05-1.037c-.114-.831-.504-1.204-1.238-1.225a1.45 1.45 0 0 0-1.507 1.18c-.012.056-.028.112-.046.178M4.901 20a17.75 17.75 0 0 1 7.4-2l2.913-8.38a.765.765 0 0 1 1.527 0L19.7 18a14.24 14.24 0 0 1 7.399 2S20.704 2.877 20.692 2.842C20.51 2.33 20.202 2 19.787 2h-7.619c-.415 0-.71.33-.904.842z"/></svg>

After

Width:  |  Height:  |  Size: 686 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#ef5350" d="M8.203 5.447 5.83 6.777l1.329-2.374-1.33-2.374 2.374 1.33 2.374-1.33-1.33 2.374 1.33 2.374zm11.394 9.305 2.374-1.329-1.33 2.374 1.33 2.373-2.374-1.329-2.374 1.33 1.33-2.374-1.33-2.374zm2.374-12.724-1.33 2.374 1.33 2.374-2.374-1.33-2.374 1.33 1.33-2.374-1.33-2.374 2.374 1.33zm-8.223 10.236 2.317-2.316-2.013-2.013-2.317 2.317zm.978-5.212 2.222 2.222c.37.35.37.968 0 1.338L5.867 21.694c-.37.37-.987.37-1.339 0l-2.222-2.221c-.37-.352-.37-.969 0-1.34l11.081-11.08c.37-.37.988-.37 1.34 0z"/></svg>

After

Width:  |  Height:  |  Size: 577 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#ef5350" d="M16 2a14 14 0 1 0 14 14A14 14 0 0 0 16 2m6 10h-4v8a4 4 0 1 1-4-4 3.96 3.96 0 0 1 2 .555V8h6Z"/></svg>

After

Width:  |  Height:  |  Size: 185 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><defs><linearGradient xlink:href="#a" id="i" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse"/><linearGradient id="a" x1="-3.881" x2="2.377" y1="-1.442" y2="4.304"><stop offset="0" stop-color="#ba68c8"/><stop offset="1" stop-color="#7e57c2"/></linearGradient><linearGradient xlink:href="#b" id="j" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse"/><linearGradient id="b" x1=".729" x2="-.971" y1=".844" y2="-1.477"><stop offset="0" stop-color="#5e35b1"/><stop offset=".14" stop-color="#8e24aa"/><stop offset=".29" stop-color="#ad1457"/><stop offset=".84" stop-color="#c2185b"/><stop offset="1" stop-color="#ec407a"/></linearGradient><linearGradient xlink:href="#c" id="k" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse"/><linearGradient id="c" x1="-2.839" x2="2.875" y1="-6.936" y2="1.017"><stop offset="0" stop-color="#ba68c8"/><stop offset="1" stop-color="#7e57c2"/></linearGradient><linearGradient xlink:href="#d" id="l" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse"/><linearGradient id="d" x1="-8.212" x2="1.02" y1="-4.691" y2="2.882"><stop offset="0" stop-color="#ba68c8"/><stop offset="1" stop-color="#7e57c2"/></linearGradient><linearGradient xlink:href="#e" id="m" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse"/><linearGradient id="e" x1="-1.404" x2="4.19" y1="-2.309" y2="2.62"><stop offset="0" stop-color="#ba68c8"/><stop offset="1" stop-color="#7e57c2"/></linearGradient><linearGradient xlink:href="#f" id="n" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse"/><linearGradient id="f" x1="1.911" x2=".204" y1="2.539" y2=".204"><stop offset="0" stop-color="#7e57c2"/><stop offset=".14" stop-color="#7b1fa2"/><stop offset=".29" stop-color="#ad1457"/><stop offset=".84" stop-color="#c2185b"/><stop offset="1" stop-color="#ec407a"/></linearGradient><linearGradient xlink:href="#g" id="o" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse"/><linearGradient id="g" x1="-3.881" x2="2.377" y1="-1.738" y2="5.19"><stop offset="0" stop-color="#ba68c8"/><stop offset="1" stop-color="#7e57c2"/></linearGradient><linearGradient xlink:href="#h" id="p" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse"/><linearGradient id="h" x1=".112" x2=".901" y1=".897" y2=".116"><stop offset="0" stop-color="#7e57c2"/><stop offset=".14" stop-color="#8e24aa"/><stop offset=".53" stop-color="#c2185b"/><stop offset=".79" stop-color="#c2185b"/><stop offset="1" stop-color="#ec407a"/></linearGradient></defs><g stroke-linejoin="round" stroke-miterlimit="1.414" clip-rule="evenodd"><path fill="url(#i)" d="M8.002 6.127 4.117 8.719.116 2.723 4 .13z" transform="translate(11.282 3.07)scale(.47102)"/><path fill="url(#j)" d="m9.179 1.887 6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="translate(12.215 13.552)scale(.47102)"/><path fill="url(#k)" d="m7.3 1.88 1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="translate(8.41 16.686)scale(.47102)"/><path fill="url(#l)" d="M2.328 1.146 4.016.02l2.619 3.925L2.75 6.537l-1.46-2.19 2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="translate(16.99 11.686)scale(.47102)"/><path fill="url(#m)" d="m5.346 9.155-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="translate(2.738 8.18)scale(.47102)"/><path fill="url(#n)" d="m14.533 9.934 1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="translate(4.753 2.36)scale(.47102)"/><path fill="url(#o)" d="M6.235 7.177 4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="translate(11.32 3.106)scale(.47102)"/><path fill="#673ab7" d="m9.632 19.05-.545-.818 2.215-1.478.546.817zm7.965-5.315-.545-.817 1.035-.691.545.817z"/><path fill="#7e57c2" d="m5.256 12.492-.564-.845 2.216-1.478.563.845zm7.965-5.315-.564-.845 1.035-.69.564.844z"/><path fill="#880e4f" d="m16.538 14.441-3.724 2.485-.545-.817 3.724-2.485z"/><path fill="#ad1457" d="m11.598 7.039.564.844-3.724 2.485-.564-.844z"/><path fill="#ab47bc" d="m4.2 6.363.703 1.054-1.053.702-.703-1.053z"/><path fill="#7e57c2" d="m7.996 18.99.703 1.054-1.054.703-.702-1.054z"/><path fill="url(#p)" d="M8.372 38.294.017 29.876 29.749.08l8.636 8.201z" transform="rotate(11.282 -5.61 25.53)scale(.47102)"/></g></svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#f44336" d="M15.787 13.71c-.275 0-.587 0-.918.047 1.098.796 1.865 1.847 1.865 3.267v2.367h5.68v-2.367c0-2.206-4.42-3.314-6.627-3.314m-7.575 0c-2.206 0-6.628 1.108-6.628 3.314v2.367H14.84v-2.367c0-2.206-4.421-3.314-6.628-3.314m0-1.894a2.84 2.84 0 0 0 2.841-2.84 2.84 2.84 0 0 0-2.84-2.84 2.84 2.84 0 0 0-2.841 2.84 2.84 2.84 0 0 0 2.84 2.84m7.575 0a2.84 2.84 0 0 0 2.84-2.84 2.84 2.84 0 0 0-2.84-2.84 2.84 2.84 0 0 0-2.84 2.84 2.84 2.84 0 0 0 2.84 2.84"/></svg>

After

Width:  |  Height:  |  Size: 532 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#ffc400" d="M8.48 4.17c.334.574 1.047.798 1.696.636A7.5 7.5 0 0 1 12 4.583c.62 0 1.223.075 1.799.217.647.159 1.357-.065 1.691-.64.39-.668.116-1.532-.63-1.751A10.1 10.1 0 0 0 12 2c-1.006 0-1.977.146-2.894.419-.743.22-1.015 1.083-.627 1.75Z"/><path fill="#ad1457" d="M5.039 4.772c.564-.535 1.458-.34 1.848.331.333.572.176 1.292-.284 1.769a7.4 7.4 0 0 0-1.456 2.17c-.242.552-.762.958-1.367.958-.854 0-1.496-.781-1.191-1.572a10 10 0 0 1 2.45-3.656"/><path fill="#cfd8dc" d="M3.197 12c.718 0 1.32.583 1.444 1.286.613 3.483 3.675 6.13 7.359 6.13s6.746-2.647 7.359-6.13c.124-.703.726-1.286 1.444-1.286.719 0 1.279.581 1.187 1.289C21.353 18.203 17.123 22 12 22s-9.353-3.797-9.99-8.711C1.918 12.58 2.478 12 3.197 12"/><path fill="#ff5252" d="M20.203 9.958c.857 0 1.5-.786 1.19-1.578a10 10 0 0 0-2.458-3.632c-.564-.533-1.455-.336-1.845.333-.333.573-.174 1.295.289 1.772a7.4 7.4 0 0 1 1.459 2.155c.243.548.762.95 1.365.95"/><path fill="#cfd8dc" d="M7.133 9.32c-.442-.488.053-1.262.657-1.027l4.912 1.91c1.114.434 1.538 1.84.862 2.855a1.785 1.785 0 0 1-2.83.222l-3.6-3.96Z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#ffc400"
d="M8.48 4.17c.334.574 1.047.798 1.696.636A7.5 7.5 0 0 1 12 4.583c.62 0 1.223.075 1.799.217.647.159 1.357-.065 1.691-.64.39-.668.116-1.532-.63-1.751A10.1 10.1 0 0 0 12 2c-1.006 0-1.977.146-2.894.419-.743.22-1.015 1.083-.627 1.75Z" />
<path fill="#ad1457"
d="M5.039 4.772c.564-.535 1.458-.34 1.848.331.333.572.176 1.292-.284 1.769a7.4 7.4 0 0 0-1.456 2.17c-.242.552-.762.958-1.367.958-.854 0-1.496-.781-1.191-1.572a10 10 0 0 1 2.45-3.656" />
<path fill="currentColor"
d="M3.197 12c.718 0 1.32.583 1.444 1.286.613 3.483 3.675 6.13 7.359 6.13s6.746-2.647 7.359-6.13c.124-.703.726-1.286 1.444-1.286.719 0 1.279.581 1.187 1.289C21.353 18.203 17.123 22 12 22s-9.353-3.797-9.99-8.711C1.918 12.58 2.478 12 3.197 12" />
<path fill="#ff5252"
d="M20.203 9.958c.857 0 1.5-.786 1.19-1.578a10 10 0 0 0-2.458-3.632c-.564-.533-1.455-.336-1.845.333-.333.573-.174 1.295.289 1.772a7.4 7.4 0 0 1 1.459 2.155c.243.548.762.95 1.365.95" />
<path fill="currentColor"
d="M7.133 9.32c-.442-.488.053-1.262.657-1.027l4.912 1.91c1.114.434 1.538 1.84.862 2.855a1.785 1.785 0 0 1-2.83.222l-3.6-3.96Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#4caf50" d="M25.333 4H6.667A2.657 2.657 0 0 0 4 6.667v18.666A2.667 2.667 0 0 0 6.667 28h18.666A2.667 2.667 0 0 0 28 25.333V6.667A2.667 2.667 0 0 0 25.333 4m-2.495 6.22a4 4 0 0 0-.163 1.01q0 .266-.058.83a9 9 0 0 0-.04.719c0 .584-.031 1.443-.092 2.55q-.074 1.253-.088 2.502c0 .412.032 1.057.097 1.91.067.865.1 1.534.1 1.988a1.62 1.62 0 0 1-.505 1.197 1.65 1.65 0 0 1-1.225.475 1.92 1.92 0 0 1-1.233-.466 1.51 1.51 0 0 1-.554-1.19q0-.644-.06-1.934-.047-.99-.056-1.979 0-.198.003-.376c-.805.065-1.766.198-2.867.398q-1.522.277-3.045.562-.032.61-.11 1.65a30 30 0 0 0-.087 2.017 1.62 1.62 0 0 1-.506 1.192 1.73 1.73 0 0 1-1.224.474l-.048.001a1.7 1.7 0 0 1-1.157-.479 1.62 1.62 0 0 1-.502-1.2c0-.615.05-1.513.155-2.738.104-1.182.157-2.077.157-2.661q0-1.15.057-3.46.054-2.302.053-3.442a1.62 1.62 0 0 1 .508-1.196 1.68 1.68 0 0 1 1.222-.478 1.7 1.7 0 0 1 1.206.484 1.63 1.63 0 0 1 .5 1.19q0 .687-.055 2.07-.036 1.01-.044 2.023.001.23-.065.905a7 7 0 0 0-.022.251l2.825-.532a28 28 0 0 1 3.086-.395q.037-.83.095-2.76a4.8 4.8 0 0 1 .466-1.778c.421-.926.957-1.395 1.591-1.395a1.75 1.75 0 0 1 1.166.434l.003.002a1.58 1.58 0 0 1 .566 1.228 1.5 1.5 0 0 1-.05.397"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#1976d2" d="M12.002 2a10 10 0 0 0-10 10 10 10 0 0 0 10 10 10 10 0 0 0 10-10 10 10 0 0 0-10-10m.139 4.419q.642 0 1.07.294.431.294.731.731l5.71 8.262H9.026l1.707-2.35h3.15q.443 0 .77.028a11 11 0 0 1-.443-.62q-.253-.376-.485-.704l-1.64-2.417-4.29 6.063H4.45l5.86-8.262q.285-.396.723-.71.437-.315 1.108-.315"/></svg>

After

Width:  |  Height:  |  Size: 384 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#64b5f6" d="M3.98 22.01h1.803v4.208H9.99v1.803H3.98Z"/><path fill="#1565c0" d="M3.98 10.991v5.51l3.505 3.61 1.503-1.606 4.508 4.508-1.502 1.502 3.506 3.506h5.51a1 1 0 0 0 1.001-1.002v-8.014L12.995 9.99H4.982a1 1 0 0 0-1.003 1.001Z"/><path fill="#1e88e5" d="M8.317 18.44a1 1 0 0 1-.125-1.265L16.407 4.87a2 2 0 0 1 1.666-.891h8.946A1 1 0 0 1 28.02 4.98v8.946a2 2 0 0 1-.891 1.667l-12.305 8.215a1 1 0 0 1-1.265-.126Z"/><path fill="#64b5f6" d="m8.976 21.542 7.648-7.648 1.48 1.481-7.647 7.648Z"/><path fill="#42a5f5" d="m11.68 21.801-1.481-1.48 6.426-6.427 1.48 1.481Z"/><path fill="#90caf9" d="M22.011 12.995a3.006 3.006 0 0 0 .096-6.011h-.096a3.006 3.006 0 0 0 0 6.01Z"/></svg>

After

Width:  |  Height:  |  Size: 747 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#01579b" d="M12.001 4h7.102l-7.372 23.181a1.14 1.14 0 0 1-1.073.819H5.13A1.166 1.166 0 0 1 4 26.801a1.3 1.3 0 0 1 .06-.385l6.87-21.599A1.14 1.14 0 0 1 12.001 4"/><path fill="#1976d2" d="M22.32 20H11.06a.537.537 0 0 0-.522.55.57.57 0 0 0 .166.408l7.236 6.716a1.1 1.1 0 0 0 .775.325h6.376Z"/><path fill="#29b6f6" d="M21.071 4.816A1.14 1.14 0 0 0 20.001 4h-7.915a1.14 1.14 0 0 1 1.072.815l6.868 21.599a1.22 1.22 0 0 1-.71 1.52 1.1 1.1 0 0 1-.362.064h7.915A1.166 1.166 0 0 0 28 26.8a1.3 1.3 0 0 0-.06-.385L21.072 4.817Z"/></svg>

After

Width:  |  Height:  |  Size: 596 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#fdd835" d="M18.23 11.21q-.045-.24-1.32-1.65c-.02-.19.29-.45.9-.8l1.74-1.55c.39-.5.62-1.28.69-2.38l-.02-.26c-.07-.78-.63-1.4-1.69-1.89-.63-.42-1.76-.65-3.38-.68-1.35.11-3.11.59-5.28 1.43-.6.43-1.28.86-2.04 1.28l.01.14.21-.08c.08-.01.13.03.14.11l.13-.07.07-.01.01.06c0 .07-.47.44-1.76 1.35l-.06.12c-.31.02-.61.25-.91.67l.08.12.25-.09.18.24c.32-.33.66-.62 1.03-.87.19.05.29.11.44.16 1.02-.75 2.03-1.3 3.04-1.64l.01.14c-.2.27-.32.42-.38.42l.1.23c.01.19-2.55 7-6.66 14.44l.08.19c.35-.08.58-.17.75-.26l.01.13.4-.03-.67 1.76.14.06c.57-.64 1-1.29 1.3-1.88 1.67-.49 2.94-.97 3.82-1.44.88-.08 1.56-.31 2.02-.7l.92-.47c1.27-.98 2.22-1.67 2.87-2.08 1.33-.98 2.2-1.93 2.6-2.85zm-3.46 2.31L13 14.91c-1.29.85-2 1.3-2.09 1.3-2.07 1.13-3.36 1.72-3.86 1.76l-.05.01c.04-.23.96-2.12 2.75-5.67.78-.06 2.02-.43 3.71-1.1l.41-.03c.85-.08 1.49.09 1.91.49l.03.26c-.31.9-.67 1.44-1.04 1.59m1.09-5.78q-.27.33-1.5 1.11c-.27.03-1.27.42-3.01 1.18l-.28-.05-.01-.12c-.02-.25.09-.57.34-.95.13-.7.28-1.12.44-1.2l1.45-3.28c-.02-.22.29-.35.93-.46l.21-.02.01.18 1.16-.16c1.15-.1 1.75.14 1.8.7l.13-.02-.03-.32.15-.02c.35.19.52.4.54.68.02.18-.08.41-.29.68-.09.01-.14-.06-.15-.18l-.14.01-.03.4c-.58.87-1.01 1.31-1.27 1.34z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#00bfa5" d="m14 12-6-2V2h6Zm-6 0 4 2.058L8 16Zm0 18V18l6-2v4l-2 10Zm10-18 6-2V2h-6Zm6 0-4 2.058L24 16Zm0 18V18l-6-2v4l2 10Z"/></svg>

After

Width:  |  Height:  |  Size: 204 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#81c784" d="m153.491 50.983 102.508 102.508-102.508 102.508L50.983 153.491z"/><path fill="#43a047" d="M50.983 153.491v102.508l102.508 102.508V255.999z"/><path fill="#81c784" d="m358.507 50.983 102.508 102.508-102.508 102.508-102.508-102.508z"/><path fill="#43a047" d="M461.015 153.491v102.508L358.507 358.507V255.999zm-205.016 0 102.508 102.508-102.508 102.508-102.508-102.508z"/><path fill="#2e7d32" d="M255.999 358.507v102.508L153.491 358.507V255.999z"/><path fill="#1b5e20" d="m255.999 358.507 102.508-102.508v102.508L255.999 461.015z"/></svg>

After

Width:  |  Height:  |  Size: 620 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="#c62828" d="M128 704v128c0 70.692 57.308 128 128 128h608c17.728 0 32-14.272 32-32V704z"/><path fill="#ffe082" d="M704 704v192h128V704z"/><path fill="#fff8e1" d="M192 704v96c0 53.184 42.816 96 96 96h544a96 96 0 0 1-96-96 96 96 0 0 1 96-96z"/><path fill="#ff1744" d="M320 832h192v192l-96-96-96 96z"/><path fill="#f44336" d="M256 64c-70.692 0-128 57.308-128 128v640c0 11.088 1.557 21.787 4.207 32.047C146.767 807.565 197.672 768.07 256 768h608c17.728 0 32-14.272 32-32V96c0-17.728-14.272-32-32-32z"/><path fill="#ffeb3b" d="M256 192c-70.912 0-128 57.088-128 128v64c0-70.912 57.088-128 128-128h448v320H256c-70.912 0-128 57.088-128 128v64c0-70.912 57.088-128 128-128h512V192z"/></svg>

After

Width:  |  Height:  |  Size: 755 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#e64a19" d="M26.471 5.736c7.383 3.577 2.04 13.636-5.547 17.984-5.998 3.44-18.128 5.76-18.877-2.22-.738-7.863 7.61-6.698 11.575-8.67 4.032-2.003 6.854-9.998 12.85-7.093zm-11.684 8.89c-1.167.438-3.695.194-3.479 2.094.215 1.932 3.483.908 5.243.097 1.788-.82 3.415-2.475 2.27-3.496-1.424-1.268-2.421.698-4.034 1.305"/></svg>

After

Width:  |  Height:  |  Size: 392 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#ffca28" d="M6.915 9.906q.42.413 1.084.404t.98-.472l3.92-5.775-5.88 3.85q-.472.309-.498.945t.394 1.048M7.999 2q1.033 0 1.987.284.953.283 1.793.85l-1.33.825q-.577-.292-1.198-.438-.622-.146-1.252-.146-2.327 0-3.963 1.607T2.4 8.875q0 .722.201 1.427.201.704.569 1.323h9.659q.402-.653.586-1.358t.184-1.46q0-.62-.149-1.204t-.446-1.134l.84-1.307q.525.808.831 1.72.306.91.324 1.89.017.98-.228 1.873-.245.894-.717 1.702-.193.31-.525.481-.333.172-.7.172h-9.66q-.367 0-.7-.172t-.524-.481q-.455-.774-.7-1.642T1 8.875q0-1.427.551-2.673T3.056 4.02q.954-.937 2.231-1.479Q6.565 2 8 2zm.123 5.38"/></svg>

After

Width:  |  Height:  |  Size: 659 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#00bcd4" d="M6.915 9.906q.42.413 1.084.404t.98-.472l3.92-5.775-5.88 3.85q-.472.309-.498.945t.394 1.048M7.999 2q1.033 0 1.987.284.953.283 1.793.85l-1.33.825q-.577-.292-1.198-.438-.622-.146-1.252-.146-2.327 0-3.963 1.607T2.4 8.875q0 .722.201 1.427.201.704.569 1.323h9.659q.402-.653.586-1.358t.184-1.46q0-.62-.149-1.204t-.446-1.134l.84-1.307q.525.808.831 1.72.306.91.324 1.89.017.98-.228 1.873-.245.894-.717 1.702-.193.31-.525.481-.333.172-.7.172h-9.66q-.367 0-.7-.172t-.524-.481q-.455-.774-.7-1.642T1 8.875q0-1.427.551-2.673T3.056 4.02q.954-.937 2.231-1.479Q6.565 2 8 2zm.123 5.38"/></svg>

After

Width:  |  Height:  |  Size: 659 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#0288d1" d="M6.915 9.906q.42.413 1.084.404t.98-.472l3.92-5.775-5.88 3.85q-.472.309-.498.945t.394 1.048M7.999 2q1.033 0 1.987.284.953.283 1.793.85l-1.33.825q-.577-.292-1.198-.438-.622-.146-1.252-.146-2.327 0-3.963 1.607T2.4 8.875q0 .722.201 1.427.201.704.569 1.323h9.659q.402-.653.586-1.358t.184-1.46q0-.62-.149-1.204t-.446-1.134l.84-1.307q.525.808.831 1.72.306.91.324 1.89.017.98-.228 1.873-.245.894-.717 1.702-.193.31-.525.481-.333.172-.7.172h-9.66q-.367 0-.7-.172t-.524-.481q-.455-.774-.7-1.642T1 8.875q0-1.427.551-2.673T3.056 4.02q.954-.937 2.231-1.479Q6.565 2 8 2zm.123 5.38"/></svg>

After

Width:  |  Height:  |  Size: 659 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="#795548" d="M96 832h832c17.728 0 32 14.272 32 32v64c0 17.728-14.272 32-32 32H96c-17.728 0-32-14.272-32-32v-64c0-17.728 14.272-32 32-32"/><path fill="#4caf50" d="M160 192h64c17.728 0 32 14.272 32 32v512c0 17.728-14.272 32-32 32h-64c-17.728 0-32-14.272-32-32V224c0-17.728 14.272-32 32-32"/><path fill="#f44336" d="M512 96c0-17.728-14.272-32-32-32H352c-17.728 0-32 14.272-32 32v640c0 17.728 14.272 32 32 32h128c17.728 0 32-14.272 32-32z"/><path fill="#2196f3" d="m530.161 158.902 57.333-27.693a31.804 31.804 0 0 1 42.634 14.936l262.693 548.17c7.66 15.984.977 35.057-14.982 42.766l-57.333 27.693a31.804 31.804 0 0 1-42.634-14.936L515.18 201.668c-7.66-15.983-.977-35.057 14.982-42.766z"/><path fill="#ffeb3b" d="M320 192v64h192v-64zm0 384v64h192v-64z"/></svg>

After

Width:  |  Height:  |  Size: 830 B

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