Compare commits

..

477 Commits

Author SHA1 Message Date
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
neriousy
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 Cline
ffa5689885 docs: subtask config option (#2682) 2025-09-18 17:52:23 -05:00
Aiden Cline
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 Clasen
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
Dax
65baf76df6 Plugin tool updates (#2670) 2025-09-18 04:26:57 -04:00
Dax
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 Quense
63c8874d2d fix: type exports in package.json for SDK package (#2654) 2025-09-17 23:39:26 -04:00
Julián Díaz
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 Cline
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 Cline
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 Clasen
16f9edc1a0 fix(TUI): display correct branch name in git worktree (#2626) 2025-09-17 09:46:18 -05:00
Aiden Cline
8c2aec43b8 fix: type 'reasoning' was provided without its required following item (#2633) 2025-09-17 09:45:13 -05:00
Aiden Cline
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 Cline
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
Mani Sundararajan
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
Dax
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 Murray
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
Dax
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
Dax
c1b4e1f19d Upgrade to Zod v4 (#2605)
Co-authored-by: GitHub Action <action@github.com>
2025-09-15 03:12:07 -04:00
Aiden Cline
89d820b1c4 fix: visual token bug (#2603) 2025-09-14 21:23:52 -05:00
Aiden Cline
e3e459fc50 fix: reasoning metadata persistence (#2602) 2025-09-14 16:28:06 -05:00
Tommy D. Rossi
4bf0541bd6 log bash output when using opencode run (#2595) 2025-09-14 09:03:40 -05:00
Aiden Cline
c81624aef7 tweak: make bash permissions key off of command pattern (#2592) 2025-09-14 09:01:57 -05:00
Kenn Costales
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 Sundararajan
1f8d396b76 fix(dev): build tui with correct file ext for windows (#2590) 2025-09-14 01:59:25 -04:00
Aiden Cline
3752bb9717 fix: token counting visual bug (#2587) 2025-09-13 19:46:24 -05:00
Aiden Cline
16d66c209d respect subagent in command, add subtask flag (#2569) 2025-09-13 12:47:18 -05:00
Aiden Cline
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
Dax
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
Tommy D. Rossi
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 Hamilton
2b69bcccdf docs: typo in web agents.mdx (#2574) 2025-09-12 23:26:52 -05:00
Trillium Smith
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 Cline
aebd50da7e fix: make permission always behavior match expectation (#2573) 2025-09-12 18:59:38 -05:00
Stephen Murray
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 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 Raad
0290b4aaf0 ignore: internal 2025-09-12 10:45:44 +00:00
opencode
4ceee53480 release: v0.7.6 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 2025-09-12 10:25:57 +00:00
opencode
3978a8e636 release: v0.7.4 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 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
Chris Covington
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 Cline
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 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-GABRIEL
5b56848c3d Update providers.mdx because of small typo (#2539) 2025-09-10 15:49:25 -05:00
Aiden Cline
780e532094 resolve nested commands (#2537) 2025-09-10 14:05:26 -05:00
Aiden Cline
29310957c8 fix: handle @dir in command (#2533) 2025-09-10 13:27:44 -05:00
opencode
2b0577c725 release: v0.7.1 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 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 Cline
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
madflow
32b47fcc1e feat: svelte lsp (#2508) 2025-09-09 13:59:58 -05:00
Aiden Cline
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 Cline
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 Jackson
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 Sundararajan
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 Cline
1cea8b9e77 tweak: reenable todowrite & todoread for qwen models (#2499) 2025-09-08 13:21:16 -05:00
Douglas Dennis
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 Cline
c8ff81bae4 fix: silent error if bad flag was passed (#2486) 2025-09-07 23:14:38 -05:00
Aiden Cline
74469a0d3d fix: shell invocations are dropped if last interaction was revert (#2485) 2025-09-07 21:45:13 -05:00
Aiden Cline
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
Ytzhak
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 Cline
93c2f5060e fix: title gen w/ gpt-5-nano (#2473) 2025-09-06 22:50:16 -05:00
Aiden Cline
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 Cline
bffe547417 fix: command model selection (#2448) 2025-09-05 20:54:39 -05:00
Aiden Cline
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-mirrors
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ł Olender
8426a0d595 Update index.tsx (#2423) 2025-09-04 17:44:26 -10:00
Aiden Cline
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 Jackson
e001af2709 feat: add createOpencodeTui() function to SDK for programmatic TUI launching (#2410) 2025-09-04 02:49:44 -04:00
Aiden Cline
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 Allan
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 Cline
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 Cline
d6350a7fa6 tweak: update ls tool to use rg (#2367) 2025-09-02 10:40:20 -05:00
Yuta URANO
ae83138832 docs: update log level configuration in troubleshooting guide (#2374) 2025-09-02 10:31:04 -05:00
OpeOginni
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
Adam Spiers
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
opencode
a9875c5531 release: v0.6.3 2025-09-02 01:52:01 +00:00
Dax Raad
4c261ab1db switch gpt-5 to default to codex prompt + high reasoning 2025-09-01 21:46:03 -04:00
opencode
2fc8263032 release: v0.6.2 2025-09-02 01:03:43 +00:00
Aiden Cline
a431b8922c fix: ensure opencode still works if no commits present (#2363) 2025-09-01 20:57:14 -04:00
Aiden Cline
0a01d20850 fix: ensure enabled lsps are all logged (#2364) 2025-09-01 17:43:31 -05:00
opencode
7b62c10553 release: v0.6.1 2025-09-01 22:07:53 +00:00
Dax Raad
61c7196bd9 catch migration failures 2025-09-01 18:00:40 -04:00
opencode
365fdd9ff8 release: v0.6.0 2025-09-01 21:43:13 +00:00
Dax Raad
f6bc9238df docs: sdk 2025-09-01 17:35:52 -04:00
Aiden Cline
26f75d4e68 fix: tui attachment bound (#2361) 2025-09-01 16:33:36 -05:00
Jay V
8ba8d3c7e3 docs: update email 2025-09-01 17:30:32 -04:00
Dax
f993541e0b Refactor to support multiple instances inside single opencode process (#2360)
This release has a bunch of minor breaking changes if you are using opencode plugins or sdk

1. storage events have been removed (we might bring this back but had some issues)
2. concept of `app` is gone - there is a new concept called `project` and endpoints to list projects and get the current project
3. plugin receives `directory` which is cwd and `worktree` which is where the root of the project is if it's a git repo
4. the session.chat function has been renamed to session.prompt in sdk. it no longer requires model to be passed in (model is now an object)
5. every endpoint takes an optional `directory` parameter to operate as though opencode is running in that directory
2025-09-01 17:15:49 -04:00
Aiden Cline
e2df3eb44d add --command to opencode run (#2348) 2025-09-01 14:19:18 -05:00
Dax Raad
38f9ce05f6 wip: cloud 2025-09-01 11:53:43 -04:00
Dax Raad
a6e09363b8 wip: cloud 2025-09-01 11:47:58 -04:00
GitHub Action
49629bb58e ignore: update download stats 2025-09-01 2025-09-01 12:04:32 +00:00
Dax Raad
2bb5b9b13a wip: cloud 2025-09-01 04:03:07 -04:00
Dax Raad
41338d1bf9 wip: cloud 2025-09-01 03:55:48 -04:00
Dax Raad
41ee9c94c7 wip: cloud 2025-09-01 03:53:49 -04:00
Dax Raad
9c16db0f36 wip: cloud 2025-09-01 03:51:45 -04:00
Dax Raad
721869353b wip: sync 2025-09-01 03:15:38 -04:00
Dax Raad
6d22ade771 wip: cloud 2025-09-01 03:13:05 -04:00
Dax Raad
fbcceeb781 wip: cloud 2025-09-01 03:10:40 -04:00
Dax Raad
95775d68b7 wip: cloud 2025-09-01 03:04:49 -04:00
Dax Raad
cf11669618 wip: cloud 2025-09-01 03:04:07 -04:00
Dax Raad
65dc19e85a wip: cloud 2025-09-01 02:57:47 -04:00
Dax Raad
cfcfceca6d wip: cloud 2025-09-01 02:55:10 -04:00
Dax Raad
9f8899a9f9 wip: cloud 2025-09-01 02:28:21 -04:00
Dax Raad
449a063fe2 wip: cloud 2025-09-01 02:21:36 -04:00
Régis Blanc
37530359ee fix: ensure gopls lsp id matches docs (#2344) 2025-08-31 21:52:08 -05:00
Aiden Cline
65f0bea146 ignore: better error logging (#2346) 2025-08-31 17:11:04 -05:00
Beshoy Girgis
e4cc05a975 feat: Allow provider timeout override (#1982) 2025-08-31 14:06:02 -04:00
Aiden Cline
029612d8d5 fix: ensure shell cmds can be properly aborted (#2339) 2025-08-31 12:48:30 -05:00
Aiden Cline
e9826e8a22 fix: adjust title generation prompt to prevent direct response instead of title gen (#2338) 2025-08-31 11:01:19 -05:00
GitHub Action
ad5f209dc8 ignore: update download stats 2025-08-31 2025-08-31 12:04:06 +00:00
Andre van Tonder
fcfeac57c5 fix: resolve virtual envs for python LSP (#2155)
Co-authored-by: rekram1-node <aidenpcline@gmail.com>
2025-08-30 23:53:03 -05:00
Aiden Cline
2946898934 fix: ensure command uses currently selected model (#2336) 2025-08-30 15:41:06 -05:00
Aiden Cline
b4d95545e0 add support for lsp workspace/didChangeConfiguration (#2334) 2025-08-30 14:49:13 -05:00
Jay V
d3bbaa141c ignore: cloud 2025-08-30 15:28:35 -04:00
Jay V
8714f23509 ignore: cloud styles 2025-08-30 15:27:46 -04:00
Dax Raad
c676f12306 wip: cloud 2025-08-30 15:20:51 -04:00
Aiden Cline
dac821229e fix: resolve [pasted lines] when passing paste as arguments for command (#2333) 2025-08-30 10:56:00 -05:00
Aiden Cline
3625766ad4 tweak: ensure run command doesn't send request if no prompt present (#2332) 2025-08-30 10:39:28 -05:00
Roderik van der Veer
924e84b0de fix: change command selection to prefer exact matches over fuzzy sear… (#2314) 2025-08-30 09:44:27 -05:00
GitHub Action
70db3cffb0 ignore: update download stats 2025-08-30 2025-08-30 12:03:53 +00:00
Anton
0c30a6f303 Use a single rust LSP server instance for entire cargo workspace (#2292) 2025-08-30 06:00:39 +00:00
opencode
0c7a887dbc release: v0.5.29 2025-08-30 06:00:39 +00:00
Dax Raad
48e01cfee7 ignore: sync 2025-08-30 01:36:25 -04:00
Dax Raad
b54aa65f5f ignore: fix stuff 2025-08-30 01:19:03 -04:00
Dax Raad
52b3eddeee ignore: fix dev remote 2025-08-30 01:06:48 -04:00
Dax Raad
f821b55514 ignore: cloud resource 2025-08-30 00:58:22 -04:00
Frank
37f284f9a9 wip: cloud 2025-08-29 23:32:17 -04:00
Frank
0178eab29b wip cloud 2025-08-29 23:02:27 -04:00
Aiden Cline
a3f4a030b4 fix: mcp tool not triggering hooks (#2320) 2025-08-29 21:51:06 -05:00
Jay V
9a330b4f0f ignore: cloud keys section 2025-08-29 20:04:57 -04:00
Dax Raad
25e53e090b ignore: create key for new workspace 2025-08-29 19:56:29 -04:00
Frank
46927ee9a5 wip: cloud 2025-08-29 19:45:17 -04:00
Frank
c3a25eff78 wip: cloud 2025-08-29 19:34:58 -04:00
Jay V
b40c02e258 ignore: cloud balance section 2025-08-29 19:20:18 -04:00
Jay V
058163333d ignore: cloud payment history 2025-08-29 19:20:18 -04:00
Jay V
28c341ad32 ignore: cloud usage history 2025-08-29 19:20:18 -04:00
Jay V
a05e677412 ignore: cloud progress 2025-08-29 19:20:18 -04:00
Parbez
918dd58a15 Fix code block formatting in sdk.mdx (#2312) 2025-08-29 14:29:18 -05:00
Jay V
9c02c4cfe8 ignore: cloud 2025-08-29 12:48:01 -04:00
Frank
fd355c15db Update sst 2025-08-29 12:26:03 -04:00
Aiden Cline
12eb1391b9 fix: lsp debug cmd log (#2310) 2025-08-29 11:11:26 -05:00
Dax Raad
4496cd4b64 ignore: cloud solid fixes 2025-08-29 11:58:17 -04:00
Aiden Cline
7f5e5fccc8 ignore: add error log for title gen failures (#2309) 2025-08-29 10:53:58 -05:00
Aiden Cline
1a5b456bb6 fix: add additional encouragement for title gen (#2298) 2025-08-29 09:47:08 -05:00
GitHub Action
b55231c106 ignore: update download stats 2025-08-29 2025-08-29 12:04:20 +00:00
Aiden Cline
d7a9f343c5 tui: show actual error if command fails (#2296) 2025-08-28 18:42:55 -05:00
Adam
5ecd7fdd0c chore: remove unused dep 2025-08-28 18:16:38 -05:00
Adam
1aaf8f11cf chore: update gitignore 2025-08-28 18:16:05 -05:00
Netanel Draiman
7fab12da28 fix: replace isomorphic-git status with direct git diff for worktree support (#1706)
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
2025-08-28 18:15:26 -05:00
Aiden Cline
6daf0fdb2b allow slash commands to resolve ~/ references (#2295) 2025-08-28 17:31:03 -05:00
Jay V
f2f4d87cc0 ignore: cloud styles 2025-08-28 18:13:51 -04:00
Frank
8a0e773add wip cloud 2025-08-28 17:52:20 -04:00
Jay V
9b27d61fe8 ignore: cloud 2025-08-28 17:37:48 -04:00
Dax Raad
7d1eb010c1 ignore: cloud 2025-08-28 17:35:54 -04:00
Dax Raad
3fa02623c3 ignore: cloud 2025-08-28 17:33:52 -04:00
Jay V
403f9b2f1b ignore: cloud 2025-08-28 17:17:00 -04:00
Jay V
4d81f90dde ignore: cloud 2025-08-28 17:10:41 -04:00
Jay V
36ec9dddb2 ignore: cloud 2025-08-28 17:06:37 -04:00
Frank
5a0e7698e1 wip cloud 2025-08-28 17:05:51 -04:00
Frank
c6ef92634d wip cloud 2025-08-28 16:44:55 -04:00
Jay V
f97fdceb01 ignore: cloud 2025-08-28 16:29:21 -04:00
Jay V
3f225e3248 ignore: cloud 2025-08-28 16:21:09 -04:00
Jay V
151ff05381 ignore: cloud styles 2025-08-28 16:21:09 -04:00
Adam
e37e878e72 feat: home dir in app info 2025-08-28 14:34:20 -05:00
Jay V
3de1ce467f ignore: cloud 2025-08-28 14:18:37 -04:00
Jay V
eff50c0aab ignore: cloud 2025-08-28 14:12:31 -04:00
Dax Raad
02e014b0a0 ignore: cloud 2025-08-28 14:09:51 -04:00
Jakub Kopecký
a928a35c96 fix: mcp client name (#2289) 2025-08-28 12:48:29 -05:00
Ethan Shea
555202f3b1 Vercel AI Gateway key deeplinks into the dashboard (#2287) 2025-08-28 11:06:45 -05:00
Aiden Cline
37cf262094 fix: tui not showing err toasts (#2290) 2025-08-28 10:55:47 -05:00
Adam
aa9ab0a304 feat: include ignored files 2025-08-28 10:49:45 -05:00
GitHub Action
4331d77b9e ignore: update download stats 2025-08-28 2025-08-28 12:04:29 +00:00
Dax Raad
cf79262dc4 ignore: cloud 2025-08-27 21:49:39 -04:00
Dax Raad
43e8047ad6 ignore: cloud 2025-08-27 21:49:04 -04:00
Dax Raad
63c7c921ed ignore: cloud 2025-08-27 21:47:45 -04:00
Jay V
bce1398b73 ignore: cloud 2025-08-27 19:36:44 -04:00
Aiden Cline
87cf08a9e7 docs: add copy button to user messages too (#2285) 2025-08-27 18:14:27 -05:00
Aiden Cline
ad8ea82611 add synthetic user message before bash execution (when using !) (#2283) 2025-08-27 17:41:24 -05:00
Jay V
d984dbd876 ignore: cloud 2025-08-27 17:53:15 -04:00
Aiden Cline
2d794ed03d fix: ensure / commands dont try to resolve @ references from cmd outputs (#2282) 2025-08-27 15:59:33 -05:00
Adam
8749c0c707 feat: file list api 2025-08-27 15:28:03 -05:00
Jay V
3359417378 ignore: cloud 2025-08-27 16:02:32 -04:00
Aiden Cline
8381760b27 docs: fix client.event.subscribe example (#2280) 2025-08-27 11:42:09 -05:00
Dax Raad
0fbd7c84fd sdk update 2025-08-27 12:18:09 -04:00
Aiden Cline
5c17ee52c5 docs: document anthropic thinking budgets (#2243) 2025-08-27 09:41:51 -05:00
GitHub Action
3606775b79 ignore: update download stats 2025-08-27 2025-08-27 12:04:25 +00:00
spoons-and-mirrors
6233251fc0 fix: shimmer cpu cost (#2084) 2025-08-27 06:18:26 -05:00
Jay V
587b8ae7ee docs: edit 2025-08-26 17:30:43 -04:00
Stibbs
877855d1ee docs: mcp access mgmt instructions (#2087) 2025-08-26 17:27:44 -04:00
opencode
eebca580e3 release: v0.5.28 2025-08-26 20:23:34 +00:00
Frank
e73a7c23d0 Revert "fix(tui): too early"
This reverts commit 564418f1ff.
2025-08-26 16:13:16 -04:00
Jay V
11de2e59f3 docs: edit commands 2025-08-26 16:10:53 -04:00
Jay V
f4b69df7a3 docs: updating config schema 2025-08-26 16:10:53 -04:00
Jay V
83b9b67c4c docs: adding new provider 2025-08-26 16:10:53 -04:00
Aiden Cline
d9de78cfe8 fix: bash tool description (#2260) 2025-08-26 13:42:01 -05:00
GitHub Action
ef6bff6386 ignore: update download stats 2025-08-26 2025-08-26 12:04:26 +00:00
Aiden Cline
cb03655aac fix: eslint ENOTEMPTY (#2252) 2025-08-25 23:11:38 -05:00
Timo Clasen
012a292948 fix: model flag in non interactive mode (#2249) 2025-08-25 15:06:54 -05:00
Frank
d2e2eae4b8 Add opencode workflow 2025-08-26 01:46:16 +08:00
Frank
fd84e8d405 Add opencode workflow 2025-08-26 01:40:52 +08:00
opencode
567a1964c0 release: v0.5.27 2025-08-25 17:10:18 +00:00
adamdotdevin
564418f1ff fix(tui): too early 2025-08-25 12:04:49 -05:00
opencode
d7c4faec58 release: v0.5.26 2025-08-25 16:54:15 +00:00
adamdotdevin
34982b5d18 fix(tui): wording 2025-08-25 16:38:25 +00:00
opencode
5b5bd146ea release: v0.5.25 2025-08-25 16:38:24 +00:00
adamdotdevin
836c2060c7 fix(tui): sort custom commands lower 2025-08-25 11:32:15 -05:00
adamdotdevin
6357136ca5 fix(tui): sort custom commands lower 2025-08-25 11:29:56 -05:00
adamdotdevin
0a0b363587 feat(tui): grok free 2025-08-25 11:27:58 -05:00
Jay V
f5f6167146 docs: edit 2025-08-25 12:11:02 -04:00
adamdotdevin
f1684c9e15 fix(tui): fix logo color 2025-08-25 10:08:52 -05:00
adamdotdevin
f597b7287b chore: cleanup 2025-08-25 10:08:10 -05:00
GitHub Action
95fabc1407 ignore: update download stats 2025-08-25 2025-08-25 12:04:21 +00:00
Aiden Cline
315c366e11 docs: fix shell examples (#2236) 2025-08-24 23:53:39 -05:00
opencode
5d68a7c2e0 release: v0.5.24 2025-08-24 23:01:00 +00:00
Dax Raad
1b2d3bf659 ci: tweak 2025-08-24 18:55:44 -04:00
1628 changed files with 48870 additions and 17360 deletions

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
.github/workflows/format.yml vendored Normal file
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

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:

View File

@@ -24,4 +24,4 @@ jobs:
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
with:
model: opencode/sonic
model: opencode/sonic

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

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 }}

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

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.DS_Store
node_modules
.worktrees
.sst
.env
.idea
@@ -7,3 +8,5 @@ node_modules
openapi.json
playground
tmp
dist
.turbo

11
.opencode/tool/foo.ts Normal file
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!"
},
})

View File

@@ -10,3 +10,7 @@
- AVOID `let` statements
- PREFER single word variable names where possible
- Use as many bun apis as possible like Bun.file()
## Debugging
- To test opencode in the `packages/opencode` directory you can run `bun dev`

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/SST_dev)
**Join our community** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)

View File

@@ -58,3 +58,29 @@
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |

1588
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +0,0 @@
import { defineConfig } from "@solidjs/start/config"
export default defineConfig({
vite: {
server: {
allowedHosts: true,
},
},
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 B

View File

@@ -1,23 +0,0 @@
import { MetaProvider, Title } from "@solidjs/meta";
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { ErrorBoundary, Suspense } from "solid-js";
import "@ibm/plex/css/ibm-plex.css";
import "./app.css";
export default function App() {
return (
<Router
root={props => (
<MetaProvider>
<Title>SolidStart - Basic</Title>
<ErrorBoundary fallback={<div>Something went wrong</div>}>
<Suspense>{props.children}</Suspense>
</ErrorBoundary>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
);
}

View File

@@ -1,24 +0,0 @@
import { JSX } from "solid-js"
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>
)
}

View File

@@ -1,109 +0,0 @@
import { useSession } from "vinxi/http"
import { createClient } from "@openauthjs/openauth/client"
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"
export async function withActor<T>(fn: () => T) {
const actor = await getActor()
return Actor.provide(actor.type, actor.properties, fn)
}
export const getActor = query(async (): Promise<Actor.Info> => {
"use server"
const evt = getRequestEvent()
const url = new URL(evt!.request.headers.get("referer") ?? evt!.request.url)
const auth = await useAuthSession()
const [workspaceHint] = url.pathname.split("/").filter((x) => x.length > 0)
if (!workspaceHint) {
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 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")
export const AuthClient = createClient({
clientID: "app",
issuer: import.meta.env.VITE_AUTH_URL,
})
export interface AuthSession {
account: Record<string, {
id: string
email: string
}>
current?: string
}
export function useAuthSession() {
return useSession<AuthSession>({
password: "0".repeat(32),
name: "auth"
})
}
export function AuthProvider() {
}

View File

@@ -1,4 +0,0 @@
// @refresh reload
import { mount, StartClient } from "@solidjs/start/client";
mount(() => <StartClient />, document.getElementById("app")!);

View File

@@ -1,21 +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.ico" />
{assets}
</head>
<body data-color-mode="dark">
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
));

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>
);
}

View File

@@ -1,15 +0,0 @@
import { createAsync, query } from "@solidjs/router"
import { getActor, withActor } from "~/context/auth"
const getPosts = query(async () => {
"use server"
return withActor(() => {
return "ok"
})
}, "posts")
export default function () {
const actor = createAsync(async () => getActor())
return <div>{JSON.stringify(actor())}</div>
}

View File

@@ -1,257 +0,0 @@
[data-page="home"] {
--color-bg: oklch(0.2097 0.008 274.53);
--color-border: oklch(0.46 0.02 269.88);
--color-text: #ffffff;
--color-text-secondary: oklch(0.72 0.01 270.15);
--color-text-dimmed: hsl(224, 7%, 46%);
padding: var(--space-6);
font-family: var(--font-mono);
color: var(--color-text);
a {
color: var(--color-text);
text-decoration: underline;
text-underline-offset: var(--space-0-75);
}
background: var(--color-bg);
position: fixed;
overflow-y: scroll;
inset: 0;
[data-component="content"] {
max-width: 67.5rem;
margin: 0 auto;
border: 2px solid var(--color-border);
}
[data-component="top"] {
padding: var(--space-12);
display: flex;
flex-direction: column;
align-items: start;
gap: var(--space-4);
[data-slot="logo"] {
height: 70px;
}
[data-slot="title"] {
font-size: var(--font-size-2xl);
text-transform: uppercase;
}
}
[data-component="cta"] {
height: var(--space-19);
border-top: 2px solid var(--color-border);
display: flex;
[data-slot="left"] {
display: flex;
padding: 0 var(--space-12);
text-transform: uppercase;
text-decoration: underline;
align-items: center;
justify-content: center;
text-underline-offset: var(--space-0-75);
border-right: 2px solid var(--color-border);
}
[data-slot="right"] {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2-5);
padding: 0 var(--space-6);
}
[data-slot="command"] {
all: unset;
display: flex;
align-items: center;
cursor: pointer;
color: var(--color-text-secondary);
font-size: var(--font-size-lg);
font-family: var(--font-mono);
gap: var(--space-2);
}
[data-slot="highlight"] {
color: var(--color-text);
font-weight: 500;
}
}
[data-component="features"] {
border-top: 2px solid var(--color-border);
padding: var(--space-12);
[data-slot="list"] {
padding-left: var(--space-4);
margin: 0;
list-style: disc;
li {
margin-bottom: var(--space-4);
strong {
text-transform: uppercase;
font-weight: 600;
}
}
li:last-child {
margin-bottom: 0;
}
}
}
[data-component="install"] {
border-top: 2px solid var(--color-border);
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
@media (max-width: 40rem) {
grid-template-columns: 1fr;
grid-template-rows: auto;
}
}
[data-component="title"] {
letter-spacing: -0.03125rem;
text-transform: uppercase;
font-weight: 400;
font-size: var(--font-size-md);
flex-shrink: 0;
color: oklch(0.55 0.02 269.87);
}
[data-component="method"] {
padding: var(--space-4) var(--space-6);
display: flex;
flex-direction: column;
align-items: start;
gap: var(--space-3);
&:nth-child(2) {
border-left: 2px solid var(--color-border);
}
&:nth-child(3) {
border-top: 2px solid var(--color-border);
}
&:nth-child(4) {
border-top: 2px solid var(--color-border);
border-left: 2px solid var(--color-border);
}
[data-slot="button"] {
all: unset;
cursor: pointer;
display: flex;
align-items: center;
color: var(--color-text-secondary);
gap: var(--space-2);
strong {
color: var(--color-text);
font-weight: 500;
}
}
}
[data-component="screenshots"] {
border-top: 2px solid var(--color-border);
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0;
[data-slot="left"] {
padding: var(--space-8) var(--space-6);
display: flex;
flex-direction: column;
img {
width: 100%;
height: auto;
}
}
[data-slot="right"] {
display: grid;
grid-template-rows: 1fr 1fr;
border-left: 2px solid var(--color-border);
}
[data-slot="filler"] {
display: flex;
flex-grow: 1;
align-items: center;
justify-content: center;
}
[data-slot="cell"] {
padding: var(--space-8) var(--space-6);
display: flex;
flex-direction: column;
gap: var(--space-4);
&:nth-child(2) {
border-top: 2px solid var(--color-border);
}
img {
width: 80%;
height: auto;
}
}
}
[data-component="copy-status"] {
[data-slot="copy"] {
display: block;
width: var(--space-4);
height: var(--space-4);
color: var(--color-text-dimmed);
[data-copied] & {
display: none;
}
}
[data-slot="check"] {
display: none;
width: var(--space-4);
height: var(--space-4);
color: white;
[data-copied] & {
display: block;
}
}
}
[data-component="footer"] {
border-top: 2px solid var(--color-border);
display: grid;
grid-template-columns: 1fr 1fr 1fr;
font-size: var(--font-size-lg);
height: var(--space-20);
[data-slot="cell"] {
display: flex;
align-items: center;
justify-content: center;
border-right: 2px solid var(--color-border);
text-transform: uppercase;
&:last-child {
border-right: none;
}
}
}
}

View File

@@ -1,90 +0,0 @@
body {
--color-white: #ffffff;
--color-black: #000000;
}
[data-color-mode="dark"] {
/* OpenCode theme colors */
--color-bg: #0c0c0e;
--color-bg-surface: #161618;
--color-bg-elevated: #1c1c1f;
--color-text: #ffffff;
--color-text-muted: #a1a1a6;
--color-text-disabled: #68686f;
--color-accent: #007aff;
--color-accent-hover: #0056b3;
--color-accent-active: #004085;
--color-success: #30d158;
--color-warning: #ff9f0a;
--color-danger: #ff453a;
--color-border: #38383a;
--color-border-muted: #2c2c2e;
/* Button colors */
--color-primary: var(--color-accent);
--color-primary-hover: var(--color-accent-hover);
--color-primary-active: var(--color-accent-active);
--color-primary-text: #ffffff;
--color-danger: #ff453a;
--color-danger-hover: #d70015;
--color-danger-active: #a50011;
--color-danger-text: #ffffff;
--color-warning: #ff9f0a;
--color-warning-hover: #cc7f08;
--color-warning-active: #995f06;
--color-warning-text: #000000;
/* Surface colors */
--color-surface: var(--color-bg-surface);
--color-surface-hover: var(--color-bg-elevated);
--color-border: var(--color-border);
}
[data-color-mode="light"] {
/* OpenCode light theme colors */
--color-bg: #ffffff;
--color-bg-surface: #f5f5f7;
--color-bg-elevated: #ffffff;
--color-text: #1d1d1f;
--color-text-muted: #6e6e73;
--color-text-disabled: #86868b;
--color-accent: #007aff;
--color-accent-hover: #0056b3;
--color-accent-active: #004085;
--color-success: #30d158;
--color-warning: #ff9f0a;
--color-danger: #ff3b30;
--color-border: #d2d2d7;
--color-border-muted: #e5e5ea;
/* Button colors */
--color-primary: var(--color-accent);
--color-primary-hover: var(--color-accent-hover);
--color-primary-active: var(--color-accent-active);
--color-primary-text: #ffffff;
--color-danger: #ff3b30;
--color-danger-hover: #d70015;
--color-danger-active: #a50011;
--color-danger-text: #ffffff;
--color-warning: #ff9f0a;
--color-warning-hover: #cc7f08;
--color-warning-active: #995f06;
--color-warning-text: #000000;
/* Surface colors */
--color-surface: var(--color-bg-surface);
--color-surface-hover: var(--color-bg-elevated);
--color-border: var(--color-border);
}

View File

@@ -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");

View File

@@ -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");

View File

@@ -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");

View File

@@ -1 +0,0 @@
ALTER TABLE "usage" DROP COLUMN "request_id";

View File

@@ -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": {}
}
}

View File

@@ -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": {}
}
}

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
}
]
}

View File

@@ -1,23 +0,0 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/cloud-core",
"version": "0.5.23",
"private": true,
"type": "module",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"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"
}
}

View File

@@ -1,71 +0,0 @@
import { Resource } from "sst"
import { Stripe } from "stripe"
import { Database, eq, sql } from "./drizzle"
import { BillingTable, 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"
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 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
})
},
)
}

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)],
)

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)],
)

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
}

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
}
}

View File

@@ -1,909 +0,0 @@
import { z } from "zod"
import { Hono, MiddlewareHandler } from "hono"
import { cors } from "hono/cors"
import { HTTPException } from "hono/http-exception"
import { zValidator } from "@hono/zod-validator"
import { Resource } from "sst"
import { type ProviderMetadata, type LanguageModelUsage, generateText, streamText } 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 { and, Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
import { UserTable } from "@opencode/cloud-core/schema/user.sql.js"
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
import { createClient } from "@openauthjs/openauth/client"
import { Log } from "@opencode/cloud-core/util/log.js"
import { Billing } from "@opencode/cloud-core/billing.js"
import { Workspace } from "@opencode/cloud-core/workspace.js"
import { BillingTable, PaymentTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
import { Identifier } from "../../core/src/identifier"
type Env = {}
let _client: ReturnType<typeof createClient>
const client = () => {
if (_client) return _client
_client = createClient({
clientID: "api",
issuer: Resource.AUTH_API_URL.value,
})
return _client
}
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 log = Log.create({
namespace: "api",
})
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 RestAuth: MiddlewareHandler = async (c, next) => {
const authorization = c.req.header("authorization")
if (!authorization) {
return Actor.provide("public", {}, next)
}
const token = authorization.split(" ")[1]
if (!token)
throw new HTTPException(403, {
message: "Bearer token is required.",
})
const verified = await client().verify(token)
if (verified.err) {
throw new HTTPException(403, {
message: "Invalid token.",
})
}
let subject = verified.subject as Actor.Info
if (subject.type === "account") {
const workspaceID = c.req.header("x-opencode-workspace")
const email = subject.properties.email
if (workspaceID) {
const user = await Database.use((tx) =>
tx
.select({
id: UserTable.id,
workspaceID: UserTable.workspaceID,
email: UserTable.email,
})
.from(UserTable)
.where(and(eq(UserTable.email, email), eq(UserTable.workspaceID, workspaceID)))
.then((rows) => rows[0]),
)
if (!user)
throw new HTTPException(403, {
message: "You do not have access to this workspace.",
})
subject = {
type: "user",
properties: {
userID: user.id,
workspaceID: workspaceID,
email: user.email,
},
}
}
}
await Actor.provide(subject.type, subject.properties, 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)
}
})
})
.use("/*", cors())
.use(RestAuth)
.get("/rest/account", async (c) => {
const account = Actor.assert("account")
let workspaces = await Workspace.list()
if (workspaces.length === 0) {
await Workspace.create()
workspaces = await Workspace.list()
}
return c.json({
id: account.properties.accountID,
email: account.properties.email,
workspaces,
})
})
.get("/billing/info", async (c) => {
const billing = await Billing.get()
const payments = await Database.use((tx) =>
tx
.select()
.from(PaymentTable)
.where(eq(PaymentTable.workspaceID, Actor.workspace()))
.orderBy(sql`${PaymentTable.timeCreated} DESC`)
.limit(100),
)
const usage = await Database.use((tx) =>
tx
.select()
.from(UsageTable)
.where(eq(UsageTable.workspaceID, Actor.workspace()))
.orderBy(sql`${UsageTable.timeCreated} DESC`)
.limit(100),
)
return c.json({ billing, payments, usage })
})
.post(
"/billing/checkout",
zValidator(
"json",
z.custom<{
success_url: string
cancel_url: string
}>(),
),
async (c) => {
const account = Actor.assert("user")
const body = await c.req.json()
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: account.properties.email,
customer_creation: "always",
}),
metadata: {
workspaceID: Actor.workspace(),
},
currency: "usd",
payment_method_types: ["card"],
success_url: body.success_url,
cancel_url: body.cancel_url,
})
return c.json({
url: session.url,
})
},
)
.post("/billing/portal", async (c) => {
const body = await c.req.json()
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: body.return_url,
})
return c.json({
url: session.url,
})
})
.post("/stripe/webhook", async (c) => {
const body = await Billing.stripe().webhooks.constructEventAsync(
await c.req.text(),
c.req.header("stripe-signature")!,
Resource.STRIPE_WEBHOOK_SECRET.value,
)
console.log(body.type, JSON.stringify(body, null, 2))
if (body.type === "checkout.session.completed") {
const workspaceID = body.data.object.metadata?.workspaceID
const customerID = body.data.object.customer as string
const paymentID = body.data.object.payment_intent as string
const amount = body.data.object.amount_total
if (!workspaceID) throw new Error("Workspace ID not found")
if (!customerID) throw new Error("Customer ID not found")
if (!amount) throw new Error("Amount not found")
if (!paymentID) throw new Error("Payment ID not found")
await Actor.provide("system", { workspaceID }, async () => {
const customer = await Billing.get()
if (customer?.customerID && customer.customerID !== customerID) throw new Error("Customer ID mismatch")
// set customer metadata
if (!customer?.customerID) {
await Billing.stripe().customers.update(customerID, {
metadata: {
workspaceID,
},
})
}
// get payment method for the payment intent
const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
expand: ["payment_method"],
})
const paymentMethod = paymentIntent.payment_method
if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
await Database.transaction(async (tx) => {
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} + ${centsToMicroCents(amount)}`,
customerID,
paymentMethodID: paymentMethod.id,
paymentMethodLast4: paymentMethod.card!.last4,
})
.where(eq(BillingTable.workspaceID, workspaceID))
await tx.insert(PaymentTable).values({
workspaceID,
id: Identifier.create("payment"),
amount: centsToMicroCents(amount),
paymentID,
customerID,
})
})
})
}
console.log("finished handling")
return c.json("ok", 200)
})
.get("/keys", async (c) => {
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 c.json({ keys })
})
.post("/keys", zValidator("json", z.object({ name: z.string().min(1).max(255) })), async (c) => {
const user = Actor.assert("user")
const { name } = c.req.valid("json")
// 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 c.json({
key: secretKey,
id: keyRecord[0].id,
name: keyRecord[0].name,
created: keyRecord[0].timeCreated,
})
})
.delete("/keys/:id", async (c) => {
const user = Actor.assert("user")
const keyId = c.req.param("id")
const result = await Database.use((tx) =>
tx
.delete(KeyTable)
.where(and(eq(KeyTable.id, keyId), eq(KeyTable.workspaceID, user.properties.workspaceID)))
.returning({ id: KeyTable.id }),
)
if (result.length === 0) {
return c.json({ error: "Key not found" }, 404)
}
return c.json({ success: true, id: result[0].id })
})
.all("*", (c) => c.text("Not Found"))
export type ApiType = typeof app
export default app

View File

@@ -1,88 +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
}
"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 {}

View File

@@ -1,2 +0,0 @@
node_modules
dist

View File

@@ -1,38 +0,0 @@
<!doctype html>
<html lang="en" data-color-mode="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OpenControl</title>
<link rel="shortcut icon" type="image/ico" href="/favicon.ico" />
<link rel="icon" href="/favicon.ico" sizes="48x48">
<link rel="icon" href="/favicon.svg" media="(prefers-color-scheme: light)">
<link rel="icon" href="/favicon-dark.svg" media="(prefers-color-scheme: dark)">
<link rel="shortcut icon" href="/favicon.svg" type="image/svg+xml">
<meta property="twitter:image" content="%BASE_URL%/social-share.png">
<meta property="og:title" content="OpenControl">
<meta property="og:url" content="%BASE_URL%">
<meta property="og:locale" content="en">
<meta property="og:description" content="Control your infrastructure with AI.">
<meta property="og:site_name" content="OpenControl">
<meta name="twitter:card" content="summary_large_image">
<meta name="description" content="Control your infrastructure with AI.">
<meta property="og:image" content="%BASE_URL%/social-share.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=Rubik:wght@300..900&display=swap" rel="stylesheet">
<!--ssr-head-->
<!--ssr-assets-->
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!--ssr-outlet-->
</div>
<script type="module" src="/src/entry-client.tsx"></script>
</body>
</html>

View File

@@ -1,29 +0,0 @@
0 info it worked if it ends with ok
1 verbose cli [
1 verbose cli '/usr/local/bin/node',
1 verbose cli '/Users/frank/Sites/opencode/node_modules/.bin/npm',
1 verbose cli 'run',
1 verbose cli 'dev'
1 verbose cli ]
2 info using npm@2.15.12
3 info using node@v20.18.1
4 verbose stack Error: Invalid name: "@opencode/cloud/web"
4 verbose stack at ensureValidName (/Users/frank/Sites/opencode/node_modules/npm/node_modules/normalize-package-data/lib/fixer.js:336:15)
4 verbose stack at Object.fixNameField (/Users/frank/Sites/opencode/node_modules/npm/node_modules/normalize-package-data/lib/fixer.js:215:5)
4 verbose stack at /Users/frank/Sites/opencode/node_modules/npm/node_modules/normalize-package-data/lib/normalize.js:32:38
4 verbose stack at Array.forEach (<anonymous>)
4 verbose stack at normalize (/Users/frank/Sites/opencode/node_modules/npm/node_modules/normalize-package-data/lib/normalize.js:31:15)
4 verbose stack at final (/Users/frank/Sites/opencode/node_modules/npm/node_modules/read-package-json/read-json.js:349:5)
4 verbose stack at then (/Users/frank/Sites/opencode/node_modules/npm/node_modules/read-package-json/read-json.js:124:5)
4 verbose stack at ReadFileContext.<anonymous> (/Users/frank/Sites/opencode/node_modules/npm/node_modules/read-package-json/read-json.js:295:20)
4 verbose stack at ReadFileContext.callback (/Users/frank/Sites/opencode/node_modules/npm/node_modules/graceful-fs/graceful-fs.js:78:16)
4 verbose stack at FSReqCallback.readFileAfterOpen [as oncomplete] (node:fs:299:13)
5 verbose cwd /Users/frank/Sites/opencode/cloud/web
6 error Darwin 24.5.0
7 error argv "/usr/local/bin/node" "/Users/frank/Sites/opencode/node_modules/.bin/npm" "run" "dev"
8 error node v20.18.1
9 error npm v2.15.12
10 error Invalid name: "@opencode/cloud/web"
11 error If you need help, you may report this error at:
11 error <https://github.com/npm/npm/issues>
12 verbose exit [ 1, true ]

View File

@@ -1,32 +0,0 @@
{
"name": "@opencode/cloud-web",
"version": "0.5.23",
"private": true,
"description": "",
"type": "module",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "bun build:server && bun build:client",
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --ssr src/entry-server.tsx --outDir dist/server",
"serve": "vite preview",
"sst:dev": "bun sst shell --target Console -- bun dev"
},
"license": "MIT",
"devDependencies": {
"typescript": "catalog:",
"vite": "6.2.2",
"vite-plugin-pages": "0.32.5",
"vite-plugin-solid": "2.11.6"
},
"dependencies": {
"@kobalte/core": "0.13.9",
"@openauthjs/solid": "0.0.0-20250322224806",
"@solid-primitives/storage": "4.3.1",
"@solidjs/meta": "0.29.4",
"@solidjs/router": "0.15.3",
"solid-js": "1.9.5",
"solid-list": "0.3.0"
}
}

View File

@@ -1,3 +0,0 @@
<svg width="28" height="32" viewBox="0 0 28 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 31.5L0 23.6873V7.81266L14 0L28 7.81266V23.6873L14 31.5ZM14 28.4664L25.3456 22.0251V9.47493L14 2.99209L2.65443 9.47493V22.0251L14 28.4664ZM13.9572 24.6016C12.2732 24.6016 10.7176 24.1999 9.29052 23.3964C7.89195 22.593 6.7788 21.5125 5.95107 20.155C5.12334 18.7698 4.70948 17.2599 4.70948 15.6253C4.70948 13.9908 5.12334 12.4947 5.95107 11.1372C6.7788 9.77968 7.89195 8.69921 9.29052 7.89578C10.7176 7.06464 12.2732 6.64908 13.9572 6.64908C15.6412 6.64908 17.1825 7.06464 18.581 7.89578C19.9796 8.69921 21.0928 9.77968 21.9205 11.1372C22.7768 12.4947 23.2049 13.9908 23.2049 15.6253C23.2049 17.2599 22.791 18.7559 21.9633 20.1135C21.1356 21.471 20.0224 22.5653 18.6239 23.3964C17.2253 24.1999 15.6697 24.6016 13.9572 24.6016ZM13.9572 22.2744C15.213 22.2744 16.3547 21.9697 17.3823 21.3602C18.4098 20.7507 19.2375 19.9472 19.8654 18.9499C20.4934 17.9248 20.8073 16.8166 20.8073 15.6253C20.8073 14.4063 20.4934 13.2982 19.8654 12.3008C19.2375 11.3034 18.4098 10.5 17.3823 9.8905C16.3547 9.281 15.213 8.97625 13.9572 8.97625C12.7299 8.97625 11.5882 9.281 10.5321 9.8905C9.50459 10.5 8.67686 11.3034 8.04893 12.3008C7.421 13.2982 7.10703 14.4063 7.10703 15.6253C7.10703 16.8166 7.421 17.9248 8.04893 18.9499C8.67686 19.9472 9.50459 20.7507 10.5321 21.3602C11.5882 21.9697 12.7299 22.2744 13.9572 22.2744Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

View File

@@ -1,3 +0,0 @@
<svg width="28" height="32" viewBox="0 0 28 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 31.5L0 23.6873V7.81266L14 0L28 7.81266V23.6873L14 31.5ZM14 28.4664L25.3456 22.0251V9.47493L14 2.99209L2.65443 9.47493V22.0251L14 28.4664ZM13.9572 24.6016C12.2732 24.6016 10.7176 24.1999 9.29052 23.3964C7.89195 22.593 6.7788 21.5125 5.95107 20.155C5.12334 18.7698 4.70948 17.2599 4.70948 15.6253C4.70948 13.9908 5.12334 12.4947 5.95107 11.1372C6.7788 9.77968 7.89195 8.69921 9.29052 7.89578C10.7176 7.06464 12.2732 6.64908 13.9572 6.64908C15.6412 6.64908 17.1825 7.06464 18.581 7.89578C19.9796 8.69921 21.0928 9.77968 21.9205 11.1372C22.7768 12.4947 23.2049 13.9908 23.2049 15.6253C23.2049 17.2599 22.791 18.7559 21.9633 20.1135C21.1356 21.471 20.0224 22.5653 18.6239 23.3964C17.2253 24.1999 15.6697 24.6016 13.9572 24.6016ZM13.9572 22.2744C15.213 22.2744 16.3547 21.9697 17.3823 21.3602C18.4098 20.7507 19.2375 19.9472 19.8654 18.9499C20.4934 17.9248 20.8073 16.8166 20.8073 15.6253C20.8073 14.4063 20.4934 13.2982 19.8654 12.3008C19.2375 11.3034 18.4098 10.5 17.3823 9.8905C16.3547 9.281 15.213 8.97625 13.9572 8.97625C12.7299 8.97625 11.5882 9.281 10.5321 9.8905C9.50459 10.5 8.67686 11.3034 8.04893 12.3008C7.421 13.2982 7.10703 14.4063 7.10703 15.6253C7.10703 16.8166 7.421 17.9248 8.04893 18.9499C8.67686 19.9472 9.50459 20.7507 10.5321 21.3602C11.5882 21.9697 12.7299 22.2744 13.9572 22.2744Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,24 +0,0 @@
import fs from "fs"
import path from "path"
import { generateHydrationScript, getAssets } from "solid-js/web"
const dist = import.meta.resolve("../dist").replace("file://", "")
const serverEntry = await import("../dist/server/entry-server.js")
const template = fs.readFileSync(path.join(dist, "client/index.html"), "utf-8")
fs.writeFileSync(path.join(dist, "client/fallback.html"), template)
const routes = ["/", "/foo"]
for (const route of routes) {
const { app } = serverEntry.render({ url: route })
const html = template
.replace("<!--ssr-outlet-->", app)
.replace("<!--ssr-head-->", generateHydrationScript())
.replace("<!--ssr-assets-->", getAssets())
const filePath = dist + `/client${route === "/" ? "/index" : route}.html`
fs.mkdirSync(path.dirname(filePath), {
recursive: true,
})
fs.writeFileSync(filePath, html)
console.log(`Pre-rendered: ${filePath}`)
}

View File

@@ -1,42 +0,0 @@
/// <reference types="vite-plugin-pages/client-solid" />
import { Router } from "@solidjs/router"
import routes from "~solid-pages"
import "./ui/style/index.css"
import { MetaProvider } from "@solidjs/meta"
import { AccountProvider } from "./components/context-account"
import { DialogProvider } from "./ui/context-dialog"
import { DialogString } from "./ui/dialog-string"
import { DialogSelect } from "./ui/dialog-select"
import { ThemeProvider } from "./components/context-theme"
import { Suspense } from "solid-js"
import { OpenAuthProvider } from "./components/context-openauth"
export function App(props: { url?: string }) {
return (
<ThemeProvider>
<Suspense>
<DialogProvider>
<DialogString />
<DialogSelect />
<OpenAuthProvider
clientID="web"
issuer={import.meta.env.VITE_AUTH_URL || "http://dummy"}
>
<AccountProvider>
<MetaProvider>
<Router
children={routes}
url={props.url}
root={(props) => {
return <>{props.children}</>
}}
/>
</MetaProvider>
</AccountProvider>
</OpenAuthProvider>
</DialogProvider>
</Suspense>
</ThemeProvider>
)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 KiB

View File

@@ -1,99 +0,0 @@
import { createContext, createEffect, ParentProps, Suspense, useContext } from "solid-js"
import { makePersisted } from "@solid-primitives/storage"
import { createStore } from "solid-js/store"
import { useOpenAuth } from "./context-openauth"
import { createAsync } from "@solidjs/router"
import { isServer } from "solid-js/web"
type Storage = {
accounts: Record<
string,
{
id: string
email: string
workspaces: {
id: string
name: string
slug: string
}[]
}
>
}
const context = createContext<ReturnType<typeof init>>()
function init() {
const auth = useOpenAuth()
const [store, setStore] = makePersisted(
createStore<Storage>({
accounts: {},
}),
{
name: "opencontrol.account",
},
)
async function refresh(id: string) {
return fetch(import.meta.env.VITE_API_URL + "/rest/account", {
headers: {
authorization: `Bearer ${await auth.access(id)}`,
},
})
.then((val) => val.json())
.then((val) => setStore("accounts", id, val as any))
}
createEffect((previous: string[]) => {
if (Object.keys(auth.all).length === 0) {
return []
}
for (const item of Object.values(auth.all)) {
if (previous.includes(item.id)) continue
refresh(item.id)
}
return Object.keys(auth.all)
}, [] as string[])
const result = {
get all() {
return Object.keys(auth.all)
.map((id) => store.accounts[id])
.filter(Boolean)
},
get current() {
if (!auth.subject) return undefined
return store.accounts[auth.subject.id]
},
refresh,
get ready() {
return Object.keys(auth.all).length === result.all.length
},
}
return result
}
export function AccountProvider(props: ParentProps) {
const ctx = init()
const resource = createAsync(async () => {
await new Promise<void>((resolve) => {
if (isServer) return resolve()
createEffect(() => {
if (ctx.ready) resolve()
})
})
return null
})
return (
<Suspense>
{resource()}
<context.Provider value={ctx}>{props.children}</context.Provider>
</Suspense>
)
}
export function useAccount() {
const result = useContext(context)
if (!result) throw new Error("no account context")
return result
}

View File

@@ -1,180 +0,0 @@
import { createClient } from "@openauthjs/openauth/client"
import { makePersisted } from "@solid-primitives/storage"
import { createAsync } from "@solidjs/router"
import {
batch,
createContext,
createEffect,
createResource,
createSignal,
onMount,
ParentProps,
Show,
Suspense,
useContext,
} from "solid-js"
import { createStore, produce } from "solid-js/store"
import { isServer } from "solid-js/web"
interface Storage {
subjects: Record<string, SubjectInfo>
current?: string
}
interface Context {
all: Record<string, SubjectInfo>
subject?: SubjectInfo
switch(id: string): void
logout(id: string): void
access(id?: string): Promise<string | undefined>
authorize(opts?: AuthorizeOptions): void
}
export interface AuthorizeOptions {
redirectPath?: string
provider?: string
}
interface SubjectInfo {
id: string
refresh: string
}
interface AuthContextOpts {
issuer: string
clientID: string
}
const context = createContext<Context>()
export function OpenAuthProvider(props: ParentProps<AuthContextOpts>) {
const client = createClient({
issuer: props.issuer,
clientID: props.clientID,
})
const [storage, setStorage] = makePersisted(
createStore<Storage>({
subjects: {},
}),
{
name: `${props.issuer}.auth`,
},
)
const resource = createAsync(async () => {
if (isServer) return true
const hash = new URLSearchParams(window.location.search.substring(1))
const code = hash.get("code")
const state = hash.get("state")
if (code && state) {
const oldState = sessionStorage.getItem("openauth.state")
const verifier = sessionStorage.getItem("openauth.verifier")
const redirect = sessionStorage.getItem("openauth.redirect")
if (redirect && verifier && oldState === state) {
const result = await client.exchange(code, redirect, verifier)
if (!result.err) {
const id = result.tokens.refresh.split(":").slice(0, -1).join(":")
batch(() => {
setStorage("subjects", id, {
id: id,
refresh: result.tokens.refresh,
})
setStorage("current", id)
})
}
}
}
return true
})
async function authorize(opts?: AuthorizeOptions) {
const redirect = new URL(window.location.origin + (opts?.redirectPath ?? "/")).toString()
const authorize = await client.authorize(redirect, "code", {
pkce: true,
provider: opts?.provider,
})
sessionStorage.setItem("openauth.state", authorize.challenge.state)
sessionStorage.setItem("openauth.redirect", redirect)
if (authorize.challenge.verifier) sessionStorage.setItem("openauth.verifier", authorize.challenge.verifier)
window.location.href = authorize.url
}
const accessCache = new Map<string, string>()
const pendingRequests = new Map<string, Promise<any>>()
async function access(id: string) {
const pending = pendingRequests.get(id)
if (pending) return pending
const promise = (async () => {
const existing = accessCache.get(id)
const subject = storage.subjects[id]
const access = await client.refresh(subject.refresh, {
access: existing,
})
if (access.err) {
pendingRequests.delete(id)
ctx.logout(id)
return
}
if (access.tokens) {
setStorage("subjects", id, "refresh", access.tokens.refresh)
accessCache.set(id, access.tokens.access)
}
pendingRequests.delete(id)
return access.tokens?.access || existing!
})()
pendingRequests.set(id, promise)
return promise
}
const ctx: Context = {
get all() {
return storage.subjects
},
get subject() {
if (!storage.current) return
return storage.subjects[storage.current!]
},
switch(id: string) {
if (!storage.subjects[id]) return
setStorage("current", id)
},
authorize,
logout(id: string) {
if (!storage.subjects[id]) return
setStorage(
produce((s) => {
delete s.subjects[id]
if (s.current === id) s.current = Object.keys(s.subjects)[0]
}),
)
},
async access(id?: string) {
id = id || storage.current
if (!id) return
return access(id || storage.current!)
},
}
createEffect(() => {
if (!resource()) return
if (storage.current) return
const [first] = Object.keys(storage.subjects)
if (first) {
setStorage("current", first)
return
}
})
return (
<>
{resource()}
<context.Provider value={ctx}>{props.children}</context.Provider>
</>
)
}
export function useOpenAuth() {
const result = useContext(context)
if (!result) throw new Error("no auth context")
return result
}

View File

@@ -1,39 +0,0 @@
import { createStore } from "solid-js/store"
import { makePersisted } from "@solid-primitives/storage"
import { createEffect } from "solid-js"
import { createInitializedContext } from "../util/context"
import { isServer } from "solid-js/web"
interface Storage {
mode: "light" | "dark"
}
export const { provider: ThemeProvider, use: useTheme } =
createInitializedContext("ThemeContext", () => {
const [store, setStore] = makePersisted(
createStore<Storage>({
mode:
!isServer &&
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light",
}),
{
name: "theme",
},
)
createEffect(() => {
document.documentElement.setAttribute("data-color-mode", store.mode)
})
return {
setMode(mode: Storage["mode"]) {
setStore("mode", mode)
},
get mode() {
return store.mode
},
ready: true,
}
})

View File

@@ -1,13 +0,0 @@
/* @refresh reload */
import { hydrate, render } from "solid-js/web"
import { App } from "./app"
if (import.meta.env.DEV) {
render(() => <App />, document.getElementById("root")!)
}
if (!import.meta.env.DEV) {
if ("_$HY" in window) hydrate(() => <App />, document.getElementById("root")!)
else render(() => <App />, document.getElementById("root")!)
}

View File

@@ -1,7 +0,0 @@
import { renderToStringAsync } from "solid-js/web"
import { App } from "./app"
export async function render(props: { url: string }) {
const app = await renderToStringAsync(() => <App url={props.url} />)
return { app }
}

View File

@@ -1,11 +0,0 @@
import { WorkspaceProvider } from "./components/context-workspace"
import { ParentProps } from "solid-js"
import Layout from "./components/layout"
export default function Index(props: ParentProps) {
return (
<WorkspaceProvider>
<Layout>{props.children}</Layout>
</WorkspaceProvider>
)
}

View File

@@ -1,56 +0,0 @@
.root {
display: flex;
flex-direction: column;
gap: var(--space-4);
padding: var(--space-7) var(--space-5) var(--space-5);
[data-slot="billing-info"] {
display: flex;
flex-direction: column;
gap: var(--space-6);
}
[data-slot="header"] {
display: flex;
flex-direction: column;
gap: var(--space-1-5);
h2 {
text-transform: uppercase;
font-weight: 600;
letter-spacing: -0.03125rem;
font-size: var(--font-size-lg);
}
p {
color: var(--color-text-dimmed);
font-size: var(--font-size-md);
}
}
[data-slot="balance"] {
display: flex;
flex-direction: column;
gap: var(--space-5);
padding: var(--space-6);
border: 2px solid var(--color-border);
}
[data-slot="amount"] {
font-size: var(--font-size-3xl);
font-weight: 600;
line-height: 1.2;
}
@media (min-width: 40rem) {
[data-slot="balance"] {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
[data-slot="amount"] {
margin: 0;
}
}
}

View File

@@ -1,132 +0,0 @@
import { Button } from "../../ui/button"
import { useApi } from "../components/context-api"
import { createEffect, createSignal, createResource, For } from "solid-js"
import { useWorkspace } from "../components/context-workspace"
import style from "./billing.module.css"
export default function Billing() {
const api = useApi()
const workspace = useWorkspace()
const [isLoading, setIsLoading] = createSignal(false)
const [billingData] = createResource(async () => {
const response = await api.billing.info.$get()
return response.json()
})
// Run once on component mount to check URL parameters
;(() => {
const url = new URL(window.location.href)
const result = url.hash
console.log("STRIPE RESULT", result)
if (url.hash === "#success") {
setIsLoading(true)
// Remove the hash from the URL
window.history.replaceState(null, "", window.location.pathname + window.location.search)
}
})()
createEffect((old?: number) => {
if (old && old !== billingData()?.billing?.balance) {
setIsLoading(false)
}
return billingData()?.billing?.balance
})
const handleBuyCredits = async () => {
try {
setIsLoading(true)
const baseUrl = window.location.href
const successUrl = new URL(baseUrl)
successUrl.hash = "success"
const response = await api.billing.checkout
.$post({
json: {
success_url: successUrl.toString(),
cancel_url: baseUrl,
},
})
.then((r) => r.json() as any)
window.location.href = response.url
} catch (error) {
console.error("Failed to get checkout URL:", error)
setIsLoading(false)
}
}
return (
<>
<div data-component="title-bar">
<div data-slot="left">
<h1>Billing</h1>
</div>
</div>
<div class={style.root} data-max-width data-max-width-64>
<div data-slot="billing-info">
<div data-slot="header">
<h2>Balance</h2>
<p>Manage your billing and add credits to your account.</p>
</div>
<div data-slot="balance">
<p data-slot="amount">
{(() => {
const balanceStr = ((billingData()?.billing?.balance ?? 0) / 100000000).toFixed(2)
return `$${balanceStr === "-0.00" ? "0.00" : balanceStr}`
})()}
</p>
<Button color="primary" disabled={isLoading()} onClick={handleBuyCredits}>
{isLoading() ? "Loading..." : "Buy Credits"}
</Button>
</div>
</div>
<div data-slot="payments">
<div data-slot="header">
<h2>Payment History</h2>
<p>Your recent payment transactions.</p>
</div>
<div data-slot="payment-list">
<For each={billingData()?.payments} fallback={<p>No payments found.</p>}>
{(payment) => (
<div data-slot="payment-item">
<span data-slot="payment-id">{payment.id}</span>
{" | "}
<span data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</span>
{" | "}
<span data-slot="payment-date">{new Date(payment.timeCreated).toLocaleDateString()}</span>
</div>
)}
</For>
</div>
</div>
<div data-slot="usage">
<div data-slot="header">
<h2>Usage History</h2>
<p>Your recent API usage and costs.</p>
</div>
<div data-slot="usage-list">
<For each={billingData()?.usage} fallback={<p>No usage found.</p>}>
{(usage) => (
<div data-slot="usage-item">
<span data-slot="usage-model">{usage.model}</span>
{" | "}
<span data-slot="usage-tokens">{usage.inputTokens + usage.outputTokens} tokens</span>
{" | "}
<span data-slot="usage-cost">${((usage.cost ?? 0) / 100000000).toFixed(4)}</span>
{" | "}
<span data-slot="usage-date">{new Date(usage.timeCreated).toLocaleDateString()}</span>
</div>
)}
</For>
</div>
</div>
</div>
</>
)
}

View File

@@ -1,11 +0,0 @@
You are OpenControl, an interactive CLI tool that helps users execute various tasks.
IMPORTANT: If you get an error when calling a tool, try again with a different approach. Be creative, do not give up, try different inputs to the tool. You should chain together multiple tool calls. ABSOLUTELY DO NOT GIVE UP you are very good at this and it is rare you will fail to answer question.
You should be concise, direct, and to the point.
IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to.
IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific query or task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1-3 sentences or a short paragraph, please do.
IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to.
IMPORTANT: Keep your responses short, since they will be displayed on a command line interface. You MUST answer concisely with fewer than 4 lines (not including tool use or code generation), unless user asks for detail. Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is <answer>.", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...".

View File

@@ -1,271 +0,0 @@
import { createResource } from "solid-js"
import { createStore, produce } from "solid-js/store"
import SYSTEM_PROMPT from "./system.txt?raw"
import type {
LanguageModelV1Prompt,
LanguageModelV1CallOptions,
LanguageModelV1,
} from "ai"
interface Tool {
name: string
description: string
inputSchema: any
}
interface ToolCallerProps {
tool: {
list: () => Promise<Tool[]>
call: (input: { name: string; arguments: any }) => Promise<any>
}
generate: (
prompt: LanguageModelV1CallOptions,
) => Promise<
| { err: "rate" }
| { err: "context" }
| { err: "balance" }
| ({ err: false } & Awaited<ReturnType<LanguageModelV1["doGenerate"]>>)
>
onPromptUpdated?: (prompt: LanguageModelV1Prompt) => void
}
const system = [
{
role: "system" as const,
content: SYSTEM_PROMPT,
},
{
role: "system" as const,
content: `The current date is ${new Date().toDateString()}. Always use this current date when responding to relative date queries.`,
},
]
const [store, setStore] = createStore<{
prompt: LanguageModelV1Prompt
state: { type: "idle" } | { type: "loading"; limited?: boolean }
}>({
prompt: [...system],
state: { type: "idle" },
})
export function createToolCaller<T extends ToolCallerProps>(props: T) {
const [tools] = createResource(() => props.tool.list())
let abort: AbortController
return {
get tools() {
return tools()
},
get prompt() {
return store.prompt
},
get state() {
return store.state
},
clear() {
setStore("prompt", [...system])
},
async chat(input: string) {
if (store.state.type !== "idle") return
abort = new AbortController()
setStore(
produce((s) => {
s.state = {
type: "loading",
limited: false,
}
s.prompt.push({
role: "user",
content: [
{
type: "text",
text: input,
},
],
})
}),
)
props.onPromptUpdated?.(store.prompt)
while (true) {
if (abort.signal.aborted) {
break
}
const response = await props.generate({
inputFormat: "messages",
prompt: store.prompt,
temperature: 0,
seed: 69,
mode: {
type: "regular",
tools: tools()?.map((tool) => ({
type: "function",
name: tool.name,
description: tool.description,
parameters: {
...tool.inputSchema,
},
})),
},
})
if (abort.signal.aborted) continue
if (!response.err) {
setStore("state", {
type: "loading",
})
if (response.text) {
setStore(
produce((s) => {
s.prompt.push({
role: "assistant",
content: [
{
type: "text",
text: response.text || "",
},
],
})
}),
)
props.onPromptUpdated?.(store.prompt)
}
if (response.finishReason === "stop") {
break
}
if (response.finishReason === "tool-calls") {
for (const item of response.toolCalls || []) {
setStore(
produce((s) => {
s.prompt.push({
role: "assistant",
content: [
{
type: "tool-call",
toolName: item.toolName,
args: JSON.parse(item.args),
toolCallId: item.toolCallId,
},
],
})
}),
)
props.onPromptUpdated?.(store.prompt)
const called = await props.tool.call({
name: item.toolName,
arguments: JSON.parse(item.args),
})
setStore(
produce((s) => {
s.prompt.push({
role: "tool",
content: [
{
type: "tool-result",
toolName: item.toolName,
toolCallId: item.toolCallId,
result: called,
},
],
})
}),
)
props.onPromptUpdated?.(store.prompt)
}
}
continue
}
if (response.err === "context") {
setStore(
produce((s) => {
s.prompt.splice(2, 1)
}),
)
props.onPromptUpdated?.(store.prompt)
}
if (response.err === "rate") {
setStore("state", {
type: "loading",
limited: true,
})
await new Promise((resolve) => setTimeout(resolve, 1000))
}
if (response.err === "balance") {
setStore(
produce((s) => {
s.prompt.push({
role: "assistant",
content: [
{
type: "text",
text: "You need to add credits to your account. Please go to Billing and add credits to continue.",
},
],
})
s.state = { type: "idle" }
}),
)
props.onPromptUpdated?.(store.prompt)
break
}
}
setStore("state", { type: "idle" })
},
async cancel() {
abort.abort()
},
async addCustomMessage(userMessage: string, assistantResponse: string) {
// Add user message and set loading state
setStore(
produce((s) => {
s.prompt.push({
role: "user",
content: [
{
type: "text",
text: userMessage,
},
],
})
s.state = {
type: "loading",
limited: false,
}
}),
)
props.onPromptUpdated?.(store.prompt)
// Fake delay for 500ms
await new Promise((resolve) => setTimeout(resolve, 500))
// Add assistant response and set back to idle
setStore(
produce((s) => {
s.prompt.push({
role: "assistant",
content: [
{
type: "text",
text: assistantResponse,
},
],
})
s.state = { type: "idle" }
}),
)
props.onPromptUpdated?.(store.prompt)
},
}
}

View File

@@ -1,239 +0,0 @@
.root {
display: contents;
[data-slot="messages"] {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
height: 0;
/* This is important for flexbox to allow scrolling */
font-family: var(--font-mono);
color: var(--color-text);
row-gap: var(--space-4);
/* Add consistent spacing between messages */
/* Remove top border for first user message */
&>[data-component="message"][data-user]:first-child::before {
display: none;
}
&:has([data-component="loading"]) [data-component="clear"] {
display: none;
}
}
[data-component="message"] {
width: 100%;
padding: var(--space-2) var(--space-4);
line-height: var(--font-line-height);
white-space: pre-wrap;
align-self: flex-start;
min-height: auto;
/* Allow natural height for all messages */
display: flex;
flex-direction: column;
align-items: flex-start;
/* User message styling */
&[data-user] {
padding: var(--space-6) var(--space-4);
position: relative;
font-weight: 600;
color: var(--color-text);
/* margin: 0.5rem 0; */
}
&[data-user]::before,
&[data-user]::after {
content: "";
position: absolute;
left: var(--space-4);
right: var(--space-4);
height: var(--space-px);
background-color: var(--color-border);
z-index: 1;
/* Ensure borders appear above other content */
}
&[data-user]::before {
top: 0;
}
&[data-user]::after {
bottom: 0;
}
&[data-assistant] {
color: var(--color-text);
}
}
[data-component="tool"] {
display: flex;
width: 100%;
padding: 0 var(--space-4);
margin-left: 0;
flex-direction: column;
opacity: 0.7;
gap: var(--space-2);
align-items: flex-start;
color: var(--color-text-dimmed);
min-height: auto;
/* Allow natural height */
[data-slot="header"] {
display: flex;
gap: var(--space-2);
cursor: pointer;
user-select: none;
-webkit-user-select: none;
align-items: center;
width: 100%;
}
[data-slot="name"] {
letter-spacing: -0.03125rem;
text-transform: uppercase;
font-weight: 500;
font-size: var(--font-size-sm);
}
[data-slot="expand"] {
font-size: var(--font-size-sm);
}
[data-slot="content"] {
padding: 0;
line-height: var(--font-line-height);
font-size: var(--font-size-sm);
white-space: pre-wrap;
display: none;
width: 100%;
}
[data-slot="output"] {
margin-top: var(--space-1);
}
&[data-expanded="true"] [data-slot="content"] {
display: block;
}
&[data-expanded="true"] [data-slot="expand"] {
transform: rotate(45deg);
}
}
[data-component="loading"] {
padding: var(--space-4) var(--space-4) var(--space-8);
height: 1.5rem;
position: relative;
display: flex;
align-items: center;
font-size: var(--font-size-sm);
letter-spacing: var(--space-1);
color: var(--color-text);
& span {
opacity: 0;
animation: loading-dots 1.4s linear infinite;
}
& span:nth-child(2) {
animation-delay: 0.2s;
}
& span:nth-child(3) {
animation-delay: 0.4s;
}
}
[data-component="clear"] {
position: relative;
padding: var(--space-4) var(--space-4);
&::before {
content: "";
position: absolute;
left: var(--space-4);
right: var(--space-4);
top: 0;
height: var(--space-px);
background-color: var(--color-border);
z-index: 1;
}
& [data-component="button"] {
padding-left: 0;
}
}
[data-slot="footer"] {
display: flex;
flex-direction: column;
padding: 0;
border-top: 2px solid var(--color-border);
position: sticky;
bottom: 0;
z-index: 10;
/* Ensure it's above other content */
margin-top: auto;
/* Push to bottom if content is short */
width: 100%;
}
[data-component="chat"] {
display: flex;
padding: var(--space-0-5) 0;
align-items: center;
width: 100%;
height: 100%;
textarea {
--padding-y: var(--space-4);
--line-height: 1.5;
--text-height: calc(var(--line-height) * var(--font-size-lg));
--height: calc(var(--text-height) + var(--padding-y) * 2);
width: 100%;
resize: none;
line-height: var(--line-height);
height: var(--height);
min-height: var(--height);
max-height: calc(5 * var(--text-height) + var(--padding-y) * 2);
padding: var(--padding-y) var(--space-4);
border-radius: 0;
background-color: transparent;
color: var(--color-text);
border: none;
outline: none;
font-size: var(--font-size-lg);
}
textarea::placeholder {
color: var(--color-text-dimmed);
opacity: 0.75;
}
textarea:focus {
outline: 0;
}
& [data-component="button"] {
height: 100%;
}
}
}
@keyframes loading-dots {
0%,
100% {
opacity: 0;
}
40%,
60% {
opacity: 1;
}
}

View File

@@ -1,18 +0,0 @@
import { Button } from "../../ui/button"
import { IconArrowRight } from "../../ui/svg/icons"
import { createSignal, For } from "solid-js"
import { createToolCaller } from "./components/tool"
import { useApi } from "../components/context-api"
import { useWorkspace } from "../components/context-workspace"
import style from "./index.module.css"
export default function Index() {
const api = useApi()
const workspace = useWorkspace()
return (
<div class={style.root}>
<h1>Hello</h1>
</div>
)
}

View File

@@ -1,97 +0,0 @@
.root {
display: flex;
flex-direction: column;
gap: 2rem;
}
.root [data-slot="keys-info"] {
display: flex;
flex-direction: column;
gap: 1rem;
}
.root [data-slot="header"] {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.root [data-slot="header"] h2 {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
}
.root [data-slot="header"] p {
margin: 0;
color: var(--color-text-secondary);
}
.root [data-slot="key-list"] {
display: flex;
flex-direction: column;
gap: 1rem;
}
.root [data-slot="key-item"] {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
border: 1px solid var(--color-border);
border-radius: 0.5rem;
background: var(--color-background-secondary);
}
.root [data-slot="key-actions"] {
display: flex;
gap: 0.5rem;
}
.root [data-slot="key-info"] {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.root [data-slot="key-value"] {
font-family: monospace;
font-size: 0.875rem;
color: var(--color-text-primary);
}
.root [data-slot="key-meta"] {
font-size: 0.75rem;
color: var(--color-text-secondary);
}
.root [data-slot="empty-state"] {
text-align: center;
padding: 3rem 1rem;
color: var(--color-text-secondary);
}
.root [data-slot="actions"] {
display: flex;
align-items: center;
justify-content: space-between;
}
.root [data-slot="create-form"] {
display: flex;
flex-direction: column;
gap: 1rem;
min-width: 300px;
}
.root [data-slot="form-actions"] {
display: flex;
gap: 0.5rem;
}
.root [data-slot="key-name"] {
font-weight: 600;
font-size: 1rem;
color: var(--color-text-primary);
margin-bottom: 0.25rem;
}

View File

@@ -1,151 +0,0 @@
import { Button } from "../../ui/button"
import { useApi } from "../components/context-api"
import { createSignal, createResource, For, Show } from "solid-js"
import style from "./keys.module.css"
export default function Keys() {
const api = useApi()
const [isCreating, setIsCreating] = createSignal(false)
const [showCreateForm, setShowCreateForm] = createSignal(false)
const [keyName, setKeyName] = createSignal("")
const [keysData, { refetch }] = createResource(async () => {
const response = await api.keys.$get()
return response.json()
})
const handleCreateKey = async () => {
if (!keyName().trim()) return
try {
setIsCreating(true)
await api.keys.$post({
json: { name: keyName().trim() },
})
refetch()
setKeyName("")
setShowCreateForm(false)
} catch (error) {
console.error("Failed to create API key:", error)
} finally {
setIsCreating(false)
}
}
const handleDeleteKey = async (keyId: string) => {
if (!confirm("Are you sure you want to delete this API key? This action cannot be undone.")) {
return
}
try {
await api.keys[":id"].$delete({
param: { id: keyId },
})
refetch()
} catch (error) {
console.error("Failed to delete API key:", error)
}
}
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString()
}
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)
}
}
return (
<>
<div data-component="title-bar">
<div data-slot="left">
<h1>API Keys</h1>
</div>
</div>
<div class={style.root} data-max-width data-max-width-64>
<div data-slot="keys-info">
<div data-slot="actions">
<div data-slot="header">
<h2>API Keys</h2>
<p>Manage your API keys to access the OpenCode gateway.</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 color="primary" disabled={isCreating() || !keyName().trim()} onClick={handleCreateKey}>
{isCreating() ? "Creating..." : "Create"}
</Button>
<Button
color="ghost"
onClick={() => {
setShowCreateForm(false)
setKeyName("")
}}
>
Cancel
</Button>
</div>
</div>
}
>
<Button color="primary" onClick={() => setShowCreateForm(true)}>
Create API Key
</Button>
</Show>
</div>
<div data-slot="key-list">
<For
each={keysData()?.keys}
fallback={
<div data-slot="empty-state">
<p>Create an API key to access opencode gateway</p>
</div>
}
>
{(key) => (
<div data-slot="key-item">
<div data-slot="key-info">
<div data-slot="key-name">{key.name}</div>
<div data-slot="key-value">{formatKey(key.key)}</div>
<div data-slot="key-meta">
Created: {formatDate(key.timeCreated)}
{key.timeUsed && ` • Last used: ${formatDate(key.timeUsed)}`}
</div>
</div>
<div data-slot="key-actions">
<Button color="ghost" onClick={() => copyToClipboard(key.key)} title="Copy API key">
Copy
</Button>
<Button color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key">
Delete
</Button>
</div>
</div>
)}
</For>
</div>
</div>
</div>
</>
)
}

View File

@@ -1,24 +0,0 @@
import { hc } from "hono/client"
import { ApiType } from "@opencode/cloud-function/src/gateway"
import { useWorkspace } from "./context-workspace"
import { useOpenAuth } from "../../components/context-openauth"
export function useApi() {
const workspace = useWorkspace()
const auth = useOpenAuth()
return hc<ApiType>(import.meta.env.VITE_API_URL, {
async fetch(...args: Parameters<typeof fetch>): Promise<Response> {
const [input, init] = args
const request = input instanceof Request ? input : new Request(input, init)
const headers = new Headers(request.headers)
headers.set("authorization", `Bearer ${await auth.access()}`)
headers.set("x-opencode-workspace", workspace.id)
return fetch(
new Request(request, {
...init,
headers,
}),
)
},
})
}

View File

@@ -1,38 +0,0 @@
import { useNavigate, useParams } from "@solidjs/router"
import { createInitializedContext } from "../../util/context"
import { useAccount } from "../../components/context-account"
import { createEffect, createMemo } from "solid-js"
export const { use: useWorkspace, provider: WorkspaceProvider } =
createInitializedContext("WorkspaceProvider", () => {
const params = useParams()
const account = useAccount()
const workspace = createMemo(() =>
account.current?.workspaces.find(
(x) => x.id === params.workspace || x.slug === params.workspace,
),
)
const nav = useNavigate()
createEffect(() => {
if (!workspace()) nav("/")
})
const result = () => workspace()!
result.ready = true
return {
get id() {
return workspace()!.id
},
get slug() {
return workspace()!.slug
},
get name() {
return workspace()!.name
},
get ready() {
return workspace() !== undefined
},
}
})

View File

@@ -1,199 +0,0 @@
.root {
--padding: var(--space-10);
--vertical-padding: var(--space-8);
--heading-font-size: var(--font-size-4xl);
--sidebar-width: 200px;
--mobile-breakpoint: 40rem;
--topbar-height: 60px;
margin: var(--space-4);
border: 2px solid var(--color-border);
height: calc(100vh - var(--space-8));
display: flex;
flex-direction: row;
overflow: hidden;
/* Prevent overall scrolling */
position: relative;
}
[data-component="mobile-top-bar"] {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--topbar-height);
background: var(--color-background);
border-bottom: 2px solid var(--color-border);
z-index: 20;
align-items: center;
padding: 0 var(--space-4) 0 0;
[data-slot="logo"] {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
div {
text-transform: uppercase;
font-weight: 600;
letter-spacing: -0.03125rem;
}
svg {
height: 28px;
width: auto;
color: var(--color-white);
}
}
[data-slot="toggle"] {
background: transparent;
border: none;
padding: var(--space-4);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
& svg {
width: 24px;
height: 24px;
color: var(--color-foreground);
}
}
}
[data-component="sidebar"] {
width: var(--sidebar-width);
border-right: 2px solid var(--color-border);
display: flex;
flex-direction: column;
padding: calc(var(--padding) / 2);
overflow-y: auto;
/* Allow scrolling if needed */
position: sticky;
top: 0;
height: 100%;
background-color: var(--color-background);
z-index: 10;
[data-slot="logo"] {
margin-top: 2px;
margin-bottom: var(--space-7);
color: var(--color-white);
& svg {
height: 32px;
width: auto;
}
}
[data-slot="nav"] {
flex: 1;
ul {
list-style-type: none;
padding: 0;
}
li {
margin-bottom: calc(var(--vertical-padding) / 2);
text-transform: uppercase;
font-weight: 500;
}
a {
display: block;
padding: var(--space-2) 0;
}
}
[data-slot="user"] {
[data-component="button"] {
padding-left: 0;
padding-bottom: 0;
height: auto;
}
}
}
.navActiveLink {
cursor: default;
text-decoration: none;
}
[data-slot="main-content"] {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
/* Full height */
overflow: hidden;
/* Prevent overflow */
position: relative;
/* For positioning footer */
width: 100%;
/* Full width */
}
/* Backdrop for mobile */
[data-component="backdrop"] {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
/* background-color: rgba(0, 0, 0, 0.5); */
z-index: 25;
backdrop-filter: blur(2px);
}
/* Mobile styles */
@media (max-width: 40rem) {
.root {
margin: 0;
border: none;
height: 100vh;
}
[data-component="mobile-top-bar"] {
display: flex;
}
[data-component="backdrop"] {
display: block;
}
[data-component="sidebar"] {
position: fixed;
left: -100%;
top: 0;
height: 100vh;
width: 80%;
max-width: 280px;
transition: left 0.3s ease-in-out;
box-shadow: none;
z-index: 30;
padding: var(--space-8);
background-color: var(--color-bg);
&[data-opened="true"] {
left: 0;
box-shadow: 8px 0 0px 0px var(--color-gray-4);
}
}
[data-slot="main-content"] {
padding-top: var(--topbar-height);
/* Add space for the top bar */
overflow-y: auto;
}
/* Hide the logo in the sidebar on mobile since it's in the top bar */
[data-component="sidebar"] [data-slot="logo"] {
display: none;
}
}

View File

@@ -1,96 +0,0 @@
import style from "./layout.module.css"
import { useAccount } from "../../components/context-account"
import { Button } from "../../ui/button"
import { IconLogomark } from "../../ui/svg"
import { IconBars3BottomLeft } from "../../ui/svg/icons"
import { ParentProps, createMemo, createSignal } from "solid-js"
import { A, useLocation } from "@solidjs/router"
import { useOpenAuth } from "../../components/context-openauth"
export default function Layout(props: ParentProps) {
const auth = useOpenAuth()
const account = useAccount()
const [sidebarOpen, setSidebarOpen] = createSignal(false)
const location = useLocation()
const workspaceId = createMemo(() => account.current?.workspaces[0].id)
const pageTitle = createMemo(() => {
const path = location.pathname
if (path.endsWith("/billing")) return "Billing"
if (path.endsWith("/keys")) return "API Keys"
return null
})
function handleLogout() {
auth.logout(auth.subject?.id!)
}
return (
<div class={style.root}>
{/* Mobile top bar */}
<div data-component="mobile-top-bar">
<button data-slot="toggle" onClick={() => setSidebarOpen(!sidebarOpen())}>
<IconBars3BottomLeft />
</button>
<div data-slot="logo">
{pageTitle() ? (
<div>{pageTitle()}</div>
) : (
<A href="/">
<IconLogomark />
</A>
)}
</div>
</div>
{/* Backdrop for mobile sidebar - closes sidebar when clicked */}
{sidebarOpen() && <div data-component="backdrop" onClick={() => setSidebarOpen(false)}></div>}
<div data-component="sidebar" data-opened={sidebarOpen() ? "true" : "false"}>
<div data-slot="logo">
<A href="/">
<IconLogomark />
</A>
</div>
<nav data-slot="nav">
<ul>
<li>
<A end activeClass={style.navActiveLink} href={`/${workspaceId()}`} onClick={() => setSidebarOpen(false)}>
Chat
</A>
</li>
<li>
<A
activeClass={style.navActiveLink}
href={`/${workspaceId()}/billing`}
onClick={() => setSidebarOpen(false)}
>
Billing
</A>
</li>
<li>
<A
activeClass={style.navActiveLink}
href={`/${workspaceId()}/keys`}
onClick={() => setSidebarOpen(false)}
>
API Keys
</A>
</li>
</ul>
</nav>
<div data-slot="user">
<Button color="ghost" onClick={handleLogout} title={account.current?.email || ""}>
Logout
</Button>
</div>
</div>
{/* Main Content */}
<div data-slot="main-content">{props.children}</div>
</div>
)
}

View File

@@ -1,39 +0,0 @@
import { Match, Switch } from "solid-js"
import { useAccount } from "../components/context-account"
import { Navigate } from "@solidjs/router"
import { IconLogo } from "../ui/svg"
import styles from "./lander.module.css"
import { useOpenAuth } from "../components/context-openauth"
export default function Index() {
const auth = useOpenAuth()
const account = useAccount()
return (
<Switch>
<Match when={account.current}>
<Navigate href={`/${account.current!.workspaces[0].id}`} />
</Match>
<Match when={!account.current}>
<div class={styles.lander}>
<div data-slot="hero">
<section data-slot="top">
<div data-slot="logo">
<IconLogo />
</div>
<h1>opencode Gateway Console</h1>
</section>
<section data-slot="cta">
<div>
<span onClick={() => auth.authorize({ provider: "github" })}>Sign in with GitHub</span>
</div>
<div>
<span onClick={() => auth.authorize({ provider: "google" })}>Sign in with Google</span>
</div>
</section>
</div>
</div>
</Match>
</Switch>
)
}

View File

@@ -1,83 +0,0 @@
.lander {
--padding: 3rem;
--vertical-padding: 2rem;
--heading-font-size: 2rem;
margin: 1rem;
@media (max-width: 30rem) {
& {
--padding: 1.5rem;
--vertical-padding: 1rem;
--heading-font-size: 1.5rem;
margin: 0.5rem;
}
}
[data-slot="hero"] {
border: 2px solid var(--color-border);
max-width: 64rem;
margin-left: auto;
margin-right: auto;
width: 100%;
}
[data-slot="top"] {
padding: var(--padding);
h1 {
margin-top: calc(var(--vertical-padding) / 8);
font-size: var(--heading-font-size);
line-height: 1.25;
text-transform: uppercase;
font-weight: 600;
}
[data-slot="logo"] {
width: clamp(200px, 70vw, 400px);
color: var(--color-white);
}
}
[data-slot="cta"] {
display: flex;
flex-direction: row;
justify-content: space-between;
border-top: 2px solid var(--color-border);
& > div {
flex: 1;
line-height: 1.4;
text-align: center;
text-transform: uppercase;
cursor: pointer;
text-decoration: underline;
letter-spacing: -0.03125rem;
&[data-slot="col-2"] {
background-color: var(--color-border);
color: var(--color-text-invert);
font-weight: 600;
}
& > * {
display: block;
width: 100%;
height: 100%;
padding: calc(var(--padding) / 2) 0.5rem;
}
}
@media (max-width: 30rem) {
& > div {
padding-bottom: calc(var(--padding) / 2 + 4px);
}
}
& > div + div {
border-left: 2px solid var(--color-border);
}
}
}

View File

@@ -1,204 +0,0 @@
.pageContainer {
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.componentTable {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
border: 2px solid var(--color-border);
}
.componentCell {
padding: 1rem;
border: 2px solid var(--color-border);
vertical-align: top;
}
.componentLabel {
text-transform: uppercase;
letter-spacing: -0.03125rem;
font-size: 0.85rem;
font-weight: 500;
margin-bottom: 0.75rem;
color: var(--color-text-dimmed);
}
.sectionTitle {
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: -0.03125rem;
font-size: 1.2rem;
}
.divider {
height: 2px;
background: var(--color-border);
margin: 3rem 0;
width: 100%;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.buttonSection {
margin-bottom: 4rem;
}
.colorSection {
margin-bottom: 4rem;
}
.labelSection {
margin-bottom: 4rem;
}
.inputSection {
margin-bottom: 4rem;
}
.dialogSection {
margin-bottom: 4rem;
}
.formGroup {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.dialogContent {
padding: 2rem;
}
.dialogContentFooter {
margin-top: 1rem;
}
.pageTitle {
font-size: var(--heading-font-size, 2rem);
text-transform: uppercase;
font-weight: 600;
}
.colorBox {
width: 100%;
height: 80px;
margin-bottom: 0.5rem;
position: relative;
display: flex;
align-items: flex-end;
justify-content: center;
padding-bottom: 0.5rem;
}
.colorOrange {
background-color: var(--color-orange);
}
.colorOrangeLow {
background-color: var(--color-orange-low);
}
.colorOrangeHigh {
background-color: var(--color-orange-high);
}
.colorGreen {
background-color: var(--color-green);
}
.colorGreenLow {
background-color: var(--color-green-low);
}
.colorGreenHigh {
background-color: var(--color-green-high);
}
.colorBlue {
background-color: var(--color-blue);
}
.colorBlueLow {
background-color: var(--color-blue-low);
}
.colorBlueHigh {
background-color: var(--color-blue-high);
}
.colorPurple {
background-color: var(--color-purple);
}
.colorPurpleLow {
background-color: var(--color-purple-low);
}
.colorPurpleHigh {
background-color: var(--color-purple-high);
}
.colorRed {
background-color: var(--color-red);
}
.colorRedLow {
background-color: var(--color-red-low);
}
.colorRedHigh {
background-color: var(--color-red-high);
}
.colorAccent {
background-color: var(--color-accent);
}
.colorAccentLow {
background-color: var(--color-accent-low);
}
.colorAccentHigh {
background-color: var(--color-accent-high);
}
.colorCode {
background-color: rgba(0, 0, 0, 0.5);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8rem;
font-family: monospace;
}
.colorVariants {
display: flex;
gap: 0.5rem;
}
.colorVariant {
flex: 1;
height: 40px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.colorVariantCode {
background-color: rgba(0, 0, 0, 0.5);
color: white;
padding: 2px 4px;
border-radius: 4px;
font-size: 0.65rem;
font-family: monospace;
white-space: nowrap;
}

View File

@@ -1,562 +0,0 @@
import { Button } from "../../ui/button"
import { Dialog } from "../../ui/dialog"
import { Navigate } from "@solidjs/router"
import { createSignal, Show } from "solid-js"
import { IconHome, IconPencilSquare } from "../../ui/svg/icons"
import { useTheme } from "../../components/context-theme"
import { useDialog } from "../../ui/context-dialog"
import { DialogString } from "../../ui/dialog-string"
import { DialogSelect } from "../../ui/dialog-select"
import styles from "./design.module.css"
export default function DesignSystem() {
const dialog = useDialog()
const [dialogOpen, setDialogOpen] = createSignal(false)
const [dialogOpenTransition, setDialogOpenTransition] = createSignal(false)
const theme = useTheme()
// Check if we're running locally
const isLocal = import.meta.env.DEV === true
if (!isLocal) {
return <Navigate href="/" />
}
// Add a toggle button for theme
const toggleTheme = () => {
theme.setMode(theme.mode === "light" ? "dark" : "light")
}
return (
<div class={styles.pageContainer}>
<div class={styles.header}>
<h1 class={styles.pageTitle}>Design System</h1>
<Button onClick={toggleTheme}>
Toggle {theme.mode === "light" ? "Dark" : "Light"} Mode
</Button>
</div>
<section class={styles.colorSection}>
<h2 class={styles.sectionTitle}>Colors</h2>
<table class={styles.componentTable}>
<tbody>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Orange</h3>
<div class={`${styles.colorBox} ${styles.colorOrange}`}>
<span class={styles.colorCode}>hsl(41, 82%, 63%)</span>
</div>
<div class={styles.colorVariants}>
<div
class={`${styles.colorVariant} ${styles.colorOrangeLow}`}
>
<span class={styles.colorVariantCode}>
hsl(41, 39%, 22%)
</span>
</div>
<div
class={`${styles.colorVariant} ${styles.colorOrangeHigh}`}
>
<span class={styles.colorVariantCode}>
hsl(41, 82%, 87%)
</span>
</div>
</div>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Green</h3>
<div class={`${styles.colorBox} ${styles.colorGreen}`}>
<span class={styles.colorCode}>hsl(101, 82%, 63%)</span>
</div>
<div class={styles.colorVariants}>
<div class={`${styles.colorVariant} ${styles.colorGreenLow}`}>
<span class={styles.colorVariantCode}>
hsl(101, 39%, 22%)
</span>
</div>
<div
class={`${styles.colorVariant} ${styles.colorGreenHigh}`}
>
<span class={styles.colorVariantCode}>
hsl(101, 82%, 80%)
</span>
</div>
</div>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Blue</h3>
<div class={`${styles.colorBox} ${styles.colorBlue}`}>
<span class={styles.colorCode}>hsl(234, 100%, 60%)</span>
</div>
<div class={styles.colorVariants}>
<div class={`${styles.colorVariant} ${styles.colorBlueLow}`}>
<span class={styles.colorVariantCode}>
hsl(234, 54%, 20%)
</span>
</div>
<div class={`${styles.colorVariant} ${styles.colorBlueHigh}`}>
<span class={styles.colorVariantCode}>
hsl(234, 100%, 87%)
</span>
</div>
</div>
</td>
</tr>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Purple</h3>
<div class={`${styles.colorBox} ${styles.colorPurple}`}>
<span class={styles.colorCode}>hsl(281, 82%, 63%)</span>
</div>
<div class={styles.colorVariants}>
<div
class={`${styles.colorVariant} ${styles.colorPurpleLow}`}
>
<span class={styles.colorVariantCode}>
hsl(281, 39%, 22%)
</span>
</div>
<div
class={`${styles.colorVariant} ${styles.colorPurpleHigh}`}
>
<span class={styles.colorVariantCode}>
hsl(281, 82%, 89%)
</span>
</div>
</div>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Red</h3>
<div class={`${styles.colorBox} ${styles.colorRed}`}>
<span class={styles.colorCode}>hsl(339, 82%, 63%)</span>
</div>
<div class={styles.colorVariants}>
<div class={`${styles.colorVariant} ${styles.colorRedLow}`}>
<span class={styles.colorVariantCode}>
hsl(339, 39%, 22%)
</span>
</div>
<div class={`${styles.colorVariant} ${styles.colorRedHigh}`}>
<span class={styles.colorVariantCode}>
hsl(339, 82%, 87%)
</span>
</div>
</div>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Accent</h3>
<div class={`${styles.colorBox} ${styles.colorAccent}`}>
<span class={styles.colorCode}>hsl(13, 88%, 57%)</span>
</div>
<div class={styles.colorVariants}>
<div
class={`${styles.colorVariant} ${styles.colorAccentLow}`}
>
<span class={styles.colorVariantCode}>
hsl(13, 75%, 30%)
</span>
</div>
<div
class={`${styles.colorVariant} ${styles.colorAccentHigh}`}
>
<span class={styles.colorVariantCode}>
hsl(13, 100%, 78%)
</span>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</section>
<div class={styles.divider}></div>
<section class={styles.buttonSection}>
<h2 class={styles.sectionTitle}>Buttons</h2>
<table class={styles.componentTable}>
<tbody>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Primary</h3>
<Button>Primary Button</Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Secondary</h3>
<Button color="secondary">Secondary Button</Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Ghost</h3>
<Button color="ghost">Ghost Button</Button>
</td>
</tr>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Primary Disabled</h3>
<Button disabled>Primary Button</Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Secondary Disabled</h3>
<Button color="secondary" disabled>
Secondary Button
</Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Ghost Disabled</h3>
<Button color="ghost" disabled>
Ghost Button
</Button>
</td>
</tr>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Small</h3>
<Button size="sm">Small Button</Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Small Secondary</h3>
<Button size="sm" color="secondary">
Small Secondary
</Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Small Ghost</h3>
<Button size="sm" color="ghost">
Small Ghost
</Button>
</td>
</tr>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>With Icon</h3>
<Button icon={<IconHome />}>With Icon</Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Icon + Secondary</h3>
<Button icon={<IconHome />} color="secondary">
Icon Secondary
</Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Icon + Ghost</h3>
<Button icon={<IconHome />} color="ghost">
Icon Ghost
</Button>
</td>
</tr>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Small + Icon</h3>
<Button size="sm" icon={<IconHome />}>
Small Icon
</Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Small + Icon + Secondary</h3>
<Button size="sm" icon={<IconHome />} color="secondary">
Small Icon Secondary
</Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Small + Icon + Ghost</h3>
<Button size="sm" icon={<IconHome />} color="ghost">
Small Icon Ghost
</Button>
</td>
</tr>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Icon Only</h3>
<Button icon={<IconHome />}></Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Icon Only + Secondary</h3>
<Button icon={<IconHome />} color="secondary"></Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Icon Only + Ghost</h3>
<Button icon={<IconHome />} color="ghost"></Button>
</td>
</tr>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Icon Only Disabled</h3>
<Button icon={<IconHome />} disabled></Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>
Icon Only + Secondary Disabled
</h3>
<Button icon={<IconHome />} color="secondary" disabled></Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>
Icon Only + Ghost Disabled
</h3>
<Button icon={<IconHome />} color="ghost" disabled></Button>
</td>
</tr>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Small Icon Only</h3>
<Button size="sm" icon={<IconHome />}></Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>
Small Icon Only + Secondary
</h3>
<Button
size="sm"
icon={<IconHome />}
color="secondary"
></Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Small Icon Only + Ghost</h3>
<Button size="sm" icon={<IconHome />} color="ghost"></Button>
</td>
</tr>
</tbody>
</table>
</section>
<div class={styles.divider}></div>
<section class={styles.labelSection}>
<h2 class={styles.sectionTitle}>Labels</h2>
<table class={styles.componentTable}>
<tbody>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Small</h3>
<label data-size="sm" data-component="label">
Small Label Text
</label>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Medium</h3>
<label data-size="md" data-component="label">
Medium Label Text
</label>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Large</h3>
<label data-size="lg" data-component="label">
Large Label Text
</label>
</td>
</tr>
</tbody>
</table>
</section>
<div class={styles.divider}></div>
<section class={styles.inputSection}>
<h2 class={styles.sectionTitle}>Inputs</h2>
<table class={styles.componentTable}>
<tbody>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Small</h3>
<input
data-component="input"
data-size="sm"
placeholder="Small input field"
/>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Medium</h3>
<input
data-component="input"
data-size="md"
placeholder="Medium input field"
/>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Large</h3>
<input
data-component="input"
data-size="lg"
placeholder="Large input field"
/>
</td>
</tr>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Disabled</h3>
<input
data-component="input"
data-size="md"
placeholder="Disabled input"
disabled
/>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>With Value</h3>
<input
data-component="input"
data-size="md"
value="Input with preset value"
readOnly
/>
</td>
</tr>
</tbody>
</table>
</section>
<div class={styles.divider}></div>
<section class={styles.dialogSection}>
<h2 class={styles.sectionTitle}>Dialogs</h2>
<table class={styles.componentTable}>
<tbody>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Default</h3>
<Button color="secondary" onClick={() => setDialogOpen(true)}>
Open Dialog
</Button>
<Dialog open={dialogOpen()} onOpenChange={setDialogOpen}>
<div data-slot="header">
<div data-slot="title">Dialog Title</div>
</div>
<div data-slot="main">
<p>This is the default dialog content.</p>
</div>
<div data-slot="footer">
<Button onClick={() => setDialogOpen(false)}>Close</Button>
</div>
</Dialog>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Small With Transition</h3>
<Button
color="secondary"
onClick={() => {
setDialogOpenTransition(true)
}}
>
Small Dialog
</Button>
<Dialog
open={dialogOpenTransition()}
onOpenChange={setDialogOpenTransition}
size="sm"
transition={true}
>
<div class={styles.dialogContent}>
<h2 class={styles.sectionTitle}>Small Dialog</h2>
<p>This is a smaller dialog with transitions.</p>
<div class={styles.dialogContentFooter}>
<Button onClick={() => setDialogOpenTransition(false)}>
Close
</Button>
</div>
</div>
</Dialog>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Input String</h3>
<Button
color="secondary"
onClick={() =>
dialog.open(DialogString, {
title: "Name",
action: "Change name",
placeholder: "Enter a name",
onSubmit: () => {},
})
}
>
String
</Button>
</td>
</tr>
<tr>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Select Input</h3>
<Button
color="secondary"
onClick={() =>
dialog.open(DialogSelect, {
placeholder: "Select",
title: "User Settings",
options: [
{
display: "Change name",
prefix: <IconPencilSquare />,
onSelect: () => {
dialog.close()
},
},
{
display: "Remove user",
prefix: <IconHome />,
onSelect: () => {
dialog.close()
},
},
],
})
}
>
Select
</Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Select Input</h3>
<Button
color="secondary"
onClick={() =>
dialog.open(DialogSelect, {
placeholder: "Select",
title: "User Settings",
options: [
{
display: "Change name",
onSelect: () => {
dialog.close()
},
},
{
display: "Remove user",
onSelect: () => {
dialog.close()
},
},
],
})
}
>
No Prefix
</Button>
</td>
<td class={styles.componentCell}>
<h3 class={styles.componentLabel}>Select No Options</h3>
<Button
color="secondary"
onClick={() =>
dialog.open(DialogSelect, {
placeholder: "Select",
title: "User Settings",
options: [],
})
}
>
No Options
</Button>
</td>
</tr>
</tbody>
</table>
</section>
</div>
)
}

View File

@@ -1,24 +0,0 @@
import { Button as Kobalte } from "@kobalte/core/button"
import { JSX, Show, splitProps } from "solid-js"
export interface ButtonProps {
color?: "primary" | "secondary" | "ghost"
size?: "md" | "sm"
icon?: JSX.Element
}
export function Button(props: JSX.IntrinsicElements["button"] & ButtonProps) {
const [split, rest] = splitProps(props, ["color", "size", "icon"])
return (
<Kobalte
{...rest}
data-component="button"
data-size={split.size || "md"}
data-color={split.color || "primary"}
>
<Show when={props.icon}>
<div data-slot="icon">{props.icon}</div>
</Show>
{props.children}
</Kobalte>
)
}

View File

@@ -1,120 +0,0 @@
import { createContext, JSX, ParentProps, useContext } from "solid-js"
import { StandardSchemaV1 } from "@standard-schema/spec"
import { createStore } from "solid-js/store"
import { Dialog } from "./dialog"
const Context = createContext<DialogControl>()
type DialogControl = {
open<Schema extends StandardSchemaV1<object>>(
component: DialogComponent<Schema>,
input: StandardSchemaV1.InferInput<Schema>,
): void
close(): void
isOpen(input: any): boolean
size: "sm" | "md"
transition?: boolean
input?: any
}
type DialogProps<Schema extends StandardSchemaV1<object>> = {
input: StandardSchemaV1.InferInput<Schema>
control: DialogControl
}
type DialogComponent<Schema extends StandardSchemaV1<object>> = ReturnType<
typeof createDialog<Schema>
>
export function createDialog<Schema extends StandardSchemaV1<object>>(props: {
schema: Schema
size: "sm" | "md"
render: (props: DialogProps<Schema>) => JSX.Element
}) {
const result = () => {
const dialog = useDialog()
return (
<Dialog
size={dialog.size}
transition={dialog.transition}
open={dialog.isOpen(result)}
onOpenChange={(val) => {
if (!val) dialog.close()
}}
>
{props.render({
input: dialog.input,
control: dialog,
})}
</Dialog>
)
}
result.schema = props.schema
result.size = props.size
return result
}
export function DialogProvider(props: ParentProps) {
const [store, setStore] = createStore<{
dialog?: DialogComponent<any>
input?: any
transition?: boolean
size: "sm" | "md"
}>({
size: "sm",
})
const control: DialogControl = {
get input() {
return store.input
},
get size() {
return store.size
},
get transition() {
return store.transition
},
isOpen(input) {
return store.dialog === input
},
open(component, input) {
setStore({
dialog: component,
input: input,
size: store.dialog !== undefined ? store.size : component.size,
transition: store.dialog !== undefined,
})
setTimeout(() => {
setStore({
size: component.size,
})
}, 0)
setTimeout(() => {
setStore({
transition: false,
})
}, 150)
},
close() {
setStore({
dialog: undefined,
})
},
}
return (
<>
<Context.Provider value={control}>{props.children}</Context.Provider>
</>
)
}
export function useDialog() {
const ctx = useContext(Context)
if (!ctx) {
throw new Error("useDialog must be used within a DialogProvider")
}
return ctx
}

View File

@@ -1,36 +0,0 @@
.options {
margin-top: var(--space-1);
border-top: 2px solid var(--color-border);
padding: var(--space-2);
[data-slot="option"] {
outline: none;
flex-shrink: 0;
height: var(--space-11);
display: flex;
justify-content: start;
align-items: center;
padding: 0 var(--space-2-5);
gap: var(--space-3);
cursor: pointer;
&[data-empty] {
cursor: default;
color: var(--color-text-dimmed);
}
&[data-active] {
background-color: var(--color-bg-surface);
}
[data-slot="title"] {
font-size: var(--font-size-md);
}
[data-slot="prefix"] {
width: var(--space-4);
height: var(--space-4);
}
}
}

View File

@@ -1,124 +0,0 @@
import style from "./dialog-select.module.css"
import { z } from "zod"
import { createMemo, createSignal, For, JSX, onMount } from "solid-js"
import { createList } from "solid-list"
import { createDialog } from "./context-dialog"
export const DialogSelect = createDialog({
size: "md",
schema: z.object({
title: z.string(),
placeholder: z.string(),
onSelect: z
.function(z.tuple([z.any()]))
.returns(z.void())
.optional(),
options: z.array(
z.object({
display: z.string(),
value: z.any().optional(),
onSelect: z.function().returns(z.void()).optional(),
prefix: z.custom<JSX.Element>().optional(),
}),
),
}),
render: (ctx) => {
let input: HTMLInputElement
onMount(() => {
input.focus()
input.value = ""
})
const [filter, setFilter] = createSignal("")
const filtered = createMemo(() =>
ctx.input.options?.filter((i) =>
i.display.toLowerCase().includes(filter().toLowerCase()),
),
)
const list = createList({
loop: true,
initialActive: 0,
items: () => filtered().map((_, i) => i),
handleTab: false,
})
const handleSelection = (index: number) => {
const option = ctx.input.options[index]
// If the option has its own onSelect handler, use it
if (option.onSelect) {
option.onSelect()
}
// Otherwise, if there's a global onSelect handler, call it with the option's value
else if (ctx.input.onSelect) {
ctx.input.onSelect(
option.value !== undefined ? option.value : option.display,
)
}
}
return (
<>
<div data-slot="header">
<label
data-size="md"
data-slot="title"
data-component="label"
for={`dialog-select-${ctx.input.title}`}
>
{ctx.input.title}
</label>
</div>
<div data-slot="main">
<input
data-size="lg"
data-component="input"
value={filter()}
onInput={(e) => {
setFilter(e.target.value)
list.setActive(0)
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
const selected = list.active()
if (selected === null) return
handleSelection(selected)
return
}
if (e.key === "Escape") {
setFilter("")
return
}
list.onKeyDown(e)
}}
id={`dialog-select-${ctx.input.title}`}
ref={(r) => (input = r)}
data-slot="input"
placeholder={ctx.input.placeholder}
/>
</div>
<div data-slot="options" class={style.options}>
<For
each={filtered()}
fallback={
<div data-slot="option" data-empty>
No results
</div>
}
>
{(option, index) => (
<div
onClick={() => handleSelection(index())}
data-slot="option"
data-active={list.active() === index() ? true : undefined}
>
{option.prefix && <div data-slot="prefix">{option.prefix}</div>}
<div data-slot="title">{option.display}</div>
</div>
)}
</For>
</div>
</>
)
},
})

View File

@@ -1,70 +0,0 @@
import { z } from "zod"
import { onMount } from "solid-js"
import { createDialog } from "./context-dialog"
import { Button } from "./button"
export const DialogString = createDialog({
size: "sm",
schema: z.object({
title: z.string(),
placeholder: z.string(),
action: z.string(),
onSubmit: z.function().args(z.string()).returns(z.void()),
}),
render: (ctx) => {
let input: HTMLInputElement
onMount(() => {
setTimeout(() => {
input.focus()
input.value = ""
}, 50)
})
function submit() {
const value = input.value.trim()
if (value) {
ctx.input.onSubmit(value)
ctx.control.close()
}
}
return (
<>
<div data-slot="header">
<label
data-size="md"
data-slot="title"
data-component="label"
for={`dialog-string-${ctx.input.title}`}
>
{ctx.input.title}
</label>
</div>
<div data-slot="main">
<input
data-slot="input"
data-size="lg"
data-component="input"
ref={(r) => (input = r)}
placeholder={ctx.input.placeholder}
id={`dialog-string-${ctx.input.title}`}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault()
submit()
}
}}
/>
</div>
<div data-slot="footer">
<Button size="md" color="ghost" onClick={() => ctx.control.close()}>
Cancel
</Button>
<Button size="md" color="secondary" onClick={submit}>
{ctx.input.action}
</Button>
</div>
</>
)
},
})

View File

@@ -1,27 +0,0 @@
import { Dialog as Kobalte } from "@kobalte/core/dialog"
import { ComponentProps, ParentProps } from "solid-js"
export type Props = ParentProps<{
size?: "sm" | "md"
transition?: boolean
}> &
ComponentProps<typeof Kobalte>
export function Dialog(props: Props) {
return (
<Kobalte {...props}>
<Kobalte.Portal>
<Kobalte.Overlay data-component="dialog-overlay" />
<div data-component="dialog-center">
<Kobalte.Content
data-transition={props.transition ? "" : undefined}
data-size={props.size}
data-slot="content"
>
{props.children}
</Kobalte.Content>
</div>
</Kobalte.Portal>
</Kobalte>
)
}

View File

@@ -1,78 +0,0 @@
[data-component="button"] {
width: fit-content;
display: flex;
line-height: 1;
align-items: center;
justify-content: center;
gap: var(--space-2);
font-size: var(--font-size-md);
text-transform: uppercase;
height: var(--space-11);
outline: none;
font-weight: 500;
padding: 0 var(--space-4);
border-width: 2px;
border-color: var(--color-border);
cursor: pointer;
&:disabled {
opacity: 0.5;
cursor: default;
}
&[data-color="primary"] {
background-color: var(--color-text);
border-color: var(--color-text);
color: var(--color-text-invert);
&:active {
border-color: var(--color-accent);
}
}
&[data-color="secondary"] {
&:active {
border-color: var(--color-accent);
}
}
&[data-color="ghost"] {
border: none;
text-decoration: underline;
&:active {
color: var(--color-text-accent);
}
}
&:has([data-slot="icon"]) {
padding-left: var(--space-3);
padding-right: var(--space-3);
}
&[data-size="sm"] {
height: var(--space-8);
padding: var(--space-3);
font-size: var(--font-size-xs);
[data-slot="icon"] {
width: var(--space-3-5);
height: var(--space-3-5);
}
&:has([data-slot="icon"]) {
padding-left: var(--space-2);
padding-right: var(--space-2);
}
}
[data-slot="icon"] {
width: var(--space-4);
height: var(--space-4);
transition: transform 0.2s ease;
}
&[data-rotate] [data-slot="icon"] {
transform: rotate(180deg);
}
}

View File

@@ -1,84 +0,0 @@
[data-component="dialog-overlay"] {
pointer-events: none !important;
position: fixed;
inset: 0;
animation-name: fadeOut;
animation-duration: 200ms;
animation-timing-function: ease;
opacity: 0;
backdrop-filter: blur(2px);
&[data-expanded] {
animation-name: fadeIn;
opacity: 1;
pointer-events: auto !important;
}
}
[data-component="dialog-center"] {
position: fixed;
inset: 0;
padding-top: 10vh;
justify-content: center;
pointer-events: none;
[data-slot="content"] {
width: 45rem;
margin: 0 auto;
transition: 150ms width;
background-color: var(--color-bg);
border-width: 2px;
border-color: var(--color-border);
overflow: hidden;
display: flex;
flex-direction: column;
gap: var(--space-3);
outline: none;
animation-duration: 1ms;
animation-name: zoomOut;
animation-timing-function: ease;
box-shadow: 8px 8px 0px 0px var(--color-gray-4);
&[data-expanded] {
animation-name: zoomIn;
}
&[data-transition] {
animation-duration: 200ms;
}
&[data-size="sm"] {
width: 30rem;
}
[data-slot="header"] {
display: flex;
padding: var(--space-4) var(--space-4) 0;
[data-slot="title"] {
}
}
[data-slot="main"] {
padding: 0 var(--space-4);
&:has([data-slot="options"]) {
padding: 0;
display: flex;
flex-direction: column;
gap: var(--space-4);
}
}
[data-slot="input"] {
}
[data-slot="footer"] {
padding: var(--space-4);
display: flex;
gap: var(--space-4);
justify-content: end;
}
}
}

View File

@@ -1,34 +0,0 @@
[data-component="input"] {
font-size: var(--font-size-md);
background: transparent;
caret-color: var(--color-accent);
font-family: var(--font-mono);
height: var(--space-11);
padding: 0 var(--space-4);
width: 100%;
resize: none;
border: 2px solid var(--color-border);
&::placeholder {
color: var(--color-text-dimmed);
opacity: 0.75;
}
&:focus {
outline: 0;
}
&[data-size="sm"] {
height: var(--space-9);
padding: 0 var(--space-3);
font-size: var(--font-size-xs);
}
&[data-size="md"] {
}
&[data-size="lg"] {
height: var(--space-12);
font-size: var(--font-size-lg);
}
}

View File

@@ -1,17 +0,0 @@
[data-component="label"] {
letter-spacing: -0.03125rem;
text-transform: uppercase;
color: var(--color-text-dimmed);
font-weight: 500;
font-size: var(--font-size-md);
&[data-size="sm"] {
font-size: var(--font-size-sm);
}
&[data-size="md"] {
}
&[data-size="lg"] {
font-size: var(--font-size-lg);
}
}

View File

@@ -1,32 +0,0 @@
[data-component="title-bar"] {
display: flex;
align-items: center;
justify-content: space-between;
height: 72px;
padding: 0 var(--space-4);
border-bottom: 2px solid var(--color-border);
[data-slot="left"] {
display: flex;
flex-direction: column;
gap: var(--space-1-5);
h1 {
letter-spacing: -0.03125rem;
font-size: var(--font-size-xl);
text-transform: uppercase;
font-weight: 600;
}
p {
color: var(--color-text-dimmed);
}
}
}
@media (max-width: 40rem) {
[data-component="title-bar"] {
display: none;
}
}

View File

@@ -1,50 +0,0 @@
/* tokens */
@import "./token/color.css";
@import "./token/reset.css";
@import "./token/animation.css";
@import "./token/font.css";
@import "./token/space.css";
/* components */
@import "./component/label.css";
@import "./component/input.css";
@import "./component/button.css";
@import "./component/dialog.css";
@import "./component/title-bar.css";
body {
font-family: var(--font-mono);
line-height: 1;
color: var(--color-text);
background-color: var(--color-bg);
cursor: default;
user-select: none;
text-underline-offset: 0.1875rem;
}
a {
text-decoration: underline;
&:active {
color: var(--color-text-accent);
}
}
::selection {
background-color: var(--color-text-accent-invert);
}
/* Responsive utilities */
[data-max-width] {
width: 100%;
& > * {
max-width: 90rem;
margin-left: auto;
margin-right: auto;
width: 100%;
}
&[data-max-width-64] > * {
max-width: 64rem;
}
}

View File

@@ -1,23 +0,0 @@
@keyframes zoomIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes zoomOut {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.95);
}
}

View File

@@ -1,88 +0,0 @@
:root {
--color-white: hsl(0, 0%, 100%);
--color-gray-1: hsl(224, 20%, 94%);
--color-gray-2: hsl(224, 6%, 77%);
--color-gray-3: hsl(224, 6%, 56%);
--color-gray-4: hsl(224, 7%, 36%);
--color-gray-5: hsl(224, 10%, 23%);
--color-gray-6: hsl(224, 14%, 16%);
--color-black: hsl(224, 10%, 10%);
--hue-orange: 41;
--color-orange-low: hsl(var(--hue-orange), 39%, 22%);
--color-orange: hsl(var(--hue-orange), 82%, 63%);
--color-orange-high: hsl(var(--hue-orange), 82%, 87%);
--hue-green: 101;
--color-green-low: hsl(var(--hue-green), 39%, 22%);
--color-green: hsl(var(--hue-green), 82%, 63%);
--color-green-high: hsl(var(--hue-green), 82%, 80%);
--hue-blue: 234;
--color-blue-low: hsl(var(--hue-blue), 54%, 20%);
--color-blue: hsl(var(--hue-blue), 100%, 60%);
--color-blue-high: hsl(var(--hue-blue), 100%, 87%);
--hue-purple: 281;
--color-purple-low: hsl(var(--hue-purple), 39%, 22%);
--color-purple: hsl(var(--hue-purple), 82%, 63%);
--color-purple-high: hsl(var(--hue-purple), 82%, 89%);
--hue-red: 339;
--color-red-low: hsl(var(--hue-red), 39%, 22%);
--color-red: hsl(var(--hue-red), 82%, 63%);
--color-red-high: hsl(var(--hue-red), 82%, 87%);
--color-accent-low: hsl(13, 75%, 30%);
--color-accent: hsl(13, 88%, 57%);
--color-accent-high: hsl(13, 100%, 78%);
--color-text: var(--color-gray-1);
--color-text-dimmed: var(--color-gray-3);
--color-text-accent: var(--color-accent);
--color-text-invert: var(--color-black);
--color-text-accent-invert: var(--color-accent-high);
--color-bg: var(--color-black);
--color-bg-surface: var(--color-gray-5);
--color-bg-accent: var(--color-accent-high);
--color-border: var(--color-gray-2);
--color-backdrop-overlay: hsla(223, 13%, 10%, 0.66);
}
:root[data-color-mode="light"] {
--color-white: hsl(224, 10%, 10%);
--color-gray-1: hsl(224, 14%, 16%);
--color-gray-2: hsl(224, 10%, 23%);
--color-gray-3: hsl(224, 7%, 36%);
--color-gray-4: hsl(224, 6%, 56%);
--color-gray-5: hsl(224, 6%, 77%);
--color-gray-6: hsl(224, 20%, 94%);
--color-gray-7: hsl(224, 19%, 97%);
--color-black: hsl(0, 0%, 100%);
--color-orange-high: hsl(var(--hue-orange), 80%, 25%);
--color-orange: hsl(var(--hue-orange), 90%, 60%);
--color-orange-low: hsl(var(--hue-orange), 90%, 88%);
--color-green-high: hsl(var(--hue-green), 80%, 22%);
--color-green: hsl(var(--hue-green), 90%, 46%);
--color-green-low: hsl(var(--hue-green), 85%, 90%);
--color-blue-high: hsl(var(--hue-blue), 80%, 30%);
--color-blue: hsl(var(--hue-blue), 90%, 60%);
--color-blue-low: hsl(var(--hue-blue), 88%, 90%);
--color-purple-high: hsl(var(--hue-purple), 90%, 30%);
--color-purple: hsl(var(--hue-purple), 90%, 60%);
--color-purple-low: hsl(var(--hue-purple), 80%, 90%);
--color-red-high: hsl(var(--hue-red), 80%, 30%);
--color-red: hsl(var(--hue-red), 90%, 60%);
--color-red-low: hsl(var(--hue-red), 80%, 90%);
--color-accent-high: hsl(13, 75%, 26%);
--color-accent: hsl(13, 88%, 60%);
--color-accent-low: hsl(13, 100%, 89%);
--color-text-accent: var(--color-accent);
--color-text-dimmed: var(--color-gray-4);
--color-text-invert: var(--color-black);
--color-text-accent-invert: var(--color-accent-low);
--color-bg-surface: var(--color-gray-6);
--color-bg-accent: var(--color-accent);
--color-backdrop-overlay: hsla(225, 9%, 36%, 0.66);
}

View File

@@ -1,20 +0,0 @@
:root {
--font-size-2xs: 0.6875rem;
--font-size-xs: 0.75rem;
--font-size-sm: 0.8125rem;
--font-size-md: 0.9375rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
--font-size-3xl: 1.875rem;
--font-size-4xl: 2.25rem;
--font-size-5xl: 3rem;
--font-size-6xl: 3.75rem;
--font-size-7xl: 4.5rem;
--font-size-8xl: 6rem;
--font-size-9xl: 8rem;
--font-mono: IBM Plex Mono, monospace;
--font-sans: Rubik, sans-serif;
--font-line-height: 1.75;
}

View File

@@ -1,212 +0,0 @@
* {
margin: 0;
padding: 0;
font: inherit;
}
*,
*::before,
*::after {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: var(--global-color-border, currentColor);
}
html {
line-height: 1.5;
--font-fallback: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
-webkit-text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-moz-tab-size: 4;
tab-size: 4;
font-family: var(--global-font-body, var(--font-fallback));
}
hr {
height: 0;
color: inherit;
border-top-width: 1px;
}
body {
height: 100%;
line-height: inherit;
}
img {
border-style: none;
}
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
vertical-align: middle;
}
img,
video {
max-width: 100%;
height: auto;
}
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
ol,
ul {
list-style: none;
}
code,
kbd,
pre,
samp {
font-size: 1em;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
background-color: transparent;
background-image: none;
}
button,
input,
optgroup,
select,
textarea {
color: inherit;
}
button,
select {
text-transform: none;
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
input::placeholder,
textarea::placeholder {
opacity: 1;
color: var(--global-color-placeholder, #9ca3af);
}
textarea {
resize: vertical;
}
summary {
display: list-item;
}
small {
font-size: 80%;
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
dialog {
padding: 0;
}
a {
color: inherit;
text-decoration: inherit;
}
abbr:where([title]) {
text-decoration: underline dotted;
}
b,
strong {
font-weight: bolder;
}
code,
kbd,
samp,
pre {
font-size: 1em;
--font-mono-fallback: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New";
font-family: var(--global-font-mono, var(--font-fallback));
}
input[type="text"],
input[type="email"],
input[type="search"],
input[type="password"] {
-webkit-appearance: none;
-moz-appearance: none;
}
input[type="search"] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
::-webkit-search-decoration,
::-webkit-search-cancel-button {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
input[type="number"] {
-moz-appearance: textfield;
}
:-moz-ui-invalid {
box-shadow: none;
}
:-moz-focusring {
outline: auto;
}

View File

@@ -1,39 +0,0 @@
:root {
--space-0: 0;
--space-px: 1px;
--space-0-5: 0.125rem;
--space-1: 0.25rem;
--space-1-5: 0.375rem;
--space-2: 0.5rem;
--space-2-5: 0.625rem;
--space-3: 0.75rem;
--space-3-5: 0.875rem;
--space-4: 1rem;
--space-4-5: 1.125rem;
--space-5: 1.25rem;
--space-6: 1.5rem;
--space-7: 1.75rem;
--space-8: 2rem;
--space-9: 2.25rem;
--space-10: 2.5rem;
--space-11: 2.75rem;
--space-12: 3rem;
--space-14: 3.5rem;
--space-16: 4rem;
--space-18: 4.5rem;
--space-20: 5rem;
--space-24: 6rem;
--space-28: 7rem;
--space-32: 8rem;
--space-36: 9rem;
--space-40: 10rem;
--space-44: 11rem;
--space-48: 12rem;
--space-52: 13rem;
--space-56: 14rem;
--space-60: 15rem;
--space-64: 16rem;
--space-72: 18rem;
--space-80: 20rem;
--space-96: 24rem;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,67 +0,0 @@
import { JSX } from "solid-js"
export function IconLogomark(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 28 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M14 31.5L0 23.6873V7.81266L14 0L28 7.81266V23.6873L14 31.5ZM14 28.4664L25.3456 22.0251V9.47493L14 2.99209L2.65443 9.47493V22.0251L14 28.4664ZM13.9572 24.6016C12.2732 24.6016 10.7176 24.1999 9.29052 23.3964C7.89195 22.593 6.7788 21.5125 5.95107 20.155C5.12334 18.7698 4.70948 17.2599 4.70948 15.6253C4.70948 13.9908 5.12334 12.4947 5.95107 11.1372C6.7788 9.77968 7.89195 8.69921 9.29052 7.89578C10.7176 7.06464 12.2732 6.64908 13.9572 6.64908C15.6412 6.64908 17.1825 7.06464 18.581 7.89578C19.9796 8.69921 21.0928 9.77968 21.9205 11.1372C22.7768 12.4947 23.2049 13.9908 23.2049 15.6253C23.2049 17.2599 22.791 18.7559 21.9633 20.1135C21.1356 21.471 20.0224 22.5653 18.6239 23.3964C17.2253 24.1999 15.6697 24.6016 13.9572 24.6016ZM13.9572 22.2744C15.213 22.2744 16.3547 21.9697 17.3823 21.3602C18.4098 20.7507 19.2375 19.9472 19.8654 18.9499C20.4934 17.9248 20.8073 16.8166 20.8073 15.6253C20.8073 14.4063 20.4934 13.2982 19.8654 12.3008C19.2375 11.3034 18.4098 10.5 17.3823 9.8905C16.3547 9.281 15.213 8.97625 13.9572 8.97625C12.7299 8.97625 11.5882 9.281 10.5321 9.8905C9.50459 10.5 8.67686 11.3034 8.04893 12.3008C7.421 13.2982 7.10703 14.4063 7.10703 15.6253C7.10703 16.8166 7.421 17.9248 8.04893 18.9499C8.67686 19.9472 9.50459 20.7507 10.5321 21.3602C11.5882 21.9697 12.7299 22.2744 13.9572 22.2744Z"
fill="currentColor"
/>
</svg>
)
}
export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 220 35" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M14 31.5L0 23.6873V7.81266L14 0L28 7.81266V23.6873L14 31.5ZM14 28.4664L25.3456 22.0251V9.47493L14 2.99208L2.65443 9.47493V22.0251L14 28.4664ZM13.9572 24.6016C12.2732 24.6016 10.7176 24.1999 9.29052 23.3964C7.89195 22.593 6.7788 21.5125 5.95107 20.155C5.12334 18.7698 4.70948 17.2599 4.70948 15.6253C4.70948 13.9908 5.12334 12.4947 5.95107 11.1372C6.7788 9.77968 7.89195 8.69921 9.29052 7.89578C10.7176 7.06464 12.2732 6.64908 13.9572 6.64908C15.6412 6.64908 17.1825 7.06464 18.581 7.89578C19.9796 8.69921 21.0928 9.77968 21.9205 11.1372C22.7768 12.4947 23.2049 13.9908 23.2049 15.6253C23.2049 17.2599 22.791 18.7559 21.9633 20.1135C21.1356 21.471 20.0224 22.5653 18.6239 23.3964C17.2253 24.1999 15.6697 24.6016 13.9572 24.6016ZM13.9572 22.2744C15.213 22.2744 16.3547 21.9697 17.3823 21.3602C18.4098 20.7507 19.2375 19.9472 19.8654 18.9499C20.4934 17.9248 20.8073 16.8166 20.8073 15.6253C20.8073 14.4063 20.4934 13.2982 19.8654 12.3008C19.2375 11.3034 18.4098 10.5 17.3823 9.8905C16.3547 9.281 15.213 8.97625 13.9572 8.97625C12.7299 8.97625 11.5882 9.281 10.5321 9.8905C9.50459 10.5 8.67686 11.3034 8.04893 12.3008C7.421 13.2982 7.10703 14.4063 7.10703 15.6253C7.10703 16.8166 7.421 17.9248 8.04893 18.9499C8.67686 19.9472 9.50459 20.7507 10.5321 21.3602C11.5882 21.9697 12.7299 22.2744 13.9572 22.2744Z"
fill="currentColor"
/>
<path
d="M45.628 28.432C42.556 28.432 40.252 27.688 38.716 26.2C37.204 24.688 36.448 22.552 36.448 19.792V12.628C36.448 9.84399 37.204 7.70799 38.716 6.21999C40.252 4.73199 42.556 3.98799 45.628 3.98799C48.7 3.98799 50.992 4.73199 52.504 6.21999C54.04 7.70799 54.808 9.84399 54.808 12.628V19.792C54.808 22.552 54.04 24.688 52.504 26.2C50.992 27.688 48.7 28.432 45.628 28.432ZM45.628 25.228C47.452 25.228 48.832 24.76 49.768 23.824C50.704 22.864 51.172 21.484 51.172 19.684V12.736C51.172 10.912 50.704 9.53199 49.768 8.59599C48.832 7.65999 47.452 7.19199 45.628 7.19199C43.828 7.19199 42.448 7.65999 41.488 8.59599C40.552 9.53199 40.084 10.912 40.084 12.736V19.684C40.084 21.484 40.552 22.864 41.488 23.824C42.448 24.76 43.828 25.228 45.628 25.228Z"
fill="currentColor"
/>
<path
d="M67.0294 28.432C66.1654 28.432 65.2414 28.312 64.2574 28.072C63.2734 27.856 62.4694 27.544 61.8454 27.136L61.7734 24.184C62.4214 24.544 63.1414 24.832 63.9334 25.048C64.7254 25.24 65.4574 25.336 66.1294 25.336C67.4254 25.336 68.3614 24.964 68.9374 24.22C69.5374 23.452 69.8374 22.312 69.8374 20.8V16.912C69.8374 15.592 69.6094 14.632 69.1534 14.032C68.6974 13.408 68.0014 13.096 67.0654 13.096C66.2734 13.096 65.4694 13.336 64.6534 13.816C63.8374 14.272 62.8174 15.064 61.5934 16.192L61.4854 13.168C62.2534 12.448 62.9854 11.848 63.6814 11.368C64.3774 10.888 65.0854 10.528 65.8054 10.288C66.5254 10.048 67.2934 9.92799 68.1094 9.92799C69.8614 9.92799 71.1814 10.468 72.0694 11.548C72.9814 12.628 73.4374 14.272 73.4374 16.48V21.196C73.4374 23.572 72.8854 25.372 71.7814 26.596C70.7014 27.82 69.1174 28.432 67.0294 28.432ZM59.1094 34.66C58.8454 34.66 58.7134 34.528 58.7134 34.264V14.536C58.7134 13.936 58.6894 13.3 58.6414 12.628C58.5934 11.956 58.5334 11.356 58.4614 10.828C58.4374 10.516 58.5694 10.36 58.8574 10.36H61.4134C61.6774 10.36 61.8214 10.48 61.8454 10.72C61.8934 10.912 61.9414 11.164 61.9894 11.476C62.0374 11.788 62.0734 12.1 62.0974 12.412C62.1214 12.7 62.1334 12.916 62.1334 13.06L62.3134 14.968V34.264C62.3134 34.528 62.1814 34.66 61.9174 34.66H59.1094Z"
fill="currentColor"
/>
<path
d="M84.0237 28.432C81.5757 28.432 79.7157 27.88 78.4437 26.776C77.1957 25.672 76.5717 24.028 76.5717 21.844V17.056C76.5717 14.728 77.1957 12.964 78.4437 11.764C79.6917 10.54 81.5397 9.92799 83.9877 9.92799C86.4117 9.92799 88.2477 10.516 89.4957 11.692C90.7437 12.868 91.3677 14.584 91.3677 16.84V19.648C91.3677 19.912 91.2477 20.044 91.0077 20.044H80.1717V21.196C80.1717 22.66 80.4837 23.728 81.1077 24.4C81.7317 25.072 82.7397 25.408 84.1317 25.408C85.1877 25.408 86.0037 25.24 86.5797 24.904C87.1557 24.568 87.4437 24.052 87.4437 23.356C87.4437 23.092 87.5877 22.96 87.8757 22.96H90.6477C90.8637 22.96 90.9957 23.08 91.0437 23.32C91.1397 24.928 90.5637 26.188 89.3157 27.1C88.0677 27.988 86.3037 28.432 84.0237 28.432ZM80.1717 17.308H87.8037V17.128C87.8037 15.688 87.4917 14.632 86.8677 13.96C86.2437 13.288 85.2957 12.952 84.0237 12.952C82.7277 12.952 81.7557 13.3 81.1077 13.996C80.4837 14.692 80.1717 15.736 80.1717 17.128V17.308Z"
fill="currentColor"
/>
<path
d="M106.647 28C106.383 28 106.251 27.868 106.251 27.604V16.3C106.251 15.196 106.023 14.392 105.567 13.888C105.135 13.36 104.451 13.096 103.515 13.096C102.627 13.096 101.751 13.36 100.887 13.888C100.023 14.392 98.9906 15.244 97.7906 16.444L97.7186 13.528C98.4866 12.712 99.2306 12.04 99.9506 11.512C100.671 10.984 101.403 10.588 102.147 10.324C102.915 10.06 103.719 9.92799 104.559 9.92799C106.287 9.92799 107.595 10.432 108.483 11.44C109.395 12.424 109.851 13.912 109.851 15.904V27.604C109.851 27.868 109.731 28 109.491 28H106.647ZM95.2346 28C94.9706 28 94.8386 27.868 94.8386 27.604V14.824C94.8386 14.152 94.8026 13.444 94.7306 12.7C94.6826 11.932 94.6346 11.32 94.5866 10.864C94.5386 10.528 94.6706 10.36 94.9826 10.36H97.5386C97.7786 10.36 97.9226 10.48 97.9706 10.72C98.0186 10.936 98.0666 11.236 98.1146 11.62C98.1626 12.004 98.2106 12.412 98.2586 12.844C98.3066 13.276 98.3306 13.648 98.3306 13.96L98.4386 15.112V27.604C98.4386 27.868 98.3066 28 98.0426 28H95.2346Z"
fill="currentColor"
/>
<path
d="M122.87 28.432C119.894 28.432 117.626 27.7 116.066 26.236C114.506 24.748 113.726 22.6 113.726 19.792V12.664C113.726 9.83199 114.506 7.68399 116.066 6.21999C117.65 4.73199 119.93 3.98799 122.906 3.98799C124.85 3.98799 126.542 4.31199 127.982 4.95999C129.422 5.60799 130.49 6.54399 131.186 7.76799C131.906 8.96799 132.146 10.408 131.906 12.088C131.882 12.232 131.846 12.352 131.798 12.448C131.75 12.544 131.654 12.592 131.51 12.592H128.63C128.366 12.592 128.246 12.46 128.27 12.196C128.342 10.612 127.922 9.37599 127.01 8.48799C126.098 7.59999 124.742 7.15599 122.942 7.15599C121.142 7.15599 119.762 7.62399 118.802 8.55999C117.842 9.49599 117.362 10.864 117.362 12.664V19.756C117.362 21.556 117.842 22.924 118.802 23.86C119.762 24.796 121.142 25.264 122.942 25.264C124.766 25.264 126.146 24.82 127.082 23.932C128.042 23.02 128.438 21.784 128.27 20.224C128.246 19.96 128.366 19.828 128.63 19.828H131.474C131.714 19.828 131.858 19.996 131.906 20.332C132.05 21.964 131.762 23.392 131.042 24.616C130.346 25.816 129.29 26.752 127.874 27.424C126.458 28.096 124.79 28.432 122.87 28.432Z"
fill="currentColor"
/>
<path
d="M142.558 28.432C140.086 28.432 138.19 27.832 136.87 26.632C135.55 25.432 134.89 23.62 134.89 21.196V17.164C134.89 14.74 135.538 12.928 136.834 11.728C138.154 10.528 140.062 9.92799 142.558 9.92799C145.03 9.92799 146.914 10.528 148.21 11.728C149.53 12.928 150.19 14.74 150.19 17.164V21.196C150.19 23.62 149.542 25.432 148.246 26.632C146.95 27.832 145.054 28.432 142.558 28.432ZM142.558 25.3C143.974 25.3 144.994 24.94 145.618 24.22C146.266 23.5 146.59 22.384 146.59 20.872V17.488C146.59 16 146.266 14.896 145.618 14.176C144.994 13.432 143.974 13.06 142.558 13.06C141.118 13.06 140.074 13.432 139.426 14.176C138.802 14.896 138.49 16 138.49 17.488V20.872C138.49 22.384 138.802 23.5 139.426 24.22C140.074 24.94 141.118 25.3 142.558 25.3Z"
fill="currentColor"
/>
<path
d="M165.493 28C165.229 28 165.097 27.868 165.097 27.604V16.3C165.097 15.196 164.869 14.392 164.413 13.888C163.981 13.36 163.297 13.096 162.361 13.096C161.473 13.096 160.597 13.36 159.733 13.888C158.869 14.392 157.837 15.244 156.637 16.444L156.565 13.528C157.333 12.712 158.077 12.04 158.797 11.512C159.517 10.984 160.249 10.588 160.993 10.324C161.761 10.06 162.565 9.92799 163.405 9.92799C165.133 9.92799 166.441 10.432 167.329 11.44C168.241 12.424 168.697 13.912 168.697 15.904V27.604C168.697 27.868 168.577 28 168.337 28H165.493ZM154.081 28C153.817 28 153.685 27.868 153.685 27.604V14.824C153.685 14.152 153.649 13.444 153.577 12.7C153.529 11.932 153.481 11.32 153.433 10.864C153.385 10.528 153.517 10.36 153.829 10.36H156.385C156.625 10.36 156.769 10.48 156.817 10.72C156.865 10.936 156.913 11.236 156.961 11.62C157.009 12.004 157.057 12.412 157.105 12.844C157.153 13.276 157.177 13.648 157.177 13.96L157.285 15.112V27.604C157.285 27.868 157.153 28 156.889 28H154.081Z"
fill="currentColor"
/>
<path
d="M179.477 28.432C178.157 28.432 177.113 28.228 176.345 27.82C175.577 27.388 175.013 26.74 174.653 25.876C174.317 24.988 174.149 23.884 174.149 22.564V13.42H171.413C171.173 13.42 171.053 13.288 171.053 13.024V10.756C171.053 10.492 171.173 10.36 171.413 10.36H174.257L174.689 6.03999C174.713 5.79999 174.857 5.67999 175.121 5.67999H177.317C177.581 5.67999 177.713 5.79999 177.713 6.03999L177.749 10.36H182.897C183.161 10.36 183.293 10.492 183.293 10.756V13.024C183.293 13.288 183.161 13.42 182.897 13.42H177.749V22.42C177.749 23.476 177.929 24.232 178.289 24.688C178.673 25.12 179.321 25.336 180.233 25.336C180.689 25.336 181.145 25.288 181.601 25.192C182.081 25.072 182.501 24.928 182.861 24.76C183.173 24.64 183.329 24.736 183.329 25.048V27.388C183.329 27.604 183.233 27.748 183.041 27.82C182.585 27.988 182.057 28.132 181.457 28.252C180.881 28.372 180.221 28.432 179.477 28.432Z"
fill="currentColor"
/>
<path
d="M186.233 28C185.969 28 185.837 27.868 185.837 27.604V14.716C185.837 14.02 185.813 13.36 185.765 12.736C185.741 12.088 185.681 11.44 185.585 10.792C185.537 10.504 185.669 10.36 185.981 10.36H188.537C188.801 10.36 188.945 10.48 188.969 10.72C189.065 11.152 189.137 11.656 189.185 12.232C189.233 12.808 189.257 13.3 189.257 13.708L189.437 15.688V27.604C189.437 27.868 189.305 28 189.041 28H186.233ZM189.041 17.02L188.897 13.744C189.401 13 189.977 12.352 190.625 11.8C191.273 11.224 191.933 10.768 192.605 10.432C193.277 10.096 193.901 9.92799 194.477 9.92799C194.885 9.92799 195.209 9.96399 195.449 10.036C195.641 10.108 195.749 10.252 195.773 10.468C195.821 10.996 195.833 11.56 195.809 12.16C195.809 12.736 195.773 13.288 195.701 13.816C195.653 14.104 195.485 14.212 195.197 14.14C195.029 14.092 194.837 14.056 194.621 14.032C194.405 14.008 194.177 13.996 193.937 13.996C193.361 13.996 192.785 14.128 192.209 14.392C191.633 14.632 191.069 14.98 190.517 15.436C189.989 15.868 189.497 16.396 189.041 17.02Z"
fill="currentColor"
/>
<path
d="M204.648 28.432C202.176 28.432 200.28 27.832 198.96 26.632C197.64 25.432 196.98 23.62 196.98 21.196V17.164C196.98 14.74 197.628 12.928 198.924 11.728C200.244 10.528 202.152 9.92799 204.648 9.92799C207.12 9.92799 209.004 10.528 210.3 11.728C211.62 12.928 212.28 14.74 212.28 17.164V21.196C212.28 23.62 211.632 25.432 210.336 26.632C209.04 27.832 207.144 28.432 204.648 28.432ZM204.648 25.3C206.064 25.3 207.084 24.94 207.708 24.22C208.356 23.5 208.68 22.384 208.68 20.872V17.488C208.68 16 208.356 14.896 207.708 14.176C207.084 13.432 206.064 13.06 204.648 13.06C203.208 13.06 202.164 13.432 201.516 14.176C200.892 14.896 200.58 16 200.58 17.488V20.872C200.58 22.384 200.892 23.5 201.516 24.22C202.164 24.94 203.208 25.3 204.648 25.3Z"
fill="currentColor"
/>
<path
d="M216.171 28C215.907 28 215.775 27.868 215.775 27.604V3.80799C215.775 3.54399 215.907 3.41199 216.171 3.41199H218.979C219.243 3.41199 219.375 3.54399 219.375 3.80799V27.604C219.375 27.868 219.243 28 218.979 28H216.171Z"
fill="currentColor"
/>
</svg>
)
}

View File

@@ -1,26 +0,0 @@
import { ParentProps, Show, createContext, useContext } from "solid-js"
export function createInitializedContext<
Name extends string,
T extends { ready: boolean },
>(name: Name, cb: () => T) {
const ctx = createContext<T>()
return {
use: () => {
const context = useContext(ctx)
if (!context) throw new Error(`No ${name} context`)
return context
},
provider: (props: ParentProps) => {
const value = cb()
return (
<Show when={value.ready}>
<ctx.Provider value={value} {...props}>
{props.children}
</ctx.Provider>
</Show>
)
},
}
}

View File

@@ -1,63 +0,0 @@
import { defineConfig } from "vite"
import solidPlugin from "vite-plugin-solid"
import pages from "vite-plugin-pages"
import fs from "fs"
import path from "path"
import { generateHydrationScript, getAssets } from "solid-js/web"
export default defineConfig({
plugins: [
pages({
exclude: ["**/~*", "**/components/*"],
}),
solidPlugin({ ssr: true }),
{
name: "vite-plugin-solid-ssr-render",
apply: (config, env) => {
return env.command === "build" && !config.build?.ssr
},
closeBundle: async () => {
console.log("Pre-rendering pages...")
const dist = path.resolve("dist")
try {
const serverEntryPath = path.join(dist, "server/entry-server.js")
const serverEntry = await import(serverEntryPath + "?t=" + Date.now())
const template = fs.readFileSync(
path.join(dist, "client/index.html"),
"utf-8",
)
fs.writeFileSync(path.join(dist, "client/fallback.html"), template)
const routes = ["/"]
for (const route of routes) {
const { app } = await serverEntry.render({ url: route })
const html = template
.replace("<!--ssr-outlet-->", app)
.replace("<!--ssr-head-->", generateHydrationScript())
.replace("<!--ssr-assets-->", getAssets())
const filePath = path.join(
dist,
`client${route === "/" ? "/index" : route}.html`,
)
fs.mkdirSync(path.dirname(filePath), {
recursive: true,
})
fs.writeFileSync(filePath, html)
console.log(`Pre-rendered: ${filePath}`)
}
} catch (error) {
console.error("Error during pre-rendering:", error)
}
},
},
],
server: {
port: 3000,
host: "0.0.0.0",
},
build: {
target: "esnext",
},
})

View File

@@ -4,7 +4,7 @@
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest"
"@types/bun": "catalog:"
},
"peerDependencies": {
"typescript": "^5"

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

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

View File

@@ -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

View File

@@ -1,20 +1,42 @@
import { WebhookEndpoint } from "pulumi-stripe"
import { domain } from "./stage"
import { web } from "./app"
////////////////
// 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: 5432,
host: password.accessHostUrl,
database: cluster.name,
username: password.username,
password: password.plaintext,
port: 3306,
},
})
@@ -22,7 +44,7 @@ new sst.x.DevCommand("Studio", {
link: [database],
dev: {
command: "bun db studio",
directory: "cloud/core",
directory: "packages/console/core",
autostart: true,
},
})
@@ -37,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],
})
@@ -46,8 +68,8 @@ export const auth = new sst.cloudflare.Worker("AuthApi", {
// GATEWAY
////////////////
export const stripeWebhook = new WebhookEndpoint("StripeWebhook", {
url: $interpolate`https://api.gateway.${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",
@@ -79,7 +101,9 @@ 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 XAI_API_KEY = new sst.Secret("XAI_API_KEY")
const BASETEN_API_KEY = new sst.Secret("BASETEN_API_KEY")
const FIREWORKS_API_KEY = new sst.Secret("FIREWORKS_API_KEY")
const 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!) },
@@ -87,10 +111,23 @@ 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,
////////////////
// CONSOLE
////////////////
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,
@@ -98,37 +135,23 @@ export const gateway = new sst.cloudflare.Worker("GatewayApi", {
STRIPE_SECRET_KEY,
ANTHROPIC_API_KEY,
OPENAI_API_KEY,
ZHIPU_API_KEY,
XAI_API_KEY,
BASETEN_API_KEY,
FIREWORKS_API_KEY,
],
})
////////////////
// CONSOLE
////////////////
/*
export const console = new sst.cloudflare.x.StaticSite("Console", {
domain: `console.${domain}`,
path: "cloud/web",
build: {
command: "bun run build",
output: "dist/client",
},
environment: {
VITE_DOCS_URL: web.url.apply((url) => url!),
VITE_API_URL: gateway.url.apply((url) => url!),
//VITE_DOCS_URL: web.url.apply((url) => url!),
//VITE_API_URL: gateway.url.apply((url) => url!),
VITE_AUTH_URL: auth.url.apply((url) => url!),
},
})
*/
new sst.x.DevCommand("Solid", {
link: [database],
dev: {
directory: "cloud/app",
command: "bun dev",
},
environment: {
VITE_AUTH_URL: auth.url.apply((url) => url!),
transform: {
server: {
transform: {
worker: {
placement: { mode: "smart" },
tailConsumers: logProcessor ? [{ service: logProcessor.nodes.worker.scriptName }] : [],
},
},
},
},
})

10
infra/desktop.ts Normal file
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",
},
})

View File

@@ -1,15 +0,0 @@
{
"keep": {
"days": true,
"amount": 14
},
"auditLog": "/Users/adam/code/opencode/dev/logs/.496fc674ed58d31f8b883da41cc2adb4564aad58-audit.json",
"files": [
{
"date": 1755891797740,
"name": "/Users/adam/code/opencode/dev/logs/mcp-puppeteer-2025-08-22.log",
"hash": "dd9b1f2e98b661ba2f56b91dd9afbdb25e50adbdd52ed1b0eef1d2045235d17c"
}
],
"hashType": "sha256"
}

View File

@@ -1,6 +0,0 @@
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-08-22 14:43:17.765"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-08-22 14:43:17.766"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-08-22 14:46:45.539"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-08-22 14:46:45.540"}
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-08-22 14:53:08.159"}
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-08-22 14:53:08.160"}

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