Compare commits
357 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bccee29d2f | ||
|
|
ad307f7f89 | ||
|
|
eac11c0753 | ||
|
|
0e804c302c | ||
|
|
fb88cb0aa3 | ||
|
|
8fc6a25142 | ||
|
|
5079ba7ce5 | ||
|
|
19cb211b62 | ||
|
|
b2440e92e7 | ||
|
|
125624489b | ||
|
|
991f85c907 | ||
|
|
c00fbbdcae | ||
|
|
d4e9c60af7 | ||
|
|
0691815c0a | ||
|
|
985fd4d9a8 | ||
|
|
87fa8dc70c | ||
|
|
a782e3dac2 | ||
|
|
70da3a9399 | ||
|
|
1024537b47 | ||
|
|
2a4f21a694 | ||
|
|
5f61945090 | ||
|
|
41ce56494b | ||
|
|
172aeaaf14 | ||
|
|
bd69c5aca8 | ||
|
|
6a7eeb39c3 | ||
|
|
35a608cd53 | ||
|
|
6e19200fca | ||
|
|
fe45a76c55 | ||
|
|
bdac22cb07 | ||
|
|
5a507023a6 | ||
|
|
c398485213 | ||
|
|
bc9ff7e99f | ||
|
|
7447460b5a | ||
|
|
5345c828ca | ||
|
|
edeaab321a | ||
|
|
478ead6a05 | ||
|
|
468201190e | ||
|
|
cc0d460904 | ||
|
|
f8ab0de0ad | ||
|
|
322363f11b | ||
|
|
acd33c2fc5 | ||
|
|
b6fba03a7d | ||
|
|
fbced21b8e | ||
|
|
e7cb5d8345 | ||
|
|
918739057d | ||
|
|
e3a7096e44 | ||
|
|
c148f10bbd | ||
|
|
06495ea964 | ||
|
|
b64cecb079 | ||
|
|
e10bb58cb3 | ||
|
|
89167ae387 | ||
|
|
4b429029df | ||
|
|
c7e5d29109 | ||
|
|
a564267b29 | ||
|
|
594bdb43c2 | ||
|
|
ea66c02633 | ||
|
|
925ce6503e | ||
|
|
8a28d34fe9 | ||
|
|
8bea479df9 | ||
|
|
468d919a92 | ||
|
|
7e5527379d | ||
|
|
fcbc78180b | ||
|
|
bab1ca54e4 | ||
|
|
d644e0b8a7 | ||
|
|
e54ec45002 | ||
|
|
4b94d98f89 | ||
|
|
d0043a4a78 | ||
|
|
26ebf85b0e | ||
|
|
53481f9790 | ||
|
|
eadc2a8535 | ||
|
|
00a5ec5bd2 | ||
|
|
0b6b9062d9 | ||
|
|
c450549d0f | ||
|
|
1ba0155943 | ||
|
|
3d332a06b5 | ||
|
|
f709e0b48b | ||
|
|
57e1bffbd5 | ||
|
|
f321661b4c | ||
|
|
7ecdc1b5d8 | ||
|
|
39917a35ce | ||
|
|
bfe3f03e03 | ||
|
|
d01af65dbc | ||
|
|
80305813f5 | ||
|
|
061877e275 | ||
|
|
05e6c3d8a0 | ||
|
|
093fbca711 | ||
|
|
81deea855f | ||
|
|
5c67bebf86 | ||
|
|
9cc1f2884f | ||
|
|
f2b547cc45 | ||
|
|
70310a37b3 | ||
|
|
eb7f4e20df | ||
|
|
ea21bfd3c6 | ||
|
|
22d5be9bf8 | ||
|
|
1c878c662b | ||
|
|
55d154d4ac | ||
|
|
f5c7a94abe | ||
|
|
7ec3900208 | ||
|
|
5d95846df1 | ||
|
|
d47feb9969 | ||
|
|
8f135d13e3 | ||
|
|
f9ab4102f6 | ||
|
|
f9117bcc7f | ||
|
|
6e712f9faf | ||
|
|
b207ed2b7b | ||
|
|
945de4eddc | ||
|
|
cd655177d9 | ||
|
|
8f90497fc4 | ||
|
|
9659efca46 | ||
|
|
d0377a95cf | ||
|
|
3b20bf6d4f | ||
|
|
c3e52580b0 | ||
|
|
2badfcdcf4 | ||
|
|
f589fc2327 | ||
|
|
d3b6545e7c | ||
|
|
3f911b22b0 | ||
|
|
5199141369 | ||
|
|
d86d3e7ea1 | ||
|
|
fe8d29cb2b | ||
|
|
edd6198999 | ||
|
|
c3b2c27997 | ||
|
|
679aeb29f0 | ||
|
|
190413580f | ||
|
|
8c9fbc7717 | ||
|
|
9d3fdda674 | ||
|
|
449994f120 | ||
|
|
d772fff776 | ||
|
|
71b43fd02e | ||
|
|
f40b91ab7a | ||
|
|
6404bd006d | ||
|
|
75157e515c | ||
|
|
5a96ee8e1b | ||
|
|
ee6ceb4c64 | ||
|
|
9d53628e19 | ||
|
|
869b476145 | ||
|
|
223d487787 | ||
|
|
5ead6d7dd5 | ||
|
|
a98454217f | ||
|
|
cbb75d8577 | ||
|
|
4ab992a9a9 | ||
|
|
80b0a93d64 | ||
|
|
e749d48534 | ||
|
|
d7e873f807 | ||
|
|
c23510346b | ||
|
|
f9c5df05a1 | ||
|
|
02b4d1e2fc | ||
|
|
0b36eb8760 | ||
|
|
36bec9948c | ||
|
|
2db73c39df | ||
|
|
6107666d04 | ||
|
|
cc2bd7141f | ||
|
|
ee442975df | ||
|
|
9b1a508657 | ||
|
|
288c977596 | ||
|
|
6b799b304c | ||
|
|
92c126d875 | ||
|
|
7123fbeb47 | ||
|
|
84bb692193 | ||
|
|
079095d7a9 | ||
|
|
28e1d67ea4 | ||
|
|
c1940d1d2c | ||
|
|
869f629c14 | ||
|
|
a55943e469 | ||
|
|
84d95a0d2a | ||
|
|
7dfed8ca35 | ||
|
|
38ea0fc051 | ||
|
|
9223b6ed8f | ||
|
|
f8528c52d9 | ||
|
|
d63ce40af2 | ||
|
|
5acdd70587 | ||
|
|
b04df6c0d2 | ||
|
|
f1cbdf441c | ||
|
|
9420d80b73 | ||
|
|
c21161b75e | ||
|
|
aaff066457 | ||
|
|
c7fbf9de44 | ||
|
|
d88c17dad0 | ||
|
|
f57c3f7cf6 | ||
|
|
2460108223 | ||
|
|
84e8eea52e | ||
|
|
9efc2eaf2e | ||
|
|
37e2644452 | ||
|
|
22a78cf13f | ||
|
|
2e9806b320 | ||
|
|
ba839d4446 | ||
|
|
2bec21d81d | ||
|
|
e5271f3d1a | ||
|
|
1edb23c2c7 | ||
|
|
b1e6b9c7c9 | ||
|
|
20cb5a7c56 | ||
|
|
b1d44482bc | ||
|
|
e11102c9df | ||
|
|
7be9dc8e49 | ||
|
|
824e035815 | ||
|
|
d652b94a14 | ||
|
|
ebef2ea2d0 | ||
|
|
b5b8a0555d | ||
|
|
ae6154e1c3 | ||
|
|
0e19ca21ed | ||
|
|
baaff81a06 | ||
|
|
ffa5689885 | ||
|
|
0e409842e8 | ||
|
|
5a7a725787 | ||
|
|
f277512938 | ||
|
|
4ceabdffa0 | ||
|
|
c87480cf93 | ||
|
|
0df6fc1226 | ||
|
|
32ba2e02aa | ||
|
|
1ffc8be2b6 | ||
|
|
5f2945ae71 | ||
|
|
65baf76df6 | ||
|
|
3b6c0ec0b3 | ||
|
|
e9d902d844 | ||
|
|
e8b4f593a6 | ||
|
|
fc4f281408 | ||
|
|
f8c4f713a5 | ||
|
|
63c8874d2d | ||
|
|
71076d5c68 | ||
|
|
0319043b49 | ||
|
|
e0334d5569 | ||
|
|
ff6a93f355 | ||
|
|
733b21e22b | ||
|
|
3c3d6b65c2 | ||
|
|
9ca48d3a39 | ||
|
|
16f9edc1a0 | ||
|
|
8c2aec43b8 | ||
|
|
2564801bde | ||
|
|
7c99a03493 | ||
|
|
0e0460f6c0 | ||
|
|
8acd537d1d | ||
|
|
40c206c2f9 | ||
|
|
259c722208 | ||
|
|
e618cbc447 | ||
|
|
abd99aeb7d | ||
|
|
ad5fc76b11 | ||
|
|
ff1f4d6bf9 | ||
|
|
170ea9c32b | ||
|
|
65ced67432 | ||
|
|
9f46068c57 | ||
|
|
479cf2fa4f | ||
|
|
39c54f367f | ||
|
|
8c71107a93 | ||
|
|
ef10097329 | ||
|
|
36ee4b5ede | ||
|
|
ae84d5a734 | ||
|
|
cd53770734 | ||
|
|
4b1eca73eb | ||
|
|
fffcf69cd4 | ||
|
|
d4c01f858b | ||
|
|
8e17570c53 | ||
|
|
7f9d08b556 | ||
|
|
32a045f60b | ||
|
|
91adc3cd41 | ||
|
|
2bb9b4212f | ||
|
|
3472a50928 | ||
|
|
3aeac02bf1 | ||
|
|
52fcdcc37b | ||
|
|
78d6b3a963 | ||
|
|
15df2710fa | ||
|
|
02e492f6eb | ||
|
|
2d5bd26a59 | ||
|
|
8f58fef5ad | ||
|
|
14cb2d2af6 | ||
|
|
52fb571739 | ||
|
|
51c647ca89 | ||
|
|
52fa7840c2 | ||
|
|
2c61b39088 | ||
|
|
6c02d4ce66 | ||
|
|
11154ba697 | ||
|
|
f8ca524bf7 | ||
|
|
56222fff3c | ||
|
|
74f9fcea88 | ||
|
|
bc213e1a61 | ||
|
|
d795a38fc7 | ||
|
|
96698ea070 | ||
|
|
6fff10b670 | ||
|
|
194aea8e54 | ||
|
|
b6d2046b0e | ||
|
|
910ea84360 | ||
|
|
bc2e4e23c9 | ||
|
|
f5e75606e3 | ||
|
|
0707890359 | ||
|
|
6dbba8e326 | ||
|
|
413c9d9ad1 | ||
|
|
5e6dd312eb | ||
|
|
7218a662ab | ||
|
|
d947df3069 | ||
|
|
5bb1f5f0a0 | ||
|
|
d38594d34a | ||
|
|
925284c6c1 | ||
|
|
e716271466 | ||
|
|
df046e5e04 | ||
|
|
e0bfbcb663 | ||
|
|
c1c6aca31e | ||
|
|
725104572e | ||
|
|
4954edf8ae | ||
|
|
c1b4e1f19d | ||
|
|
89d820b1c4 | ||
|
|
e3e459fc50 | ||
|
|
4bf0541bd6 | ||
|
|
c81624aef7 | ||
|
|
df61aa801b | ||
|
|
6af0c2ec21 | ||
|
|
ce9d2ee04f | ||
|
|
4b30705c42 | ||
|
|
1f8d396b76 | ||
|
|
3752bb9717 | ||
|
|
16d66c209d | ||
|
|
6506e48c54 | ||
|
|
f0e8b7c29b | ||
|
|
a00b49d65b | ||
|
|
b1589be4ba | ||
|
|
eb24d2f847 | ||
|
|
9bb25a9260 | ||
|
|
535230dce4 | ||
|
|
555fb53505 | ||
|
|
b1e0a23351 | ||
|
|
2b69bcccdf | ||
|
|
e03f27381f | ||
|
|
aebd50da7e | ||
|
|
c02f58c2af | ||
|
|
c8f4d54f7f | ||
|
|
4983d255dd | ||
|
|
f2b4891ff0 | ||
|
|
efcb5abbf7 | ||
|
|
d37e58719e | ||
|
|
c6c153de95 | ||
|
|
417e8f619c | ||
|
|
f2094b7bb3 | ||
|
|
176dc51b2e | ||
|
|
f7d9a031e6 | ||
|
|
3e2478ebf9 | ||
|
|
1f4e8b4954 | ||
|
|
9a346a00fb | ||
|
|
0a13820927 | ||
|
|
c5fa3ee9f8 | ||
|
|
c294a18155 | ||
|
|
c3dc6d6df6 | ||
|
|
ef3425a177 | ||
|
|
0290b4aaf0 | ||
|
|
4ceee53480 | ||
|
|
469dc9095f | ||
|
|
661d50f95f | ||
|
|
3978a8e636 | ||
|
|
983e3b2ee3 | ||
|
|
1bd198eb34 | ||
|
|
3c502861a7 | ||
|
|
a52b352b24 | ||
|
|
79c73267cf | ||
|
|
54f7fb5019 | ||
|
|
dd97d784b6 | ||
|
|
91832bd5d7 | ||
|
|
3abca8fd4b | ||
|
|
f5b3992479 | ||
|
|
53f1f16122 | ||
|
|
4614e4983e | ||
|
|
37b6a55eb1 |
6
.github/workflows/format.yml
vendored
@@ -1,8 +1,12 @@
|
||||
name: Format
|
||||
name: format
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- production
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- production
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
format:
|
||||
|
||||
1
.github/workflows/publish.yml
vendored
@@ -71,3 +71,4 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
|
||||
50
.github/workflows/snapshot.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: snapshot
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- opentui
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- run: git fetch --force --tags
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ">=1.24.0"
|
||||
cache: true
|
||||
cache-dependency-path: go.sum
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.21
|
||||
|
||||
- name: Cache ~/.bun
|
||||
id: cache-bun
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-1-2-21-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-1-2-21-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
./packages/opencode/script/publish.ts
|
||||
env:
|
||||
OPENCODE_SNAPSHOT: true
|
||||
OPENCODE_TAG: ${{ github.ref_name }}
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
32
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- production
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- production
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.2.21
|
||||
|
||||
- name: run
|
||||
run: |
|
||||
git config --global user.email "bot@opencode.ai"
|
||||
git config --global user.name "opencode"
|
||||
bun install
|
||||
bun turbo test
|
||||
env:
|
||||
CI: true
|
||||
2
.github/workflows/typecheck.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Typecheck
|
||||
name: typecheck
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
2
.gitignore
vendored
@@ -8,3 +8,5 @@ node_modules
|
||||
openapi.json
|
||||
playground
|
||||
tmp
|
||||
dist
|
||||
.turbo
|
||||
|
||||
2
.husky/pre-push
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
bun run typecheck
|
||||
@@ -7,3 +7,6 @@ core:
|
||||
ci:
|
||||
ignore:
|
||||
wip:
|
||||
|
||||
prefer to explain WHY something was done from an end user perspective instead of
|
||||
WHAT was done.
|
||||
|
||||
172
STATS.md
@@ -1,78 +1,98 @@
|
||||
# Download Stats
|
||||
|
||||
| Date | GitHub Downloads | npm Downloads | Total |
|
||||
| ---------- | ---------------- | ---------------- | ----------------- |
|
||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
||||
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
||||
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
||||
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
||||
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
||||
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
||||
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
||||
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
||||
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
||||
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
||||
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
||||
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
||||
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
|
||||
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
|
||||
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
||||
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
||||
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
|
||||
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
|
||||
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
|
||||
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
|
||||
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
|
||||
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
|
||||
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
|
||||
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
|
||||
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
|
||||
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
|
||||
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
|
||||
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
||||
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
||||
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
||||
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
||||
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
||||
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
||||
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
|
||||
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
|
||||
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
|
||||
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
|
||||
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
|
||||
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
|
||||
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
|
||||
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
|
||||
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
|
||||
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
|
||||
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
|
||||
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
|
||||
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
|
||||
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
|
||||
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
|
||||
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
|
||||
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
|
||||
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
|
||||
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
|
||||
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
|
||||
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
|
||||
| Date | GitHub Downloads | npm Downloads | Total |
|
||||
| ---------- | ---------------- | ----------------- | ----------------- |
|
||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
||||
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
||||
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
||||
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
||||
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
||||
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
||||
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
||||
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
||||
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
||||
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
||||
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
||||
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
||||
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
|
||||
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
|
||||
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
||||
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
||||
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
|
||||
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
|
||||
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
|
||||
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
|
||||
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
|
||||
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
|
||||
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
|
||||
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
|
||||
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
|
||||
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
|
||||
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
|
||||
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
||||
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
||||
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
||||
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
||||
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
||||
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
||||
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
|
||||
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
|
||||
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
|
||||
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
|
||||
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
|
||||
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
|
||||
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
|
||||
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
|
||||
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
|
||||
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
|
||||
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
|
||||
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
|
||||
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
|
||||
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
|
||||
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
|
||||
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
|
||||
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
|
||||
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
|
||||
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
|
||||
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
|
||||
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
|
||||
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
|
||||
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
|
||||
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
|
||||
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
|
||||
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
|
||||
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
|
||||
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
|
||||
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
|
||||
| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
|
||||
| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
|
||||
| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
|
||||
| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
|
||||
| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
|
||||
| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
|
||||
| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
|
||||
| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
|
||||
| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
|
||||
| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
|
||||
| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
|
||||
| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,2 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 212 B |
@@ -1,2 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512"><rect width="336" height="336" x="128" y="128" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" rx="57" ry="57"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 443 B |
|
Before Width: | Height: | Size: 902 KiB |
|
Before Width: | Height: | Size: 456 KiB |
|
Before Width: | Height: | Size: 998 KiB |
|
Before Width: | Height: | Size: 592 KiB |
@@ -1,19 +0,0 @@
|
||||
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.5 16.5H24.5V33H8.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M48.5 16.5H64.5V33H48.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M120.5 16.5H136.5V33H120.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M160.5 16.5H176.5V33H160.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M192.5 16.5H208.5V33H192.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M232.5 16.5H248.5V33H232.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="white" fill-opacity="0.95"/>
|
||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="white" fill-opacity="0.95"/>
|
||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="white" fill-opacity="0.95"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="white" fill-opacity="0.95"/>
|
||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="white" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="white" fill-opacity="0.95"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,18 +0,0 @@
|
||||
<svg width="288" height="50" viewBox="0 0 288 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 16.5H24V33H8V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M48 16.5H64V33H48V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M120 16.5H136V33H120V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M160 16.5H176V33H160V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M192 16.5H208V33H192V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M232 16.5H248V33H232V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M264 0H288V8.5H272V16.5H288V25H272V33H288V41.5H264V0Z" fill="black" fill-opacity="0.95"/>
|
||||
<path d="M248 0H224V41.5H248V33H232V8.5H248V0Z" fill="black" fill-opacity="0.95"/>
|
||||
<path d="M256 8.5H248V33H256V8.5Z" fill="black" fill-opacity="0.95"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184 0H216V41.5H184V0ZM208 8.5H192V33H208V8.5Z" fill="black" fill-opacity="0.95"/>
|
||||
<path d="M144 8.5H136V41.5H144V8.5Z" fill="black" fill-opacity="0.55"/>
|
||||
<path d="M136 0H112V41.5H120V8.5H136V0Z" fill="black" fill-opacity="0.55"/>
|
||||
<path d="M80 0H104V8.5H88V16.5H104V25H88V33H104V41.5H80V0Z" fill="black" fill-opacity="0.55"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40 0H72V41.5H48V49.5H40V0ZM64 8.5H48V33H64V8.5Z" fill="black" fill-opacity="0.55"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="black" fill-opacity="0.55"/>
|
||||
<path d="M152 0H176V8.5H160V33H176V41.5H152V0Z" fill="black" fill-opacity="0.95"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,12 +0,0 @@
|
||||
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="black"/>
|
||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="black"/>
|
||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="black"/>
|
||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="black"/>
|
||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="black"/>
|
||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="black"/>
|
||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 981 B |
@@ -1,308 +0,0 @@
|
||||
/**
|
||||
* @deprecated Use zen/v1/chat/completions instead
|
||||
*/
|
||||
import { Resource } from "@opencode/cloud-resource"
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
||||
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
|
||||
import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
||||
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
|
||||
import { Identifier } from "@opencode/cloud-core/identifier.js"
|
||||
|
||||
const MODELS = {
|
||||
// "anthropic/claude-sonnet-4": {
|
||||
// auth: true,
|
||||
// api: "https://api.anthropic.com",
|
||||
// apiKey: Resource.ANTHROPIC_API_KEY.value,
|
||||
// model: "claude-sonnet-4-20250514",
|
||||
// cost: {
|
||||
// input: 0.0000015,
|
||||
// output: 0.000006,
|
||||
// reasoning: 0.0000015,
|
||||
// cacheRead: 0.0000001,
|
||||
// cacheWrite: 0.0000001,
|
||||
// },
|
||||
// headerMappings: {},
|
||||
// },
|
||||
"qwen/qwen3-coder": {
|
||||
id: "qwen/qwen3-coder",
|
||||
auth: true,
|
||||
api: "https://inference.baseten.co",
|
||||
apiKey: Resource.BASETEN_API_KEY.value,
|
||||
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||
cost: {
|
||||
input: 0.00000038,
|
||||
output: 0.00000153,
|
||||
reasoning: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
headerMappings: {},
|
||||
},
|
||||
"grok-code": {
|
||||
id: "x-ai/grok-code-fast-1",
|
||||
auth: false,
|
||||
api: "https://api.x.ai",
|
||||
apiKey: Resource.XAI_API_KEY.value,
|
||||
model: "grok-code",
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
reasoning: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
headerMappings: {
|
||||
"x-grok-conv-id": "x-opencode-session",
|
||||
"x-grok-req-id": "x-opencode-request",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class AuthError extends Error {}
|
||||
class CreditsError extends Error {}
|
||||
class ModelError extends Error {}
|
||||
|
||||
export async function POST(input: APIEvent) {
|
||||
try {
|
||||
const url = new URL(input.request.url)
|
||||
const body = await input.request.json()
|
||||
const MODEL = validateModel()
|
||||
const apiKey = await authenticate()
|
||||
await checkCredits()
|
||||
|
||||
// Request to model provider
|
||||
const res = await fetch(new URL(url.pathname.replace(/^\/gateway/, "") + url.search, MODEL.api), {
|
||||
method: "POST",
|
||||
headers: (() => {
|
||||
const headers = input.request.headers
|
||||
headers.delete("host")
|
||||
headers.delete("content-length")
|
||||
headers.set("authorization", `Bearer ${MODEL.apiKey}`)
|
||||
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
|
||||
headers.set(k, headers.get(v)!)
|
||||
})
|
||||
return headers
|
||||
})(),
|
||||
body: JSON.stringify({
|
||||
...body,
|
||||
model: MODEL.model,
|
||||
stream_options: {
|
||||
include_usage: true,
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
// Scrub response headers
|
||||
const resHeaders = new Headers()
|
||||
const keepHeaders = ["content-type", "cache-control"]
|
||||
for (const [k, v] of res.headers.entries()) {
|
||||
if (keepHeaders.includes(k.toLowerCase())) {
|
||||
resHeaders.set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-streaming response
|
||||
if (!body.stream) {
|
||||
const body = await res.json()
|
||||
await trackUsage(body)
|
||||
return new Response(JSON.stringify(body), {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
headers: resHeaders,
|
||||
})
|
||||
}
|
||||
|
||||
// Handle streaming response
|
||||
const stream = new ReadableStream({
|
||||
start(c) {
|
||||
const reader = res.body?.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ""
|
||||
|
||||
function pump(): Promise<void> {
|
||||
return (
|
||||
reader?.read().then(async ({ done, value }) => {
|
||||
if (done) {
|
||||
c.close()
|
||||
return
|
||||
}
|
||||
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
|
||||
const parts = buffer.split("\n\n")
|
||||
buffer = parts.pop() ?? ""
|
||||
|
||||
const usage = parts
|
||||
.map((part) => part.trim())
|
||||
.filter((part) => part.startsWith("data: "))
|
||||
.map((part) => {
|
||||
try {
|
||||
return JSON.parse(part.slice(6))
|
||||
} catch (e) {
|
||||
return {}
|
||||
}
|
||||
})
|
||||
.find((part) => part.usage)
|
||||
if (usage) await trackUsage(usage)
|
||||
|
||||
c.enqueue(value)
|
||||
|
||||
return pump()
|
||||
}) || Promise.resolve()
|
||||
)
|
||||
}
|
||||
|
||||
return pump()
|
||||
},
|
||||
})
|
||||
|
||||
return new Response(stream, {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
headers: resHeaders,
|
||||
})
|
||||
|
||||
function validateModel() {
|
||||
if (!(body.model in MODELS)) {
|
||||
throw new ModelError(`Model ${body.model} not supported`)
|
||||
}
|
||||
return MODELS[body.model as keyof typeof MODELS]
|
||||
}
|
||||
|
||||
async function authenticate() {
|
||||
try {
|
||||
const authHeader = input.request.headers.get("authorization")
|
||||
if (!authHeader || !authHeader.startsWith("Bearer ")) throw new AuthError("Missing API key.")
|
||||
|
||||
const apiKey = authHeader.split(" ")[1]
|
||||
const key = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
id: KeyTable.id,
|
||||
workspaceID: KeyTable.workspaceID,
|
||||
})
|
||||
.from(KeyTable)
|
||||
.where(eq(KeyTable.key, apiKey))
|
||||
.then((rows) => rows[0]),
|
||||
)
|
||||
|
||||
if (!key) throw new AuthError("Invalid API key.")
|
||||
return key
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
// ignore error if model does not require authentication
|
||||
if (!MODEL.auth) return
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async function checkCredits() {
|
||||
if (!apiKey || !MODEL.auth) return
|
||||
|
||||
const billing = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
balance: BillingTable.balance,
|
||||
})
|
||||
.from(BillingTable)
|
||||
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
|
||||
.then((rows) => rows[0]),
|
||||
)
|
||||
|
||||
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
|
||||
}
|
||||
|
||||
async function trackUsage(chunk: any) {
|
||||
console.log(`trackUsage ${apiKey}`)
|
||||
|
||||
if (!apiKey) return
|
||||
|
||||
const usage = chunk.usage
|
||||
const inputTokens = usage.prompt_tokens ?? 0
|
||||
const outputTokens = usage.completion_tokens ?? 0
|
||||
const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? 0
|
||||
const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens ?? 0
|
||||
//const cacheWriteTokens = providerMetadata?.["anthropic"]?.["cacheCreationInputTokens"] ?? 0
|
||||
const cacheWriteTokens = 0
|
||||
|
||||
const inputCost = MODEL.cost.input * inputTokens
|
||||
const outputCost = MODEL.cost.output * outputTokens
|
||||
const reasoningCost = MODEL.cost.reasoning * reasoningTokens
|
||||
const cacheReadCost = MODEL.cost.cacheRead * cacheReadTokens
|
||||
const cacheWriteCost = MODEL.cost.cacheWrite * cacheWriteTokens
|
||||
const costInCents = (inputCost + outputCost + reasoningCost + cacheReadCost + cacheWriteCost) * 100
|
||||
const cost = centsToMicroCents(costInCents)
|
||||
|
||||
await Database.transaction(async (tx) => {
|
||||
await tx.insert(UsageTable).values({
|
||||
workspaceID: apiKey.workspaceID,
|
||||
id: Identifier.create("usage"),
|
||||
model: MODEL.id,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
reasoningTokens,
|
||||
cacheReadTokens,
|
||||
cacheWriteTokens,
|
||||
cost,
|
||||
})
|
||||
await tx
|
||||
.update(BillingTable)
|
||||
.set({
|
||||
balance: sql`${BillingTable.balance} - ${cost}`,
|
||||
})
|
||||
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
|
||||
})
|
||||
|
||||
await Database.use((tx) =>
|
||||
tx
|
||||
.update(KeyTable)
|
||||
.set({ timeUsed: sql`now()` })
|
||||
.where(eq(KeyTable.id, apiKey.id)),
|
||||
)
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error instanceof AuthError) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
message: error.message,
|
||||
type: "invalid_request_error",
|
||||
param: null,
|
||||
code: "unauthorized",
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 401,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (error instanceof CreditsError) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
message: error.message,
|
||||
type: "insufficient_quota",
|
||||
param: null,
|
||||
code: "insufficient_quota",
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 401,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (error instanceof ModelError) {
|
||||
return new Response(JSON.stringify({ error: { message: error.message } }), {
|
||||
status: 401,
|
||||
})
|
||||
}
|
||||
|
||||
console.log(error)
|
||||
return new Response(JSON.stringify({ error: { message: error.message } }), {
|
||||
status: 500,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,504 +0,0 @@
|
||||
[data-page="home"] {
|
||||
--color-text: hsl(224, 10%, 10%);
|
||||
--color-text-secondary: hsl(224, 7%, 46%);
|
||||
--color-text-dimmed: hsl(224, 6%, 63%);
|
||||
--color-text-inverted: hsl(0, 0%, 100%);
|
||||
|
||||
--color-border: hsl(224, 6%, 77%);
|
||||
}
|
||||
|
||||
[data-page="home"] {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--color-text: hsl(0, 0%, 100%);
|
||||
--color-text-secondary: hsl(224, 6%, 66%);
|
||||
--color-text-dimmed: hsl(224, 7%, 46%);
|
||||
--color-text-inverted: hsl(224, 10%, 10%);
|
||||
|
||||
--color-border: hsl(224, 6%, 36%);
|
||||
}
|
||||
}
|
||||
|
||||
[data-page="home"] {
|
||||
--padding: 3rem;
|
||||
--vertical-padding: 1.5rem;
|
||||
--heading-font-size: 1.375rem;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
--padding: 1rem;
|
||||
--vertical-padding: 0.75rem;
|
||||
--heading-font-size: 1rem;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
gap: var(--vertical-padding);
|
||||
flex-direction: column;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text);
|
||||
padding: calc(var(--padding) + 1rem);
|
||||
|
||||
a {
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: var(--space-0-75);
|
||||
text-decoration-thickness: 1px;
|
||||
}
|
||||
|
||||
[data-component="content"] {
|
||||
max-width: 67.5rem;
|
||||
margin: 0 auto;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
[data-component="top"] {
|
||||
padding: calc(var(--padding) * 1.5) var(--padding) var(--padding);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: calc(var(--vertical-padding) / 2);
|
||||
|
||||
img {
|
||||
height: auto;
|
||||
width: clamp(200px, 85vw, 552px);
|
||||
}
|
||||
|
||||
[data-slot="logo dark"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
[data-slot="logo light"] {
|
||||
display: none;
|
||||
}
|
||||
[data-slot="logo dark"] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="title"] {
|
||||
line-height: 1.25;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
font-size: var(--heading-font-size);
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
[data-slot="login"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-width: 0 0 1px 1px;
|
||||
border-style: solid;
|
||||
border-color: var(--color-border);
|
||||
background-color: var(--color-bg);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 0.5rem 1rem calc(0.5rem + 4px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="cta"] {
|
||||
border-top: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
|
||||
& > div + div {
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
[data-slot="left"] {
|
||||
flex: 0 0 auto;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
padding: var(--vertical-padding) 2rem;
|
||||
text-transform: uppercase;
|
||||
font-size: 1.125rem;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: 1rem;
|
||||
padding-bottom: calc(var(--vertical-padding) + 4px);
|
||||
}
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="center"] {
|
||||
display: none;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
display: block;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: var(--vertical-padding) 0.5rem;
|
||||
border-top: 1px solid var(--color-border);
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="right"] {
|
||||
flex: 1;
|
||||
padding: var(--vertical-padding) 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 50rem) {
|
||||
flex-direction: column;
|
||||
|
||||
[data-slot="right"] {
|
||||
border-left: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="command"] {
|
||||
all: unset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 1.125rem;
|
||||
font-family: var(--font-mono);
|
||||
gap: var(--space-2);
|
||||
width: 100%;
|
||||
|
||||
& > span {
|
||||
@media (max-width: 24rem) {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
@media (max-width: 56rem) {
|
||||
[data-slot="protocol"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (max-width: 38rem) {
|
||||
text-align: center;
|
||||
span:first-child {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="highlight"] {
|
||||
color: var(--color-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="features"] {
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding: var(--padding);
|
||||
|
||||
[data-slot="list"] {
|
||||
padding-left: var(--space-4);
|
||||
margin: 0;
|
||||
list-style: disc;
|
||||
|
||||
li {
|
||||
margin-bottom: var(--space-4);
|
||||
line-height: 1.6;
|
||||
|
||||
strong {
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
label {
|
||||
line-height: 1;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.03125rem;
|
||||
background: var(--color-border);
|
||||
padding: 0.125rem 0.375rem;
|
||||
color: var(--color-text-inverted);
|
||||
}
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="install"] {
|
||||
border-top: 1px 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="method"] {
|
||||
display: flex;
|
||||
padding: calc(var(--vertical-padding) / 2) calc(var(--padding) / 2) calc(var(--vertical-padding) / 2 + 0.125rem);
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
gap: var(--space-2-5);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
gap: 0.3125rem;
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
border-left: 1px solid var(--color-border);
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
border-left: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
border-top: 1px solid var(--color-border);
|
||||
border-left: 1px solid var(--color-border);
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="title"] {
|
||||
letter-spacing: -0.03125rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
font-size: 1rem;
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-dimmed);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="button"] {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-text-secondary);
|
||||
gap: var(--space-2-5);
|
||||
font-size: 1rem;
|
||||
|
||||
@media (max-width: 24rem) {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: var(--color-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="screenshots"] {
|
||||
border-top: 1px solid var(--color-border);
|
||||
|
||||
figure {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--padding) / 4);
|
||||
padding: calc(var(--padding) / 2);
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
border-color: var(--color-border);
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
& > div,
|
||||
figcaption {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
letter-spacing: -0.03125rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-dimmed);
|
||||
flex-shrink: 0;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > [data-slot="left"] figure {
|
||||
height: var(--images-height);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
& > [data-slot="right"] figure {
|
||||
height: calc(var(--images-height) / 2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
& > [data-slot="left"] img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
& > [data-slot="right"] img {
|
||||
width: 100%;
|
||||
height: calc(100% - 2rem);
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
& {
|
||||
--images-height: auto;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
}
|
||||
|
||||
& > [data-slot="left"] {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
& > [data-slot="right"] {
|
||||
grid-row: 2;
|
||||
grid-column: 1;
|
||||
border-left: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
|
||||
& > [data-slot="row1"],
|
||||
& > [data-slot="row2"] {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
& > [data-slot="left"] figure,
|
||||
& > [data-slot="right"] figure {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
& > [data-slot="left"] img,
|
||||
& > [data-slot="right"] img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="copy-status"] {
|
||||
@media (max-width: 38rem) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[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: var(--color-text);
|
||||
|
||||
[data-copied] & {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="footer"] {
|
||||
border-top: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
[data-slot="cell"] {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
padding: var(--vertical-padding) 0.5rem;
|
||||
}
|
||||
|
||||
[data-slot="cell"] + [data-slot="cell"] {
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* Mobile: third column on its own row */
|
||||
@media (max-width: 30rem) {
|
||||
flex-wrap: wrap;
|
||||
|
||||
[data-slot="cell"]:nth-child(1),
|
||||
[data-slot="cell"]:nth-child(2) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
[data-slot="cell"]:nth-child(3) {
|
||||
flex: 1 0 100%;
|
||||
border-left: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="legal"] {
|
||||
color: var(--color-text-dimmed);
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
color: var(--color-text-dimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import { Billing } from "@opencode/cloud-core/billing.js"
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
||||
import { BillingTable, PaymentTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
||||
import { Identifier } from "@opencode/cloud-core/identifier.js"
|
||||
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
|
||||
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||
import { Resource } from "@opencode/cloud-resource"
|
||||
|
||||
export async function POST(input: APIEvent) {
|
||||
const body = await Billing.stripe().webhooks.constructEventAsync(
|
||||
await input.request.text(),
|
||||
input.request.headers.get("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")
|
||||
|
||||
const chargedAmount = 2000
|
||||
|
||||
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(chargedAmount)}`,
|
||||
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(chargedAmount),
|
||||
paymentID,
|
||||
customerID,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
console.log("finished handling")
|
||||
|
||||
return Response.json("ok", { status: 200 })
|
||||
}
|
||||
@@ -1,634 +0,0 @@
|
||||
[data-page="workspace-[id]"] {
|
||||
max-width: 64rem;
|
||||
padding: var(--space-10) var(--space-4);
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-10);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
padding-top: var(--space-4);
|
||||
padding-bottom: var(--space-4);
|
||||
|
||||
gap: var(--space-8);
|
||||
}
|
||||
|
||||
[data-slot="sections"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-16);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
gap: var(--space-8);
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-6);
|
||||
|
||||
/* Section titles */
|
||||
[data-slot="section-title"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03125rem;
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: var(--font-size-md);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
section:not(:last-child) {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding-bottom: var(--space-16);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
padding-bottom: var(--space-8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="empty-state"] {
|
||||
padding: var(--space-20) var(--space-6);
|
||||
text-align: center;
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* Title section */
|
||||
[data-component="title-section"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
padding-bottom: var(--space-8);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
padding-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03125rem;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text-muted);
|
||||
|
||||
a {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* API Keys Section */
|
||||
[data-component="api-keys-section"] {
|
||||
[data-slot="create-form"] {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="form-actions"] {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="api-keys-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="api-keys-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&[data-slot="key-name"] {
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&[data-slot="key-value"] {
|
||||
font-family: var(--font-mono);
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 400;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
border-radius: var(--border-radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
text-transform: none;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: var(--color-bg-surface);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
span {
|
||||
font-family: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[data-slot="key-date"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="key-actions"] {
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(3) /* Date */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(3) /* Date */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Balance Section */
|
||||
[data-component="balance-section"] {
|
||||
[data-slot="balance"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
min-width: 14.5rem;
|
||||
width: fit-content;
|
||||
|
||||
[data-slot="amount"] {
|
||||
padding: var(--space-3-5) var(--space-4);
|
||||
background-color: var(--color-bg-surface);
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--space-1);
|
||||
justify-content: flex-end;
|
||||
|
||||
&[data-state="danger"] {
|
||||
[data-slot="value"] {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
[data-slot="currency"] {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="currency"] {
|
||||
position: relative;
|
||||
bottom: 2px;
|
||||
font-size: var(--font-size-lg);
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
[data-slot="value"] {
|
||||
font-size: var(--font-size-3xl);
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Payments Section */
|
||||
[data-component="payments-section"] {
|
||||
[data-slot="payments-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="payments-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&[data-slot="payment-date"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="payment-id"] {
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 400;
|
||||
color: var(--color-text-muted);
|
||||
max-width: 200px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&[data-slot="payment-amount"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(2) /* Payment ID */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(2) /* Payment ID */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Usage Section */
|
||||
[data-component="usage-section"] {
|
||||
[data-slot="usage-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="usage-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&[data-slot="usage-date"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="usage-model"] {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 400;
|
||||
color: var(--color-text-secondary);
|
||||
max-width: 200px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&[data-slot="usage-cost"] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(2) /* Model */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(2) /* Model */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="new-user-sections"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-16);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
gap: var(--space-8);
|
||||
}
|
||||
|
||||
[data-component="feature-grid"] {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: var(--space-6);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
[data-slot="feature"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg-surface);
|
||||
|
||||
h3 {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--color-text);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: -0.025rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="api-key-highlight"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-6);
|
||||
|
||||
[data-slot="section-title"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03125rem;
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: var(--font-size-md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="key-display"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
|
||||
[data-slot="key-container"] {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
border: 2px solid var(--color-accent);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg-surface);
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
[data-slot="key-value"] {
|
||||
flex: 1;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg);
|
||||
padding: var(--space-3);
|
||||
border-radius: var(--border-radius-sm);
|
||||
border: 1px solid var(--color-border);
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
font-size: var(--font-size-xs);
|
||||
padding: var(--space-2-5);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
min-width: 130px;
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
justify-content: center;
|
||||
padding: var(--space-2-5) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
min-width: 96px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="next-steps"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-6);
|
||||
|
||||
[data-slot="section-title"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03125rem;
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: var(--font-size-md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
list-style-position: inside;
|
||||
|
||||
li {
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.5;
|
||||
color: var(--color-text-muted);
|
||||
|
||||
code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--font-size-xs);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
background-color: var(--color-bg-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,588 +0,0 @@
|
||||
import "./[id].css"
|
||||
import { Billing } from "@opencode/cloud-core/billing.js"
|
||||
import { Key } from "@opencode/cloud-core/key.js"
|
||||
import { json, query, action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router"
|
||||
import { createMemo, createSignal, For, Show } from "solid-js"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { IconCopy, IconCheck } from "~/component/icon"
|
||||
|
||||
function formatDateForTable(date: Date) {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
}
|
||||
return date.toLocaleDateString("en-GB", options).replace(",", ",")
|
||||
}
|
||||
|
||||
function formatDateUTC(date: Date) {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
weekday: "short",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
timeZoneName: "short",
|
||||
timeZone: "UTC",
|
||||
}
|
||||
return date.toLocaleDateString("en-US", options)
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
// Keys related queries and actions
|
||||
/////////////////////////////////////
|
||||
|
||||
const listKeys = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(() => Key.list(), workspaceID)
|
||||
}, "key.list")
|
||||
|
||||
const createKey = action(async (workspaceID: string, name: string) => {
|
||||
"use server"
|
||||
return json(
|
||||
withActor(() => Key.create({ name }), workspaceID),
|
||||
{ revalidate: listKeys.key },
|
||||
)
|
||||
}, "key.create")
|
||||
|
||||
const removeKey = action(async (workspaceID: string, id: string) => {
|
||||
"use server"
|
||||
return json(
|
||||
withActor(() => Key.remove({ id }), workspaceID),
|
||||
{ revalidate: listKeys.key },
|
||||
)
|
||||
}, "key.remove")
|
||||
|
||||
/////////////////////////////////////
|
||||
// Billing related queries and actions
|
||||
/////////////////////////////////////
|
||||
|
||||
const getBalanceInfo = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
return await Billing.get()
|
||||
}, workspaceID)
|
||||
}, "balanceInfo")
|
||||
|
||||
const getUsageInfo = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
return await Billing.usages()
|
||||
}, workspaceID)
|
||||
}, "usageInfo")
|
||||
|
||||
const getPaymentsInfo = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
return await Billing.payments()
|
||||
}, workspaceID)
|
||||
}, "paymentsInfo")
|
||||
|
||||
const createCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => {
|
||||
"use server"
|
||||
return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID)
|
||||
}, "checkoutUrl")
|
||||
|
||||
// const createPortalUrl = action(async (workspaceID: string, returnUrl: string) => {
|
||||
// "use server"
|
||||
// return withActor(() => Billing.generatePortalUrl({ returnUrl }), workspaceID)
|
||||
// }, "portalUrl")
|
||||
|
||||
function KeysSection() {
|
||||
// Dummy data for testing
|
||||
const dummyKeys = [
|
||||
{
|
||||
id: "key_1",
|
||||
name: "Development API Key",
|
||||
key: "oc_dev_1234567890abcdef1234567890abcdef12345678",
|
||||
timeCreated: new Date("2024-01-15T10:30:00Z"),
|
||||
},
|
||||
{
|
||||
id: "key_2",
|
||||
name: "Production API Key",
|
||||
key: "oc_prod_abcdef1234567890abcdef1234567890abcdef12",
|
||||
timeCreated: new Date("2024-02-01T14:22:00Z"),
|
||||
},
|
||||
{
|
||||
id: "key_3",
|
||||
name: "Testing Environment",
|
||||
key: "oc_test_9876543210fedcba9876543210fedcba98765432",
|
||||
timeCreated: new Date("2024-02-10T09:15:00Z"),
|
||||
},
|
||||
]
|
||||
|
||||
const params = useParams()
|
||||
const keys = createAsync(() => listKeys(params.id))
|
||||
// const keys = () => dummyKeys
|
||||
const [showForm, setShowForm] = createSignal(false)
|
||||
const [name, setName] = createSignal("")
|
||||
const removeAction = useAction(removeKey)
|
||||
const createAction = useAction(createKey)
|
||||
const createSubmission = useSubmission(createKey)
|
||||
const [copiedId, setCopiedId] = createSignal<string | null>(null)
|
||||
|
||||
function formatKey(key: string) {
|
||||
if (key.length <= 11) return key
|
||||
return `${key.slice(0, 7)}...${key.slice(-4)}`
|
||||
}
|
||||
|
||||
async function handleCreateKey() {
|
||||
if (!name().trim()) return
|
||||
|
||||
try {
|
||||
await createAction(params.id, name().trim())
|
||||
setName("")
|
||||
setShowForm(false)
|
||||
} catch (error) {
|
||||
console.error("Failed to create API key:", error)
|
||||
}
|
||||
}
|
||||
|
||||
async function copyKeyToClipboard(text: string, keyId: string) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
setCopiedId(keyId)
|
||||
setTimeout(() => setCopiedId(null), 1500)
|
||||
} catch (error) {
|
||||
console.error("Failed to copy to clipboard:", error)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteKey(keyId: string) {
|
||||
if (!confirm("Are you sure you want to delete this API key?")) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await removeAction(params.id, keyId)
|
||||
} catch (error) {
|
||||
console.error("Failed to delete API key:", error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section data-component="api-keys-section">
|
||||
<div data-slot="section-title">
|
||||
<h2>API Keys</h2>
|
||||
<p>Manage your API keys for accessing opencode services.</p>
|
||||
</div>
|
||||
<Show
|
||||
when={!showForm()}
|
||||
fallback={
|
||||
<div data-slot="create-form">
|
||||
<input
|
||||
data-component="input"
|
||||
type="text"
|
||||
placeholder="Enter key name"
|
||||
value={name()}
|
||||
onInput={(e) => setName(e.currentTarget.value)}
|
||||
onKeyPress={(e) => e.key === "Enter" && handleCreateKey()}
|
||||
/>
|
||||
<div data-slot="form-actions">
|
||||
<button
|
||||
data-color="ghost"
|
||||
onClick={() => {
|
||||
setShowForm(false)
|
||||
setName("")
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
data-color="primary"
|
||||
disabled={createSubmission.pending || !name().trim()}
|
||||
onClick={handleCreateKey}
|
||||
>
|
||||
{createSubmission.pending ? "Creating..." : "Create"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<button
|
||||
data-color="primary"
|
||||
onClick={() => {
|
||||
console.log("clicked")
|
||||
setShowForm(true)
|
||||
}}
|
||||
>
|
||||
Create API Key
|
||||
</button>
|
||||
</Show>
|
||||
<div data-slot="api-keys-table">
|
||||
<Show
|
||||
when={keys()?.length}
|
||||
fallback={
|
||||
<div data-component="empty-state">
|
||||
<p>Create an opencode Gateway API key</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<table data-slot="api-keys-table-element">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Key</th>
|
||||
<th>Created</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={keys()!}>
|
||||
{(key) => (
|
||||
<tr>
|
||||
<td data-slot="key-name">{key.name}</td>
|
||||
<td data-slot="key-value">
|
||||
<button
|
||||
data-color="ghost"
|
||||
disabled={copiedId() === key.id}
|
||||
onClick={() => copyKeyToClipboard(key.key, key.id)}
|
||||
title="Copy API key"
|
||||
>
|
||||
<span>{formatKey(key.key)}</span>
|
||||
<Show
|
||||
when={copiedId() === key.id}
|
||||
fallback={<IconCopy style={{ width: "14px", height: "14px" }} />}
|
||||
>
|
||||
<IconCheck style={{ width: "14px", height: "14px" }} />
|
||||
</Show>
|
||||
</button>
|
||||
</td>
|
||||
<td data-slot="key-date" title={formatDateUTC(key.timeCreated)}>
|
||||
{formatDateForTable(key.timeCreated)}
|
||||
</td>
|
||||
<td data-slot="key-actions">
|
||||
<button data-color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key">
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function BalanceSection() {
|
||||
const params = useParams()
|
||||
const dummyBalanceInfo = { balance: 2500000000 } // $25.00 in cents
|
||||
|
||||
const balanceInfo = createAsync(() => getBalanceInfo(params.id))
|
||||
// const balanceInfo = () => dummyBalanceInfo
|
||||
const createCheckoutUrlAction = useAction(createCheckoutUrl)
|
||||
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
|
||||
|
||||
async function handleBuyCredits() {
|
||||
try {
|
||||
const baseUrl = window.location.href
|
||||
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
|
||||
if (checkoutUrl) {
|
||||
window.location.href = checkoutUrl
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to get checkout URL:", error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section data-component="balance-section">
|
||||
<div data-slot="section-title">
|
||||
<h2>Balance</h2>
|
||||
<p>Add credits to your account.</p>
|
||||
</div>
|
||||
<div data-slot="balance">
|
||||
<div
|
||||
data-slot="amount"
|
||||
data-state={(() => {
|
||||
const balanceStr = ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
|
||||
return balanceStr === "0.00" || balanceStr === "-0.00" ? "danger" : undefined
|
||||
})()}
|
||||
>
|
||||
<span data-slot="currency">$</span>
|
||||
<span data-slot="value">
|
||||
{(() => {
|
||||
const balanceStr = ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
|
||||
return balanceStr === "-0.00" ? "0.00" : balanceStr
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
<button data-color="primary" disabled={createCheckoutUrlSubmission.pending} onClick={handleBuyCredits}>
|
||||
{createCheckoutUrlSubmission.pending ? "Loading..." : "Buy Credits"}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function UsageSection() {
|
||||
const params = useParams()
|
||||
const dummyUsage = [
|
||||
{
|
||||
id: "usage_1",
|
||||
model: "claude-3-sonnet-20240229",
|
||||
inputTokens: 1250,
|
||||
outputTokens: 890,
|
||||
cost: 125000000, // $1.25 in cents
|
||||
timeCreated: "2024-02-10T15:30:00Z",
|
||||
},
|
||||
{
|
||||
id: "usage_2",
|
||||
model: "gpt-4-turbo-preview",
|
||||
inputTokens: 2100,
|
||||
outputTokens: 1456,
|
||||
cost: 340000000, // $3.40 in cents
|
||||
timeCreated: "2024-02-09T09:45:00Z",
|
||||
},
|
||||
{
|
||||
id: "usage_3",
|
||||
model: "claude-3-haiku-20240307",
|
||||
inputTokens: 850,
|
||||
outputTokens: 620,
|
||||
cost: 45000000, // $0.45 in cents
|
||||
timeCreated: "2024-02-08T13:22:00Z",
|
||||
},
|
||||
{
|
||||
id: "usage_4",
|
||||
model: "gpt-3.5-turbo",
|
||||
inputTokens: 1800,
|
||||
outputTokens: 1200,
|
||||
cost: 85000000, // $0.85 in cents
|
||||
timeCreated: "2024-02-07T11:15:00Z",
|
||||
},
|
||||
]
|
||||
|
||||
const usage = createAsync(() => getUsageInfo(params.id))
|
||||
// const usage = () => dummyUsage
|
||||
return (
|
||||
<section data-component="usage-section">
|
||||
<div data-slot="section-title">
|
||||
<h2>Usage History</h2>
|
||||
<p>Recent API usage and costs.</p>
|
||||
</div>
|
||||
<div data-slot="usage-table">
|
||||
<Show
|
||||
when={usage() && usage()!.length > 0}
|
||||
fallback={
|
||||
<div data-component="empty-state">
|
||||
<p>Make your first API call to get started.</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<table data-slot="usage-table-element">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Model</th>
|
||||
<th>Input</th>
|
||||
<th>Output</th>
|
||||
<th>Cost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={usage()!}>
|
||||
{(usage) => {
|
||||
const date = createMemo(() => new Date(usage.timeCreated))
|
||||
return (
|
||||
<tr>
|
||||
<td data-slot="usage-date" title={formatDateUTC(date())}>
|
||||
{formatDateForTable(date())}
|
||||
</td>
|
||||
<td data-slot="usage-model">{usage.model}</td>
|
||||
<td data-slot="usage-tokens">{usage.inputTokens}</td>
|
||||
<td data-slot="usage-tokens">{usage.outputTokens}</td>
|
||||
<td data-slot="usage-cost">${((usage.cost ?? 0) / 100000000).toFixed(4)}</td>
|
||||
</tr>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function PaymentsSection() {
|
||||
const params = useParams()
|
||||
const dummyPayments = [
|
||||
{
|
||||
id: "pi_1234567890",
|
||||
amount: 5000000000, // $50.00 in cents
|
||||
timeCreated: "2024-02-01T10:00:00Z",
|
||||
},
|
||||
{
|
||||
id: "pi_0987654321",
|
||||
amount: 2500000000, // $25.00 in cents
|
||||
timeCreated: "2024-01-15T14:30:00Z",
|
||||
},
|
||||
]
|
||||
|
||||
const payments = createAsync(() => getPaymentsInfo(params.id))
|
||||
// const payments = () => dummyPayments
|
||||
|
||||
return (
|
||||
payments() &&
|
||||
payments()!.length > 0 && (
|
||||
<section data-component="payments-section">
|
||||
<div data-slot="section-title">
|
||||
<h2>Payments History</h2>
|
||||
<p>Recent payment transactions.</p>
|
||||
</div>
|
||||
<div data-slot="payments-table">
|
||||
<table data-slot="payments-table-element">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Payment ID</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={payments()!}>
|
||||
{(payment) => {
|
||||
const date = new Date(payment.timeCreated)
|
||||
return (
|
||||
<tr>
|
||||
<td data-slot="payment-date" title={formatDateUTC(date)}>
|
||||
{formatDateForTable(date)}
|
||||
</td>
|
||||
<td data-slot="payment-id">{payment.id}</td>
|
||||
<td data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</td>
|
||||
</tr>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function NewUserSection() {
|
||||
const params = useParams()
|
||||
const keys = createAsync(() => listKeys(params.id))
|
||||
const [copiedKey, setCopiedKey] = createSignal(false)
|
||||
|
||||
async function copyKeyToClipboard(text: string) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
setCopiedKey(true)
|
||||
setTimeout(() => setCopiedKey(false), 2000)
|
||||
} catch (error) {
|
||||
console.error("Failed to copy to clipboard:", error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-slot="new-user-sections">
|
||||
<div data-component="feature-grid">
|
||||
<div data-slot="feature">
|
||||
<h3>Tested & Verified Models</h3>
|
||||
<p>We've benchmarked and tested models specifically for coding agents to ensure the best performance.</p>
|
||||
</div>
|
||||
<div data-slot="feature">
|
||||
<h3>Highest Quality</h3>
|
||||
<p>Access models configured for optimal performance - no downgrades or routing to cheaper providers.</p>
|
||||
</div>
|
||||
<div data-slot="feature">
|
||||
<h3>No Lock-in</h3>
|
||||
<p>Use Zen with any coding agent, and continue using other providers with opencode whenever you want.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-component="api-key-highlight">
|
||||
<div data-slot="section-title">
|
||||
<h2>Your API Key</h2>
|
||||
</div>
|
||||
|
||||
<Show when={keys()?.length}>
|
||||
<div data-slot="key-display">
|
||||
<div data-slot="key-container">
|
||||
<code data-slot="key-value">{keys()![0].key}</code>
|
||||
<button
|
||||
data-color="primary"
|
||||
disabled={copiedKey()}
|
||||
onClick={() => copyKeyToClipboard(keys()![0].key)}
|
||||
title="Copy API key"
|
||||
>
|
||||
<Show
|
||||
when={copiedKey()}
|
||||
fallback={
|
||||
<>
|
||||
<IconCopy style={{ width: "16px", height: "16px" }} /> Copy Key
|
||||
</>
|
||||
}
|
||||
>
|
||||
<IconCheck style={{ width: "16px", height: "16px" }} /> Copied!
|
||||
</Show>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div data-component="next-steps">
|
||||
<div data-slot="section-title">
|
||||
<h2>Next Steps</h2>
|
||||
</div>
|
||||
<ol>
|
||||
<li>Copy your API key above</li>
|
||||
<li>
|
||||
Run <code>opencode auth login</code> and select opencode
|
||||
</li>
|
||||
<li>Paste your API key when prompted</li>
|
||||
<li>
|
||||
Run <code>/models</code> to see available models
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const params = useParams()
|
||||
const keys = createAsync(() => listKeys(params.id))
|
||||
const usage = createAsync(() => getUsageInfo(params.id))
|
||||
|
||||
const isNewUser = createMemo(() => {
|
||||
const keysList = keys()
|
||||
const usageList = usage()
|
||||
return keysList?.length === 1 && (!usageList || usageList.length === 0)
|
||||
})
|
||||
|
||||
return (
|
||||
<div data-page="workspace-[id]">
|
||||
<section data-component="title-section">
|
||||
<h1>Zen</h1>
|
||||
<p>
|
||||
Curated list of models provided by opencode.{" "}
|
||||
<a target="_blank" href="/docs/zen">
|
||||
Learn more
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<Show when={!isNewUser()} fallback={<NewUserSection />}>
|
||||
<div data-slot="sections">
|
||||
<KeysSection />
|
||||
<BalanceSection />
|
||||
<UsageSection />
|
||||
<PaymentsSection />
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
html {
|
||||
line-height: 1;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
@@ -1,526 +0,0 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import path from "node:path"
|
||||
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
||||
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
|
||||
import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
||||
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
|
||||
import { Identifier } from "@opencode/cloud-core/identifier.js"
|
||||
import { Resource } from "@opencode/cloud-resource"
|
||||
|
||||
type ModelCost = {
|
||||
input: number
|
||||
output: number
|
||||
cacheRead: number
|
||||
cacheWrite5m: number
|
||||
cacheWrite1h: number
|
||||
}
|
||||
type Model = {
|
||||
id: string
|
||||
auth: boolean
|
||||
cost: ModelCost | ((usage: any) => ModelCost)
|
||||
headerMappings: Record<string, string>
|
||||
providers: Record<
|
||||
string,
|
||||
{
|
||||
api: string
|
||||
apiKey: string
|
||||
model: string
|
||||
weight?: number
|
||||
}
|
||||
>
|
||||
}
|
||||
|
||||
export async function handler(
|
||||
input: APIEvent,
|
||||
opts: {
|
||||
modifyBody?: (body: any) => any
|
||||
setAuthHeader: (headers: Headers, apiKey: string) => void
|
||||
parseApiKey: (headers: Headers) => string | undefined
|
||||
onStreamPart: (chunk: string) => void
|
||||
getStreamUsage: () => any
|
||||
normalizeUsage: (body: any) => {
|
||||
inputTokens: number
|
||||
outputTokens: number
|
||||
reasoningTokens?: number
|
||||
cacheReadTokens: number
|
||||
cacheWrite5mTokens?: number
|
||||
cacheWrite1hTokens?: number
|
||||
}
|
||||
},
|
||||
) {
|
||||
class AuthError extends Error {}
|
||||
class CreditsError extends Error {}
|
||||
class ModelError extends Error {}
|
||||
|
||||
const MODELS: Record<string, Model> = {
|
||||
"claude-opus-4-1": {
|
||||
id: "claude-opus-4-1" as const,
|
||||
auth: true,
|
||||
cost: {
|
||||
input: 0.000015,
|
||||
output: 0.000075,
|
||||
cacheRead: 0.0000015,
|
||||
cacheWrite5m: 0.00001875,
|
||||
cacheWrite1h: 0.00003,
|
||||
},
|
||||
headerMappings: {},
|
||||
providers: {
|
||||
anthropic: {
|
||||
api: "https://api.anthropic.com",
|
||||
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
||||
model: "claude-opus-4-1-20250805",
|
||||
},
|
||||
},
|
||||
},
|
||||
"claude-sonnet-4": {
|
||||
id: "claude-sonnet-4" as const,
|
||||
auth: true,
|
||||
cost: (usage: any) => {
|
||||
const totalInputTokens =
|
||||
usage.inputTokens + usage.cacheReadTokens + usage.cacheWrite5mTokens + usage.cacheWrite1hTokens
|
||||
return totalInputTokens <= 200_000
|
||||
? {
|
||||
input: 0.000003,
|
||||
output: 0.000015,
|
||||
cacheRead: 0.0000003,
|
||||
cacheWrite5m: 0.00000375,
|
||||
cacheWrite1h: 0.000006,
|
||||
}
|
||||
: {
|
||||
input: 0.000006,
|
||||
output: 0.0000225,
|
||||
cacheRead: 0.0000006,
|
||||
cacheWrite5m: 0.0000075,
|
||||
cacheWrite1h: 0.000012,
|
||||
}
|
||||
},
|
||||
headerMappings: {},
|
||||
providers: {
|
||||
anthropic: {
|
||||
api: "https://api.anthropic.com",
|
||||
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
||||
model: "claude-sonnet-4-20250514",
|
||||
},
|
||||
},
|
||||
},
|
||||
"claude-3-5-haiku": {
|
||||
id: "claude-3-5-haiku" as const,
|
||||
auth: true,
|
||||
cost: {
|
||||
input: 0.0000008,
|
||||
output: 0.000004,
|
||||
cacheRead: 0.00000008,
|
||||
cacheWrite5m: 0.000001,
|
||||
cacheWrite1h: 0.0000016,
|
||||
},
|
||||
headerMappings: {},
|
||||
providers: {
|
||||
anthropic: {
|
||||
api: "https://api.anthropic.com",
|
||||
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
||||
model: "claude-3-5-haiku-20241022",
|
||||
},
|
||||
},
|
||||
},
|
||||
"gpt-5": {
|
||||
id: "gpt-5" as const,
|
||||
auth: true,
|
||||
cost: {
|
||||
input: 0.00000125,
|
||||
output: 0.00001,
|
||||
cacheRead: 0.000000125,
|
||||
cacheWrite5m: 0,
|
||||
cacheWrite1h: 0,
|
||||
},
|
||||
headerMappings: {},
|
||||
providers: {
|
||||
openai: {
|
||||
api: "https://api.openai.com",
|
||||
apiKey: Resource.OPENAI_API_KEY.value,
|
||||
model: "gpt-5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"qwen3-coder": {
|
||||
id: "qwen3-coder" as const,
|
||||
auth: true,
|
||||
cost: {
|
||||
input: 0.00000045,
|
||||
output: 0.0000018,
|
||||
cacheRead: 0,
|
||||
cacheWrite5m: 0,
|
||||
cacheWrite1h: 0,
|
||||
},
|
||||
headerMappings: {},
|
||||
providers: {
|
||||
baseten: {
|
||||
api: "https://inference.baseten.co",
|
||||
apiKey: Resource.BASETEN_API_KEY.value,
|
||||
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||
weight: 4,
|
||||
},
|
||||
fireworks: {
|
||||
api: "https://api.fireworks.ai/inference",
|
||||
apiKey: Resource.FIREWORKS_API_KEY.value,
|
||||
model: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
|
||||
weight: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"kimi-k2": {
|
||||
id: "kimi-k2" as const,
|
||||
auth: true,
|
||||
cost: {
|
||||
input: 0.0000006,
|
||||
output: 0.0000025,
|
||||
cacheRead: 0,
|
||||
cacheWrite5m: 0,
|
||||
cacheWrite1h: 0,
|
||||
},
|
||||
headerMappings: {},
|
||||
providers: {
|
||||
baseten: {
|
||||
api: "https://inference.baseten.co",
|
||||
apiKey: Resource.BASETEN_API_KEY.value,
|
||||
model: "moonshotai/Kimi-K2-Instruct-0905",
|
||||
weight: 4,
|
||||
},
|
||||
fireworks: {
|
||||
api: "https://api.fireworks.ai/inference",
|
||||
apiKey: Resource.FIREWORKS_API_KEY.value,
|
||||
model: "accounts/fireworks/models/kimi-k2-instruct-0905",
|
||||
weight: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"grok-code": {
|
||||
id: "grok-code" as const,
|
||||
auth: false,
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite5m: 0,
|
||||
cacheWrite1h: 0,
|
||||
},
|
||||
headerMappings: {
|
||||
"x-grok-conv-id": "x-opencode-session",
|
||||
"x-grok-req-id": "x-opencode-request",
|
||||
},
|
||||
providers: {
|
||||
xai: {
|
||||
api: "https://api.x.ai",
|
||||
apiKey: Resource.XAI_API_KEY.value,
|
||||
model: "grok-code",
|
||||
},
|
||||
},
|
||||
},
|
||||
// deprecated
|
||||
"qwen/qwen3-coder": {
|
||||
id: "qwen/qwen3-coder" as const,
|
||||
auth: true,
|
||||
cost: {
|
||||
input: 0.00000038,
|
||||
output: 0.00000153,
|
||||
cacheRead: 0,
|
||||
cacheWrite5m: 0,
|
||||
cacheWrite1h: 0,
|
||||
},
|
||||
headerMappings: {},
|
||||
providers: {
|
||||
baseten: {
|
||||
api: "https://inference.baseten.co",
|
||||
apiKey: Resource.BASETEN_API_KEY.value,
|
||||
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||
weight: 5,
|
||||
},
|
||||
fireworks: {
|
||||
api: "https://api.fireworks.ai/inference",
|
||||
apiKey: Resource.FIREWORKS_API_KEY.value,
|
||||
model: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
|
||||
weight: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const FREE_WORKSPACES = [
|
||||
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
|
||||
]
|
||||
|
||||
const logger = {
|
||||
metric: (values: Record<string, any>) => {
|
||||
console.log(`_metric:${JSON.stringify(values)}`)
|
||||
},
|
||||
log: console.log,
|
||||
debug: (message: string) => {
|
||||
if (Resource.App.stage === "production") return
|
||||
console.debug(message)
|
||||
},
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(input.request.url)
|
||||
const body = await input.request.json()
|
||||
logger.debug(JSON.stringify(body))
|
||||
logger.metric({
|
||||
is_tream: !!body.stream,
|
||||
session: input.request.headers.get("x-opencode-session"),
|
||||
request: input.request.headers.get("x-opencode-request"),
|
||||
})
|
||||
const MODEL = validateModel()
|
||||
const apiKey = await authenticate()
|
||||
const isFree = FREE_WORKSPACES.includes(apiKey?.workspaceID ?? "")
|
||||
await checkCredits()
|
||||
const providerName = selectProvider()
|
||||
const providerData = MODEL.providers[providerName]
|
||||
logger.metric({ provider: providerName })
|
||||
|
||||
// Request to model provider
|
||||
const startTimestamp = Date.now()
|
||||
const res = await fetch(path.posix.join(providerData.api, url.pathname.replace(/^\/zen/, "") + url.search), {
|
||||
method: "POST",
|
||||
headers: (() => {
|
||||
const headers = input.request.headers
|
||||
headers.delete("host")
|
||||
headers.delete("content-length")
|
||||
opts.setAuthHeader(headers, providerData.apiKey)
|
||||
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
|
||||
headers.set(k, headers.get(v)!)
|
||||
})
|
||||
return headers
|
||||
})(),
|
||||
body: JSON.stringify({
|
||||
...(opts.modifyBody?.(body) ?? body),
|
||||
model: providerData.model,
|
||||
}),
|
||||
})
|
||||
|
||||
// Scrub response headers
|
||||
const resHeaders = new Headers()
|
||||
const keepHeaders = ["content-type", "cache-control"]
|
||||
for (const [k, v] of res.headers.entries()) {
|
||||
if (keepHeaders.includes(k.toLowerCase())) {
|
||||
resHeaders.set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-streaming response
|
||||
if (!body.stream) {
|
||||
const json = await res.json()
|
||||
const body = JSON.stringify(json)
|
||||
logger.metric({ response_length: body.length })
|
||||
logger.debug(body)
|
||||
await trackUsage(json.usage)
|
||||
return new Response(body, {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
headers: resHeaders,
|
||||
})
|
||||
}
|
||||
|
||||
// Handle streaming response
|
||||
const stream = new ReadableStream({
|
||||
start(c) {
|
||||
const reader = res.body?.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ""
|
||||
let responseLength = 0
|
||||
|
||||
function pump(): Promise<void> {
|
||||
return (
|
||||
reader?.read().then(async ({ done, value }) => {
|
||||
if (done) {
|
||||
logger.metric({ response_length: responseLength })
|
||||
const usage = opts.getStreamUsage()
|
||||
if (usage) await trackUsage(usage)
|
||||
c.close()
|
||||
return
|
||||
}
|
||||
|
||||
if (responseLength === 0) {
|
||||
logger.metric({ time_to_first_byte: Date.now() - startTimestamp })
|
||||
}
|
||||
responseLength += value.length
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
|
||||
const parts = buffer.split("\n\n")
|
||||
buffer = parts.pop() ?? ""
|
||||
|
||||
for (const part of parts) {
|
||||
logger.debug(part)
|
||||
opts.onStreamPart(part.trim())
|
||||
}
|
||||
|
||||
c.enqueue(value)
|
||||
|
||||
return pump()
|
||||
}) || Promise.resolve()
|
||||
)
|
||||
}
|
||||
|
||||
return pump()
|
||||
},
|
||||
})
|
||||
|
||||
return new Response(stream, {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
headers: resHeaders,
|
||||
})
|
||||
|
||||
function validateModel() {
|
||||
if (!(body.model in MODELS)) {
|
||||
throw new ModelError(`Model ${body.model} not supported`)
|
||||
}
|
||||
const model = MODELS[body.model as keyof typeof MODELS]
|
||||
logger.metric({ model: model.id })
|
||||
return model
|
||||
}
|
||||
|
||||
async function authenticate() {
|
||||
try {
|
||||
const apiKey = opts.parseApiKey(input.request.headers)
|
||||
if (!apiKey) throw new AuthError("Missing API key.")
|
||||
|
||||
const key = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
id: KeyTable.id,
|
||||
workspaceID: KeyTable.workspaceID,
|
||||
})
|
||||
.from(KeyTable)
|
||||
.where(eq(KeyTable.key, apiKey))
|
||||
.then((rows) => rows[0]),
|
||||
)
|
||||
|
||||
if (!key) throw new AuthError("Invalid API key.")
|
||||
logger.metric({
|
||||
api_key: key.id,
|
||||
workspace: key.workspaceID,
|
||||
})
|
||||
return key
|
||||
} catch (e) {
|
||||
// ignore error if model does not require authentication
|
||||
if (!MODEL.auth) return
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async function checkCredits() {
|
||||
if (!apiKey || !MODEL.auth || isFree) return
|
||||
|
||||
const billing = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
balance: BillingTable.balance,
|
||||
})
|
||||
.from(BillingTable)
|
||||
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
|
||||
.then((rows) => rows[0]),
|
||||
)
|
||||
|
||||
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
|
||||
}
|
||||
|
||||
function selectProvider() {
|
||||
const picks = Object.entries(MODEL.providers).flatMap(([name, provider]) =>
|
||||
Array<string>(provider.weight ?? 1).fill(name),
|
||||
)
|
||||
return picks[Math.floor(Math.random() * picks.length)]
|
||||
}
|
||||
|
||||
async function trackUsage(usage: any) {
|
||||
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
|
||||
opts.normalizeUsage(usage)
|
||||
|
||||
const modelCost = typeof MODEL.cost === "function" ? MODEL.cost(usage) : MODEL.cost
|
||||
|
||||
const inputCost = modelCost.input * inputTokens * 100
|
||||
const outputCost = modelCost.output * outputTokens * 100
|
||||
const reasoningCost = reasoningTokens ? modelCost.output * reasoningTokens * 100 : undefined
|
||||
const cacheReadCost = modelCost.cacheRead * cacheReadTokens * 100
|
||||
const cacheWrite5mCost = cacheWrite5mTokens ? modelCost.cacheWrite5m * cacheWrite5mTokens * 100 : undefined
|
||||
const cacheWrite1hCost = cacheWrite1hTokens ? modelCost.cacheWrite1h * cacheWrite1hTokens * 100 : undefined
|
||||
const totalCostInCent =
|
||||
inputCost +
|
||||
outputCost +
|
||||
(reasoningCost ?? 0) +
|
||||
cacheReadCost +
|
||||
(cacheWrite5mCost ?? 0) +
|
||||
(cacheWrite1hCost ?? 0)
|
||||
|
||||
logger.metric({
|
||||
"tokens.input": inputTokens,
|
||||
"tokens.output": outputTokens,
|
||||
"tokens.reasoning": reasoningTokens,
|
||||
"tokens.cache_read": cacheReadTokens,
|
||||
"tokens.cache_write_5m": cacheWrite5mTokens,
|
||||
"tokens.cache_write_1h": cacheWrite1hTokens,
|
||||
"cost.input": Math.round(inputCost),
|
||||
"cost.output": Math.round(outputCost),
|
||||
"cost.reasoning": reasoningCost ? Math.round(reasoningCost) : undefined,
|
||||
"cost.cache_read": Math.round(cacheReadCost),
|
||||
"cost.cache_write_5m": cacheWrite5mCost ? Math.round(cacheWrite5mCost) : undefined,
|
||||
"cost.cache_write_1h": cacheWrite1hCost ? Math.round(cacheWrite1hCost) : undefined,
|
||||
"cost.total": Math.round(totalCostInCent),
|
||||
})
|
||||
|
||||
if (!apiKey) return
|
||||
|
||||
const cost = isFree ? 0 : centsToMicroCents(totalCostInCent)
|
||||
await Database.transaction(async (tx) => {
|
||||
await tx.insert(UsageTable).values({
|
||||
workspaceID: apiKey.workspaceID,
|
||||
id: Identifier.create("usage"),
|
||||
model: MODEL.id,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
reasoningTokens,
|
||||
cacheReadTokens,
|
||||
cacheWriteTokens: (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0),
|
||||
cost,
|
||||
})
|
||||
await tx
|
||||
.update(BillingTable)
|
||||
.set({
|
||||
balance: sql`${BillingTable.balance} - ${cost}`,
|
||||
})
|
||||
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
|
||||
})
|
||||
|
||||
await Database.use((tx) =>
|
||||
tx
|
||||
.update(KeyTable)
|
||||
.set({ timeUsed: sql`now()` })
|
||||
.where(eq(KeyTable.id, apiKey.id)),
|
||||
)
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.metric({
|
||||
"error.type": error.constructor.name,
|
||||
"error.message": error.message,
|
||||
})
|
||||
|
||||
// Note: both top level "type" and "error.type" fields are used by the @ai-sdk/anthropic client to render the error message.
|
||||
if (error instanceof AuthError || error instanceof CreditsError || error instanceof ModelError)
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
type: "error",
|
||||
error: { type: error.constructor.name, message: error.message },
|
||||
}),
|
||||
{ status: 401 },
|
||||
)
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
type: "error",
|
||||
error: {
|
||||
type: "error",
|
||||
message: error.message,
|
||||
},
|
||||
}),
|
||||
{ status: 500 },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "mysql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1756796050935,
|
||||
"tag": "0000_fluffy_raza",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "5",
|
||||
"when": 1756871639102,
|
||||
"tag": "0001_serious_whistler",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
import { Stripe } from "stripe"
|
||||
import { Database, eq, sql } from "./drizzle"
|
||||
import { BillingTable, PaymentTable, UsageTable } from "./schema/billing.sql"
|
||||
import { Actor } from "./actor"
|
||||
import { fn } from "./util/fn"
|
||||
import { z } from "zod"
|
||||
import { User } from "./user"
|
||||
import { Resource } from "@opencode/cloud-resource"
|
||||
|
||||
export namespace Billing {
|
||||
export const stripe = () =>
|
||||
new Stripe(Resource.STRIPE_SECRET_KEY.value, {
|
||||
apiVersion: "2025-03-31.basil",
|
||||
})
|
||||
|
||||
export const get = async () => {
|
||||
return Database.use(async (tx) =>
|
||||
tx
|
||||
.select({
|
||||
customerID: BillingTable.customerID,
|
||||
paymentMethodID: BillingTable.paymentMethodID,
|
||||
balance: BillingTable.balance,
|
||||
reload: BillingTable.reload,
|
||||
})
|
||||
.from(BillingTable)
|
||||
.where(eq(BillingTable.workspaceID, Actor.workspace()))
|
||||
.then((r) => r[0]),
|
||||
)
|
||||
}
|
||||
|
||||
export const payments = async () => {
|
||||
return await Database.use((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(PaymentTable)
|
||||
.where(eq(PaymentTable.workspaceID, Actor.workspace()))
|
||||
.orderBy(sql`${PaymentTable.timeCreated} DESC`)
|
||||
.limit(100),
|
||||
)
|
||||
}
|
||||
|
||||
export const usages = async () => {
|
||||
return await Database.use((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(UsageTable)
|
||||
.where(eq(UsageTable.workspaceID, Actor.workspace()))
|
||||
.orderBy(sql`${UsageTable.timeCreated} DESC`)
|
||||
.limit(100),
|
||||
)
|
||||
}
|
||||
|
||||
export const generateCheckoutUrl = fn(
|
||||
z.object({
|
||||
successUrl: z.string(),
|
||||
cancelUrl: z.string(),
|
||||
}),
|
||||
async (input) => {
|
||||
const account = Actor.assert("user")
|
||||
const { successUrl, cancelUrl } = input
|
||||
|
||||
const user = await User.fromID(account.properties.userID)
|
||||
const customer = await Billing.get()
|
||||
const session = await Billing.stripe().checkout.sessions.create({
|
||||
mode: "payment",
|
||||
line_items: [
|
||||
{
|
||||
price_data: {
|
||||
currency: "usd",
|
||||
product_data: {
|
||||
name: "opencode credits",
|
||||
},
|
||||
unit_amount: 2123, // $20 minimum + Stripe fee 4.4% + $0.30
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
payment_intent_data: {
|
||||
setup_future_usage: "on_session",
|
||||
},
|
||||
...(customer.customerID
|
||||
? { customer: customer.customerID }
|
||||
: {
|
||||
customer_email: user.email,
|
||||
customer_creation: "always",
|
||||
}),
|
||||
metadata: {
|
||||
workspaceID: Actor.workspace(),
|
||||
},
|
||||
currency: "usd",
|
||||
payment_method_types: ["card"],
|
||||
success_url: successUrl,
|
||||
cancel_url: cancelUrl,
|
||||
})
|
||||
|
||||
return session.url
|
||||
},
|
||||
)
|
||||
|
||||
export const generatePortalUrl = fn(
|
||||
z.object({
|
||||
returnUrl: z.string(),
|
||||
}),
|
||||
async (input) => {
|
||||
const { returnUrl } = input
|
||||
|
||||
const customer = await Billing.get()
|
||||
if (!customer?.customerID) {
|
||||
throw new Error("No stripe customer ID")
|
||||
}
|
||||
|
||||
const session = await Billing.stripe().billingPortal.sessions.create({
|
||||
customer: customer.customerID,
|
||||
return_url: returnUrl,
|
||||
})
|
||||
|
||||
return session.url
|
||||
},
|
||||
)
|
||||
}
|
||||
96
cloud/function/sst-env.d.ts
vendored
@@ -1,96 +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
|
||||
}
|
||||
BASETEN_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
Console: {
|
||||
type: "sst.cloudflare.SolidStart"
|
||||
url: string
|
||||
}
|
||||
Database: {
|
||||
database: string
|
||||
host: string
|
||||
password: string
|
||||
port: number
|
||||
type: "sst.sst.Linkable"
|
||||
username: string
|
||||
}
|
||||
FIREWORKS_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: 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
|
||||
}
|
||||
HONEYCOMB_API_KEY: {
|
||||
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
|
||||
}
|
||||
XAI_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
|
||||
LogProcessor: cloudflare.Service
|
||||
}
|
||||
}
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
96
cloud/resource/sst-env.d.ts
vendored
@@ -1,96 +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
|
||||
}
|
||||
BASETEN_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
Console: {
|
||||
type: "sst.cloudflare.SolidStart"
|
||||
url: string
|
||||
}
|
||||
Database: {
|
||||
database: string
|
||||
host: string
|
||||
password: string
|
||||
port: number
|
||||
type: "sst.sst.Linkable"
|
||||
username: string
|
||||
}
|
||||
FIREWORKS_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: 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
|
||||
}
|
||||
HONEYCOMB_API_KEY: {
|
||||
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
|
||||
}
|
||||
XAI_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
|
||||
LogProcessor: cloudflare.Service
|
||||
}
|
||||
}
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@opencode/cloud-scripts",
|
||||
"version": "0.7.2",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "tsx",
|
||||
"shell": "sst shell"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode/cloud-core": "workspace:*",
|
||||
"tsx": "4.20.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
// placeholder
|
||||
2
github/sst-env.d.ts
vendored
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
@@ -2,6 +2,7 @@ import { domain } from "./stage"
|
||||
|
||||
const GITHUB_APP_ID = new sst.Secret("GITHUB_APP_ID")
|
||||
const GITHUB_APP_PRIVATE_KEY = new sst.Secret("GITHUB_APP_PRIVATE_KEY")
|
||||
export const EMAILOCTOPUS_API_KEY = new sst.Secret("EMAILOCTOPUS_API_KEY")
|
||||
const bucket = new sst.cloudflare.Bucket("Bucket")
|
||||
|
||||
export const api = new sst.cloudflare.Worker("Api", {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebhookEndpoint } from "pulumi-stripe"
|
||||
import { domain } from "./stage"
|
||||
import { EMAILOCTOPUS_API_KEY } from "./app"
|
||||
|
||||
////////////////
|
||||
// DATABASE
|
||||
@@ -44,7 +44,7 @@ new sst.x.DevCommand("Studio", {
|
||||
link: [database],
|
||||
dev: {
|
||||
command: "bun db studio",
|
||||
directory: "cloud/core",
|
||||
directory: "packages/console/core",
|
||||
autostart: true,
|
||||
},
|
||||
})
|
||||
@@ -59,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],
|
||||
})
|
||||
@@ -68,13 +68,14 @@ export const auth = new sst.cloudflare.Worker("AuthApi", {
|
||||
// GATEWAY
|
||||
////////////////
|
||||
|
||||
export const stripeWebhook = new WebhookEndpoint("StripeWebhookEndpoint", {
|
||||
export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint", {
|
||||
url: $interpolate`https://${domain}/stripe/webhook`,
|
||||
enabledEvents: [
|
||||
"checkout.session.async_payment_failed",
|
||||
"checkout.session.async_payment_succeeded",
|
||||
"checkout.session.completed",
|
||||
"checkout.session.expired",
|
||||
"charge.refunded",
|
||||
"customer.created",
|
||||
"customer.deleted",
|
||||
"customer.updated",
|
||||
@@ -93,17 +94,10 @@ export const stripeWebhook = new WebhookEndpoint("StripeWebhookEndpoint", {
|
||||
"customer.subscription.resumed",
|
||||
"customer.subscription.trial_will_end",
|
||||
"customer.subscription.updated",
|
||||
"customer.tax_id.created",
|
||||
"customer.tax_id.deleted",
|
||||
"customer.tax_id.updated",
|
||||
],
|
||||
})
|
||||
|
||||
const ANTHROPIC_API_KEY = new sst.Secret("ANTHROPIC_API_KEY")
|
||||
const OPENAI_API_KEY = new sst.Secret("OPENAI_API_KEY")
|
||||
const XAI_API_KEY = new sst.Secret("XAI_API_KEY")
|
||||
const BASETEN_API_KEY = new sst.Secret("BASETEN_API_KEY")
|
||||
const FIREWORKS_API_KEY = new sst.Secret("FIREWORKS_API_KEY")
|
||||
const ZEN_MODELS = new sst.Secret("ZEN_MODELS")
|
||||
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
|
||||
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
||||
properties: { value: auth.url.apply((url) => url!) },
|
||||
@@ -116,28 +110,30 @@ const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
|
||||
// CONSOLE
|
||||
////////////////
|
||||
|
||||
const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
|
||||
const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
|
||||
|
||||
let logProcessor
|
||||
if ($app.stage === "production" || $app.stage === "frank") {
|
||||
const HONEYCOMB_API_KEY = new sst.Secret("HONEYCOMB_API_KEY")
|
||||
logProcessor = new sst.cloudflare.Worker("LogProcessor", {
|
||||
handler: "cloud/function/src/log-processor.ts",
|
||||
handler: "packages/console/function/src/log-processor.ts",
|
||||
link: [HONEYCOMB_API_KEY],
|
||||
})
|
||||
}
|
||||
|
||||
new sst.cloudflare.x.SolidStart("Console", {
|
||||
domain,
|
||||
path: "cloud/app",
|
||||
path: "packages/console/app",
|
||||
link: [
|
||||
database,
|
||||
AUTH_API_URL,
|
||||
STRIPE_WEBHOOK_SECRET,
|
||||
STRIPE_SECRET_KEY,
|
||||
ANTHROPIC_API_KEY,
|
||||
OPENAI_API_KEY,
|
||||
XAI_API_KEY,
|
||||
BASETEN_API_KEY,
|
||||
FIREWORKS_API_KEY,
|
||||
ZEN_MODELS,
|
||||
EMAILOCTOPUS_API_KEY,
|
||||
AWS_SES_ACCESS_KEY_ID,
|
||||
AWS_SES_SECRET_ACCESS_KEY,
|
||||
],
|
||||
environment: {
|
||||
//VITE_DOCS_URL: web.url.apply((url) => url!),
|
||||
10
infra/desktop.ts
Normal 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",
|
||||
},
|
||||
})
|
||||
@@ -3,3 +3,11 @@ export const domain = (() => {
|
||||
if ($app.stage === "dev") return "dev.opencode.ai"
|
||||
return `${$app.stage}.dev.opencode.ai`
|
||||
})()
|
||||
|
||||
export const zoneID = "430ba34c138cfb5360826c4909f99be8"
|
||||
|
||||
// new cloudflare.RegionalHostname("RegionalHostname", {
|
||||
// hostname: domain,
|
||||
// regionKey: "us",
|
||||
// zoneId: zoneID,
|
||||
// })
|
||||
|
||||
2
install
@@ -46,7 +46,7 @@ mkdir -p "$INSTALL_DIR"
|
||||
|
||||
if [ -z "$requested_version" ]; then
|
||||
url="https://github.com/sst/opencode/releases/latest/download/$filename"
|
||||
specific_version=$(curl -s https://api.github.com/repos/sst/opencode/releases/latest | awk -F'"' '/"tag_name": "/ {gsub(/^v/, "", $4); print $4}')
|
||||
specific_version=$(curl -s https://api.github.com/repos/sst/opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
|
||||
|
||||
if [[ $? -ne 0 || -z "$specific_version" ]]; then
|
||||
echo -e "${RED}Failed to fetch version information${NC}"
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"weather": {
|
||||
"type": "local",
|
||||
"command": ["opencode", "x", "@h1deya/mcp-server-weather"]
|
||||
}
|
||||
}
|
||||
"$schema": "https://opencode.ai/config.json"
|
||||
}
|
||||
|
||||
26
package.json
@@ -5,36 +5,37 @@
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.2.21",
|
||||
"scripts": {
|
||||
"dev": "bun run --conditions=development packages/opencode/src/index.ts",
|
||||
"typecheck": "bun run --filter='*' typecheck",
|
||||
"generate": "(cd packages/sdk && ./js/script/generate.ts) && (cd packages/sdk/stainless && ./generate.ts)",
|
||||
"postinstall": "./script/hooks"
|
||||
"dev": "bun run packages/opencode/src/index.ts",
|
||||
"typecheck": "bun turbo typecheck",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"cloud/*",
|
||||
"packages/*",
|
||||
"packages/console/*",
|
||||
"packages/sdk/js"
|
||||
],
|
||||
"catalog": {
|
||||
"@types/bun": "1.2.21",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@types/node": "22.13.9",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"ai": "5.0.8",
|
||||
"hono": "4.7.10",
|
||||
"fuzzysort": "3.1.0",
|
||||
"luxon": "3.6.1",
|
||||
"typescript": "5.8.2",
|
||||
"zod": "3.25.76",
|
||||
"zod": "4.1.8",
|
||||
"remeda": "2.26.0",
|
||||
"solid-js": "1.9.9"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"pulumi-stripe": "0.0.24"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "3.5.3",
|
||||
"sst": "3.17.12"
|
||||
"husky": "9.1.7",
|
||||
"prettier": "3.6.2",
|
||||
"sst": "3.17.13",
|
||||
"turbo": "2.5.6"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -53,9 +54,6 @@
|
||||
"tree-sitter-bash",
|
||||
"web-tree-sitter"
|
||||
],
|
||||
"overrides": {
|
||||
"zod": "3.25.76"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@solidjs/start@1.1.7": "patches/@solidjs%2Fstart@1.1.7.patch"
|
||||
}
|
||||
|
||||
1
packages/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
src/assets/theme.css
|
||||
28
packages/app/AGENTS.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Agent Guidelines for @opencode/app
|
||||
|
||||
## Build/Test Commands
|
||||
|
||||
- **Development**: `bun run dev` (starts Vite dev server on port 3000)
|
||||
- **Build**: `bun run build` (production build)
|
||||
- **Preview**: `bun run serve` (preview production build)
|
||||
- **Validation**: Use `bun run typecheck` only - do not build or run project for validation
|
||||
- **Testing**: Do not create or run automated tests
|
||||
|
||||
## Code Style
|
||||
|
||||
- **Framework**: SolidJS with TypeScript
|
||||
- **Imports**: Use `@/` alias for src/ directory (e.g., `import Button from "@/ui/button"`)
|
||||
- **Formatting**: Prettier configured with semicolons disabled, 120 character line width
|
||||
- **Components**: Use function declarations, splitProps for component props
|
||||
- **Types**: Define interfaces for component props, avoid `any` type
|
||||
- **CSS**: TailwindCSS with custom CSS variables theme system
|
||||
- **Naming**: PascalCase for components, camelCase for variables/functions, snake_case for file names
|
||||
- **File Structure**: UI primitives in `/ui/`, higher-level components in `/components/`, pages in `/pages/`, providers in `/providers/`
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
- SolidJS, @solidjs/router, @kobalte/core (UI primitives)
|
||||
- TailwindCSS 4.x with @tailwindcss/vite
|
||||
- Custom theme system with CSS variables
|
||||
|
||||
No special rules files found.
|
||||
34
packages/app/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
## Usage
|
||||
|
||||
Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.
|
||||
|
||||
This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template.
|
||||
|
||||
```bash
|
||||
$ npm install # or pnpm install or yarn install
|
||||
```
|
||||
|
||||
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm run dev` or `npm start`
|
||||
|
||||
Runs the app in the development mode.<br>
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br>
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `dist` folder.<br>
|
||||
It correctly bundles Solid in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br>
|
||||
Your app is ready to be deployed!
|
||||
|
||||
## Deployment
|
||||
|
||||
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)
|
||||
24
packages/app/index.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="h-full bg-background">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.svg" />
|
||||
<link rel="stylesheet" href="/src/assets/theme.css" />
|
||||
<title>opencode</title>
|
||||
</head>
|
||||
<body class="h-full overscroll-none select-none">
|
||||
<script>
|
||||
;(function () {
|
||||
const savedTheme = localStorage.getItem("theme") || "opencode"
|
||||
const savedDarkMode = localStorage.getItem("darkMode") !== "false"
|
||||
document.documentElement.setAttribute("data-theme", savedTheme)
|
||||
document.documentElement.setAttribute("data-dark", savedDarkMode.toString())
|
||||
})()
|
||||
</script>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script src="/src/index.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
48
packages/app/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@opencode/app",
|
||||
"version": "0.13.9",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vite": "^6.0.0",
|
||||
"vite-plugin-icons-spritesheet": "3.0.1",
|
||||
"vite-plugin-solid": "^2.11.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@shikijs/transformers": "3.9.2",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
"@solid-primitives/resize-observer": "2.1.3",
|
||||
"@solid-primitives/scroll": "2.1.3",
|
||||
"@solidjs/router": "0.15.3",
|
||||
"@thisbeyond/solid-dnd": "0.7.5",
|
||||
"diff": "8.0.2",
|
||||
"fuzzysort": "catalog:",
|
||||
"luxon": "catalog:",
|
||||
"marked": "16.2.0",
|
||||
"marked-shiki": "1.2.1",
|
||||
"remeda": "catalog:",
|
||||
"shiki": "3.9.2",
|
||||
"solid-js": "catalog:",
|
||||
"solid-list": "0.3.0",
|
||||
"tailwindcss": "4.1.11",
|
||||
"virtua": "0.42.3"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 120
|
||||
}
|
||||
}
|
||||
163
packages/app/scripts/vite-theme-plugin.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import type { Plugin } from "vite"
|
||||
import { readdir, readFile, writeFile } from "fs/promises"
|
||||
import { join, resolve } from "path"
|
||||
|
||||
interface ThemeDefinition {
|
||||
$schema?: string
|
||||
defs?: Record<string, string>
|
||||
theme: Record<string, any>
|
||||
}
|
||||
|
||||
interface ResolvedThemeColor {
|
||||
dark: string
|
||||
light: string
|
||||
}
|
||||
|
||||
class ColorResolver {
|
||||
private colors: Map<string, any> = new Map()
|
||||
private visited: Set<string> = new Set()
|
||||
|
||||
constructor(defs: Record<string, string> = {}, theme: Record<string, any> = {}) {
|
||||
Object.entries(defs).forEach(([key, value]) => {
|
||||
this.colors.set(key, value)
|
||||
})
|
||||
Object.entries(theme).forEach(([key, value]) => {
|
||||
this.colors.set(key, value)
|
||||
})
|
||||
}
|
||||
|
||||
resolveColor(key: string, value: any): ResolvedThemeColor {
|
||||
if (this.visited.has(key)) {
|
||||
throw new Error(`Circular reference detected for color ${key}`)
|
||||
}
|
||||
|
||||
this.visited.add(key)
|
||||
|
||||
try {
|
||||
if (typeof value === "string") {
|
||||
if (value === "none") return { dark: value, light: value }
|
||||
if (value.startsWith("#")) {
|
||||
return { dark: value.toLowerCase(), light: value.toLowerCase() }
|
||||
}
|
||||
const resolved = this.resolveReference(value)
|
||||
return { dark: resolved, light: resolved }
|
||||
}
|
||||
if (typeof value === "object" && value !== null) {
|
||||
const dark = this.resolveColorValue(value.dark || value.light || "#000000")
|
||||
const light = this.resolveColorValue(value.light || value.dark || "#FFFFFF")
|
||||
return { dark, light }
|
||||
}
|
||||
return { dark: "#000000", light: "#FFFFFF" }
|
||||
} finally {
|
||||
this.visited.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
private resolveColorValue(value: any): string {
|
||||
if (typeof value === "string") {
|
||||
if (value === "none") return value
|
||||
if (value.startsWith("#")) {
|
||||
return value.toLowerCase()
|
||||
}
|
||||
return this.resolveReference(value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private resolveReference(ref: string): string {
|
||||
const colorValue = this.colors.get(ref)
|
||||
if (colorValue === undefined) {
|
||||
throw new Error(`Color reference '${ref}' not found`)
|
||||
}
|
||||
if (typeof colorValue === "string") {
|
||||
if (colorValue === "none") return colorValue
|
||||
if (colorValue.startsWith("#")) {
|
||||
return colorValue.toLowerCase()
|
||||
}
|
||||
return this.resolveReference(colorValue)
|
||||
}
|
||||
return colorValue
|
||||
}
|
||||
}
|
||||
|
||||
function kebabCase(str: string): string {
|
||||
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()
|
||||
}
|
||||
|
||||
function parseTheme(themeData: ThemeDefinition): Record<string, ResolvedThemeColor> {
|
||||
const resolver = new ColorResolver(themeData.defs, themeData.theme)
|
||||
const colors: Record<string, ResolvedThemeColor> = {}
|
||||
Object.entries(themeData.theme).forEach(([key, value]) => {
|
||||
colors[key] = resolver.resolveColor(key, value)
|
||||
})
|
||||
return colors
|
||||
}
|
||||
|
||||
async function loadThemes(): Promise<Record<string, Record<string, ResolvedThemeColor>>> {
|
||||
const themesDir = resolve(__dirname, "../../tui/internal/theme/themes")
|
||||
const files = await readdir(themesDir)
|
||||
const themes: Record<string, Record<string, ResolvedThemeColor>> = {}
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.endsWith(".json")) continue
|
||||
|
||||
const themeName = file.replace(".json", "")
|
||||
const themeData: ThemeDefinition = JSON.parse(await readFile(join(themesDir, file), "utf-8"))
|
||||
|
||||
themes[themeName] = parseTheme(themeData)
|
||||
}
|
||||
|
||||
return themes
|
||||
}
|
||||
|
||||
function generateCSS(themes: Record<string, Record<string, ResolvedThemeColor>>): string {
|
||||
let css = `/* Auto-generated theme CSS - Do not edit manually */\n:root {\n`
|
||||
|
||||
const defaultTheme = themes["opencode"] || Object.values(themes)[0]
|
||||
if (defaultTheme) {
|
||||
Object.entries(defaultTheme).forEach(([key, color]) => {
|
||||
const cssVar = `--theme-${kebabCase(key)}`
|
||||
css += ` ${cssVar}: ${color.light};\n`
|
||||
})
|
||||
}
|
||||
css += `}\n\n`
|
||||
|
||||
Object.entries(themes).forEach(([themeName, colors]) => {
|
||||
css += `[data-theme="${themeName}"][data-dark="false"] {\n`
|
||||
Object.entries(colors).forEach(([key, color]) => {
|
||||
const cssVar = `--theme-${kebabCase(key)}`
|
||||
css += ` ${cssVar}: ${color.light};\n`
|
||||
})
|
||||
css += `}\n\n`
|
||||
|
||||
css += `[data-theme="${themeName}"][data-dark="true"] {\n`
|
||||
Object.entries(colors).forEach(([key, color]) => {
|
||||
const cssVar = `--theme-${kebabCase(key)}`
|
||||
css += ` ${cssVar}: ${color.dark};\n`
|
||||
})
|
||||
css += `}\n\n`
|
||||
})
|
||||
|
||||
return css
|
||||
}
|
||||
|
||||
export function generateThemeCSS(): Plugin {
|
||||
return {
|
||||
name: "generate-theme-css",
|
||||
async buildStart() {
|
||||
try {
|
||||
console.log("Generating theme CSS...")
|
||||
const themes = await loadThemes()
|
||||
const css = generateCSS(themes)
|
||||
|
||||
const outputPath = resolve(__dirname, "../src/assets/theme.css")
|
||||
await writeFile(outputPath, css)
|
||||
|
||||
console.log(`✅ Generated theme CSS with ${Object.keys(themes).length} themes`)
|
||||
console.log(` Output: ${outputPath}`)
|
||||
} catch (error) {
|
||||
throw new Error(`Theme CSS generation failed: ${error}`)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 377 B After Width: | Height: | Size: 377 B |
1
packages/app/src/assets/file-icons/3d.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#29b6f6" d="M21 16.5c0 .38-.21.71-.53.88l-7.9 4.44c-.16.12-.36.18-.57.18s-.41-.06-.57-.18l-7.9-4.44A.99.99 0 0 1 3 16.5v-9c0-.38.21-.71.53-.88l7.9-4.44c.16-.12.36-.18.57-.18s.41.06.57.18l7.9 4.44c.32.17.53.5.53.88zM12 4.15 6.04 7.5 12 10.85l5.96-3.35zM5 15.91l6 3.38v-6.71L5 9.21zm14 0v-6.7l-6 3.37v6.71z"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
1
packages/app/src/assets/file-icons/abap.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#0288d1" d="M2 10v12h14l14-12"/></svg>
|
||||
|
After Width: | Height: | Size: 110 B |
1
packages/app/src/assets/file-icons/abc.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#ff5722" d="M13.295 11.033V7.65l2.126-2.136c.774-.763.919-1.981.377-2.929a2.38 2.38 0 0 0-2.068-1.217c-.203 0-.435.029-.619.087-1.044.28-1.749 1.246-1.749 2.33v3.13L8.327 9.98a5.75 5.75 0 0 0-1.208 6.214 5.62 5.62 0 0 0 4.243 3.432v.59a.5.5 0 0 1-.483.482h-1.45v1.934h1.45a2.43 2.43 0 0 0 2.416-2.417v-.483c1.962 0 4.02-1.856 4.02-4.591 0-2.223-1.855-4.108-4.02-4.108m0-7.249c0-.222.106-.396.31-.454a.47.47 0 0 1 .54.222.48.48 0 0 1-.077.59l-.773.83V3.785m-1.933 7.732c-.938.619-1.643 1.682-1.894 2.668l1.894.503v2.948a3.73 3.73 0 0 1-2.484-2.185 3.8 3.8 0 0 1 .802-4.098l1.682-1.769zm1.933 6.283v-4.89c1.13 0 2.107 1.062 2.107 2.232 0 1.691-1.227 2.658-2.107 2.658"/></svg>
|
||||
|
After Width: | Height: | Size: 746 B |
1
packages/app/src/assets/file-icons/actionscript.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="#f44336" d="M560-160v-80h120q17 0 28.5-11.5T720-280v-80q0-38 22-69t58-44v-14q-36-13-58-44t-22-69v-80q0-17-11.5-28.5T680-720H560v-80h120q50 0 85 35t35 85v80q0 17 11.5 28.5T840-560h40v160h-40q-17 0-28.5 11.5T800-360v80q0 50-35 85t-85 35zm-280 0q-50 0-85-35t-35-85v-80q0-17-11.5-28.5T120-400H80v-160h40q17 0 28.5-11.5T160-600v-80q0-50 35-85t85-35h120v80H280q-17 0-28.5 11.5T240-680v80q0 38-22 69t-58 44v14q36 13 58 44t22 69v80q0 17 11.5 28.5T280-240h120v80z"/><path fill="#f44336" d="M360-600h80v40h-80zm80 240h40v-200h-40v80h-80v-80h-40v200h40v-80h80zm200-200v-40H530a10 10 0 0 0-10 10v100a10 10 0 0 0 10 10h70v80h-80v40h110a10 10 0 0 0 10-10v-140a10 10 0 0 0-10-10h-70v-40z"/></svg>
|
||||
|
After Width: | Height: | Size: 758 B |
1
packages/app/src/assets/file-icons/ada.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#0277bd" d="m2 12 2.9-1.07c.25-1.1.87-1.73.87-1.73a3.996 3.996 0 0 1 5.65 0l1.41 1.41 6.31-6.7c.95 3.81 0 7.62-2.33 10.69L22 19.62s-8.47 1.9-13.4-1.95c-2.63-2.06-3.22-3.26-3.59-4.52zm5.04.21c.37.37.98.37 1.35 0s.37-.97 0-1.34a.96.96 0 0 0-1.35 0c-.37.37-.37.97 0 1.34"/></svg>
|
||||
|
After Width: | Height: | Size: 348 B |
1
packages/app/src/assets/file-icons/adobe-illustrator.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="28" height="28" x="2" y="2" fill="#5d4037" rx="4"/><path fill="#ffb74d" d="M20.988 9.999a.96.96 0 0 1-.687-.269 1 1 0 0 1-.263-.704.9.9 0 0 1 .278-.681 1 1 0 0 1 .687-.268.93.93 0 0 1 .703.268 1.046 1.046 0 0 1-.015 1.385.9.9 0 0 1-.703.268M20 12h2v10h-2zm-5.63-1.98-.01-.02h-2.08a.12.12 0 0 0-.1.13 4.5 4.5 0 0 1-.06.74c-.05.13-.08.26-.12.37l-.27.78L8 22h2.14l.75-2h5.24l.79 2h2.16zM11.64 18l1.8-4.84.01.04.02.04L14.95 17l.39 1z"/></svg>
|
||||
|
After Width: | Height: | Size: 511 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="28" height="28" x="2" y="2" fill="#795548" rx="4"/><path fill="#ffb74d" d="M20.988 9.999a.96.96 0 0 1-.687-.269 1 1 0 0 1-.263-.704.9.9 0 0 1 .278-.681 1 1 0 0 1 .687-.268.93.93 0 0 1 .703.268 1.046 1.046 0 0 1-.015 1.385.9.9 0 0 1-.703.268M20 12h2v10h-2zm-5.63-1.98-.01-.02h-2.08a.12.12 0 0 0-.1.13 4.5 4.5 0 0 1-.06.74c-.05.13-.08.26-.12.37l-.27.78L8 22h2.14l.75-2h5.24l.79 2h2.16zM11.64 18l1.8-4.84.01.04.02.04L14.95 17l.39 1z"/></svg>
|
||||
|
After Width: | Height: | Size: 511 B |
1
packages/app/src/assets/file-icons/adobe-photoshop.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="28" height="28" x="2" y="2" fill="#37474f" rx="4"/><path fill="#64b5f6" d="M23.744 14.716a3.7 3.7 0 0 0-1.066-.408 5.4 5.4 0 0 0-1.245-.157 2.1 2.1 0 0 0-.666.085.57.57 0 0 0-.345.24.7.7 0 0 0-.089.324.56.56 0 0 0 .111.313 1.3 1.3 0 0 0 .378.324q.386.217.79.397a7.8 7.8 0 0 1 1.71.877 2.7 2.7 0 0 1 .878.998 2.8 2.8 0 0 1 .256 1.238 2.96 2.96 0 0 1-.434 1.599 2.83 2.83 0 0 1-1.244 1.07 4.75 4.75 0 0 1-2.011.384 7 7 0 0 1-1.511-.156 4.2 4.2 0 0 1-1.134-.385.24.24 0 0 1-.122-.228v-2.092a.14.14 0 0 1 .044-.108c.034-.024.067-.012.1.012a4.6 4.6 0 0 0 1.378.59 4.8 4.8 0 0 0 1.311.18 2 2 0 0 0 .923-.169.56.56 0 0 0 .3-.505.65.65 0 0 0-.267-.48 4.6 4.6 0 0 0-1.089-.565 6.6 6.6 0 0 1-1.578-.866 3 3 0 0 1-.844-1.021 2.76 2.76 0 0 1-.256-1.226 3 3 0 0 1 .378-1.455 2.8 2.8 0 0 1 1.167-1.105A4 4 0 0 1 21.533 12a9 9 0 0 1 1.378.108 3.7 3.7 0 0 1 .956.277.2.2 0 0 1 .11.108.7.7 0 0 1 .023.144v1.96a.15.15 0 0 1-.056.12.28.28 0 0 1-.2 0M12.38 10H9.99v-.03h-2v12h2V18h2.39A3.62 3.62 0 0 0 16 14.38v-.76A3.62 3.62 0 0 0 12.38 10M14 14.38A1.626 1.626 0 0 1 12.38 16H9.99v-4h2.39A1.626 1.626 0 0 1 14 13.62Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="28" height="28" x="2" y="2" fill="#455a64" rx="4"/><path fill="#64b5f6" d="M23.744 14.716a3.7 3.7 0 0 0-1.066-.408 5.4 5.4 0 0 0-1.245-.157 2.1 2.1 0 0 0-.666.085.57.57 0 0 0-.345.24.7.7 0 0 0-.089.324.56.56 0 0 0 .111.313 1.3 1.3 0 0 0 .378.324q.386.217.79.397a7.8 7.8 0 0 1 1.71.877 2.7 2.7 0 0 1 .878.998 2.8 2.8 0 0 1 .256 1.238 2.96 2.96 0 0 1-.434 1.599 2.83 2.83 0 0 1-1.244 1.07 4.75 4.75 0 0 1-2.011.384 7 7 0 0 1-1.511-.156 4.2 4.2 0 0 1-1.134-.385.24.24 0 0 1-.122-.228v-2.092a.14.14 0 0 1 .044-.108c.034-.024.067-.012.1.012a4.6 4.6 0 0 0 1.378.59 4.8 4.8 0 0 0 1.311.18 2 2 0 0 0 .923-.169.56.56 0 0 0 .3-.505.65.65 0 0 0-.267-.48 4.6 4.6 0 0 0-1.089-.565 6.6 6.6 0 0 1-1.578-.866 3 3 0 0 1-.844-1.021 2.76 2.76 0 0 1-.256-1.226 3 3 0 0 1 .378-1.455 2.8 2.8 0 0 1 1.167-1.105A4 4 0 0 1 21.533 12a9 9 0 0 1 1.378.108 3.7 3.7 0 0 1 .956.277.2.2 0 0 1 .11.108.7.7 0 0 1 .023.144v1.96a.15.15 0 0 1-.056.12.28.28 0 0 1-.2 0M12.38 10H9.99v-.03h-2v12h2V18h2.39A3.62 3.62 0 0 0 16 14.38v-.76A3.62 3.62 0 0 0 12.38 10M14 14.38A1.626 1.626 0 0 1 12.38 16H9.99v-4h2.39A1.626 1.626 0 0 1 14 13.62Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
packages/app/src/assets/file-icons/adobe-swc.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#e53935" d="M4 5v22a1 1 0 0 0 1 1h22a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1m20 7c-2.926 0-4.21.722-5.012 2H22v4h-4.582C16.34 20.857 14.393 24 8 24v-4c4.559 0 5.14-1.744 6.103-4.632C15.139 12.258 16.559 8 24 8Z"/></svg>
|
||||
|
After Width: | Height: | Size: 297 B |
1
packages/app/src/assets/file-icons/adonis.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path fill="#7c4dff" d="m79.579 25.741-66.481 115.15h63.305l11.218-19.433H47.613L79.804 65.7l20.005 34.649 11.423-19.783zm42.118 50.221-45.203 78.297h90.408z" paint-order="fill markers stroke"/></svg>
|
||||
|
After Width: | Height: | Size: 262 B |
1
packages/app/src/assets/file-icons/advpl.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#7986cb" fill-rule="evenodd" d="M6.752 1.158C2.234 1.96-.271 6.943 1.758 11.09c2.537 5.185 10.047 5.142 12.511-.07C16.69 5.9 12.321.17 6.752 1.159m.587 2.335c2.576.517 5.233 1.323 5.326 1.615.26.808.256 4.849-.004 5.34-.066.125-1.209-.012-2.08-.247l-.351-.094-.001-.437c-.005-1.308-.138-2.547-.29-2.7-.176-.176-1.312-.545-3.052-.99L5.78 5.697l-.014-.267c-.033-.6.117-1.95.232-2.093.063-.079.315-.05 1.34.157M4.029 5.39c.5.066 1.083.178 1.492.289l.178.048.03.984c.058 1.844.117 2.13.475 2.29.448.2 2.083.679 3.62 1.061l.34.084-.01.653c-.012.735-.083 1.393-.175 1.617l-.062.15-.261-.03c-.976-.113-4.175-.896-5.567-1.362-.611-.205-.759-.284-.811-.435-.23-.66-.23-4.905 0-5.337.054-.1.08-.1.75-.012"/></svg>
|
||||
|
After Width: | Height: | Size: 775 B |
1
packages/app/src/assets/file-icons/amplify.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#ff9800" d="M14 10 5 28h12l-2-4h-4l3-6 5 10h4zm1-2 2-4 12 24h-4l-8-16z"/></svg>
|
||||
|
After Width: | Height: | Size: 151 B |
1
packages/app/src/assets/file-icons/android.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="4" height="10" x="2" y="12" fill="#8bc34a" rx="2"/><rect width="4" height="10" x="26" y="12" fill="#8bc34a" rx="2"/><path fill="#8bc34a" d="M8 12h16v12H8zm2 12h4v4a2 2 0 0 1-2 2 2 2 0 0 1-2-2zm8 0h4v4a2 2 0 0 1-2 2 2 2 0 0 1-2-2zm3.545-19.759 2.12-2.12A1 1 0 0 0 22.251.707l-2.326 2.326a7.97 7.97 0 0 0-7.85 0L9.75.707a1 1 0 1 0-1.414 1.414l2.12 2.12A7.97 7.97 0 0 0 8 10h16a7.97 7.97 0 0 0-2.455-5.759M14 8h-2V6h2Zm6 0h-2V6h2Z"/></svg>
|
||||
|
After Width: | Height: | Size: 509 B |
1
packages/app/src/assets/file-icons/angular.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 24 24"><path fill="#e53935" d="M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z"/></svg>
|
||||
|
After Width: | Height: | Size: 263 B |
1
packages/app/src/assets/file-icons/antlr.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 24 24"><path fill="#f44336" d="M10.355 1.614a10.469 10.483 0 0 1 11.813 7.792 10.327 10.34 0 0 1-1.565 8.673 10.583 10.597 0 0 1-14.819 2.428 10.416 10.43 0 0 1-4.222-7.14 10.641 10.656 0 0 1 .999-5.994 10.498 10.512 0 0 1 7.795-5.76m.27 3.825c-.949 2.08-1.9 4.16-2.83 6.25-.479 1.345-1.127 2.615-1.716 3.915-.174.408-.468.853-.287 1.312a1.088 1.09 0 0 0 1.575.556c.458-.261.566-.828.778-1.272.952-2.405 2.13-4.708 3.11-7.104a7.356 7.366 0 0 1 .776-1.6c.568 1.406 1.186 2.791 1.773 4.19a14.819 14.839 0 0 1 .969 2.197c-1.51-.015-3.02-.004-4.531-.01 2.073 1.233 4.202 2.379 6.305 3.562a1.094 1.094 0 0 0 1.698-1.036c-.425-1.15-1.014-2.237-1.5-3.364-.917-2.393-2.076-4.685-3.097-7.036a2.685 2.689 0 0 0-.738-1.163 1.564 1.566 0 0 0-2.285.602z"/></svg>
|
||||
|
After Width: | Height: | Size: 823 B |
1
packages/app/src/assets/file-icons/apiblueprint.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="12" height="12" x="10" y="2" fill="#42a5f5" rx="6"/><rect width="12" height="12" x="18" y="18" fill="#42a5f5" rx="6"/><rect width="12" height="12" x="2" y="18" fill="#42a5f5" rx="6"/><path fill="none" stroke="#42a5f5" stroke-miterlimit="10" stroke-width="3" d="m16 8 8 16M16 8 8 24"/></svg>
|
||||
|
After Width: | Height: | Size: 363 B |
1
packages/app/src/assets/file-icons/apollo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#7e57c2" d="M31.93 14.457a.51.51 0 0 0-.506-.457h-2.01a.497.497 0 0 0-.491.559l.014.134c.616 6.284-4.097 12.817-10.29 14.044A13.009 13.009 0 1 1 24.3 6h4.19a16.013 16.013 0 1 0 3.44 8.457"/><circle cx="24.533" cy="4.267" r="4.267" fill="#7e57c2"/><path fill="#7e57c2" d="M17 8h-3L8 24h3z"/><path fill="#7e57c2" d="M15 8h3l6 16h-3zm2.88 13H12v-3h4.75z"/></svg>
|
||||
|
After Width: | Height: | Size: 431 B |
1
packages/app/src/assets/file-icons/applescript.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#78909c" d="M25.425 26.498c-1.162 1.736-2.394 3.43-4.27 3.458-1.875.042-2.477-1.106-4.605-1.106-2.142 0-2.8 1.078-4.578 1.148-1.834.07-3.22-1.848-4.396-3.542C5.183 23 3.35 16.63 5.813 12.346a6.84 6.84 0 0 1 5.767-3.514c1.792-.028 3.5 1.217 4.606 1.217 1.092 0 3.164-1.497 5.334-1.273a6.5 6.5 0 0 1 5.095 2.771 6.38 6.38 0 0 0-3.01 5.334 6.18 6.18 0 0 0 3.752 5.656 15.5 15.5 0 0 1-1.932 3.961M17.432 4.1A6.36 6.36 0 0 1 21.548 2a6.13 6.13 0 0 1-1.456 4.466 5.11 5.11 0 0 1-4.13 1.988 5.98 5.98 0 0 1 1.47-4.354"/></svg>
|
||||
|
After Width: | Height: | Size: 591 B |
1
packages/app/src/assets/file-icons/apps-script.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#f44336" d="M6.053 20.055H21.21a3.01 3.01 0 0 1 3.049 2.966 3.01 3.01 0 0 1-3.049 2.966H6.053a3.01 3.01 0 0 1-3.049-2.966 3.01 3.01 0 0 1 3.049-2.966"/><path fill="#ffc107" d="M19.44 25.433 7.179 16.765a2.914 2.914 0 0 1-.674-4.143 3.104 3.104 0 0 1 4.258-.656l12.263 8.668a2.914 2.914 0 0 1 .674 4.143 3.104 3.104 0 0 1-4.258.656Z"/><path fill="#43a047" d="m19.489 8.05 4.683 14.026a2.95 2.95 0 0 1-1.957 3.737 3.067 3.067 0 0 1-3.841-1.904L13.69 9.884a2.95 2.95 0 0 1 1.957-3.738 3.067 3.067 0 0 1 3.842 1.905Z"/><path fill="#448aff" d="M18.363 22.076 23.047 8.05a3.067 3.067 0 0 1 3.841-1.904 2.95 2.95 0 0 1 1.958 3.737L24.162 23.91a3.067 3.067 0 0 1-3.842 1.904 2.95 2.95 0 0 1-1.957-3.737Z"/></svg>
|
||||
|
After Width: | Height: | Size: 776 B |
1
packages/app/src/assets/file-icons/appveyor.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" viewBox="0 0 256 256"><path fill="#00b8d4" fill-rule="evenodd" d="M127.646 17.356c61.588 0 110.999 49.414 110.999 110.29a110.64 110.64 0 0 1-110.999 110.999c-60.873 0-110.29-49.414-110.29-110.999 0-60.873 49.414-110.29 110.29-110.29m27.213 131.77c-12.174 15.756-34.375 18.62-49.414 6.446-15.039-11.459-17.187-33.66-5.013-49.414 12.891-15.039 35.091-17.904 50.131-6.445 15.039 12.174 17.187 34.375 4.297 49.414zm-58.723 72.331 42.252-40.82c-15.756 3.58-32.227.716-45.117-10.026-15.039-11.459-21.484-30.795-19.336-48.699L35.98 163.45s-5.013-9.31-6.446-26.498l66.602-52.278c20.052-14.323 47.266-15.04 66.602 0 21.484 17.187 25.781 48.698 10.027 72.33l-48.699 69.466c-7.161 0-21.484-2.149-27.93-5.013"/></svg>
|
||||
|
After Width: | Height: | Size: 776 B |
1
packages/app/src/assets/file-icons/architecture.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#66bb6a" d="M6.278 22 6 19.556l3.167-8.723a4.37 4.37 0 0 0 1.944 1.056l-3.055 8.389zm11.666 0-1.777-1.722-3.056-8.39q.556-.138 1.042-.402a4.4 4.4 0 0 0 .903-.653l3.166 8.723zm-5.833-11.111q-1.389 0-2.361-.972-.972-.973-.972-2.361 0-1.084.624-1.932.626-.846 1.598-1.18V2h2.222v2.444a3.27 3.27 0 0 1 1.598 1.18q.624.849.624 1.932 0 1.389-.972 2.36-.972.973-2.36.973Zm0-2.222q.473 0 .792-.32t.32-.791q0-.473-.32-.793a1.08 1.08 0 0 0-.792-.319q-.472 0-.791.32t-.32.792.32.79q.319.32.791.32Z"/></svg>
|
||||
|
After Width: | Height: | Size: 579 B |
1
packages/app/src/assets/file-icons/arduino.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#0097a7" d="M2 14h10v2H2zm22-4h2v10h-2z"/><path fill="#0097a7" d="M20 14h10v2H20z"/><path fill="none" stroke="#0097a7" stroke-width="2" d="M2 5h4a10 10 0 0 1 10 10 10 10 0 0 0 10 10h4"/><path fill="#0097a7" d="M11.644 22A8.95 8.95 0 0 1 6 24H2v2h4a10.98 10.98 0 0 0 8.479-4ZM26 4a10.98 10.98 0 0 0-8.479 4h2.835A8.95 8.95 0 0 1 26 6h4V4Z"/></svg>
|
||||
|
After Width: | Height: | Size: 418 B |
1
packages/app/src/assets/file-icons/asciidoc.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#0097a7" d="M4 18V8l5.39 10Zm0 4v3.67A2.33 2.33 0 0 0 6.33 28h8.9l-3.496-6Zm12.444 0 3.177 5.444A11.88 11.88 0 0 0 26.448 22Zm11.419-4A15 15 0 0 0 28 16 12 12 0 0 0 16 4L6 3.995q-.08 0-.158.005L14 18Z"/></svg>
|
||||
|
After Width: | Height: | Size: 281 B |
1
packages/app/src/assets/file-icons/assembly.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#ff6e40" d="M8 6V2H4a2 2 0 0 0-2 2v24a2 2 0 0 0 2 2h4v-4H4V6Zm16-4v4h4v20h-4v4h4a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2Zm-4 4h-2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2m-2 6V8h2v4Zm-4 6h-2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2m-2 6v-4h2v4Zm0-18c0 2 0 2-2 2v2h2v4h2V6Zm8 12c0 2 0 2-2 2v2h2v4h2v-8Z"/></svg>
|
||||
|
After Width: | Height: | Size: 415 B |
1
packages/app/src/assets/file-icons/astro-config.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#757575" d="M15 2H6a2.006 2.006 0 0 0-2 2v22a2.006 2.006 0 0 0 2 2h6v-4H6v-2h6v-2H6v-2h6v-2H6v-2h6v-2h2V4l8 8h2v-1Z"/><path fill="#7c4dff" d="M12 12v18h18V12Zm10 16c-.9 0-2.025-1.267-2.025-3.005-.914 0-.975.464-.975 1.005-.881-.213-1-1.15-1-2h6c0 1.919-2 1.787-2 4m2.542-6a2.5 2.5 0 0 1-2.308-1.641l-.946-2.42a.305.305 0 0 0-.576 0l-.946 2.42A2.5 2.5 0 0 1 17.458 22H16l2.965-7.59a.63.63 0 0 1 .577-.41h2.916a.63.63 0 0 1 .577.41L26 22Z"/></svg>
|
||||
|
After Width: | Height: | Size: 517 B |
1
packages/app/src/assets/file-icons/astro.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#7c4dff" d="M12.106 25.849c-1.262-1.156-1.63-3.586-1.105-5.346a5.18 5.18 0 0 0 3.484 1.66 9.68 9.68 0 0 0 5.882-.734c.215-.106.413-.247.648-.39a3.5 3.5 0 0 1 .16 1.555 4.26 4.26 0 0 1-1.798 3.021c-.404.3-.832.569-1.25.852a2.613 2.613 0 0 0-1.15 3.372l.048.161a3.4 3.4 0 0 1-1.5-1.285 3.6 3.6 0 0 1-.578-1.962 9 9 0 0 0-.05-1.037c-.114-.831-.504-1.204-1.238-1.225a1.45 1.45 0 0 0-1.507 1.18c-.012.056-.028.112-.046.178M4.901 20a17.75 17.75 0 0 1 7.4-2l2.913-8.38a.765.765 0 0 1 1.527 0L19.7 18a14.24 14.24 0 0 1 7.399 2S20.704 2.877 20.692 2.842C20.51 2.33 20.202 2 19.787 2h-7.619c-.415 0-.71.33-.904.842z"/></svg>
|
||||
|
After Width: | Height: | Size: 686 B |
1
packages/app/src/assets/file-icons/astyle.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#ef5350" d="M8.203 5.447 5.83 6.777l1.329-2.374-1.33-2.374 2.374 1.33 2.374-1.33-1.33 2.374 1.33 2.374zm11.394 9.305 2.374-1.329-1.33 2.374 1.33 2.373-2.374-1.329-2.374 1.33 1.33-2.374-1.33-2.374zm2.374-12.724-1.33 2.374 1.33 2.374-2.374-1.33-2.374 1.33 1.33-2.374-1.33-2.374 2.374 1.33zm-8.223 10.236 2.317-2.316-2.013-2.013-2.317 2.317zm.978-5.212 2.222 2.222c.37.35.37.968 0 1.338L5.867 21.694c-.37.37-.987.37-1.339 0l-2.222-2.221c-.37-.352-.37-.969 0-1.34l11.081-11.08c.37-.37.988-.37 1.34 0z"/></svg>
|
||||
|
After Width: | Height: | Size: 577 B |
1
packages/app/src/assets/file-icons/audio.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#ef5350" d="M16 2a14 14 0 1 0 14 14A14 14 0 0 0 16 2m6 10h-4v8a4 4 0 1 1-4-4 3.96 3.96 0 0 1 2 .555V8h6Z"/></svg>
|
||||
|
After Width: | Height: | Size: 185 B |
1
packages/app/src/assets/file-icons/aurelia.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><defs><linearGradient xlink:href="#a" id="i" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse"/><linearGradient id="a" x1="-3.881" x2="2.377" y1="-1.442" y2="4.304"><stop offset="0" stop-color="#ba68c8"/><stop offset="1" stop-color="#7e57c2"/></linearGradient><linearGradient xlink:href="#b" id="j" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse"/><linearGradient id="b" x1=".729" x2="-.971" y1=".844" y2="-1.477"><stop offset="0" stop-color="#5e35b1"/><stop offset=".14" stop-color="#8e24aa"/><stop offset=".29" stop-color="#ad1457"/><stop offset=".84" stop-color="#c2185b"/><stop offset="1" stop-color="#ec407a"/></linearGradient><linearGradient xlink:href="#c" id="k" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse"/><linearGradient id="c" x1="-2.839" x2="2.875" y1="-6.936" y2="1.017"><stop offset="0" stop-color="#ba68c8"/><stop offset="1" stop-color="#7e57c2"/></linearGradient><linearGradient xlink:href="#d" id="l" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse"/><linearGradient id="d" x1="-8.212" x2="1.02" y1="-4.691" y2="2.882"><stop offset="0" stop-color="#ba68c8"/><stop offset="1" stop-color="#7e57c2"/></linearGradient><linearGradient xlink:href="#e" id="m" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse"/><linearGradient id="e" x1="-1.404" x2="4.19" y1="-2.309" y2="2.62"><stop offset="0" stop-color="#ba68c8"/><stop offset="1" stop-color="#7e57c2"/></linearGradient><linearGradient xlink:href="#f" id="n" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse"/><linearGradient id="f" x1="1.911" x2=".204" y1="2.539" y2=".204"><stop offset="0" stop-color="#7e57c2"/><stop offset=".14" stop-color="#7b1fa2"/><stop offset=".29" stop-color="#ad1457"/><stop offset=".84" stop-color="#c2185b"/><stop offset="1" stop-color="#ec407a"/></linearGradient><linearGradient xlink:href="#g" id="o" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse"/><linearGradient id="g" x1="-3.881" x2="2.377" y1="-1.738" y2="5.19"><stop offset="0" stop-color="#ba68c8"/><stop offset="1" stop-color="#7e57c2"/></linearGradient><linearGradient xlink:href="#h" id="p" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse"/><linearGradient id="h" x1=".112" x2=".901" y1=".897" y2=".116"><stop offset="0" stop-color="#7e57c2"/><stop offset=".14" stop-color="#8e24aa"/><stop offset=".53" stop-color="#c2185b"/><stop offset=".79" stop-color="#c2185b"/><stop offset="1" stop-color="#ec407a"/></linearGradient></defs><g stroke-linejoin="round" stroke-miterlimit="1.414" clip-rule="evenodd"><path fill="url(#i)" d="M8.002 6.127 4.117 8.719.116 2.723 4 .13z" transform="translate(11.282 3.07)scale(.47102)"/><path fill="url(#j)" d="m9.179 1.887 6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="translate(12.215 13.552)scale(.47102)"/><path fill="url(#k)" d="m7.3 1.88 1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="translate(8.41 16.686)scale(.47102)"/><path fill="url(#l)" d="M2.328 1.146 4.016.02l2.619 3.925L2.75 6.537l-1.46-2.19 2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="translate(16.99 11.686)scale(.47102)"/><path fill="url(#m)" d="m5.346 9.155-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="translate(2.738 8.18)scale(.47102)"/><path fill="url(#n)" d="m14.533 9.934 1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="translate(4.753 2.36)scale(.47102)"/><path fill="url(#o)" d="M6.235 7.177 4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="translate(11.32 3.106)scale(.47102)"/><path fill="#673ab7" d="m9.632 19.05-.545-.818 2.215-1.478.546.817zm7.965-5.315-.545-.817 1.035-.691.545.817z"/><path fill="#7e57c2" d="m5.256 12.492-.564-.845 2.216-1.478.563.845zm7.965-5.315-.564-.845 1.035-.69.564.844z"/><path fill="#880e4f" d="m16.538 14.441-3.724 2.485-.545-.817 3.724-2.485z"/><path fill="#ad1457" d="m11.598 7.039.564.844-3.724 2.485-.564-.844z"/><path fill="#ab47bc" d="m4.2 6.363.703 1.054-1.053.702-.703-1.053z"/><path fill="#7e57c2" d="m7.996 18.99.703 1.054-1.054.703-.702-1.054z"/><path fill="url(#p)" d="M8.372 38.294.017 29.876 29.749.08l8.636 8.201z" transform="rotate(11.282 -5.61 25.53)scale(.47102)"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
1
packages/app/src/assets/file-icons/authors.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#f44336" d="M15.787 13.71c-.275 0-.587 0-.918.047 1.098.796 1.865 1.847 1.865 3.267v2.367h5.68v-2.367c0-2.206-4.42-3.314-6.627-3.314m-7.575 0c-2.206 0-6.628 1.108-6.628 3.314v2.367H14.84v-2.367c0-2.206-4.421-3.314-6.628-3.314m0-1.894a2.84 2.84 0 0 0 2.841-2.84 2.84 2.84 0 0 0-2.84-2.84 2.84 2.84 0 0 0-2.841 2.84 2.84 2.84 0 0 0 2.84 2.84m7.575 0a2.84 2.84 0 0 0 2.84-2.84 2.84 2.84 0 0 0-2.84-2.84 2.84 2.84 0 0 0-2.84 2.84 2.84 2.84 0 0 0 2.84 2.84"/></svg>
|
||||
|
After Width: | Height: | Size: 532 B |
1
packages/app/src/assets/file-icons/auto.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#ffc400" d="M8.48 4.17c.334.574 1.047.798 1.696.636A7.5 7.5 0 0 1 12 4.583c.62 0 1.223.075 1.799.217.647.159 1.357-.065 1.691-.64.39-.668.116-1.532-.63-1.751A10.1 10.1 0 0 0 12 2c-1.006 0-1.977.146-2.894.419-.743.22-1.015 1.083-.627 1.75Z"/><path fill="#ad1457" d="M5.039 4.772c.564-.535 1.458-.34 1.848.331.333.572.176 1.292-.284 1.769a7.4 7.4 0 0 0-1.456 2.17c-.242.552-.762.958-1.367.958-.854 0-1.496-.781-1.191-1.572a10 10 0 0 1 2.45-3.656"/><path fill="#cfd8dc" d="M3.197 12c.718 0 1.32.583 1.444 1.286.613 3.483 3.675 6.13 7.359 6.13s6.746-2.647 7.359-6.13c.124-.703.726-1.286 1.444-1.286.719 0 1.279.581 1.187 1.289C21.353 18.203 17.123 22 12 22s-9.353-3.797-9.99-8.711C1.918 12.58 2.478 12 3.197 12"/><path fill="#ff5252" d="M20.203 9.958c.857 0 1.5-.786 1.19-1.578a10 10 0 0 0-2.458-3.632c-.564-.533-1.455-.336-1.845.333-.333.573-.174 1.295.289 1.772a7.4 7.4 0 0 1 1.459 2.155c.243.548.762.95 1.365.95"/><path fill="#cfd8dc" d="M7.133 9.32c-.442-.488.053-1.262.657-1.027l4.912 1.91c1.114.434 1.538 1.84.862 2.855a1.785 1.785 0 0 1-2.83.222l-3.6-3.96Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
12
packages/app/src/assets/file-icons/auto_light.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path fill="#ffc400"
|
||||
d="M8.48 4.17c.334.574 1.047.798 1.696.636A7.5 7.5 0 0 1 12 4.583c.62 0 1.223.075 1.799.217.647.159 1.357-.065 1.691-.64.39-.668.116-1.532-.63-1.751A10.1 10.1 0 0 0 12 2c-1.006 0-1.977.146-2.894.419-.743.22-1.015 1.083-.627 1.75Z" />
|
||||
<path fill="#ad1457"
|
||||
d="M5.039 4.772c.564-.535 1.458-.34 1.848.331.333.572.176 1.292-.284 1.769a7.4 7.4 0 0 0-1.456 2.17c-.242.552-.762.958-1.367.958-.854 0-1.496-.781-1.191-1.572a10 10 0 0 1 2.45-3.656" />
|
||||
<path fill="currentColor"
|
||||
d="M3.197 12c.718 0 1.32.583 1.444 1.286.613 3.483 3.675 6.13 7.359 6.13s6.746-2.647 7.359-6.13c.124-.703.726-1.286 1.444-1.286.719 0 1.279.581 1.187 1.289C21.353 18.203 17.123 22 12 22s-9.353-3.797-9.99-8.711C1.918 12.58 2.478 12 3.197 12" />
|
||||
<path fill="#ff5252"
|
||||
d="M20.203 9.958c.857 0 1.5-.786 1.19-1.578a10 10 0 0 0-2.458-3.632c-.564-.533-1.455-.336-1.845.333-.333.573-.174 1.295.289 1.772a7.4 7.4 0 0 1 1.459 2.155c.243.548.762.95 1.365.95" />
|
||||
<path fill="currentColor"
|
||||
d="M7.133 9.32c-.442-.488.053-1.262.657-1.027l4.912 1.91c1.114.434 1.538 1.84.862 2.855a1.785 1.785 0 0 1-2.83.222l-3.6-3.96Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
packages/app/src/assets/file-icons/autohotkey.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#4caf50" d="M25.333 4H6.667A2.657 2.657 0 0 0 4 6.667v18.666A2.667 2.667 0 0 0 6.667 28h18.666A2.667 2.667 0 0 0 28 25.333V6.667A2.667 2.667 0 0 0 25.333 4m-2.495 6.22a4 4 0 0 0-.163 1.01q0 .266-.058.83a9 9 0 0 0-.04.719c0 .584-.031 1.443-.092 2.55q-.074 1.253-.088 2.502c0 .412.032 1.057.097 1.91.067.865.1 1.534.1 1.988a1.62 1.62 0 0 1-.505 1.197 1.65 1.65 0 0 1-1.225.475 1.92 1.92 0 0 1-1.233-.466 1.51 1.51 0 0 1-.554-1.19q0-.644-.06-1.934-.047-.99-.056-1.979 0-.198.003-.376c-.805.065-1.766.198-2.867.398q-1.522.277-3.045.562-.032.61-.11 1.65a30 30 0 0 0-.087 2.017 1.62 1.62 0 0 1-.506 1.192 1.73 1.73 0 0 1-1.224.474l-.048.001a1.7 1.7 0 0 1-1.157-.479 1.62 1.62 0 0 1-.502-1.2c0-.615.05-1.513.155-2.738.104-1.182.157-2.077.157-2.661q0-1.15.057-3.46.054-2.302.053-3.442a1.62 1.62 0 0 1 .508-1.196 1.68 1.68 0 0 1 1.222-.478 1.7 1.7 0 0 1 1.206.484 1.63 1.63 0 0 1 .5 1.19q0 .687-.055 2.07-.036 1.01-.044 2.023.001.23-.065.905a7 7 0 0 0-.022.251l2.825-.532a28 28 0 0 1 3.086-.395q.037-.83.095-2.76a4.8 4.8 0 0 1 .466-1.778c.421-.926.957-1.395 1.591-1.395a1.75 1.75 0 0 1 1.166.434l.003.002a1.58 1.58 0 0 1 .566 1.228 1.5 1.5 0 0 1-.05.397"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
packages/app/src/assets/file-icons/autoit.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#1976d2" d="M12.002 2a10 10 0 0 0-10 10 10 10 0 0 0 10 10 10 10 0 0 0 10-10 10 10 0 0 0-10-10m.139 4.419q.642 0 1.07.294.431.294.731.731l5.71 8.262H9.026l1.707-2.35h3.15q.443 0 .77.028a11 11 0 0 1-.443-.62q-.253-.376-.485-.704l-1.64-2.417-4.29 6.063H4.45l5.86-8.262q.285-.396.723-.71.437-.315 1.108-.315"/></svg>
|
||||
|
After Width: | Height: | Size: 384 B |
1
packages/app/src/assets/file-icons/azure-pipelines.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#64b5f6" d="M3.98 22.01h1.803v4.208H9.99v1.803H3.98Z"/><path fill="#1565c0" d="M3.98 10.991v5.51l3.505 3.61 1.503-1.606 4.508 4.508-1.502 1.502 3.506 3.506h5.51a1 1 0 0 0 1.001-1.002v-8.014L12.995 9.99H4.982a1 1 0 0 0-1.003 1.001Z"/><path fill="#1e88e5" d="M8.317 18.44a1 1 0 0 1-.125-1.265L16.407 4.87a2 2 0 0 1 1.666-.891h8.946A1 1 0 0 1 28.02 4.98v8.946a2 2 0 0 1-.891 1.667l-12.305 8.215a1 1 0 0 1-1.265-.126Z"/><path fill="#64b5f6" d="m8.976 21.542 7.648-7.648 1.48 1.481-7.647 7.648Z"/><path fill="#42a5f5" d="m11.68 21.801-1.481-1.48 6.426-6.427 1.48 1.481Z"/><path fill="#90caf9" d="M22.011 12.995a3.006 3.006 0 0 0 .096-6.011h-.096a3.006 3.006 0 0 0 0 6.01Z"/></svg>
|
||||
|
After Width: | Height: | Size: 747 B |
1
packages/app/src/assets/file-icons/azure.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#01579b" d="M12.001 4h7.102l-7.372 23.181a1.14 1.14 0 0 1-1.073.819H5.13A1.166 1.166 0 0 1 4 26.801a1.3 1.3 0 0 1 .06-.385l6.87-21.599A1.14 1.14 0 0 1 12.001 4"/><path fill="#1976d2" d="M22.32 20H11.06a.537.537 0 0 0-.522.55.57.57 0 0 0 .166.408l7.236 6.716a1.1 1.1 0 0 0 .775.325h6.376Z"/><path fill="#29b6f6" d="M21.071 4.816A1.14 1.14 0 0 0 20.001 4h-7.915a1.14 1.14 0 0 1 1.072.815l6.868 21.599a1.22 1.22 0 0 1-.71 1.52 1.1 1.1 0 0 1-.362.064h7.915A1.166 1.166 0 0 0 28 26.8a1.3 1.3 0 0 0-.06-.385L21.072 4.817Z"/></svg>
|
||||
|
After Width: | Height: | Size: 596 B |
1
packages/app/src/assets/file-icons/babel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#fdd835" d="M18.23 11.21q-.045-.24-1.32-1.65c-.02-.19.29-.45.9-.8l1.74-1.55c.39-.5.62-1.28.69-2.38l-.02-.26c-.07-.78-.63-1.4-1.69-1.89-.63-.42-1.76-.65-3.38-.68-1.35.11-3.11.59-5.28 1.43-.6.43-1.28.86-2.04 1.28l.01.14.21-.08c.08-.01.13.03.14.11l.13-.07.07-.01.01.06c0 .07-.47.44-1.76 1.35l-.06.12c-.31.02-.61.25-.91.67l.08.12.25-.09.18.24c.32-.33.66-.62 1.03-.87.19.05.29.11.44.16 1.02-.75 2.03-1.3 3.04-1.64l.01.14c-.2.27-.32.42-.38.42l.1.23c.01.19-2.55 7-6.66 14.44l.08.19c.35-.08.58-.17.75-.26l.01.13.4-.03-.67 1.76.14.06c.57-.64 1-1.29 1.3-1.88 1.67-.49 2.94-.97 3.82-1.44.88-.08 1.56-.31 2.02-.7l.92-.47c1.27-.98 2.22-1.67 2.87-2.08 1.33-.98 2.2-1.93 2.6-2.85zm-3.46 2.31L13 14.91c-1.29.85-2 1.3-2.09 1.3-2.07 1.13-3.36 1.72-3.86 1.76l-.05.01c.04-.23.96-2.12 2.75-5.67.78-.06 2.02-.43 3.71-1.1l.41-.03c.85-.08 1.49.09 1.91.49l.03.26c-.31.9-.67 1.44-1.04 1.59m1.09-5.78q-.27.33-1.5 1.11c-.27.03-1.27.42-3.01 1.18l-.28-.05-.01-.12c-.02-.25.09-.57.34-.95.13-.7.28-1.12.44-1.2l1.45-3.28c-.02-.22.29-.35.93-.46l.21-.02.01.18 1.16-.16c1.15-.1 1.75.14 1.8.7l.13-.02-.03-.32.15-.02c.35.19.52.4.54.68.02.18-.08.41-.29.68-.09.01-.14-.06-.15-.18l-.14.01-.03.4c-.58.87-1.01 1.31-1.27 1.34z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
packages/app/src/assets/file-icons/ballerina.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#00bfa5" d="m14 12-6-2V2h6Zm-6 0 4 2.058L8 16Zm0 18V18l6-2v4l-2 10Zm10-18 6-2V2h-6Zm6 0-4 2.058L24 16Zm0 18V18l-6-2v4l2 10Z"/></svg>
|
||||
|
After Width: | Height: | Size: 204 B |
1
packages/app/src/assets/file-icons/bazel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#81c784" d="m153.491 50.983 102.508 102.508-102.508 102.508L50.983 153.491z"/><path fill="#43a047" d="M50.983 153.491v102.508l102.508 102.508V255.999z"/><path fill="#81c784" d="m358.507 50.983 102.508 102.508-102.508 102.508-102.508-102.508z"/><path fill="#43a047" d="M461.015 153.491v102.508L358.507 358.507V255.999zm-205.016 0 102.508 102.508-102.508 102.508-102.508-102.508z"/><path fill="#2e7d32" d="M255.999 358.507v102.508L153.491 358.507V255.999z"/><path fill="#1b5e20" d="m255.999 358.507 102.508-102.508v102.508L255.999 461.015z"/></svg>
|
||||
|
After Width: | Height: | Size: 620 B |
1
packages/app/src/assets/file-icons/bbx.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="#c62828" d="M128 704v128c0 70.692 57.308 128 128 128h608c17.728 0 32-14.272 32-32V704z"/><path fill="#ffe082" d="M704 704v192h128V704z"/><path fill="#fff8e1" d="M192 704v96c0 53.184 42.816 96 96 96h544a96 96 0 0 1-96-96 96 96 0 0 1 96-96z"/><path fill="#ff1744" d="M320 832h192v192l-96-96-96 96z"/><path fill="#f44336" d="M256 64c-70.692 0-128 57.308-128 128v640c0 11.088 1.557 21.787 4.207 32.047C146.767 807.565 197.672 768.07 256 768h608c17.728 0 32-14.272 32-32V96c0-17.728-14.272-32-32-32z"/><path fill="#ffeb3b" d="M256 192c-70.912 0-128 57.088-128 128v64c0-70.912 57.088-128 128-128h448v320H256c-70.912 0-128 57.088-128 128v64c0-70.912 57.088-128 128-128h512V192z"/></svg>
|
||||
|
After Width: | Height: | Size: 755 B |
1
packages/app/src/assets/file-icons/beancount.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#e64a19" d="M26.471 5.736c7.383 3.577 2.04 13.636-5.547 17.984-5.998 3.44-18.128 5.76-18.877-2.22-.738-7.863 7.61-6.698 11.575-8.67 4.032-2.003 6.854-9.998 12.85-7.093zm-11.684 8.89c-1.167.438-3.695.194-3.479 2.094.215 1.932 3.483.908 5.243.097 1.788-.82 3.415-2.475 2.27-3.496-1.424-1.268-2.421.698-4.034 1.305"/></svg>
|
||||
|
After Width: | Height: | Size: 392 B |
1
packages/app/src/assets/file-icons/bench-js.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#ffca28" d="M6.915 9.906q.42.413 1.084.404t.98-.472l3.92-5.775-5.88 3.85q-.472.309-.498.945t.394 1.048M7.999 2q1.033 0 1.987.284.953.283 1.793.85l-1.33.825q-.577-.292-1.198-.438-.622-.146-1.252-.146-2.327 0-3.963 1.607T2.4 8.875q0 .722.201 1.427.201.704.569 1.323h9.659q.402-.653.586-1.358t.184-1.46q0-.62-.149-1.204t-.446-1.134l.84-1.307q.525.808.831 1.72.306.91.324 1.89.017.98-.228 1.873-.245.894-.717 1.702-.193.31-.525.481-.333.172-.7.172h-9.66q-.367 0-.7-.172t-.524-.481q-.455-.774-.7-1.642T1 8.875q0-1.427.551-2.673T3.056 4.02q.954-.937 2.231-1.479Q6.565 2 8 2zm.123 5.38"/></svg>
|
||||
|
After Width: | Height: | Size: 659 B |
1
packages/app/src/assets/file-icons/bench-jsx.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#00bcd4" d="M6.915 9.906q.42.413 1.084.404t.98-.472l3.92-5.775-5.88 3.85q-.472.309-.498.945t.394 1.048M7.999 2q1.033 0 1.987.284.953.283 1.793.85l-1.33.825q-.577-.292-1.198-.438-.622-.146-1.252-.146-2.327 0-3.963 1.607T2.4 8.875q0 .722.201 1.427.201.704.569 1.323h9.659q.402-.653.586-1.358t.184-1.46q0-.62-.149-1.204t-.446-1.134l.84-1.307q.525.808.831 1.72.306.91.324 1.89.017.98-.228 1.873-.245.894-.717 1.702-.193.31-.525.481-.333.172-.7.172h-9.66q-.367 0-.7-.172t-.524-.481q-.455-.774-.7-1.642T1 8.875q0-1.427.551-2.673T3.056 4.02q.954-.937 2.231-1.479Q6.565 2 8 2zm.123 5.38"/></svg>
|
||||
|
After Width: | Height: | Size: 659 B |
1
packages/app/src/assets/file-icons/bench-ts.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#0288d1" d="M6.915 9.906q.42.413 1.084.404t.98-.472l3.92-5.775-5.88 3.85q-.472.309-.498.945t.394 1.048M7.999 2q1.033 0 1.987.284.953.283 1.793.85l-1.33.825q-.577-.292-1.198-.438-.622-.146-1.252-.146-2.327 0-3.963 1.607T2.4 8.875q0 .722.201 1.427.201.704.569 1.323h9.659q.402-.653.586-1.358t.184-1.46q0-.62-.149-1.204t-.446-1.134l.84-1.307q.525.808.831 1.72.306.91.324 1.89.017.98-.228 1.873-.245.894-.717 1.702-.193.31-.525.481-.333.172-.7.172h-9.66q-.367 0-.7-.172t-.524-.481q-.455-.774-.7-1.642T1 8.875q0-1.427.551-2.673T3.056 4.02q.954-.937 2.231-1.479Q6.565 2 8 2zm.123 5.38"/></svg>
|
||||
|
After Width: | Height: | Size: 659 B |
1
packages/app/src/assets/file-icons/bibliography.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="#795548" d="M96 832h832c17.728 0 32 14.272 32 32v64c0 17.728-14.272 32-32 32H96c-17.728 0-32-14.272-32-32v-64c0-17.728 14.272-32 32-32"/><path fill="#4caf50" d="M160 192h64c17.728 0 32 14.272 32 32v512c0 17.728-14.272 32-32 32h-64c-17.728 0-32-14.272-32-32V224c0-17.728 14.272-32 32-32"/><path fill="#f44336" d="M512 96c0-17.728-14.272-32-32-32H352c-17.728 0-32 14.272-32 32v640c0 17.728 14.272 32 32 32h128c17.728 0 32-14.272 32-32z"/><path fill="#2196f3" d="m530.161 158.902 57.333-27.693a31.804 31.804 0 0 1 42.634 14.936l262.693 548.17c7.66 15.984.977 35.057-14.982 42.766l-57.333 27.693a31.804 31.804 0 0 1-42.634-14.936L515.18 201.668c-7.66-15.983-.977-35.057 14.982-42.766z"/><path fill="#ffeb3b" d="M320 192v64h192v-64zm0 384v64h192v-64z"/></svg>
|
||||
|
After Width: | Height: | Size: 830 B |
1
packages/app/src/assets/file-icons/bibtex-style.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="#795548" d="M96 832h832c17.728 0 32 14.272 32 32v64c0 17.728-14.272 32-32 32H96c-17.728 0-32-14.272-32-32v-64c0-17.728 14.272-32 32-32"/><path fill="#4caf50" d="M160 192h64c17.728 0 32 14.272 32 32v512c0 17.728-14.272 32-32 32h-64c-17.728 0-32-14.272-32-32V224c0-17.728 14.272-32 32-32"/><path fill="#f44336" d="M512 96c0-17.728-14.272-32-32-32H352c-17.728 0-32 14.272-32 32v640c0 17.728 14.272 32 32 32h128c17.728 0 32-14.272 32-32z"/><path fill="#ffeb3b" d="M320 192v64h192v-64zm0 384v64h192v-64z"/><path fill="#bbdefb" d="M608 320h256c17.728 0 32 14.272 32 32v384c0 17.728-14.272 32-32 32H608c-17.728 0-32-14.272-32-32V352c0-17.728 14.272-32 32-32"/><path fill="#2196f3" d="M608 320c-17.673 0-32 14.327-32 32v352c35.346 0 64-28.654 64-64v-32a32 32 0 0 1 32-32c17.673 0 32-14.327 32-32v-64a32 32 0 0 1 32-32c17.673 0 32-14.327 32-32v-96z"/><path d="M745.606 339.205 924.74 473.693a15.965 15.965 0 0 1 3.19 22.401 15.965 15.965 0 0 1-22.403 3.19l-179.133-134.49a15.965 15.965 0 0 1-3.19-22.401 15.965 15.965 0 0 1 22.402-3.19z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
packages/app/src/assets/file-icons/bicep.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#fbc02d" d="M3 18S4.15 6.885 7 3l5 1-1 3H9v7h1c1.9-2.915 5.783-3.98 8.157-2.915 4.475 1.915 2.998 5.967.148 7.905C16.025 20.548 10.113 23.05 3 18"/></svg>
|
||||
|
After Width: | Height: | Size: 226 B |
1
packages/app/src/assets/file-icons/biome.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 74 74"><path fill="#42a5f5" d="M37 9 22.745 33.69a32.2 32.2 0 0 1 16.869-.584l4.818 1.137-4.533 19.22-4.825-1.137c-5.93-1.399-11.628 1.716-14.036 6.685l-4.46-2.158c3.404-7.029 11.425-11.285 19.637-9.347l2.259-9.58A27.23 27.23 0 0 0 5 64.424l64 .001z"/></svg>
|
||||
|
After Width: | Height: | Size: 311 B |
1
packages/app/src/assets/file-icons/bitbucket.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><defs><linearGradient id="a" x1="64.01" x2="32.99" y1="65.26" y2="89.48" gradientUnits="userSpaceOnUse"><stop offset=".18" stop-color="#1565c0"/><stop offset="1" stop-color="#1e88e5"/></linearGradient></defs><path fill="#1e88e5" d="M2.985 3.333a.618.618 0 0 0-.617.716l2.621 15.914a.84.84 0 0 0 .822.701h12.576a.62.62 0 0 0 .618-.519l2.627-16.09a.618.618 0 0 0-.617-.716zm11.039 11.501H10.01L8.923 9.16h6.074z"/><path fill="url(#a)" d="M59.67 60.12H40.9L37.75 78.5h-13L9.4 96.73a2.7 2.7 0 0 0 1.75.66h40.74a2 2 0 0 0 2-1.68z" transform="translate(2.368 -9.404)scale(.30877)"/></svg>
|
||||
|
After Width: | Height: | Size: 685 B |
1
packages/app/src/assets/file-icons/bithound.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill-opacity=".05" viewBox="0 0 400 400"><g fill="#e53935" fill-opacity="1"><path d="M350.738 186.163c-1.32-13.024-4.224-26.312-8.36-38.72-11.88-35.464-33.968-71.808-61.864-96.888-1.232-1.056-5.896-3.872-7.656-2.904-4.576 2.552 4.048 20.064 5.104 23.232 6.512 19.36 10.648 39.864 5.984 60.104-6.248 26.752-26.752 45.496-54.12 47.784-15.048 1.232-30.184-.44-45.232 1.32-22.528 2.64-45.496 10.384-59.84 28.864-1.672 2.112-3.168 4.488-4.576 6.952h-.352c-5.544.616-11.088-1.76-13.816-3.256-.704-.44-1.408-.792-1.936-1.056-16.72-9.24-29.04-29.92-36.608-46.992-3.432-7.92-6.336-16.192-8.184-24.552-.88-3.784-.968-7.744-1.144-11.616-.088-2.376.264-5.72-1.056-7.832-2.904-4.576-6.6-.176-7.216 3.52-.968 6.072-1.848 12.056-1.584 18.216.44 10.384 3.344 20.68 7.04 30.36 5.456 14.256 13.112 27.368 23.056 39.072 4.136 4.84 8.536 9.328 13.288 13.464 4.224 3.784 9.592 6.776 12.76 11.616 3.696 5.544 4.312 12.408 3.96 18.832-.88 16.984-1.408 32.912 3.432 49.456 4.224 14.696 9.504 29.744 18.304 42.328 4.4 6.248 9.856 12.848 15.84 17.512 4.048 3.168 11.704 3.52 7.304-8.096-9.768-25.784-10.648-52.536 4.576-76.648 12.76-20.064 35.288-37.928 60.72-34.76 37.4 4.664 63.448 38.984 61.6 75.68-.528 10.296-.88 19.096-4.136 28.776-1.32 3.872-2.288 8.8-1.32 12.848 1.584 6.864 9.24 4.312 12.584-.176 9.064-12.32 18.568-24.288 27.104-36.96 27.808-41.536 41.36-89.584 36.344-139.48"/><path d="M141.21 85.051c.616 2.024 1.232 4.224 1.672 6.6.088.968.352 2.024.88 2.992 2.288 5.984 7.832 9.24 13.024 12.32 3.168 1.936 8.888 3.784 12.408 5.192 4.576 1.848 14.432-.528 19.096-.88 10.736-.88 20.68-4.664 30.536 1.056-50.512 59.224-2.816 72.424 34.144 43.912 42.24-32.56 2.464-109.384 2.464-109.384s-.88-5.984-16.896-9.504a52 52 0 0 0-4.488-2.112c-15.84-7.304-30.096 4.664-41.536 14.432-3.344 2.816-6.6 5.632-10.12 8.272-4.752 3.52-9.856 6.424-15.224 8.976-5.632 2.64-12.32 5.632-18.568 5.896-.88 0-2.552.176-4.312.528-2.728.264-4.136.968-4.752 2.2-1.056.88-1.76 2.112-1.584 3.696.176 2.2 1.232 4.048 2.376 5.456.352.088.616.264.88.352"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
1
packages/app/src/assets/file-icons/blender.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><g fill="#ff9800" fill-rule="evenodd"><path d="M14.004 19a5 5 0 0 1 4.993-5.001 5 5 0 0 1 5.008 4.985A5 5 0 0 1 19.028 24a5 5 0 0 1-5.024-4.97z" paint-order="stroke fill markers"/><path d="m23.148 7.152-4.355-4.451s-1.488-1.534-3.022-.046-.046 3.022-.046 3.022l.165.264c.693.69 1.412 1.368 2.105 2.058h-12s-2 0-2 2 2 2 2 2h4l-6 6-1.332 1.41s-1.503 1.503 0 3.007c1.503 1.503 3.006 0 3.006 0l2.327-2.417.063.19q.06.555.173 1.09a11.02 11.02 0 0 0 8.599 8.508 11 11 0 0 0 2.216.213c6.068-.028 10.967-4.965 10.949-11.034 0-5.452-4-8.967-6.848-11.815zm-4.162 4.847a7 7 0 1 1-6.99 7.044V19a7 7 0 0 1 6.99-7"/></g></svg>
|
||||
|
After Width: | Height: | Size: 672 B |