Compare commits
473
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcfeb52983 | ||
|
|
46790e57e9 | ||
|
|
4bc3fa0826 | ||
|
|
88cfb979be | ||
|
|
32b5db754e | ||
|
|
f33f8ca109 | ||
|
|
598d63db63 | ||
|
|
38bff1b372 | ||
|
|
e8c9b21f20 | ||
|
|
6d3fc63658 | ||
|
|
ee4437ff32 | ||
|
|
7a4aa68706 | ||
|
|
f00380d285 | ||
|
|
c00d4885c6 | ||
|
|
70f4722356 | ||
|
|
8898bf7ca4 | ||
|
|
e5b13b767e | ||
|
|
3181c68cbb | ||
|
|
c3c9003dbb | ||
|
|
921b98066d | ||
|
|
c5b4cc80cc | ||
|
|
0bccd1d578 | ||
|
|
91db82c138 | ||
|
|
0eb97086fc | ||
|
|
5b34636afa | ||
|
|
f1138b9a55 | ||
|
|
23ff6dbba4 | ||
|
|
b457923970 | ||
|
|
66e4a5a64e | ||
|
|
6c25e64658 | ||
|
|
f2fd0f8f00 | ||
|
|
44cdde5422 | ||
|
|
88235dc618 | ||
|
|
4d2b671d7b | ||
|
|
8098031eac | ||
|
|
74c882d9d0 | ||
|
|
80636fec43 | ||
|
|
a8ad74aef3 | ||
|
|
e2e2b7934e | ||
|
|
28c802f399 | ||
|
|
bcfa63aa4e | ||
|
|
d40feafb01 | ||
|
|
2a8473891b | ||
|
|
a4e3451d5c | ||
|
|
53a7c2885b | ||
|
|
f354507d42 | ||
|
|
f17e1def32 | ||
|
|
3183e8b7d4 | ||
|
|
9d2b9ef2d4 | ||
|
|
733e5cd876 | ||
|
|
4ee4f7bcb3 | ||
|
|
da7ecda9ea | ||
|
|
1f11d4fb1a | ||
|
|
b308503ab5 | ||
|
|
3b10de4a28 | ||
|
|
6ce1de476a | ||
|
|
d9b0848a61 | ||
|
|
46dd3b8166 | ||
|
|
eca07be072 | ||
|
|
165d57b88e | ||
|
|
28c44f7e5a | ||
|
|
39d5bdff4b | ||
|
|
b9f8480b2f | ||
|
|
58b30d678a | ||
|
|
408cdaf5e0 | ||
|
|
cae23cde09 | ||
|
|
702fb2562c | ||
|
|
785d0b60b6 | ||
|
|
67ab9dc4d0 | ||
|
|
77494cb7df | ||
|
|
08f8856d6c | ||
|
|
79a4c1544d | ||
|
|
c0a35141e6 | ||
|
|
221bb64aeb | ||
|
|
2555dba188 | ||
|
|
6355ed6ae7 | ||
|
|
1864e8c863 | ||
|
|
61d0d66dae | ||
|
|
86522f1b3e | ||
|
|
dc32705bc9 | ||
|
|
1eaf5c31d3 | ||
|
|
677b19e22e | ||
|
|
8e248ae045 | ||
|
|
0acefd5c08 | ||
|
|
0331931f56 | ||
|
|
01e2c9cc21 | ||
|
|
4acb645f04 | ||
|
|
ef664039ac | ||
|
|
993422c6af | ||
|
|
c950d59047 | ||
|
|
cc726e0200 | ||
|
|
3d99dc78db | ||
|
|
95c3a8b805 | ||
|
|
f1bb5870ce | ||
|
|
540407e193 | ||
|
|
027d43b5ea | ||
|
|
729a6eda23 | ||
|
|
cbbd21e2e5 | ||
|
|
e7d45ca617 | ||
|
|
0ddfdb55d3 | ||
|
|
fc439455a7 | ||
|
|
c0fc02769b | ||
|
|
068ac68496 | ||
|
|
7da6a22df2 | ||
|
|
e37aeb6e6a | ||
|
|
eeb0d2b8e0 | ||
|
|
bf8c866bf7 | ||
|
|
75354b0002 | ||
|
|
4eb4d97d51 | ||
|
|
b1b82977ec | ||
|
|
f6262460ff | ||
|
|
560a610384 | ||
|
|
0308b2ff98 | ||
|
|
5b92d49be7 | ||
|
|
0386d0ae09 | ||
|
|
28bec57e1d | ||
|
|
aaa31f02af | ||
|
|
ff609a52c1 | ||
|
|
1e30793f0a | ||
|
|
5268eb479d | ||
|
|
a4eba2e6e9 | ||
|
|
0f30115205 | ||
|
|
ae500ea01d | ||
|
|
087479d459 | ||
|
|
6e2379a28c | ||
|
|
262fa184fd | ||
|
|
9a7e1c154d | ||
|
|
5bf9193dfa | ||
|
|
180fb3f39d | ||
|
|
7e6b7314f4 | ||
|
|
a262508fb8 | ||
|
|
80ff24b65a | ||
|
|
012aa67e42 | ||
|
|
0a1f12a583 | ||
|
|
f17dc812d0 | ||
|
|
1854d85ccc | ||
|
|
2c4d1fb8b4 | ||
|
|
d8fa7cf65d | ||
|
|
7d8d360138 | ||
|
|
d80880350d | ||
|
|
b693ed0dbd | ||
|
|
83f961a7c2 | ||
|
|
a093917db1 | ||
|
|
52716db649 | ||
|
|
9ca4b464ea | ||
|
|
204a31b6bb | ||
|
|
813d287a09 | ||
|
|
4dd9f33eba | ||
|
|
5953378a12 | ||
|
|
b419eed295 | ||
|
|
52deb7f352 | ||
|
|
a4f3aecbaa | ||
|
|
49ff6a852a | ||
|
|
7f537d2e98 | ||
|
|
753443b16f | ||
|
|
33c63be980 | ||
|
|
b6efca42b4 | ||
|
|
fa6eadc39a | ||
|
|
8789acefa6 | ||
|
|
0e280017e6 | ||
|
|
17e8322c29 | ||
|
|
96eda740cd | ||
|
|
fa84612357 | ||
|
|
cf1f63eda3 | ||
|
|
9704f5ce89 | ||
|
|
0eaec2af82 | ||
|
|
398d35dc97 | ||
|
|
5efeaae093 | ||
|
|
cb2dd34a5e | ||
|
|
7112a706b8 | ||
|
|
025a47d01f | ||
|
|
13f89fdb8f | ||
|
|
cc78d50ef6 | ||
|
|
a8985b1849 | ||
|
|
6a1552f65c | ||
|
|
776091cc23 | ||
|
|
f385524f48 | ||
|
|
350982e636 | ||
|
|
5854455815 | ||
|
|
9ecaf618db | ||
|
|
95b667d21e | ||
|
|
a0b689c140 | ||
|
|
ea52ed41be | ||
|
|
5a50d54fda | ||
|
|
35d118b0c4 | ||
|
|
ea7c213f5d | ||
|
|
70dd6dd394 | ||
|
|
049510afbd | ||
|
|
c120447fd0 | ||
|
|
feb1f36126 | ||
|
|
d6ef47bb2d | ||
|
|
50fd416d49 | ||
|
|
aef6904247 | ||
|
|
0bf40faf95 | ||
|
|
c90987c4b0 | ||
|
|
0e08655407 | ||
|
|
427887db9c | ||
|
|
a718622498 | ||
|
|
4e83107d79 | ||
|
|
04b6e72820 | ||
|
|
501a2539c7 | ||
|
|
6a9856d480 | ||
|
|
2c8d42d997 | ||
|
|
9c237f0bfb | ||
|
|
63bfe76720 | ||
|
|
99d7ff47c4 | ||
|
|
3ff0eb3065 | ||
|
|
4d2b265dc4 | ||
|
|
1854245bd3 | ||
|
|
4d07034930 | ||
|
|
98031173b6 | ||
|
|
e8e474597c | ||
|
|
382758790c | ||
|
|
c33920f59d | ||
|
|
33f004d4b6 | ||
|
|
8963b536ee | ||
|
|
51455e2a1e | ||
|
|
30d6a26e3e | ||
|
|
cd4fabd11b | ||
|
|
9a8b8f26ac | ||
|
|
2f73b16b57 | ||
|
|
df9952c291 | ||
|
|
ee946d8128 | ||
|
|
ec8f2e078e | ||
|
|
335f46122b | ||
|
|
73eae191e9 | ||
|
|
14e823e938 | ||
|
|
2fbd462e6e | ||
|
|
e1cc98d448 | ||
|
|
0ce64962d4 | ||
|
|
338229193f | ||
|
|
57644a4be8 | ||
|
|
da2099137a | ||
|
|
09bc8d9ca4 | ||
|
|
d95f724303 | ||
|
|
c413c3ed8f | ||
|
|
5f56be0ad4 | ||
|
|
ef441d5cff | ||
|
|
16a188c524 | ||
|
|
50c40a8d99 | ||
|
|
4114c8715c | ||
|
|
ced5fdbe70 | ||
|
|
b16aa81e0d | ||
|
|
b44971668c | ||
|
|
0ff4c284e2 | ||
|
|
e8db95be16 | ||
|
|
69c2dd53ad | ||
|
|
14a910bd64 | ||
|
|
52f97ffdc9 | ||
|
|
a1e87f6cd9 | ||
|
|
c2fc41dcd5 | ||
|
|
b62c7943e7 | ||
|
|
64caeeb12d | ||
|
|
e8ac4a1e99 | ||
|
|
19c8654195 | ||
|
|
00d7aed797 | ||
|
|
4477132448 | ||
|
|
eaeea45ace | ||
|
|
e404bf33b1 | ||
|
|
79a7edea5e | ||
|
|
2b05fe2859 | ||
|
|
f8996f0a90 | ||
|
|
eb04cdac41 | ||
|
|
125938c7a1 | ||
|
|
ec1260d8aa | ||
|
|
9eabbe2e76 | ||
|
|
5f35c579e2 | ||
|
|
99a23bdc8f | ||
|
|
d914a08896 | ||
|
|
ceccc30cb8 | ||
|
|
3366a71155 | ||
|
|
cd67804412 | ||
|
|
4a95db6013 | ||
|
|
8bf552ae25 | ||
|
|
ccd0c2382f | ||
|
|
d74663bf53 | ||
|
|
020ee56f25 | ||
|
|
87b295bc3d | ||
|
|
b9b1e92788 | ||
|
|
4000705701 | ||
|
|
673dbeee09 | ||
|
|
5288041782 | ||
|
|
4273fa9ccf | ||
|
|
01bcb9dff9 | ||
|
|
101f86ad1f | ||
|
|
2575dc2db0 | ||
|
|
b4e6f128d7 | ||
|
|
7d5e6718dc | ||
|
|
072aa7569c | ||
|
|
62871283e2 | ||
|
|
0f1dd24f97 | ||
|
|
995f551e80 | ||
|
|
15facd8cfd | ||
|
|
57bd47a446 | ||
|
|
0568c943ab | ||
|
|
b1aaa8570e | ||
|
|
3b2aa7e91d | ||
|
|
997aacf7f0 | ||
|
|
3183978c6b | ||
|
|
6a0b20456f | ||
|
|
8841112b07 | ||
|
|
16dbac6026 | ||
|
|
9b6d03c497 | ||
|
|
d3ea044619 | ||
|
|
364355c8e1 | ||
|
|
88c4e95428 | ||
|
|
9403f6ced5 | ||
|
|
56fe84e516 | ||
|
|
69d1381ba3 | ||
|
|
ccec8c4792 | ||
|
|
83c47e0ed7 | ||
|
|
6d630901b6 | ||
|
|
320622fc27 | ||
|
|
fb8ef1f27b | ||
|
|
333948711d | ||
|
|
001b4be0ae | ||
|
|
427c62d052 | ||
|
|
99097d418b | ||
|
|
75654afeaa | ||
|
|
f10d18956a | ||
|
|
b9253d0b3b | ||
|
|
2458e7597b | ||
|
|
23a721f0a2 | ||
|
|
9bd6be5c6d | ||
|
|
dd6113c9d1 | ||
|
|
d39bcd9c55 | ||
|
|
823d7da4c1 | ||
|
|
7fff191c57 | ||
|
|
9e44085a69 | ||
|
|
5230b91b25 | ||
|
|
493b3d72e4 | ||
|
|
337590c450 | ||
|
|
8e1c7cfe89 | ||
|
|
c60cb6c329 | ||
|
|
acf1dd8500 | ||
|
|
3fb57044d1 | ||
|
|
46a76a778a | ||
|
|
a9a2c23736 | ||
|
|
ccde319937 | ||
|
|
0ed7fac5fb | ||
|
|
80b9cd1292 | ||
|
|
b6c1df41fb | ||
|
|
125af820d0 | ||
|
|
8167e90801 | ||
|
|
82ebf66cba | ||
|
|
883ed4d424 | ||
|
|
e6bf1754c3 | ||
|
|
bcb494d5d1 | ||
|
|
75c0c0a098 | ||
|
|
840d2694b4 | ||
|
|
abdc7b276a | ||
|
|
d4f6deb9ef | ||
|
|
502e85b903 | ||
|
|
ac1e2bfd49 | ||
|
|
b9b071c744 | ||
|
|
83186b6fed | ||
|
|
a3a239967f | ||
|
|
b4fd4bb257 | ||
|
|
eb009d5959 | ||
|
|
4a81bd0f50 | ||
|
|
bbc9142fc5 | ||
|
|
7413c2715c | ||
|
|
7579c3bb15 | ||
|
|
24683058fd | ||
|
|
bf81e3108c | ||
|
|
5ade90416e | ||
|
|
8f2a8086c0 | ||
|
|
38b70f7877 | ||
|
|
5e112a17a5 | ||
|
|
59a3e7e3cc | ||
|
|
2c93f065cb | ||
|
|
488d33c1ed | ||
|
|
de4660ac12 | ||
|
|
27ae341684 | ||
|
|
25b3846694 | ||
|
|
8e2f9f6544 | ||
|
|
76b5870f89 | ||
|
|
604891e793 | ||
|
|
5814df7eaa | ||
|
|
2509d03f42 | ||
|
|
a256df9823 | ||
|
|
af96ec5a30 | ||
|
|
55df80b80e | ||
|
|
7d11986a0a | ||
|
|
d75d90c53e | ||
|
|
35fead2eca | ||
|
|
9bb2efd3ef | ||
|
|
30ffcaa667 | ||
|
|
ba11455786 | ||
|
|
d69ba27f84 | ||
|
|
448b72d046 | ||
|
|
d96d89bcb8 | ||
|
|
7a013d4f40 | ||
|
|
e4597df3b9 | ||
|
|
2b014fcd75 | ||
|
|
c2bf6975f8 | ||
|
|
733e1f79ac | ||
|
|
2a6cbfd5fd | ||
|
|
6173b69a8b | ||
|
|
fc72cfe784 | ||
|
|
768c81cdfd | ||
|
|
bcea8ed593 | ||
|
|
f93bb1dd21 | ||
|
|
b92e8510f9 | ||
|
|
1b692ec7eb | ||
|
|
4c97e2e8bb | ||
|
|
b652198979 | ||
|
|
31c4a1d853 | ||
|
|
6afdb5c0e5 | ||
|
|
bdcf864678 | ||
|
|
0dd5039668 | ||
|
|
a0831eade1 | ||
|
|
f1dc9818b6 | ||
|
|
b52b7c6ded | ||
|
|
e03a41144a | ||
|
|
37bb07e7a3 | ||
|
|
78a6325b64 | ||
|
|
c96923d2c9 | ||
|
|
59742fbfee | ||
|
|
2938a25ec5 | ||
|
|
d163eb3888 | ||
|
|
75c29d4d1c | ||
|
|
e103fb1f93 | ||
|
|
bd79ff87cc | ||
|
|
ac21ec2f46 | ||
|
|
5bcf017c10 | ||
|
|
85d99198b5 | ||
|
|
7f183f7404 | ||
|
|
85284df725 | ||
|
|
87054ee983 | ||
|
|
81245c2548 | ||
|
|
6f82b321d8 | ||
|
|
f4593c6653 | ||
|
|
15902cf54d | ||
|
|
d1102c33ac | ||
|
|
aabbd3383c | ||
|
|
afb55cb7d4 | ||
|
|
ade794a937 | ||
|
|
34271a82ff | ||
|
|
b20a31098a | ||
|
|
b5a039e5ae | ||
|
|
986cc0a01c | ||
|
|
b20fd36c48 | ||
|
|
e4e6bf66e1 | ||
|
|
3ae27273c6 | ||
|
|
eefb3c43dd | ||
|
|
cc229e726e | ||
|
|
49408c00e9 | ||
|
|
76192fbced | ||
|
|
a1c76c79de | ||
|
|
db9e2b1aac | ||
|
|
45c4970d68 | ||
|
|
1d7a9309d6 | ||
|
|
f5ac98251e | ||
|
|
78239045ba | ||
|
|
d8b60875c4 | ||
|
|
48949a6e9d | ||
|
|
082a330ea3 | ||
|
|
adf7df0d5c | ||
|
|
a76ad48563 | ||
|
|
00f991162f | ||
|
|
d6cdd24fad | ||
|
|
c9473756df | ||
|
|
b5d0c56b4c | ||
|
|
96a2f5268c | ||
|
|
5083f9c9c2 | ||
|
|
e34df15ff5 | ||
|
|
26ec87803a | ||
|
|
037e8d4555 | ||
|
|
08a366c4dc | ||
|
|
670e1523e0 | ||
|
|
416f2964b5 | ||
|
|
e018e16898 |
@@ -1,57 +0,0 @@
|
|||||||
#
|
|
||||||
# This file is intentionally in the wrong dir, will move and add later....
|
|
||||||
#
|
|
||||||
|
|
||||||
name: Guidelines Check
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Disabled - uncomment to re-enable
|
|
||||||
# pull_request_target:
|
|
||||||
# types: [opened, synchronize]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check-guidelines:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: Install opencode
|
|
||||||
run: curl -fsSL https://opencode.ai/install | bash
|
|
||||||
|
|
||||||
- name: Check PR guidelines compliance
|
|
||||||
env:
|
|
||||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
OPENCODE_PERMISSION: '{ "bash": { "gh*": "allow", "gh pr review*": "deny", "*": "deny" } }'
|
|
||||||
run: |
|
|
||||||
opencode run -m anthropic/claude-sonnet-4-20250514 "A new pull request has been created: '${{ github.event.pull_request.title }}'
|
|
||||||
|
|
||||||
<pr-number>
|
|
||||||
${{ github.event.pull_request.number }}
|
|
||||||
</pr-number>
|
|
||||||
|
|
||||||
<pr-description>
|
|
||||||
${{ github.event.pull_request.body }}
|
|
||||||
</pr-description>
|
|
||||||
|
|
||||||
Please check all the code changes in this pull request against the guidelines in AGENTS.md file in this repository. Diffs are important but make sure you read the entire file to get proper context. Make it clear the suggestions are merely suggestions and the human can decide what to do
|
|
||||||
|
|
||||||
Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block.
|
|
||||||
|
|
||||||
Command MUST be like this.
|
|
||||||
```
|
|
||||||
gh api \
|
|
||||||
--method POST \
|
|
||||||
-H "Accept: application/vnd.github+json" \
|
|
||||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
||||||
/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments \
|
|
||||||
-f 'body=[summary of issue]' -f 'commit_id=${{ github.event.pull_request.head.sha }}' -f 'path=[path-to-file]' -F "line=[line]" -f 'side=RIGHT'
|
|
||||||
```
|
|
||||||
|
|
||||||
Only create comments for actual violations. If the code follows all guidelines, don't run any gh commands."
|
|
||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
auto-label:
|
auto-label:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
issues: write
|
issues: write
|
||||||
|
|||||||
@@ -11,12 +11,16 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-bun
|
- uses: ./.github/actions/setup-bun
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
|
||||||
- run: bun sst deploy --stage=${{ github.ref_name }}
|
- run: bun sst deploy --stage=${{ github.ref_name }}
|
||||||
env:
|
env:
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-duplicates:
|
check-duplicates:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
issues: write
|
issues: write
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for duplicate issues
|
- name: Check for duplicate issues
|
||||||
env:
|
env:
|
||||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
OPENCODE_PERMISSION: |
|
OPENCODE_PERMISSION: |
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
jobs:
|
jobs:
|
||||||
format:
|
format:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
notify:
|
notify:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: Send nicely-formatted embed to Discord
|
- name: Send nicely-formatted embed to Discord
|
||||||
uses: SethCohen/github-releases-to-discord@v1
|
uses: SethCohen/github-releases-to-discord@v1
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ name: opencode
|
|||||||
on:
|
on:
|
||||||
issue_comment:
|
issue_comment:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
pull_request_review_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
opencode:
|
opencode:
|
||||||
@@ -11,7 +13,7 @@ jobs:
|
|||||||
startsWith(github.event.comment.body, '/oc') ||
|
startsWith(github.event.comment.body, '/oc') ||
|
||||||
contains(github.event.comment.body, ' /opencode') ||
|
contains(github.event.comment.body, ' /opencode') ||
|
||||||
startsWith(github.event.comment.body, '/opencode')
|
startsWith(github.event.comment.body, '/opencode')
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
contents: read
|
contents: read
|
||||||
@@ -21,9 +23,11 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: ./.github/actions/setup-bun
|
||||||
|
|
||||||
- name: Run opencode
|
- name: Run opencode
|
||||||
uses: sst/opencode/github@latest
|
uses: sst/opencode/github@latest
|
||||||
env:
|
env:
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
with:
|
with:
|
||||||
model: opencode/glm-4.6
|
model: opencode/claude-haiku-4-5
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@@ -61,6 +61,13 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: |
|
run: |
|
||||||
./script/publish.ts
|
./script/publish.ts
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
name: Guidelines Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, ready_for_review]
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-guidelines:
|
||||||
|
if: |
|
||||||
|
(github.event_name == 'pull_request_target' &&
|
||||||
|
github.event.pull_request.draft == false) ||
|
||||||
|
(github.event_name == 'issue_comment' &&
|
||||||
|
github.event.issue.pull_request &&
|
||||||
|
startsWith(github.event.comment.body, '/review'))
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Check if user has write permission
|
||||||
|
if: github.event_name == 'issue_comment'
|
||||||
|
run: |
|
||||||
|
PERMISSION=$(gh api /repos/${{ github.repository }}/collaborators/${{ github.event.comment.user.login }}/permission --jq '.permission')
|
||||||
|
if [[ "$PERMISSION" != "write" && "$PERMISSION" != "admin" ]]; then
|
||||||
|
echo "User does not have write permission"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get PR number
|
||||||
|
id: pr-number
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" = "pull_request_target" ]; then
|
||||||
|
echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Install opencode
|
||||||
|
run: curl -fsSL https://opencode.ai/install | bash
|
||||||
|
|
||||||
|
- name: Get PR details
|
||||||
|
id: pr-details
|
||||||
|
run: |
|
||||||
|
gh api /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }} > pr_data.json
|
||||||
|
echo "title=$(jq -r .title pr_data.json)" >> $GITHUB_OUTPUT
|
||||||
|
echo "sha=$(jq -r .head.sha pr_data.json)" >> $GITHUB_OUTPUT
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Check PR guidelines compliance
|
||||||
|
env:
|
||||||
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
OPENCODE_PERMISSION: '{ "bash": { "gh*": "allow", "gh pr review*": "deny", "*": "deny" } }'
|
||||||
|
run: |
|
||||||
|
PR_BODY=$(jq -r .body pr_data.json)
|
||||||
|
opencode run -m anthropic/claude-sonnet-4-5 "A new pull request has been created: '${{ steps.pr-details.outputs.title }}'
|
||||||
|
|
||||||
|
<pr-number>
|
||||||
|
${{ steps.pr-number.outputs.number }}
|
||||||
|
</pr-number>
|
||||||
|
|
||||||
|
<pr-description>
|
||||||
|
$PR_BODY
|
||||||
|
</pr-description>
|
||||||
|
|
||||||
|
Please check all the code changes in this pull request against the style guide, also look for any bugs if they exist. Diffs are important but make sure you read the entire file to get proper context. Make it clear the suggestions are merely suggestions and the human can decide what to do
|
||||||
|
|
||||||
|
When critiquing code against the style guide, be sure that the code is ACTUALLY in violation, don't complain about else statements if they already use early returns there. You may complain about excessive nesting though, regardless of else statement usage.
|
||||||
|
When critiquing code style don't be a zealot, we don't like "let" statements but sometimes they are the simpliest option, if someone does a bunch of nesting with let, they should consider using iife (see packages/opencode/src/util.iife.ts)
|
||||||
|
|
||||||
|
Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block.
|
||||||
|
|
||||||
|
Command MUST be like this.
|
||||||
|
\`\`\`
|
||||||
|
gh api \
|
||||||
|
--method POST \
|
||||||
|
-H \"Accept: application/vnd.github+json\" \
|
||||||
|
-H \"X-GitHub-Api-Version: 2022-11-28\" \
|
||||||
|
/repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }}/comments \
|
||||||
|
-f 'body=[summary of issue]' -f 'commit_id=${{ steps.pr-details.outputs.sha }}' -f 'path=[path-to-file]' -F \"line=[line]\" -f 'side=RIGHT'
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Only create comments for actual violations. If the code follows all guidelines, don't run any gh commands."
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
name: snapshot
|
name: snapshot
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
- fix-snapshot-2
|
- test-bedrock
|
||||||
- v0
|
- v0
|
||||||
|
- otui-diffs
|
||||||
|
- snapshot-*
|
||||||
|
|
||||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stats:
|
stats:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
zed:
|
zed:
|
||||||
name: Release Zed Extension
|
name: Release Zed Extension
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
typecheck:
|
typecheck:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update:
|
update:
|
||||||
runs-on: ubuntu-latest
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
env:
|
env:
|
||||||
SYSTEM: x86_64-linux
|
SYSTEM: x86_64-linux
|
||||||
|
|
||||||
@@ -28,6 +29,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
ref: ${{ github.head_ref || github.ref_name }}
|
||||||
|
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||||
|
|
||||||
- name: Setup Nix
|
- name: Setup Nix
|
||||||
uses: DeterminateSystems/nix-installer-action@v20
|
uses: DeterminateSystems/nix-installer-action@v20
|
||||||
@@ -40,12 +43,16 @@ jobs:
|
|||||||
- name: Update flake.lock
|
- name: Update flake.lock
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
echo "📦 Updating flake.lock..."
|
||||||
nix flake update
|
nix flake update
|
||||||
|
echo "✅ flake.lock updated successfully"
|
||||||
|
|
||||||
- name: Update node_modules hash
|
- name: Update node_modules hash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
echo "🔄 Updating node_modules hash..."
|
||||||
nix/scripts/update-hashes.sh
|
nix/scripts/update-hashes.sh
|
||||||
|
echo "✅ node_modules hash updated successfully"
|
||||||
|
|
||||||
- name: Commit hash changes
|
- name: Commit hash changes
|
||||||
env:
|
env:
|
||||||
@@ -53,6 +60,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "🔍 Checking for changes in tracked Nix files..."
|
||||||
|
|
||||||
summarize() {
|
summarize() {
|
||||||
local status="$1"
|
local status="$1"
|
||||||
{
|
{
|
||||||
@@ -70,15 +79,24 @@ jobs:
|
|||||||
FILES=(flake.lock flake.nix nix/node-modules.nix nix/hashes.json)
|
FILES=(flake.lock flake.nix nix/node-modules.nix nix/hashes.json)
|
||||||
STATUS="$(git status --short -- "${FILES[@]}" || true)"
|
STATUS="$(git status --short -- "${FILES[@]}" || true)"
|
||||||
if [ -z "$STATUS" ]; then
|
if [ -z "$STATUS" ]; then
|
||||||
|
echo "✅ No changes detected. Hashes are already up to date."
|
||||||
summarize "no changes"
|
summarize "no changes"
|
||||||
echo "No changes to tracked Nix files. Hashes are already up to date."
|
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "📝 Changes detected:"
|
||||||
|
echo "$STATUS"
|
||||||
|
echo "🔗 Staging files..."
|
||||||
git add "${FILES[@]}"
|
git add "${FILES[@]}"
|
||||||
|
echo "💾 Committing changes..."
|
||||||
git commit -m "Update Nix flake.lock and hashes"
|
git commit -m "Update Nix flake.lock and hashes"
|
||||||
|
echo "✅ Changes committed"
|
||||||
|
|
||||||
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
||||||
|
echo "🌳 Pulling latest from branch: $BRANCH"
|
||||||
|
git pull --rebase origin "$BRANCH"
|
||||||
|
echo "🚀 Pushing changes to branch: $BRANCH"
|
||||||
git push origin HEAD:"$BRANCH"
|
git push origin HEAD:"$BRANCH"
|
||||||
|
echo "✅ Changes pushed successfully"
|
||||||
|
|
||||||
summarize "committed $(git rev-parse --short HEAD)"
|
summarize "committed $(git rev-parse --short HEAD)"
|
||||||
|
|||||||
@@ -1,2 +1,9 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
# Check if bun version matches package.json
|
||||||
|
EXPECTED_VERSION=$(grep '"packageManager"' package.json | sed 's/.*"bun@\([^"]*\)".*/\1/')
|
||||||
|
CURRENT_VERSION=$(bun --version)
|
||||||
|
if [ "$CURRENT_VERSION" != "$EXPECTED_VERSION" ]; then
|
||||||
|
echo "Error: Bun version $CURRENT_VERSION does not match expected version $EXPECTED_VERSION from package.json"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
bun typecheck
|
bun typecheck
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
description: Git commit and push
|
description: git commit and push
|
||||||
subtask: true
|
|
||||||
---
|
---
|
||||||
|
|
||||||
commit and push
|
commit and push
|
||||||
@@ -22,3 +21,6 @@ WHAT was done.
|
|||||||
|
|
||||||
do not do generic messages like "improved agent experience" be very specific
|
do not do generic messages like "improved agent experience" be very specific
|
||||||
about what user facing changes were made
|
about what user facing changes were made
|
||||||
|
|
||||||
|
if there are changes do a git pull --rebase
|
||||||
|
if there are conflicts DO NOT FIX THEM. notify me and I will fix them
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
description: "Find issue(s) on github"
|
description: "find issue(s) on github"
|
||||||
model: opencode/claude-haiku-4-5
|
model: opencode/claude-haiku-4-5
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
description: Spellcheck all markdown file changes
|
description: spellcheck all markdown file changes
|
||||||
---
|
---
|
||||||
|
|
||||||
Look at all the unstaged changes to markdown (.md, .mdx) files, pull out the lines that have changed, and check for spelling and grammar errors.
|
Look at all the unstaged changes to markdown (.md, .mdx) files, pull out the lines that have changed, and check for spelling and grammar errors.
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
"plugin": ["opencode-openai-codex-auth"],
|
"plugin": ["opencode-openai-codex-auth"],
|
||||||
|
// "enterprise": {
|
||||||
|
// "url": "https://enterprise.dev.opencode.ai",
|
||||||
|
// },
|
||||||
|
"instructions": ["STYLE_GUIDE.md"],
|
||||||
"provider": {
|
"provider": {
|
||||||
"opencode": {
|
"opencode": {
|
||||||
"options": {
|
"options": {
|
||||||
@@ -8,4 +12,17 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"mcp": {
|
||||||
|
"exa": {
|
||||||
|
"type": "remote",
|
||||||
|
"url": "https://mcp.exa.ai/mcp",
|
||||||
|
},
|
||||||
|
"morph": {
|
||||||
|
"type": "local",
|
||||||
|
"command": ["bunx", "@morphllm/morphmcp"],
|
||||||
|
"environment": {
|
||||||
|
"ENABLED_TOOLS": "warp_grep",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
sst-env.d.ts
|
||||||
@@ -1,16 +1,3 @@
|
|||||||
## IMPORTANT
|
|
||||||
|
|
||||||
- Try to keep things in one function unless composable or reusable
|
|
||||||
- DO NOT do unnecessary destructuring of variables
|
|
||||||
- DO NOT use `else` statements unless necessary
|
|
||||||
- DO NOT use `try`/`catch` if it can be avoided
|
|
||||||
- AVOID `try`/`catch` where possible
|
|
||||||
- AVOID `else` statements
|
|
||||||
- AVOID using `any` type
|
|
||||||
- AVOID `let` statements
|
|
||||||
- PREFER single word variable names where possible
|
|
||||||
- Use as many bun apis as possible like Bun.file()
|
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
|
|
||||||
- To test opencode in the `packages/opencode` directory you can run `bun dev`
|
- To test opencode in the `packages/opencode` directory you can run `bun dev`
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ Want to take on an issue? Leave a comment and a maintainer may assign it to you
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> After touching `packages/opencode/src/server/server.ts`, run "./packages/sdk/js/script/build.ts" to regenerate the JS sdk.
|
> After touching `packages/opencode/src/server/server.ts`, run "./packages/sdk/js/script/build.ts" to regenerate the JS sdk.
|
||||||
|
|
||||||
|
Please try to follow the [style guide](./STYLE_GUIDE.md)
|
||||||
|
|
||||||
### Setting up a Debugger
|
### Setting up a Debugger
|
||||||
|
|
||||||
Bun debugging is currently rough around the edges. We hope this guide helps you get set up and avoid some pain points.
|
Bun debugging is currently rough around the edges. We hope this guide helps you get set up and avoid some pain points.
|
||||||
|
|||||||
@@ -146,3 +146,16 @@
|
|||||||
| 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) |
|
| 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) |
|
||||||
| 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) |
|
| 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) |
|
||||||
| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) |
|
| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) |
|
||||||
|
| 2025-11-21 | 826,309 (+11,689) | 769,307 (+11,400) | 1,595,616 (+23,089) |
|
||||||
|
| 2025-11-22 | 837,269 (+10,960) | 780,996 (+11,689) | 1,618,265 (+22,649) |
|
||||||
|
| 2025-11-23 | 846,609 (+9,340) | 795,069 (+14,073) | 1,641,678 (+23,413) |
|
||||||
|
| 2025-11-24 | 856,733 (+10,124) | 804,033 (+8,964) | 1,660,766 (+19,088) |
|
||||||
|
| 2025-11-25 | 869,423 (+12,690) | 817,339 (+13,306) | 1,686,762 (+25,996) |
|
||||||
|
| 2025-11-26 | 881,414 (+11,991) | 832,518 (+15,179) | 1,713,932 (+27,170) |
|
||||||
|
| 2025-11-27 | 893,960 (+12,546) | 846,180 (+13,662) | 1,740,140 (+26,208) |
|
||||||
|
| 2025-11-28 | 901,741 (+7,781) | 856,482 (+10,302) | 1,758,223 (+18,083) |
|
||||||
|
| 2025-11-29 | 908,689 (+6,948) | 863,361 (+6,879) | 1,772,050 (+13,827) |
|
||||||
|
| 2025-11-30 | 916,116 (+7,427) | 870,194 (+6,833) | 1,786,310 (+14,260) |
|
||||||
|
| 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
|
||||||
|
| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
|
||||||
|
| 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) |
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
## Style Guide
|
||||||
|
|
||||||
|
- Try to keep things in one function unless composable or reusable
|
||||||
|
- DO NOT do unnecessary destructuring of variables
|
||||||
|
- DO NOT use `else` statements unless necessary
|
||||||
|
- DO NOT use `try`/`catch` if it can be avoided
|
||||||
|
- AVOID `try`/`catch` where possible
|
||||||
|
- AVOID `else` statements
|
||||||
|
- AVOID using `any` type
|
||||||
|
- AVOID `let` statements
|
||||||
|
- PREFER single word variable names where possible
|
||||||
|
- Use as many bun apis as possible like Bun.file()
|
||||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1763618868,
|
"lastModified": 1764733908,
|
||||||
"narHash": "sha256-v5afmLjn/uyD9EQuPBn7nZuaZVV9r+JerayK/4wvdWA=",
|
"narHash": "sha256-QJiih52NU+nm7XQWCj+K8SwUdIEayDQ1FQgjkYISt4I=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a8d610af3f1a5fb71e23e08434d8d61a466fc942",
|
"rev": "cadcc8de247676e4751c9d4a935acb2c0b059113",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -30,6 +30,24 @@ Leave the following comment on a GitHub PR. opencode will implement the requeste
|
|||||||
Delete the attachment from S3 when the note is removed /oc
|
Delete the attachment from S3 when the note is removed /oc
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Review specific code lines
|
||||||
|
|
||||||
|
Leave a comment directly on code lines in the PR's "Files" tab. opencode will automatically detect the file, line numbers, and diff context to provide precise responses.
|
||||||
|
|
||||||
|
```
|
||||||
|
[Comment on specific lines in Files tab]
|
||||||
|
/oc add error handling here
|
||||||
|
```
|
||||||
|
|
||||||
|
When commenting on specific lines, opencode receives:
|
||||||
|
|
||||||
|
- The exact file being reviewed
|
||||||
|
- The specific lines of code
|
||||||
|
- The surrounding diff context
|
||||||
|
- Line number information
|
||||||
|
|
||||||
|
This allows for more targeted requests without needing to specify file paths or line numbers manually.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Run the following command in the terminal from your GitHub repo:
|
Run the following command in the terminal from your GitHub repo:
|
||||||
@@ -51,6 +69,8 @@ This will walk you through installing the GitHub app, creating the workflow, and
|
|||||||
on:
|
on:
|
||||||
issue_comment:
|
issue_comment:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
pull_request_review_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
opencode:
|
opencode:
|
||||||
@@ -135,3 +155,9 @@ Replace the image URL `https://github.com/user-attachments/assets/xxxxxxxx` with
|
|||||||
```
|
```
|
||||||
MOCK_EVENT='{"eventName":"issue_comment","repo":{"owner":"sst","repo":"hello-world"},"actor":"fwang","payload":{"issue":{"number":4,"pull_request":{}},"comment":{"id":1,"body":"hey opencode, summarize thread"}}}'
|
MOCK_EVENT='{"eventName":"issue_comment","repo":{"owner":"sst","repo":"hello-world"},"actor":"fwang","payload":{"issue":{"number":4,"pull_request":{}},"comment":{"id":1,"body":"hey opencode, summarize thread"}}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### PR review comment event
|
||||||
|
|
||||||
|
```
|
||||||
|
MOCK_EVENT='{"eventName":"pull_request_review_comment","repo":{"owner":"sst","repo":"hello-world"},"actor":"fwang","payload":{"pull_request":{"number":7},"comment":{"id":1,"body":"hey opencode, add error handling","path":"src/components/Button.tsx","diff_hunk":"@@ -45,8 +45,11 @@\n- const handleClick = () => {\n- console.log('clicked')\n+ const handleClick = useCallback(() => {\n+ console.log('clicked')\n+ doSomething()\n+ }, [doSomething])","line":47,"original_line":45,"position":10,"commit_id":"abc123","original_commit_id":"def456"}}}'
|
||||||
|
```
|
||||||
|
|||||||
+44
-7
@@ -5,7 +5,7 @@ import { graphql } from "@octokit/graphql"
|
|||||||
import * as core from "@actions/core"
|
import * as core from "@actions/core"
|
||||||
import * as github from "@actions/github"
|
import * as github from "@actions/github"
|
||||||
import type { Context as GitHubContext } from "@actions/github/lib/context"
|
import type { Context as GitHubContext } from "@actions/github/lib/context"
|
||||||
import type { IssueCommentEvent } from "@octokit/webhooks-types"
|
import type { IssueCommentEvent, PullRequestReviewCommentEvent } from "@octokit/webhooks-types"
|
||||||
import { createOpencodeClient } from "@opencode-ai/sdk"
|
import { createOpencodeClient } from "@opencode-ai/sdk"
|
||||||
import { spawn } from "node:child_process"
|
import { spawn } from "node:child_process"
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ let exitCode = 0
|
|||||||
type PromptFiles = Awaited<ReturnType<typeof getUserPrompt>>["promptFiles"]
|
type PromptFiles = Awaited<ReturnType<typeof getUserPrompt>>["promptFiles"]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
assertContextEvent("issue_comment")
|
assertContextEvent("issue_comment", "pull_request_review_comment")
|
||||||
assertPayloadKeyword()
|
assertPayloadKeyword()
|
||||||
await assertOpencodeConnected()
|
await assertOpencodeConnected()
|
||||||
|
|
||||||
@@ -241,19 +241,43 @@ function createOpencode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function assertPayloadKeyword() {
|
function assertPayloadKeyword() {
|
||||||
const payload = useContext().payload as IssueCommentEvent
|
const payload = useContext().payload as IssueCommentEvent | PullRequestReviewCommentEvent
|
||||||
const body = payload.comment.body.trim()
|
const body = payload.comment.body.trim()
|
||||||
if (!body.match(/(?:^|\s)(?:\/opencode|\/oc)(?=$|\s)/)) {
|
if (!body.match(/(?:^|\s)(?:\/opencode|\/oc)(?=$|\s)/)) {
|
||||||
throw new Error("Comments must mention `/opencode` or `/oc`")
|
throw new Error("Comments must mention `/opencode` or `/oc`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getReviewCommentContext() {
|
||||||
|
const context = useContext()
|
||||||
|
if (context.eventName !== "pull_request_review_comment") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = context.payload as PullRequestReviewCommentEvent
|
||||||
|
return {
|
||||||
|
file: payload.comment.path,
|
||||||
|
diffHunk: payload.comment.diff_hunk,
|
||||||
|
line: payload.comment.line,
|
||||||
|
originalLine: payload.comment.original_line,
|
||||||
|
position: payload.comment.position,
|
||||||
|
commitId: payload.comment.commit_id,
|
||||||
|
originalCommitId: payload.comment.original_commit_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function assertOpencodeConnected() {
|
async function assertOpencodeConnected() {
|
||||||
let retry = 0
|
let retry = 0
|
||||||
let connected = false
|
let connected = false
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
await client.app.get<true>()
|
await client.app.log<true>({
|
||||||
|
body: {
|
||||||
|
service: "github-workflow",
|
||||||
|
level: "info",
|
||||||
|
message: "Prepare to react to Github Workflow event",
|
||||||
|
},
|
||||||
|
})
|
||||||
connected = true
|
connected = true
|
||||||
break
|
break
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
@@ -383,11 +407,24 @@ async function createComment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getUserPrompt() {
|
async function getUserPrompt() {
|
||||||
|
const context = useContext()
|
||||||
|
const payload = context.payload as IssueCommentEvent | PullRequestReviewCommentEvent
|
||||||
|
const reviewContext = getReviewCommentContext()
|
||||||
|
|
||||||
let prompt = (() => {
|
let prompt = (() => {
|
||||||
const payload = useContext().payload as IssueCommentEvent
|
|
||||||
const body = payload.comment.body.trim()
|
const body = payload.comment.body.trim()
|
||||||
if (body === "/opencode" || body === "/oc") return "Summarize this thread"
|
if (body === "/opencode" || body === "/oc") {
|
||||||
if (body.includes("/opencode") || body.includes("/oc")) return body
|
if (reviewContext) {
|
||||||
|
return `Review this code change and suggest improvements for the commented lines:\n\nFile: ${reviewContext.file}\nLines: ${reviewContext.line}\n\n${reviewContext.diffHunk}`
|
||||||
|
}
|
||||||
|
return "Summarize this thread"
|
||||||
|
}
|
||||||
|
if (body.includes("/opencode") || body.includes("/oc")) {
|
||||||
|
if (reviewContext) {
|
||||||
|
return `${body}\n\nContext: You are reviewing a comment on file "${reviewContext.file}" at line ${reviewContext.line}.\n\nDiff context:\n${reviewContext.diffHunk}`
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
throw new Error("Comments must mention `/opencode` or `/oc`")
|
throw new Error("Comments must mention `/opencode` or `/oc`")
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
|||||||
+10
-4
@@ -97,8 +97,12 @@ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint",
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const ZEN_MODELS1 = new sst.Secret("ZEN_MODELS1")
|
const ZEN_MODELS = [
|
||||||
const ZEN_MODELS2 = new sst.Secret("ZEN_MODELS2")
|
new sst.Secret("ZEN_MODELS1"),
|
||||||
|
new sst.Secret("ZEN_MODELS2"),
|
||||||
|
new sst.Secret("ZEN_MODELS3"),
|
||||||
|
new sst.Secret("ZEN_MODELS4"),
|
||||||
|
]
|
||||||
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
|
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
|
||||||
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
||||||
properties: { value: auth.url.apply((url) => url!) },
|
properties: { value: auth.url.apply((url) => url!) },
|
||||||
@@ -112,6 +116,8 @@ const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
|
|||||||
// CONSOLE
|
// CONSOLE
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
|
const bucket = new sst.cloudflare.Bucket("ZenData")
|
||||||
|
|
||||||
const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
|
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")
|
const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
|
||||||
|
|
||||||
@@ -128,15 +134,15 @@ new sst.cloudflare.x.SolidStart("Console", {
|
|||||||
domain,
|
domain,
|
||||||
path: "packages/console/app",
|
path: "packages/console/app",
|
||||||
link: [
|
link: [
|
||||||
|
bucket,
|
||||||
database,
|
database,
|
||||||
AUTH_API_URL,
|
AUTH_API_URL,
|
||||||
STRIPE_WEBHOOK_SECRET,
|
STRIPE_WEBHOOK_SECRET,
|
||||||
STRIPE_SECRET_KEY,
|
STRIPE_SECRET_KEY,
|
||||||
ZEN_MODELS1,
|
|
||||||
ZEN_MODELS2,
|
|
||||||
EMAILOCTOPUS_API_KEY,
|
EMAILOCTOPUS_API_KEY,
|
||||||
AWS_SES_ACCESS_KEY_ID,
|
AWS_SES_ACCESS_KEY_ID,
|
||||||
AWS_SES_SECRET_ACCESS_KEY,
|
AWS_SES_SECRET_ACCESS_KEY,
|
||||||
|
...ZEN_MODELS,
|
||||||
...($dev
|
...($dev
|
||||||
? [
|
? [
|
||||||
new sst.Secret("CLOUDFLARE_DEFAULT_ACCOUNT_ID", process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID!),
|
new sst.Secret("CLOUDFLARE_DEFAULT_ACCOUNT_ID", process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID!),
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { SECRET } from "./secret"
|
||||||
|
import { domain } from "./stage"
|
||||||
|
|
||||||
|
const storage = new sst.cloudflare.Bucket("EnterpriseStorage")
|
||||||
|
|
||||||
|
const enterprise = new sst.cloudflare.x.SolidStart("Enterprise", {
|
||||||
|
domain: "enterprise." + domain,
|
||||||
|
path: "packages/enterprise",
|
||||||
|
buildCommand: "bun run build:cloudflare",
|
||||||
|
environment: {
|
||||||
|
OPENCODE_STORAGE_ADAPTER: "r2",
|
||||||
|
OPENCODE_STORAGE_ACCOUNT_ID: sst.cloudflare.DEFAULT_ACCOUNT_ID,
|
||||||
|
OPENCODE_STORAGE_ACCESS_KEY_ID: SECRET.R2AccessKey.value,
|
||||||
|
OPENCODE_STORAGE_SECRET_ACCESS_KEY: SECRET.R2SecretKey.value,
|
||||||
|
OPENCODE_STORAGE_BUCKET: storage.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export const SECRET = {
|
||||||
|
R2AccessKey: new sst.Secret("R2AccessKey", "unknown"),
|
||||||
|
R2SecretKey: new sst.Secret("R2SecretKey", "unknown"),
|
||||||
|
}
|
||||||
@@ -2,42 +2,39 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
APP=opencode
|
APP=opencode
|
||||||
|
|
||||||
|
MUTED='\033[0;2m'
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
ORANGE='\033[38;5;214m'
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
ORANGE='\033[38;2;255;140;0m'
|
|
||||||
NC='\033[0m' # No Color
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
requested_version=${VERSION:-}
|
requested_version=${VERSION:-}
|
||||||
|
|
||||||
raw_os=$(uname -s)
|
raw_os=$(uname -s)
|
||||||
os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
|
os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
|
||||||
# Normalize various Unix-like identifiers
|
|
||||||
case "$raw_os" in
|
case "$raw_os" in
|
||||||
Darwin*) os="darwin" ;;
|
Darwin*) os="darwin" ;;
|
||||||
Linux*) os="linux" ;;
|
Linux*) os="linux" ;;
|
||||||
MINGW*|MSYS*|CYGWIN*) os="windows" ;;
|
MINGW*|MSYS*|CYGWIN*) os="windows" ;;
|
||||||
esac
|
esac
|
||||||
arch=$(uname -m)
|
|
||||||
|
|
||||||
|
arch=$(uname -m)
|
||||||
if [[ "$arch" == "aarch64" ]]; then
|
if [[ "$arch" == "aarch64" ]]; then
|
||||||
arch="arm64"
|
arch="arm64"
|
||||||
elif [[ "$arch" == "x86_64" ]]; then
|
fi
|
||||||
|
if [[ "$arch" == "x86_64" ]]; then
|
||||||
arch="x64"
|
arch="x64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
filename="$APP-$os-$arch.zip"
|
if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
|
||||||
|
rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)
|
||||||
|
if [ "$rosetta_flag" = "1" ]; then
|
||||||
|
arch="arm64"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
combo="$os-$arch"
|
||||||
case "$filename" in
|
case "$combo" in
|
||||||
*"-linux-"*)
|
linux-x64|linux-arm64|darwin-x64|darwin-arm64|windows-x64)
|
||||||
[[ "$arch" == "x64" || "$arch" == "arm64" ]] || exit 1
|
|
||||||
;;
|
|
||||||
*"-darwin-"*)
|
|
||||||
[[ "$arch" == "x64" || "$arch" == "arm64" ]] || exit 1
|
|
||||||
;;
|
|
||||||
*"-windows-"*)
|
|
||||||
[[ "$arch" == "x64" ]] || exit 1
|
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}"
|
echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}"
|
||||||
@@ -45,6 +42,63 @@ case "$filename" in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
archive_ext=".zip"
|
||||||
|
if [ "$os" = "linux" ]; then
|
||||||
|
archive_ext=".tar.gz"
|
||||||
|
fi
|
||||||
|
|
||||||
|
is_musl=false
|
||||||
|
if [ "$os" = "linux" ]; then
|
||||||
|
if [ -f /etc/alpine-release ]; then
|
||||||
|
is_musl=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v ldd >/dev/null 2>&1; then
|
||||||
|
if ldd --version 2>&1 | grep -qi musl; then
|
||||||
|
is_musl=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
needs_baseline=false
|
||||||
|
if [ "$arch" = "x64" ]; then
|
||||||
|
if [ "$os" = "linux" ]; then
|
||||||
|
if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then
|
||||||
|
needs_baseline=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$os" = "darwin" ]; then
|
||||||
|
avx2=$(sysctl -n hw.optional.avx2_0 2>/dev/null || echo 0)
|
||||||
|
if [ "$avx2" != "1" ]; then
|
||||||
|
needs_baseline=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
target="$os-$arch"
|
||||||
|
if [ "$needs_baseline" = "true" ]; then
|
||||||
|
target="$target-baseline"
|
||||||
|
fi
|
||||||
|
if [ "$is_musl" = "true" ]; then
|
||||||
|
target="$target-musl"
|
||||||
|
fi
|
||||||
|
|
||||||
|
filename="$APP-$target$archive_ext"
|
||||||
|
|
||||||
|
|
||||||
|
if [ "$os" = "linux" ]; then
|
||||||
|
if ! command -v tar >/dev/null 2>&1; then
|
||||||
|
echo -e "${RED}Error: 'tar' is required but not installed.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! command -v unzip >/dev/null 2>&1; then
|
||||||
|
echo -e "${RED}Error: 'unzip' is required but not installed.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
INSTALL_DIR=$HOME/.opencode/bin
|
INSTALL_DIR=$HOME/.opencode/bin
|
||||||
mkdir -p "$INSTALL_DIR"
|
mkdir -p "$INSTALL_DIR"
|
||||||
|
|
||||||
@@ -67,8 +121,8 @@ print_message() {
|
|||||||
local color=""
|
local color=""
|
||||||
|
|
||||||
case $level in
|
case $level in
|
||||||
info) color="${GREEN}" ;;
|
info) color="${NC}" ;;
|
||||||
warning) color="${YELLOW}" ;;
|
warning) color="${NC}" ;;
|
||||||
error) color="${RED}" ;;
|
error) color="${RED}" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -86,19 +140,119 @@ check_version() {
|
|||||||
installed_version=$(echo $installed_version | awk '{print $2}')
|
installed_version=$(echo $installed_version | awk '{print $2}')
|
||||||
|
|
||||||
if [[ "$installed_version" != "$specific_version" ]]; then
|
if [[ "$installed_version" != "$specific_version" ]]; then
|
||||||
print_message info "Installed version: ${YELLOW}$installed_version."
|
print_message info "${MUTED}Installed version: ${NC}$installed_version."
|
||||||
else
|
else
|
||||||
print_message info "Version ${YELLOW}$specific_version${GREEN} already installed"
|
print_message info "${MUTED}Version ${NC}$specific_version${MUTED} already installed"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unbuffered_sed() {
|
||||||
|
if echo | sed -u -e "" >/dev/null 2>&1; then
|
||||||
|
sed -nu "$@"
|
||||||
|
elif echo | sed -l -e "" >/dev/null 2>&1; then
|
||||||
|
sed -nl "$@"
|
||||||
|
else
|
||||||
|
local pad="$(printf "\n%512s" "")"
|
||||||
|
sed -ne "s/$/\\${pad}/" "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
print_progress() {
|
||||||
|
local bytes="$1"
|
||||||
|
local length="$2"
|
||||||
|
[ "$length" -gt 0 ] || return 0
|
||||||
|
|
||||||
|
local width=50
|
||||||
|
local percent=$(( bytes * 100 / length ))
|
||||||
|
[ "$percent" -gt 100 ] && percent=100
|
||||||
|
local on=$(( percent * width / 100 ))
|
||||||
|
local off=$(( width - on ))
|
||||||
|
|
||||||
|
local filled=$(printf "%*s" "$on" "")
|
||||||
|
filled=${filled// /■}
|
||||||
|
local empty=$(printf "%*s" "$off" "")
|
||||||
|
empty=${empty// /・}
|
||||||
|
|
||||||
|
printf "\r${ORANGE}%s%s %3d%%${NC}" "$filled" "$empty" "$percent" >&4
|
||||||
|
}
|
||||||
|
|
||||||
|
download_with_progress() {
|
||||||
|
local url="$1"
|
||||||
|
local output="$2"
|
||||||
|
|
||||||
|
if [ -t 2 ]; then
|
||||||
|
exec 4>&2
|
||||||
|
else
|
||||||
|
exec 4>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
local tmp_dir=${TMPDIR:-/tmp}
|
||||||
|
local basename="${tmp_dir}/opencode_install_$$"
|
||||||
|
local tracefile="${basename}.trace"
|
||||||
|
|
||||||
|
rm -f "$tracefile"
|
||||||
|
mkfifo "$tracefile"
|
||||||
|
|
||||||
|
# Hide cursor
|
||||||
|
printf "\033[?25l" >&4
|
||||||
|
|
||||||
|
trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN
|
||||||
|
|
||||||
|
(
|
||||||
|
curl --trace-ascii "$tracefile" -s -L -o "$output" "$url"
|
||||||
|
) &
|
||||||
|
local curl_pid=$!
|
||||||
|
|
||||||
|
unbuffered_sed \
|
||||||
|
-e 'y/ACDEGHLNORTV/acdeghlnortv/' \
|
||||||
|
-e '/^0000: content-length:/p' \
|
||||||
|
-e '/^<= recv data/p' \
|
||||||
|
"$tracefile" | \
|
||||||
|
{
|
||||||
|
local length=0
|
||||||
|
local bytes=0
|
||||||
|
|
||||||
|
while IFS=" " read -r -a line; do
|
||||||
|
[ "${#line[@]}" -lt 2 ] && continue
|
||||||
|
local tag="${line[0]} ${line[1]}"
|
||||||
|
|
||||||
|
if [ "$tag" = "0000: content-length:" ]; then
|
||||||
|
length="${line[2]}"
|
||||||
|
length=$(echo "$length" | tr -d '\r')
|
||||||
|
bytes=0
|
||||||
|
elif [ "$tag" = "<= recv" ]; then
|
||||||
|
local size="${line[3]}"
|
||||||
|
bytes=$(( bytes + size ))
|
||||||
|
if [ "$length" -gt 0 ]; then
|
||||||
|
print_progress "$bytes" "$length"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
wait $curl_pid
|
||||||
|
local ret=$?
|
||||||
|
echo "" >&4
|
||||||
|
return $ret
|
||||||
|
}
|
||||||
|
|
||||||
download_and_install() {
|
download_and_install() {
|
||||||
print_message info "Downloading ${ORANGE}opencode ${GREEN}version: ${YELLOW}$specific_version ${GREEN}..."
|
print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version"
|
||||||
mkdir -p opencodetmp && cd opencodetmp
|
mkdir -p opencodetmp && cd opencodetmp
|
||||||
|
|
||||||
|
if [[ "$os" == "windows" ]] || ! download_with_progress "$url" "$filename"; then
|
||||||
|
# Fallback to standard curl on Windows or if custom progress fails
|
||||||
curl -# -L -o "$filename" "$url"
|
curl -# -L -o "$filename" "$url"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$os" = "linux" ]; then
|
||||||
|
tar -xzf "$filename"
|
||||||
|
else
|
||||||
unzip -q "$filename"
|
unzip -q "$filename"
|
||||||
|
fi
|
||||||
|
|
||||||
mv opencode "$INSTALL_DIR"
|
mv opencode "$INSTALL_DIR"
|
||||||
chmod 755 "${INSTALL_DIR}/opencode"
|
chmod 755 "${INSTALL_DIR}/opencode"
|
||||||
cd .. && rm -rf opencodetmp
|
cd .. && rm -rf opencodetmp
|
||||||
@@ -117,7 +271,7 @@ add_to_path() {
|
|||||||
elif [[ -w $config_file ]]; then
|
elif [[ -w $config_file ]]; then
|
||||||
echo -e "\n# opencode" >> "$config_file"
|
echo -e "\n# opencode" >> "$config_file"
|
||||||
echo "$command" >> "$config_file"
|
echo "$command" >> "$config_file"
|
||||||
print_message info "Successfully added ${ORANGE}opencode ${GREEN}to \$PATH in $config_file"
|
print_message info "${MUTED}Successfully added ${NC}opencode ${MUTED}to \$PATH in ${NC}$config_file"
|
||||||
else
|
else
|
||||||
print_message warning "Manually add the directory to $config_file (or similar):"
|
print_message warning "Manually add the directory to $config_file (or similar):"
|
||||||
print_message info " $command"
|
print_message info " $command"
|
||||||
@@ -191,3 +345,19 @@ if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
|
|||||||
echo "$INSTALL_DIR" >> $GITHUB_PATH
|
echo "$INSTALL_DIR" >> $GITHUB_PATH
|
||||||
print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
|
print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo -e ""
|
||||||
|
echo -e "${MUTED} ${NC} ▄ "
|
||||||
|
echo -e "${MUTED}█▀▀█ █▀▀█ █▀▀█ █▀▀▄ ${NC}█▀▀▀ █▀▀█ █▀▀█ █▀▀█"
|
||||||
|
echo -e "${MUTED}█░░█ █░░█ █▀▀▀ █░░█ ${NC}█░░░ █░░█ █░░█ █▀▀▀"
|
||||||
|
echo -e "${MUTED}▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ${NC}▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"
|
||||||
|
echo -e ""
|
||||||
|
echo -e ""
|
||||||
|
echo -e "${MUTED}OpenCode includes free models, to start:${NC}"
|
||||||
|
echo -e "cd <project> ${MUTED}# Open directory${NC}"
|
||||||
|
echo -e "opencode ${MUTED}# Run command${NC}"
|
||||||
|
echo -e ""
|
||||||
|
echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/docs"
|
||||||
|
echo -e ""
|
||||||
|
echo -e ""
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
import solidPlugin from "./node_modules/@opentui/solid/scripts/solid-plugin"
|
||||||
|
import path from "path"
|
||||||
|
import fs from "fs"
|
||||||
|
|
||||||
|
const dir = process.cwd()
|
||||||
|
const parser = fs.realpathSync(path.join(dir, "node_modules/@opentui/core/parser.worker.js"))
|
||||||
|
const worker = "./src/cli/cmd/tui/worker.ts"
|
||||||
|
const version = process.env.OPENCODE_VERSION ?? "local"
|
||||||
|
const channel = process.env.OPENCODE_CHANNEL ?? "local"
|
||||||
|
|
||||||
|
fs.rmSync(path.join(dir, "dist"), { recursive: true, force: true })
|
||||||
|
|
||||||
|
const result = await Bun.build({
|
||||||
|
entrypoints: ["./src/index.ts", worker, parser],
|
||||||
|
outdir: "./dist",
|
||||||
|
target: "bun",
|
||||||
|
sourcemap: "none",
|
||||||
|
tsconfig: "./tsconfig.json",
|
||||||
|
plugins: [solidPlugin],
|
||||||
|
external: ["@opentui/core"],
|
||||||
|
define: {
|
||||||
|
OPENCODE_VERSION: `'${version}'`,
|
||||||
|
OPENCODE_CHANNEL: `'${channel}'`,
|
||||||
|
// Leave undefined so runtime picks bundled/dist worker or fallback in code.
|
||||||
|
OPENCODE_WORKER_PATH: "undefined",
|
||||||
|
OTUI_TREE_SITTER_WORKER_PATH: 'new URL("./cli/cmd/tui/parser.worker.js", import.meta.url).href',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
console.error("bundle failed")
|
||||||
|
for (const log of result.logs) console.error(log)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const parserOut = path.join(dir, "dist/src/cli/cmd/tui/parser.worker.js")
|
||||||
|
fs.mkdirSync(path.dirname(parserOut), { recursive: true })
|
||||||
|
await Bun.write(parserOut, Bun.file(parser))
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": "sha256-bPiUpHGtgwVxHQHXBprpc6fFeJqW6/x7dwtQZBq29oU="
|
"nodeModules": "sha256-ZGKC7h4ScHDzVYj8qb1lN/weZhyZivPS8kpNAZvgO0I="
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-33
@@ -1,4 +1,4 @@
|
|||||||
{ lib, stdenv, stdenvNoCC, bun, fzf, ripgrep, makeBinaryWrapper }:
|
{ lib, stdenvNoCC, bun, fzf, ripgrep, makeBinaryWrapper }:
|
||||||
args:
|
args:
|
||||||
let
|
let
|
||||||
scripts = args.scripts;
|
scripts = args.scripts;
|
||||||
@@ -28,66 +28,93 @@ stdenvNoCC.mkDerivation (finalAttrs: {
|
|||||||
makeBinaryWrapper
|
makeBinaryWrapper
|
||||||
];
|
];
|
||||||
|
|
||||||
configurePhase = ''
|
|
||||||
runHook preConfigure
|
|
||||||
cp -R ${finalAttrs.node_modules}/. .
|
|
||||||
runHook postConfigure
|
|
||||||
'';
|
|
||||||
|
|
||||||
env.MODELS_DEV_API_JSON = args.modelsDev;
|
env.MODELS_DEV_API_JSON = args.modelsDev;
|
||||||
env.OPENCODE_VERSION = args.version;
|
env.OPENCODE_VERSION = args.version;
|
||||||
env.OPENCODE_CHANNEL = "stable";
|
env.OPENCODE_CHANNEL = "stable";
|
||||||
|
dontConfigure = true;
|
||||||
|
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
runHook preBuild
|
runHook preBuild
|
||||||
|
|
||||||
cp ${scripts + "/bun-build.ts"} bun-build.ts
|
cp -r ${finalAttrs.node_modules}/node_modules .
|
||||||
|
cp -r ${finalAttrs.node_modules}/packages .
|
||||||
|
|
||||||
substituteInPlace bun-build.ts \
|
(
|
||||||
--replace '@VERSION@' "${finalAttrs.version}"
|
cd packages/opencode
|
||||||
|
|
||||||
export BUN_COMPILE_TARGET=${args.target}
|
chmod -R u+w ./node_modules
|
||||||
bun --bun bun-build.ts
|
mkdir -p ./node_modules/@opencode-ai
|
||||||
|
rm -f ./node_modules/@opencode-ai/{script,sdk,plugin}
|
||||||
|
ln -s $(pwd)/../../packages/script ./node_modules/@opencode-ai/script
|
||||||
|
ln -s $(pwd)/../../packages/sdk/js ./node_modules/@opencode-ai/sdk
|
||||||
|
ln -s $(pwd)/../../packages/plugin ./node_modules/@opencode-ai/plugin
|
||||||
|
|
||||||
|
cp ${./bundle.ts} ./bundle.ts
|
||||||
|
chmod +x ./bundle.ts
|
||||||
|
bun run ./bundle.ts
|
||||||
|
)
|
||||||
|
|
||||||
runHook postBuild
|
runHook postBuild
|
||||||
'';
|
'';
|
||||||
|
|
||||||
dontStrip = true;
|
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
runHook preInstall
|
runHook preInstall
|
||||||
|
|
||||||
cd packages/opencode
|
cd packages/opencode
|
||||||
if [ ! -f opencode ]; then
|
if [ ! -d dist ]; then
|
||||||
echo "ERROR: opencode binary not found in $(pwd)"
|
echo "ERROR: dist directory missing after bundle step"
|
||||||
ls -la
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ ! -f opencode-worker.js ]; then
|
|
||||||
echo "ERROR: opencode worker bundle not found in $(pwd)"
|
|
||||||
ls -la
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install -Dm755 opencode $out/bin/opencode
|
mkdir -p $out/lib/opencode
|
||||||
install -Dm644 opencode-worker.js $out/bin/opencode-worker.js
|
cp -r dist $out/lib/opencode/
|
||||||
if [ -f opencode-assets.manifest ]; then
|
chmod -R u+w $out/lib/opencode/dist
|
||||||
while IFS= read -r asset; do
|
|
||||||
[ -z "$asset" ] && continue
|
# Select bundled worker assets deterministically (sorted find output)
|
||||||
if [ ! -f "$asset" ]; then
|
worker_file=$(find "$out/lib/opencode/dist" -type f \( -path '*/tui/worker.*' -o -name 'worker.*' \) | sort | head -n1)
|
||||||
echo "ERROR: referenced asset \"$asset\" missing"
|
parser_worker_file=$(find "$out/lib/opencode/dist" -type f -name 'parser.worker.*' | sort | head -n1)
|
||||||
|
if [ -z "$worker_file" ]; then
|
||||||
|
echo "ERROR: bundled worker not found"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
install -Dm644 "$asset" "$out/bin/$(basename "$asset")"
|
|
||||||
done < opencode-assets.manifest
|
main_wasm=$(printf '%s\n' "$out"/lib/opencode/dist/tree-sitter-*.wasm | sort | head -n1)
|
||||||
|
wasm_list=$(find "$out/lib/opencode/dist" -maxdepth 1 -name 'tree-sitter-*.wasm' -print)
|
||||||
|
for patch_file in "$worker_file" "$parser_worker_file"; do
|
||||||
|
[ -z "$patch_file" ] && continue
|
||||||
|
[ ! -f "$patch_file" ] && continue
|
||||||
|
if [ -n "$wasm_list" ] && grep -q 'tree-sitter' "$patch_file"; then
|
||||||
|
# Rewrite wasm references to absolute store paths to avoid runtime resolve failures.
|
||||||
|
bun --bun ${scripts + "/patch-wasm.ts"} "$patch_file" "$main_wasm" $wasm_list
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p $out/lib/opencode/node_modules
|
||||||
|
cp -r ../../node_modules/.bun $out/lib/opencode/node_modules/
|
||||||
|
mkdir -p $out/lib/opencode/node_modules/@opentui
|
||||||
|
|
||||||
|
mkdir -p $out/bin
|
||||||
|
makeWrapper ${bun}/bin/bun $out/bin/opencode \
|
||||||
|
--add-flags "run" \
|
||||||
|
--add-flags "$out/lib/opencode/dist/src/index.js" \
|
||||||
|
--prefix PATH : ${lib.makeBinPath [ fzf ripgrep ]} \
|
||||||
|
--argv0 opencode
|
||||||
|
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
'';
|
'';
|
||||||
|
|
||||||
postFixup = ''
|
postInstall = ''
|
||||||
wrapProgram "$out/bin/opencode" --prefix PATH : ${lib.makeBinPath [ fzf ripgrep ]}
|
for pkg in $out/lib/opencode/node_modules/.bun/@opentui+core-* $out/lib/opencode/node_modules/.bun/@opentui+solid-* $out/lib/opencode/node_modules/.bun/@opentui+core@* $out/lib/opencode/node_modules/.bun/@opentui+solid@*; do
|
||||||
|
if [ -d "$pkg" ]; then
|
||||||
|
pkgName=$(basename "$pkg" | sed 's/@opentui+\([^@]*\)@.*/\1/')
|
||||||
|
ln -sf ../.bun/$(basename "$pkg")/node_modules/@opentui/$pkgName \
|
||||||
|
$out/lib/opencode/node_modules/@opentui/$pkgName
|
||||||
|
fi
|
||||||
|
done
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
dontFixup = true;
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
description = "AI coding agent built for the terminal";
|
description = "AI coding agent built for the terminal";
|
||||||
longDescription = ''
|
longDescription = ''
|
||||||
|
|||||||
@@ -24,15 +24,13 @@ for (const entry of directories) {
|
|||||||
if (!info.isDirectory()) {
|
if (!info.isDirectory()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const marker = entry.lastIndexOf("@")
|
const parsed = parseEntry(entry)
|
||||||
if (marker <= 0) {
|
if (!parsed) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const slug = entry.slice(0, marker).replace(/\+/g, "/")
|
const list = versions.get(parsed.name) ?? []
|
||||||
const version = entry.slice(marker + 1)
|
list.push({ dir: full, version: parsed.version, label: entry })
|
||||||
const list = versions.get(slug) ?? []
|
versions.set(parsed.name, list)
|
||||||
list.push({ dir: full, version, label: entry })
|
|
||||||
versions.set(slug, list)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const semverModule = (await import(join(bunRoot, "node_modules/semver"))) as
|
const semverModule = (await import(join(bunRoot, "node_modules/semver"))) as
|
||||||
@@ -79,6 +77,12 @@ for (const [slug, entry] of Array.from(selections.entries()).sort((a, b) => a[0]
|
|||||||
await mkdir(parent, { recursive: true })
|
await mkdir(parent, { recursive: true })
|
||||||
const linkPath = join(parent, leaf)
|
const linkPath = join(parent, leaf)
|
||||||
const desired = join(entry.dir, "node_modules", slug)
|
const desired = join(entry.dir, "node_modules", slug)
|
||||||
|
const exists = await lstat(desired)
|
||||||
|
.then((info) => info.isDirectory())
|
||||||
|
.catch(() => false)
|
||||||
|
if (!exists) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
const relativeTarget = relative(parent, desired)
|
const relativeTarget = relative(parent, desired)
|
||||||
const resolved = relativeTarget.length === 0 ? "." : relativeTarget
|
const resolved = relativeTarget.length === 0 ? "." : relativeTarget
|
||||||
await rm(linkPath, { recursive: true, force: true })
|
await rm(linkPath, { recursive: true, force: true })
|
||||||
@@ -94,3 +98,16 @@ for (const line of rewrites.slice(0, 20)) {
|
|||||||
if (rewrites.length > 20) {
|
if (rewrites.length > 20) {
|
||||||
console.log(" ...")
|
console.log(" ...")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseEntry(label: string) {
|
||||||
|
const marker = label.startsWith("@") ? label.indexOf("@", 1) : label.indexOf("@")
|
||||||
|
if (marker <= 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const name = label.slice(0, marker).replace(/\+/g, "/")
|
||||||
|
const version = label.slice(marker + 1)
|
||||||
|
if (!name || !version) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return { name, version }
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
import fs from "fs"
|
||||||
|
import path from "path"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewrite tree-sitter wasm references inside a JS file to absolute paths.
|
||||||
|
* argv: [node, script, file, mainWasm, ...wasmPaths]
|
||||||
|
*/
|
||||||
|
const [, , file, mainWasm, ...wasmPaths] = process.argv
|
||||||
|
|
||||||
|
if (!file || !mainWasm) {
|
||||||
|
console.error("usage: patch-wasm <file> <mainWasm> [wasmPaths...]")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(file, "utf8")
|
||||||
|
const byName = new Map<string, string>()
|
||||||
|
|
||||||
|
for (const wasm of wasmPaths) {
|
||||||
|
const name = path.basename(wasm)
|
||||||
|
byName.set(name, wasm)
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = content
|
||||||
|
|
||||||
|
for (const [name, wasmPath] of byName) {
|
||||||
|
next = next.replaceAll(name, wasmPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
next = next.replaceAll("tree-sitter.wasm", mainWasm).replaceAll("web-tree-sitter/tree-sitter.wasm", mainWasm)
|
||||||
|
|
||||||
|
// Collapse any relative prefixes before absolute store paths (e.g., "../../../..//nix/store/...")
|
||||||
|
next = next.replace(/(\.\/)+/g, "./")
|
||||||
|
next = next.replace(/(\.\.\/)+\/?(\/nix\/store[^"']+)/g, "/$2")
|
||||||
|
next = next.replace(/(["'])\/{2,}(\/nix\/store[^"']+)(["'])/g, "$1/$2$3")
|
||||||
|
next = next.replace(/(["'])\/\/(nix\/store[^"']+)(["'])/g, "$1/$2$3")
|
||||||
|
|
||||||
|
if (next !== content) fs.writeFileSync(file, next)
|
||||||
+15
-11
@@ -4,12 +4,13 @@
|
|||||||
"description": "AI-powered development tool",
|
"description": "AI-powered development tool",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "bun@1.3.2",
|
"packageManager": "bun@1.3.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
||||||
"typecheck": "bun turbo typecheck",
|
"typecheck": "bun turbo typecheck",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"random": "echo 'Random script'"
|
"random": "echo 'Random script'",
|
||||||
|
"hello": "echo 'Hello World!'"
|
||||||
},
|
},
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": [
|
"packages": [
|
||||||
@@ -19,33 +20,37 @@
|
|||||||
"packages/slack"
|
"packages/slack"
|
||||||
],
|
],
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"@types/bun": "1.3.0",
|
"@types/bun": "1.3.3",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"ulid": "3.0.1",
|
"ulid": "3.0.1",
|
||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
|
"@types/luxon": "3.7.1",
|
||||||
"@types/node": "22.13.9",
|
"@types/node": "22.13.9",
|
||||||
"@tsconfig/node22": "22.0.2",
|
"@tsconfig/node22": "22.0.2",
|
||||||
"@tsconfig/bun": "1.0.9",
|
"@tsconfig/bun": "1.0.9",
|
||||||
"@cloudflare/workers-types": "4.20251008.0",
|
"@cloudflare/workers-types": "4.20251008.0",
|
||||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||||
"@pierre/precision-diffs": "0.4.4",
|
"@pierre/precision-diffs": "0.6.0-beta.3",
|
||||||
"@solidjs/meta": "0.29.4",
|
|
||||||
"@tailwindcss/vite": "4.1.11",
|
"@tailwindcss/vite": "4.1.11",
|
||||||
"diff": "8.0.2",
|
"diff": "8.0.2",
|
||||||
"ai": "5.0.97",
|
"ai": "5.0.97",
|
||||||
"hono": "4.7.10",
|
"hono": "4.7.10",
|
||||||
|
"hono-openapi": "1.1.1",
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
"luxon": "3.6.1",
|
"luxon": "3.6.1",
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.2",
|
||||||
"@typescript/native-preview": "7.0.0-dev.20251014.1",
|
"@typescript/native-preview": "7.0.0-dev.20251014.1",
|
||||||
"zod": "4.1.8",
|
"zod": "4.1.8",
|
||||||
"remeda": "2.26.0",
|
"remeda": "2.26.0",
|
||||||
"solid-js": "1.9.9",
|
|
||||||
"solid-list": "0.3.0",
|
"solid-list": "0.3.0",
|
||||||
"tailwindcss": "4.1.11",
|
"tailwindcss": "4.1.11",
|
||||||
"virtua": "0.42.3",
|
"virtua": "0.42.3",
|
||||||
"vite": "7.1.4",
|
"vite": "7.1.4",
|
||||||
"vite-plugin-solid": "2.11.8"
|
"@solidjs/meta": "0.29.4",
|
||||||
|
"@solidjs/router": "0.15.4",
|
||||||
|
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
|
||||||
|
"solid-js": "1.9.10",
|
||||||
|
"vite-plugin-solid": "2.11.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -56,8 +61,10 @@
|
|||||||
"turbo": "2.5.6"
|
"turbo": "2.5.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "3.933.0",
|
||||||
"@opencode-ai/script": "workspace:*",
|
"@opencode-ai/script": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*"
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
|
"typescript": "catalog:"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -76,9 +83,6 @@
|
|||||||
"tree-sitter-bash",
|
"tree-sitter-bash",
|
||||||
"web-tree-sitter"
|
"web-tree-sitter"
|
||||||
],
|
],
|
||||||
"patchedDependencies": {
|
|
||||||
"@solidjs/start@1.1.7": "patches/@solidjs%2Fstart@1.1.7.patch"
|
|
||||||
},
|
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
"@types/node": "catalog:"
|
"@types/node": "catalog:"
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ dist
|
|||||||
.output
|
.output
|
||||||
.vercel
|
.vercel
|
||||||
.netlify
|
.netlify
|
||||||
.vinxi
|
|
||||||
app.config.timestamp_*.js
|
app.config.timestamp_*.js
|
||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import { defineConfig } from "@solidjs/start/config"
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
middleware: "./src/middleware.ts",
|
|
||||||
vite: {
|
|
||||||
server: {
|
|
||||||
allowedHosts: true,
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
external: ["cloudflare:workers"],
|
|
||||||
},
|
|
||||||
minify: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
compatibilityDate: "2024-09-19",
|
|
||||||
preset: "cloudflare_module",
|
|
||||||
cloudflare: {
|
|
||||||
nodeCompat: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
|
"version": "1.0.130",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
"dev": "vinxi dev --host 0.0.0.0",
|
"dev": "vite dev --host 0.0.0.0",
|
||||||
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
||||||
"build": "./script/generate-sitemap.ts && vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
"build": "./script/generate-sitemap.ts && vite build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
||||||
"start": "vinxi start",
|
"start": "vite start"
|
||||||
"version": "1.0.85"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
@@ -17,17 +18,20 @@
|
|||||||
"@opencode-ai/console-core": "workspace:*",
|
"@opencode-ai/console-core": "workspace:*",
|
||||||
"@opencode-ai/console-mail": "workspace:*",
|
"@opencode-ai/console-mail": "workspace:*",
|
||||||
"@opencode-ai/console-resource": "workspace:*",
|
"@opencode-ai/console-resource": "workspace:*",
|
||||||
"@solidjs/meta": "^0.29.4",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@solidjs/router": "^0.15.0",
|
"@solidjs/meta": "catalog:",
|
||||||
"@solidjs/start": "^1.1.0",
|
"@solidjs/router": "catalog:",
|
||||||
|
"@solidjs/start": "catalog:",
|
||||||
"chart.js": "4.5.1",
|
"chart.js": "4.5.1",
|
||||||
|
"nitro": "3.0.1-alpha.1",
|
||||||
"solid-js": "catalog:",
|
"solid-js": "catalog:",
|
||||||
"vinxi": "^0.5.7",
|
"vite": "catalog:",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@typescript/native-preview": "catalog:",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"@typescript/native-preview": "catalog:"
|
"wrangler": "4.50.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../ui/src/assets/favicon/apple-touch-icon.png
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../ui/src/assets/favicon/favicon-96x96.png
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect width="400" height="400" fill="#FDFCFC"/>
|
|
||||||
<path d="M96 122.001V70.001H148V122.001H96Z" fill="#17181C"/>
|
|
||||||
<path d="M148.004 122.001V70.001H200.004V122.001H148.004Z" fill="#17181C"/>
|
|
||||||
<path d="M200.008 122.001V70.001H252.008V122.001H200.008Z" fill="#17181C"/>
|
|
||||||
<path d="M251.996 122.001V70.001H303.996V122.001H251.996Z" fill="#17181C"/>
|
|
||||||
<path d="M251.996 173.988V121.988H303.996V173.988H251.996Z" fill="#17181C"/>
|
|
||||||
<path d="M96 225.998V173.998H148V225.998H96Z" fill="#CFCECD"/>
|
|
||||||
<rect width="52" height="52" transform="translate(148.004 173.998)" fill="#17181C"/>
|
|
||||||
<path d="M148.004 225.998V173.998H200.004V225.998H148.004Z" fill="#17181C" fill-opacity="0.1"/>
|
|
||||||
<path d="M200.008 225.998V173.998H252.008V225.998H200.008Z" fill="#17181C"/>
|
|
||||||
<path d="M252.016 225.998V173.998H304.016V225.998H252.016Z" fill="#CFCECD"/>
|
|
||||||
<rect width="52" height="52" transform="translate(96 226.002)" fill="#17181C"/>
|
|
||||||
<path d="M96 278.002V226.002H148V278.002H96Z" fill="#17181C" fill-opacity="0.1"/>
|
|
||||||
<rect width="52" height="52" transform="translate(148.004 226.002)" fill="white"/>
|
|
||||||
<path d="M148.004 278.002V226.002H200.004V278.002H148.004Z" fill="#CFCECD"/>
|
|
||||||
<path d="M200.008 278.002V226.002H252.008V278.002H200.008Z" fill="#CFCECD"/>
|
|
||||||
<path d="M252.016 278.002V226.002H304.016V278.002H252.016Z" fill="#CFCECD"/>
|
|
||||||
<path d="M96 330.012V278.012H148V330.012H96Z" fill="#17181C"/>
|
|
||||||
<path d="M148.004 330.012V278.012H200.004V330.012H148.004Z" fill="#17181C"/>
|
|
||||||
<path d="M200.008 329.99V277.99H252.008V329.99H200.008Z" fill="#17181C"/>
|
|
||||||
<path d="M251.996 330.012V278.012H303.996V330.012H251.996Z" fill="#17181C"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
+1
@@ -0,0 +1 @@
|
|||||||
|
../../../ui/src/assets/favicon/favicon.ico
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect width="400" height="400" fill="#0E0E0E"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M312 340H88V60H312V340ZM256 116H144V284H256V116Z" fill="white"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 42 B |
+1
@@ -0,0 +1 @@
|
|||||||
|
../../../ui/src/assets/favicon/favicon.svg
|
||||||
|
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 42 B |
@@ -3,3 +3,4 @@ Allow: /
|
|||||||
|
|
||||||
# Disallow shared content pages
|
# Disallow shared content pages
|
||||||
Disallow: /s/
|
Disallow: /s/
|
||||||
|
Disallow: /share/
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../ui/src/assets/favicon/site.webmanifest
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../ui/src/assets/favicon/web-app-manifest-192x192.png
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../ui/src/assets/favicon/web-app-manifest-512x512.png
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { MetaProvider, Title, Meta } from "@solidjs/meta"
|
import { MetaProvider, Title, Meta } from "@solidjs/meta"
|
||||||
import { Router } from "@solidjs/router"
|
import { Router } from "@solidjs/router"
|
||||||
import { FileRoutes } from "@solidjs/start/router"
|
import { FileRoutes } from "@solidjs/start/router"
|
||||||
import { ErrorBoundary, Suspense } from "solid-js"
|
import { Suspense } from "solid-js"
|
||||||
|
import { Favicon } from "@opencode-ai/ui/favicon"
|
||||||
import "@ibm/plex/css/ibm-plex.css"
|
import "@ibm/plex/css/ibm-plex.css"
|
||||||
import "./app.css"
|
import "./app.css"
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ export default function App() {
|
|||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
<Title>opencode</Title>
|
<Title>opencode</Title>
|
||||||
<Meta name="description" content="OpenCode - The AI coding agent built for the terminal." />
|
<Meta name="description" content="OpenCode - The AI coding agent built for the terminal." />
|
||||||
|
<Favicon />
|
||||||
<Suspense>{props.children}</Suspense>
|
<Suspense>{props.children}</Suspense>
|
||||||
</MetaProvider>
|
</MetaProvider>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ export function IconZai(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IconGoogle(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
export function IconGemini(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
<svg {...props} viewBox="0 0 50 50" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
<svg {...props} viewBox="0 0 50 50" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M49.04,24.001l-1.082-0.043h-0.001C36.134,23.492,26.508,13.866,26.042,2.043L25.999,0.96C25.978,0.424,25.537,0,25,0 s-0.978,0.424-0.999,0.96l-0.043,1.083C23.492,13.866,13.866,23.492,2.042,23.958L0.96,24.001C0.424,24.022,0,24.463,0,25 c0,0.537,0.424,0.978,0.961,0.999l1.082,0.042c11.823,0.467,21.449,10.093,21.915,21.916l0.043,1.083C24.022,49.576,24.463,50,25,50 s0.978-0.424,0.999-0.96l0.043-1.083c0.466-11.823,10.092-21.449,21.915-21.916l1.082-0.042C49.576,25.978,50,25.537,50,25 C50,24.463,49.576,24.022,49.04,24.001z"></path>
|
<path d="M49.04,24.001l-1.082-0.043h-0.001C36.134,23.492,26.508,13.866,26.042,2.043L25.999,0.96C25.978,0.424,25.537,0,25,0 s-0.978,0.424-0.999,0.96l-0.043,1.083C23.492,13.866,13.866,23.492,2.042,23.958L0.96,24.001C0.424,24.022,0,24.463,0,25 c0,0.537,0.424,0.978,0.961,0.999l1.082,0.042c11.823,0.467,21.449,10.093,21.915,21.916l0.043,1.083C24.022,49.576,24.463,50,25,50 s0.978-0.424,0.999-0.96l0.043-1.083c0.466-11.823,10.092-21.449,21.915-21.916l1.082-0.042C49.576,25.978,50,25.537,50,25 C50,24.463,49.576,24.022,49.04,24.001z"></path>
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ export const config = {
|
|||||||
github: {
|
github: {
|
||||||
repoUrl: "https://github.com/sst/opencode",
|
repoUrl: "https://github.com/sst/opencode",
|
||||||
starsFormatted: {
|
starsFormatted: {
|
||||||
compact: "30K",
|
compact: "35K",
|
||||||
full: "30,000",
|
full: "35,000",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -22,8 +22,8 @@ export const config = {
|
|||||||
|
|
||||||
// Static stats (used on landing page)
|
// Static stats (used on landing page)
|
||||||
stats: {
|
stats: {
|
||||||
contributors: "300",
|
contributors: "350",
|
||||||
commits: "4,000",
|
commits: "5,000",
|
||||||
monthlyUsers: "300,000",
|
monthlyUsers: "400,000",
|
||||||
},
|
},
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useSession } from "vinxi/http"
|
import { useSession } from "@solidjs/start/http"
|
||||||
|
|
||||||
export interface AuthSession {
|
export interface AuthSession {
|
||||||
account?: Record<
|
account?: Record<
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export default createHandler(
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" href="/favicon.svg" />
|
|
||||||
<meta property="og:image" content="/social-share.png" />
|
<meta property="og:image" content="/social-share.png" />
|
||||||
<meta property="twitter:image" content="/social-share.png" />
|
<meta property="twitter:image" content="/social-share.png" />
|
||||||
{assets}
|
{assets}
|
||||||
|
|||||||
Vendored
+4
@@ -1 +1,5 @@
|
|||||||
/// <reference types="@solidjs/start/env" />
|
/// <reference types="@solidjs/start/env" />
|
||||||
|
|
||||||
|
export declare module "@solidjs/start/server" {
|
||||||
|
export type APIEvent = { request: Request }
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineMiddleware } from "vinxi/http"
|
import { createMiddleware } from "@solidjs/start/middleware"
|
||||||
|
|
||||||
export default defineMiddleware({
|
export default createMiddleware({
|
||||||
onBeforeResponse() {},
|
onBeforeResponse() {},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ ${body.email}`.trim()
|
|||||||
to: "contact@anoma.ly",
|
to: "contact@anoma.ly",
|
||||||
subject: `Enterprise Inquiry from ${body.name}`,
|
subject: `Enterprise Inquiry from ${body.name}`,
|
||||||
body: emailContent,
|
body: emailContent,
|
||||||
|
replyTo: body.email,
|
||||||
})
|
})
|
||||||
|
|
||||||
return Response.json({ success: true, message: "Form submitted successfully" }, { status: 200 })
|
return Response.json({ success: true, message: "Form submitted successfully" }, { status: 200 })
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export async function GET(input: APIEvent) {
|
|||||||
return {
|
return {
|
||||||
...value,
|
...value,
|
||||||
account: {
|
account: {
|
||||||
|
...value.account,
|
||||||
[id]: {
|
[id]: {
|
||||||
id,
|
id,
|
||||||
email: decoded.subject.properties.email,
|
email: decoded.subject.properties.email,
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { redirect } from "@solidjs/router"
|
||||||
|
import { APIEvent } from "@solidjs/start"
|
||||||
|
import { useAuthSession } from "~/context/auth.session"
|
||||||
|
|
||||||
|
export async function GET(event: APIEvent) {
|
||||||
|
const auth = await useAuthSession()
|
||||||
|
const current = auth.data.current
|
||||||
|
if (current)
|
||||||
|
await auth.update((val) => {
|
||||||
|
delete val.account?.[current]
|
||||||
|
const first = Object.keys(val.account ?? {})[0]
|
||||||
|
val.current = first
|
||||||
|
event!.locals.actor = undefined
|
||||||
|
return val
|
||||||
|
})
|
||||||
|
return redirect("/zen")
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { APIEvent } from "@solidjs/start"
|
||||||
|
import { useAuthSession } from "~/context/auth.session"
|
||||||
|
|
||||||
|
export async function GET(input: APIEvent) {
|
||||||
|
const session = await useAuthSession()
|
||||||
|
return Response.json(session.data)
|
||||||
|
}
|
||||||
@@ -338,6 +338,11 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-slot="installation-instructions"] {
|
||||||
|
color: var(--color-text-strong);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="installation"] {
|
[data-slot="installation"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@@ -348,6 +353,11 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-slot="installation-options"] {
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
[data-component="tabs"] {
|
[data-component="tabs"] {
|
||||||
[data-slot="tablist"] {
|
[data-slot="tablist"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -480,10 +490,10 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 28px;
|
font-size: 38px;
|
||||||
color: var(--color-text-strong);
|
color: var(--color-text-strong);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 8px;
|
||||||
|
|
||||||
@media (max-width: 60rem) {
|
@media (max-width: 60rem) {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
@@ -492,7 +502,7 @@ body {
|
|||||||
|
|
||||||
p {
|
p {
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
margin-bottom: 24px;
|
margin-bottom: 40px;
|
||||||
max-width: 82%;
|
max-width: 82%;
|
||||||
|
|
||||||
@media (max-width: 50rem) {
|
@media (max-width: 50rem) {
|
||||||
@@ -607,6 +617,25 @@ body {
|
|||||||
padding: var(--vertical-padding) var(--padding);
|
padding: var(--vertical-padding) var(--padding);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
|
|
||||||
|
a {
|
||||||
|
background: var(--color-background-strong);
|
||||||
|
padding: 8px 12px 8px 20px;
|
||||||
|
color: var(--color-text-inverted);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 40px;
|
||||||
|
display: flex;
|
||||||
|
width: fit-content;
|
||||||
|
gap: 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
background: var(--color-background-strong-hover);
|
||||||
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
li {
|
li {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import "./index.css"
|
import "./index.css"
|
||||||
import { Title, Meta, Link } from "@solidjs/meta"
|
import { Title, Meta, Link } from "@solidjs/meta"
|
||||||
import { HttpHeader } from "@solidjs/start"
|
// import { HttpHeader } from "@solidjs/start"
|
||||||
import video from "../asset/lander/opencode-min.mp4"
|
import video from "../asset/lander/opencode-min.mp4"
|
||||||
import videoPoster from "../asset/lander/opencode-poster.png"
|
import videoPoster from "../asset/lander/opencode-poster.png"
|
||||||
import { IconCopy, IconCheck } from "../component/icon"
|
import { IconCopy, IconCheck } from "../component/icon"
|
||||||
@@ -42,10 +42,9 @@ export default function Home() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main data-page="opencode">
|
<main data-page="opencode">
|
||||||
<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />
|
{/*<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />*/}
|
||||||
<Title>OpenCode | The AI coding agent built for the terminal</Title>
|
<Title>OpenCode | The open source AI coding agent</Title>
|
||||||
<Link rel="canonical" href={config.baseUrl} />
|
<Link rel="canonical" href={config.baseUrl} />
|
||||||
<Link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
||||||
<Meta property="og:image" content="/social-share.png" />
|
<Meta property="og:image" content="/social-share.png" />
|
||||||
<Meta name="twitter:image" content="/social-share.png" />
|
<Meta name="twitter:image" content="/social-share.png" />
|
||||||
<div data-component="container">
|
<div data-component="container">
|
||||||
@@ -57,23 +56,13 @@ export default function Home() {
|
|||||||
<a data-slot="releases" href={release()?.url ?? `${config.github.repoUrl}/releases`} target="_blank">
|
<a data-slot="releases" href={release()?.url ?? `${config.github.repoUrl}/releases`} target="_blank">
|
||||||
What’s new in {release()?.name ?? "the latest release"}
|
What’s new in {release()?.name ?? "the latest release"}
|
||||||
</a>
|
</a>
|
||||||
<h1>The AI coding agent built for the terminal</h1>
|
<h1>The open source coding agent</h1>
|
||||||
<p>
|
<p>
|
||||||
OpenCode is fully open source, giving you control and freedom to use any provider, any model, and any
|
OpenCode includes free models or connect from any provider to <br />
|
||||||
editor.
|
use other models, including Claude, GPT, Gemini and more.
|
||||||
</p>
|
</p>
|
||||||
<a href="/docs">
|
|
||||||
<span>Read docs </span>
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path
|
|
||||||
d="M6.5 12L17 12M13 16.5L17.5 12L13 7.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke-linecap="square"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p data-slot="installation-instructions">Install and use. No account, no email, and no credit card.</p>
|
||||||
<div data-slot="installation">
|
<div data-slot="installation">
|
||||||
<Tabs
|
<Tabs
|
||||||
as="section"
|
as="section"
|
||||||
@@ -152,6 +141,11 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
<p data-slot="installation-options">
|
||||||
|
Available in terminal, web, and desktop (coming soon).
|
||||||
|
<br />
|
||||||
|
Extensions for VS Code, Cursor, Windsurf, and more.
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section data-component="video">
|
<section data-component="video">
|
||||||
@@ -209,6 +203,17 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<a href="/docs">
|
||||||
|
<span>Read docs </span>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M6.5 12L17 12M13 16.5L17.5 12L13 7.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="square"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section data-component="growth">
|
<section data-component="growth">
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
[data-slot="item"] {
|
[data-slot="item"] {
|
||||||
color: var(--color-danger);
|
color: var(--color-danger);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { action, redirect } from "@solidjs/router"
|
import { action } from "@solidjs/router"
|
||||||
import { getRequestEvent } from "solid-js/web"
|
import { getRequestEvent } from "solid-js/web"
|
||||||
import { useAuthSession } from "~/context/auth.session"
|
import { useAuthSession } from "~/context/auth.session"
|
||||||
import { Dropdown } from "~/component/dropdown"
|
import { Dropdown } from "~/component/dropdown"
|
||||||
@@ -17,18 +17,15 @@ const logout = action(async () => {
|
|||||||
event!.locals.actor = undefined
|
event!.locals.actor = undefined
|
||||||
return val
|
return val
|
||||||
})
|
})
|
||||||
throw redirect("/zen")
|
}, "auth.logout")
|
||||||
})
|
|
||||||
|
|
||||||
export function UserMenu(props: { email: string | null | undefined }) {
|
export function UserMenu(props: { email: string | null | undefined }) {
|
||||||
return (
|
return (
|
||||||
<div data-component="user-menu">
|
<div data-component="user-menu">
|
||||||
<Dropdown trigger={props.email ?? ""} align="right">
|
<Dropdown trigger={props.email ?? ""} align="right">
|
||||||
<form action={logout} method="post">
|
<a href="/auth/logout" data-slot="item">
|
||||||
<button type="submit" formaction={logout} data-slot="item">
|
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</a>
|
||||||
</form>
|
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import "./workspace-picker.css"
|
|||||||
const getWorkspaces = query(async () => {
|
const getWorkspaces = query(async () => {
|
||||||
"use server"
|
"use server"
|
||||||
return withActor(async () => {
|
return withActor(async () => {
|
||||||
return Database.transaction((tx) =>
|
return Database.use((tx) =>
|
||||||
tx
|
tx
|
||||||
.select({
|
.select({
|
||||||
id: WorkspaceTable.id,
|
id: WorkspaceTable.id,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { UserMenu } from "./user-menu"
|
|||||||
import { withActor } from "~/context/auth.withActor"
|
import { withActor } from "~/context/auth.withActor"
|
||||||
import { User } from "@opencode-ai/console-core/user.js"
|
import { User } from "@opencode-ai/console-core/user.js"
|
||||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
import { Link } from "@solidjs/meta"
|
|
||||||
|
|
||||||
const getUserEmail = query(async (workspaceID: string) => {
|
const getUserEmail = query(async (workspaceID: string) => {
|
||||||
"use server"
|
"use server"
|
||||||
@@ -22,7 +21,6 @@ export default function WorkspaceLayout(props: RouteSectionProps) {
|
|||||||
const userEmail = createAsync(() => getUserEmail(params.id!))
|
const userEmail = createAsync(() => getUserEmail(params.id!))
|
||||||
return (
|
return (
|
||||||
<main data-page="workspace">
|
<main data-page="workspace">
|
||||||
<Link rel="icon" type="image/svg+xml" href="/favicon-zen.svg" />
|
|
||||||
<header data-component="workspace-header">
|
<header data-component="workspace-header">
|
||||||
<div data-slot="header-brand">
|
<div data-slot="header-brand">
|
||||||
<A href="/" data-component="site-title">
|
<A href="/" data-component="site-title">
|
||||||
|
|||||||
@@ -158,9 +158,24 @@ export function GraphSection() {
|
|||||||
model: null as string | null,
|
model: null as string | null,
|
||||||
modelDropdownOpen: false,
|
modelDropdownOpen: false,
|
||||||
keyDropdownOpen: false,
|
keyDropdownOpen: false,
|
||||||
|
colorScheme: "light" as "light" | "dark",
|
||||||
})
|
})
|
||||||
const initialData = createAsync(() => queryCosts(params.id!, store.year, store.month))
|
const initialData = createAsync(() => queryCosts(params.id!, store.year, store.month))
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (typeof window === "undefined") return
|
||||||
|
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
||||||
|
setStore({ colorScheme: mediaQuery.matches ? "dark" : "light" })
|
||||||
|
|
||||||
|
const handleColorSchemeChange = (e: MediaQueryListEvent) => {
|
||||||
|
setStore({ colorScheme: e.matches ? "dark" : "light" })
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaQuery.addEventListener("change", handleColorSchemeChange)
|
||||||
|
onCleanup(() => mediaQuery.removeEventListener("change", handleColorSchemeChange))
|
||||||
|
})
|
||||||
|
|
||||||
const onPreviousMonth = async () => {
|
const onPreviousMonth = async () => {
|
||||||
const month = store.month === 0 ? 11 : store.month - 1
|
const month = store.month === 0 ? 11 : store.month - 1
|
||||||
const year = store.month === 0 ? store.year - 1 : store.year
|
const year = store.month === 0 ? store.year - 1 : store.year
|
||||||
@@ -206,10 +221,21 @@ export function GraphSection() {
|
|||||||
const isCurrentMonth = () => store.year === now.getFullYear() && store.month === now.getMonth()
|
const isCurrentMonth = () => store.year === now.getFullYear() && store.month === now.getMonth()
|
||||||
|
|
||||||
const chartConfig = createMemo((): ChartConfiguration | null => {
|
const chartConfig = createMemo((): ChartConfiguration | null => {
|
||||||
|
if (typeof window === "undefined") return null
|
||||||
|
|
||||||
const data = getData()
|
const data = getData()
|
||||||
const dates = getDates()
|
const dates = getDates()
|
||||||
if (!data?.usage?.length) return null
|
if (!data?.usage?.length) return null
|
||||||
|
|
||||||
|
store.colorScheme
|
||||||
|
const styles = getComputedStyle(document.documentElement)
|
||||||
|
const colorTextMuted = styles.getPropertyValue("--color-text-muted").trim()
|
||||||
|
const colorBorderMuted = styles.getPropertyValue("--color-border-muted").trim()
|
||||||
|
const colorBgElevated = styles.getPropertyValue("--color-bg-elevated").trim()
|
||||||
|
const colorText = styles.getPropertyValue("--color-text").trim()
|
||||||
|
const colorTextSecondary = styles.getPropertyValue("--color-text-secondary").trim()
|
||||||
|
const colorBorder = styles.getPropertyValue("--color-border").trim()
|
||||||
|
|
||||||
const dailyData = new Map<string, Map<string, number>>()
|
const dailyData = new Map<string, Map<string, number>>()
|
||||||
for (const dateKey of dates) dailyData.set(dateKey, new Map())
|
for (const dateKey of dates) dailyData.set(dateKey, new Map())
|
||||||
|
|
||||||
@@ -252,7 +278,7 @@ export function GraphSection() {
|
|||||||
ticks: {
|
ticks: {
|
||||||
maxRotation: 0,
|
maxRotation: 0,
|
||||||
autoSkipPadding: 20,
|
autoSkipPadding: 20,
|
||||||
color: "rgba(255, 255, 255, 0.5)",
|
color: colorTextMuted,
|
||||||
font: {
|
font: {
|
||||||
family: "monospace",
|
family: "monospace",
|
||||||
size: 11,
|
size: 11,
|
||||||
@@ -263,10 +289,10 @@ export function GraphSection() {
|
|||||||
stacked: true,
|
stacked: true,
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
grid: {
|
grid: {
|
||||||
color: "rgba(255, 255, 255, 0.1)",
|
color: colorBorderMuted,
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
color: "rgba(255, 255, 255, 0.5)",
|
color: colorTextMuted,
|
||||||
font: {
|
font: {
|
||||||
family: "monospace",
|
family: "monospace",
|
||||||
size: 11,
|
size: 11,
|
||||||
@@ -282,10 +308,10 @@ export function GraphSection() {
|
|||||||
tooltip: {
|
tooltip: {
|
||||||
mode: "index",
|
mode: "index",
|
||||||
intersect: false,
|
intersect: false,
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.9)",
|
backgroundColor: colorBgElevated,
|
||||||
titleColor: "rgba(255, 255, 255, 0.9)",
|
titleColor: colorText,
|
||||||
bodyColor: "rgba(255, 255, 255, 0.8)",
|
bodyColor: colorTextSecondary,
|
||||||
borderColor: "rgba(255, 255, 255, 0.1)",
|
borderColor: colorBorder,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
padding: 12,
|
padding: 12,
|
||||||
displayColors: true,
|
displayColors: true,
|
||||||
@@ -301,7 +327,7 @@ export function GraphSection() {
|
|||||||
display: true,
|
display: true,
|
||||||
position: "bottom",
|
position: "bottom",
|
||||||
labels: {
|
labels: {
|
||||||
color: "rgba(255, 255, 255, 0.7)",
|
color: colorTextSecondary,
|
||||||
font: {
|
font: {
|
||||||
size: 12,
|
size: 12,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { querySessionInfo } from "../common"
|
|||||||
import {
|
import {
|
||||||
IconAlibaba,
|
IconAlibaba,
|
||||||
IconAnthropic,
|
IconAnthropic,
|
||||||
IconGoogle,
|
IconGemini,
|
||||||
IconMoonshotAI,
|
IconMoonshotAI,
|
||||||
IconOpenAI,
|
IconOpenAI,
|
||||||
IconStealth,
|
IconStealth,
|
||||||
@@ -117,7 +117,7 @@ export function ModelSection() {
|
|||||||
case "Anthropic":
|
case "Anthropic":
|
||||||
return <IconAnthropic width={16} height={16} />
|
return <IconAnthropic width={16} height={16} />
|
||||||
case "Google":
|
case "Google":
|
||||||
return <IconGoogle width={16} height={16} />
|
return <IconGemini width={16} height={16} />
|
||||||
case "Moonshot AI":
|
case "Moonshot AI":
|
||||||
return <IconMoonshotAI width={16} height={16} />
|
return <IconMoonshotAI width={16} height={16} />
|
||||||
case "Z.ai":
|
case "Z.ai":
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import styles from "./provider-section.module.css"
|
|||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
{ name: "OpenAI", key: "openai", prefix: "sk-" },
|
{ name: "OpenAI", key: "openai", prefix: "sk-" },
|
||||||
{ name: "Anthropic", key: "anthropic", prefix: "sk-ant-" },
|
{ name: "Anthropic", key: "anthropic", prefix: "sk-ant-" },
|
||||||
|
{ name: "Google Gemini", key: "google", prefix: "AI" },
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
type Provider = (typeof PROVIDERS)[number]
|
type Provider = (typeof PROVIDERS)[number]
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ export function UsageSection() {
|
|||||||
return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWrite5mTokens ?? 0) + (u.cacheWrite1hTokens ?? 0)
|
return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWrite5mTokens ?? 0) + (u.cacheWrite1hTokens ?? 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const calculateTotalOutputTokens = (u: Awaited<ReturnType<typeof getUsageInfo>>[0]) => {
|
||||||
|
return u.outputTokens + (u.reasoningTokens ?? 0)
|
||||||
|
}
|
||||||
|
|
||||||
const goPrev = async () => {
|
const goPrev = async () => {
|
||||||
const usage = await getUsageInfo(params.id!, store.page - 1)
|
const usage = await getUsageInfo(params.id!, store.page - 1)
|
||||||
setStore({
|
setStore({
|
||||||
@@ -95,8 +99,11 @@ export function UsageSection() {
|
|||||||
{(usage, index) => {
|
{(usage, index) => {
|
||||||
const date = createMemo(() => new Date(usage.timeCreated))
|
const date = createMemo(() => new Date(usage.timeCreated))
|
||||||
const totalInputTokens = createMemo(() => calculateTotalInputTokens(usage))
|
const totalInputTokens = createMemo(() => calculateTotalInputTokens(usage))
|
||||||
const breakdownId = `breakdown-${index()}`
|
const totalOutputTokens = createMemo(() => calculateTotalOutputTokens(usage))
|
||||||
const isOpen = createMemo(() => openBreakdownId() === breakdownId)
|
const inputBreakdownId = `input-breakdown-${index()}`
|
||||||
|
const outputBreakdownId = `output-breakdown-${index()}`
|
||||||
|
const isInputOpen = createMemo(() => openBreakdownId() === inputBreakdownId)
|
||||||
|
const isOutputOpen = createMemo(() => openBreakdownId() === outputBreakdownId)
|
||||||
const isClaude = usage.model.toLowerCase().includes("claude")
|
const isClaude = usage.model.toLowerCase().includes("claude")
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
@@ -110,13 +117,13 @@ export function UsageSection() {
|
|||||||
data-slot="breakdown-button"
|
data-slot="breakdown-button"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setOpenBreakdownId(isOpen() ? null : breakdownId)
|
setOpenBreakdownId(isInputOpen() ? null : inputBreakdownId)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconBreakdown />
|
<IconBreakdown />
|
||||||
</button>
|
</button>
|
||||||
<span onClick={() => setOpenBreakdownId(null)}>{totalInputTokens()}</span>
|
<span onClick={() => setOpenBreakdownId(null)}>{totalInputTokens()}</span>
|
||||||
<Show when={isOpen()}>
|
<Show when={isInputOpen()}>
|
||||||
<div data-slot="breakdown-popup" onClick={(e) => e.stopPropagation()}>
|
<div data-slot="breakdown-popup" onClick={(e) => e.stopPropagation()}>
|
||||||
<div data-slot="breakdown-row">
|
<div data-slot="breakdown-row">
|
||||||
<span data-slot="breakdown-label">Input</span>
|
<span data-slot="breakdown-label">Input</span>
|
||||||
@@ -136,7 +143,32 @@ export function UsageSection() {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td data-slot="usage-tokens">{usage.outputTokens}</td>
|
<td data-slot="usage-tokens">
|
||||||
|
<div data-slot="tokens-with-breakdown" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<button
|
||||||
|
data-slot="breakdown-button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setOpenBreakdownId(isOutputOpen() ? null : outputBreakdownId)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconBreakdown />
|
||||||
|
</button>
|
||||||
|
<span onClick={() => setOpenBreakdownId(null)}>{totalOutputTokens()}</span>
|
||||||
|
<Show when={isOutputOpen()}>
|
||||||
|
<div data-slot="breakdown-popup" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div data-slot="breakdown-row">
|
||||||
|
<span data-slot="breakdown-label">Output</span>
|
||||||
|
<span data-slot="breakdown-value">{usage.outputTokens}</span>
|
||||||
|
</div>
|
||||||
|
<div data-slot="breakdown-row">
|
||||||
|
<span data-slot="breakdown-label">Reasoning</span>
|
||||||
|
<span data-slot="breakdown-value">{usage.reasoningTokens ?? 0}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td data-slot="usage-cost">${((usage.cost ?? 0) / 100000000).toFixed(4)}</td>
|
<td data-slot="usage-cost">${((usage.cost ?? 0) / 100000000).toFixed(4)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import "./index.css"
|
import "./index.css"
|
||||||
import { createAsync, query, redirect } from "@solidjs/router"
|
import { createAsync, query, redirect } from "@solidjs/router"
|
||||||
import { Title, Meta, Link } from "@solidjs/meta"
|
import { Title, Meta, Link } from "@solidjs/meta"
|
||||||
import { HttpHeader } from "@solidjs/start"
|
// import { HttpHeader } from "@solidjs/start"
|
||||||
import zenLogoLight from "../../asset/zen-ornate-light.svg"
|
import zenLogoLight from "../../asset/zen-ornate-light.svg"
|
||||||
import { config } from "~/config"
|
import { config } from "~/config"
|
||||||
import zenLogoDark from "../../asset/zen-ornate-dark.svg"
|
import zenLogoDark from "../../asset/zen-ornate-dark.svg"
|
||||||
@@ -18,23 +18,24 @@ import { Legal } from "~/component/legal"
|
|||||||
import { Footer } from "~/component/footer"
|
import { Footer } from "~/component/footer"
|
||||||
import { Header } from "~/component/header"
|
import { Header } from "~/component/header"
|
||||||
import { getLastSeenWorkspaceID } from "../workspace/common"
|
import { getLastSeenWorkspaceID } from "../workspace/common"
|
||||||
|
import { IconGemini, IconZai } from "~/component/icon"
|
||||||
|
|
||||||
const checkLoggedIn = query(async () => {
|
const checkLoggedIn = query(async () => {
|
||||||
"use server"
|
"use server"
|
||||||
const workspaceID = await getLastSeenWorkspaceID()
|
const workspaceID = await getLastSeenWorkspaceID().catch(() => {})
|
||||||
if (workspaceID) throw redirect(`/workspace/${workspaceID}`)
|
if (workspaceID) throw redirect(`/workspace/${workspaceID}`)
|
||||||
}, "checkLoggedIn.get")
|
}, "checkLoggedIn.get")
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
createAsync(() => checkLoggedIn())
|
const loggedin = createAsync(() => checkLoggedIn())
|
||||||
return (
|
return (
|
||||||
<main data-page="zen">
|
<main data-page="zen">
|
||||||
<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />
|
{/*<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />*/}
|
||||||
<Title>OpenCode Zen | A curated set of reliable optimized models for coding agents</Title>
|
<Title>OpenCode Zen | A curated set of reliable optimized models for coding agents</Title>
|
||||||
<Link rel="canonical" href={`${config.baseUrl}/zen`} />
|
<Link rel="canonical" href={`${config.baseUrl}/zen`} />
|
||||||
<Link rel="icon" type="image/svg+xml" href="/favicon-zen.svg" />
|
|
||||||
<Meta property="og:image" content="/social-share-zen.png" />
|
<Meta property="og:image" content="/social-share-zen.png" />
|
||||||
<Meta name="twitter:image" content="/social-share-zen.png" />
|
<Meta name="twitter:image" content="/social-share-zen.png" />
|
||||||
|
<Meta name="opencode:auth" content={loggedin() ? "true" : "false"} />
|
||||||
|
|
||||||
<div data-component="container">
|
<div data-component="container">
|
||||||
<Header zen />
|
<Header zen />
|
||||||
@@ -81,6 +82,9 @@ export default function Home() {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<IconGemini width="24" height="24" />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
@@ -111,6 +115,9 @@ export default function Home() {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<IconZai width="24" height="24" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/auth">
|
<a href="/auth">
|
||||||
<span>Get started with Zen </span>
|
<span>Get started with Zen </span>
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { Resource, waitUntil } from "@opencode-ai/console-resource"
|
||||||
|
|
||||||
|
export function createDataDumper(sessionId: string, requestId: string, projectId: string) {
|
||||||
|
if (Resource.App.stage !== "production") return
|
||||||
|
if (sessionId === "") return
|
||||||
|
|
||||||
|
let data: Record<string, any> = { sessionId, requestId, projectId }
|
||||||
|
let metadata: Record<string, any> = { sessionId, requestId, projectId }
|
||||||
|
|
||||||
|
return {
|
||||||
|
provideModel: (model?: string) => {
|
||||||
|
data.modelName = model
|
||||||
|
metadata.modelName = model
|
||||||
|
},
|
||||||
|
provideRequest: (request: string) => (data.request = request),
|
||||||
|
provideResponse: (response: string) => (data.response = response),
|
||||||
|
provideStream: (chunk: string) => (data.response = (data.response ?? "") + chunk),
|
||||||
|
flush: () => {
|
||||||
|
if (!data.modelName) return
|
||||||
|
|
||||||
|
const timestamp = new Date().toISOString().replace(/[^0-9]/g, "")
|
||||||
|
|
||||||
|
waitUntil(
|
||||||
|
Resource.ZenData.put(
|
||||||
|
`data/${data.modelName}/${sessionId}/${requestId}.json`,
|
||||||
|
JSON.stringify({ timestamp, ...data }),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
waitUntil(
|
||||||
|
Resource.ZenData.put(
|
||||||
|
`meta/${data.modelName}/${timestamp}/${requestId}.json`,
|
||||||
|
JSON.stringify({ timestamp, ...metadata }),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,12 +13,14 @@ import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
|
|||||||
import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
|
import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
|
||||||
import { logger } from "./logger"
|
import { logger } from "./logger"
|
||||||
import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError, RateLimitError } from "./error"
|
import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError, RateLimitError } from "./error"
|
||||||
import { createBodyConverter, createStreamPartConverter, createResponseConverter } from "./provider/provider"
|
import { createBodyConverter, createStreamPartConverter, createResponseConverter, UsageInfo } from "./provider/provider"
|
||||||
import { anthropicHelper } from "./provider/anthropic"
|
import { anthropicHelper } from "./provider/anthropic"
|
||||||
import { googleHelper } from "./provider/google"
|
import { googleHelper } from "./provider/google"
|
||||||
import { openaiHelper } from "./provider/openai"
|
import { openaiHelper } from "./provider/openai"
|
||||||
import { oaCompatHelper } from "./provider/openai-compatible"
|
import { oaCompatHelper } from "./provider/openai-compatible"
|
||||||
import { createRateLimiter } from "./rateLimiter"
|
import { createRateLimiter } from "./rateLimiter"
|
||||||
|
import { createDataDumper } from "./dataDumper"
|
||||||
|
import { createTrialLimiter } from "./trialLimiter"
|
||||||
|
|
||||||
type ZenData = Awaited<ReturnType<typeof ZenData.list>>
|
type ZenData = Awaited<ReturnType<typeof ZenData.list>>
|
||||||
type RetryOptions = {
|
type RetryOptions = {
|
||||||
@@ -48,21 +50,27 @@ export async function handler(
|
|||||||
try {
|
try {
|
||||||
const url = input.request.url
|
const url = input.request.url
|
||||||
const body = await input.request.json()
|
const body = await input.request.json()
|
||||||
const ip = input.request.headers.get("x-real-ip") ?? ""
|
|
||||||
const model = opts.parseModel(url, body)
|
const model = opts.parseModel(url, body)
|
||||||
const isStream = opts.parseIsStream(url, body)
|
const isStream = opts.parseIsStream(url, body)
|
||||||
|
const ip = input.request.headers.get("x-real-ip") ?? ""
|
||||||
|
const sessionId = input.request.headers.get("x-opencode-session") ?? ""
|
||||||
|
const requestId = input.request.headers.get("x-opencode-request") ?? ""
|
||||||
|
const projectId = input.request.headers.get("x-opencode-project") ?? ""
|
||||||
logger.metric({
|
logger.metric({
|
||||||
is_tream: isStream,
|
is_tream: isStream,
|
||||||
session: input.request.headers.get("x-opencode-session"),
|
session: sessionId,
|
||||||
request: input.request.headers.get("x-opencode-request"),
|
request: requestId,
|
||||||
})
|
})
|
||||||
const zenData = ZenData.list()
|
const zenData = ZenData.list()
|
||||||
const modelInfo = validateModel(zenData, model)
|
const modelInfo = validateModel(zenData, model)
|
||||||
|
const dataDumper = createDataDumper(sessionId, requestId, projectId)
|
||||||
|
const trialLimiter = createTrialLimiter(modelInfo.trial?.limit, ip)
|
||||||
|
const isTrial = await trialLimiter?.isTrial()
|
||||||
const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip)
|
const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip)
|
||||||
await rateLimiter?.check()
|
await rateLimiter?.check()
|
||||||
|
|
||||||
const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
|
const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
|
||||||
const providerInfo = selectProvider(zenData, modelInfo, ip, retry)
|
const providerInfo = selectProvider(zenData, modelInfo, sessionId, isTrial ?? false, retry)
|
||||||
const authInfo = await authenticate(modelInfo, providerInfo)
|
const authInfo = await authenticate(modelInfo, providerInfo)
|
||||||
validateBilling(authInfo, modelInfo)
|
validateBilling(authInfo, modelInfo)
|
||||||
validateModelSettings(authInfo)
|
validateModelSettings(authInfo)
|
||||||
@@ -104,10 +112,14 @@ export async function handler(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return { providerInfo, authInfo, res, startTimestamp }
|
return { providerInfo, authInfo, reqBody, res, startTimestamp }
|
||||||
}
|
}
|
||||||
|
|
||||||
const { providerInfo, authInfo, res, startTimestamp } = await retriableRequest()
|
const { providerInfo, authInfo, reqBody, res, startTimestamp } = await retriableRequest()
|
||||||
|
|
||||||
|
// Store model request
|
||||||
|
dataDumper?.provideModel(providerInfo.storeModel)
|
||||||
|
dataDumper?.provideRequest(reqBody)
|
||||||
|
|
||||||
// Scrub response headers
|
// Scrub response headers
|
||||||
const resHeaders = new Headers()
|
const resHeaders = new Headers()
|
||||||
@@ -126,8 +138,12 @@ export async function handler(
|
|||||||
const body = JSON.stringify(responseConverter(json))
|
const body = JSON.stringify(responseConverter(json))
|
||||||
logger.metric({ response_length: body.length })
|
logger.metric({ response_length: body.length })
|
||||||
logger.debug("RESPONSE: " + body)
|
logger.debug("RESPONSE: " + body)
|
||||||
|
dataDumper?.provideResponse(body)
|
||||||
|
dataDumper?.flush()
|
||||||
|
const tokensInfo = providerInfo.normalizeUsage(json.usage)
|
||||||
|
await trialLimiter?.track(tokensInfo)
|
||||||
await rateLimiter?.track()
|
await rateLimiter?.track()
|
||||||
await trackUsage(authInfo, modelInfo, providerInfo, json.usage)
|
await trackUsage(authInfo, modelInfo, providerInfo, tokensInfo)
|
||||||
await reload(authInfo)
|
await reload(authInfo)
|
||||||
return new Response(body, {
|
return new Response(body, {
|
||||||
status: res.status,
|
status: res.status,
|
||||||
@@ -155,10 +171,13 @@ export async function handler(
|
|||||||
response_length: responseLength,
|
response_length: responseLength,
|
||||||
"timestamp.last_byte": Date.now(),
|
"timestamp.last_byte": Date.now(),
|
||||||
})
|
})
|
||||||
|
dataDumper?.flush()
|
||||||
await rateLimiter?.track()
|
await rateLimiter?.track()
|
||||||
const usage = usageParser.retrieve()
|
const usage = usageParser.retrieve()
|
||||||
if (usage) {
|
if (usage) {
|
||||||
await trackUsage(authInfo, modelInfo, providerInfo, usage)
|
const tokensInfo = providerInfo.normalizeUsage(usage)
|
||||||
|
await trialLimiter?.track(tokensInfo)
|
||||||
|
await trackUsage(authInfo, modelInfo, providerInfo, tokensInfo)
|
||||||
await reload(authInfo)
|
await reload(authInfo)
|
||||||
}
|
}
|
||||||
c.close()
|
c.close()
|
||||||
@@ -174,6 +193,7 @@ export async function handler(
|
|||||||
}
|
}
|
||||||
responseLength += value.length
|
responseLength += value.length
|
||||||
buffer += decoder.decode(value, { stream: true })
|
buffer += decoder.decode(value, { stream: true })
|
||||||
|
dataDumper?.provideStream(buffer)
|
||||||
|
|
||||||
const parts = buffer.split(providerInfo.streamSeparator)
|
const parts = buffer.split(providerInfo.streamSeparator)
|
||||||
buffer = parts.pop() ?? ""
|
buffer = parts.pop() ?? ""
|
||||||
@@ -263,8 +283,18 @@ export async function handler(
|
|||||||
return { id: modelId, ...modelData }
|
return { id: modelId, ...modelData }
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectProvider(zenData: ZenData, modelInfo: ModelInfo, ip: string, retry: RetryOptions) {
|
function selectProvider(
|
||||||
|
zenData: ZenData,
|
||||||
|
modelInfo: ModelInfo,
|
||||||
|
sessionId: string,
|
||||||
|
isTrial: boolean,
|
||||||
|
retry: RetryOptions,
|
||||||
|
) {
|
||||||
const provider = (() => {
|
const provider = (() => {
|
||||||
|
if (isTrial) {
|
||||||
|
return modelInfo.providers.find((provider) => provider.id === modelInfo.trial!.provider)
|
||||||
|
}
|
||||||
|
|
||||||
if (retry.retryCount === MAX_RETRIES) {
|
if (retry.retryCount === MAX_RETRIES) {
|
||||||
return modelInfo.providers.find((provider) => provider.id === modelInfo.fallbackProvider)
|
return modelInfo.providers.find((provider) => provider.id === modelInfo.fallbackProvider)
|
||||||
}
|
}
|
||||||
@@ -274,9 +304,13 @@ export async function handler(
|
|||||||
.filter((provider) => !retry.excludeProviders.includes(provider.id))
|
.filter((provider) => !retry.excludeProviders.includes(provider.id))
|
||||||
.flatMap((provider) => Array<typeof provider>(provider.weight ?? 1).fill(provider))
|
.flatMap((provider) => Array<typeof provider>(provider.weight ?? 1).fill(provider))
|
||||||
|
|
||||||
// Use the last 2 characters of IP address to select a provider
|
// Use the last 4 characters of session ID to select a provider
|
||||||
const lastChars = ip.slice(-2)
|
let h = 0
|
||||||
const index = parseInt(lastChars, 16) % providers.length
|
const l = sessionId.length
|
||||||
|
for (let i = l - 4; i < l; i++) {
|
||||||
|
h = (h * 31 + sessionId.charCodeAt(i)) | 0 // 32-bit int
|
||||||
|
}
|
||||||
|
const index = (h >>> 0) % providers.length // make unsigned + range 0..length-1
|
||||||
return providers[index || 0]
|
return providers[index || 0]
|
||||||
})()
|
})()
|
||||||
|
|
||||||
@@ -416,9 +450,14 @@ export async function handler(
|
|||||||
providerInfo.apiKey = authInfo.provider.credentials
|
providerInfo.apiKey = authInfo.provider.credentials
|
||||||
}
|
}
|
||||||
|
|
||||||
async function trackUsage(authInfo: AuthInfo, modelInfo: ModelInfo, providerInfo: ProviderInfo, usage: any) {
|
async function trackUsage(
|
||||||
|
authInfo: AuthInfo,
|
||||||
|
modelInfo: ModelInfo,
|
||||||
|
providerInfo: ProviderInfo,
|
||||||
|
usageInfo: UsageInfo,
|
||||||
|
) {
|
||||||
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
|
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
|
||||||
providerInfo.normalizeUsage(usage)
|
usageInfo
|
||||||
|
|
||||||
const modelCost =
|
const modelCost =
|
||||||
modelInfo.cost200K &&
|
modelInfo.cost200K &&
|
||||||
|
|||||||
@@ -24,6 +24,15 @@ import {
|
|||||||
toOaCompatibleResponse,
|
toOaCompatibleResponse,
|
||||||
} from "./openai-compatible"
|
} from "./openai-compatible"
|
||||||
|
|
||||||
|
export type UsageInfo = {
|
||||||
|
inputTokens: number
|
||||||
|
outputTokens: number
|
||||||
|
reasoningTokens?: number
|
||||||
|
cacheReadTokens?: number
|
||||||
|
cacheWrite5mTokens?: number
|
||||||
|
cacheWrite1hTokens?: number
|
||||||
|
}
|
||||||
|
|
||||||
export type ProviderHelper = {
|
export type ProviderHelper = {
|
||||||
format: ZenData.Format
|
format: ZenData.Format
|
||||||
modifyUrl: (providerApi: string, model?: string, isStream?: boolean) => string
|
modifyUrl: (providerApi: string, model?: string, isStream?: boolean) => string
|
||||||
@@ -34,14 +43,7 @@ export type ProviderHelper = {
|
|||||||
parse: (chunk: string) => void
|
parse: (chunk: string) => void
|
||||||
retrieve: () => any
|
retrieve: () => any
|
||||||
}
|
}
|
||||||
normalizeUsage: (usage: any) => {
|
normalizeUsage: (usage: any) => UsageInfo
|
||||||
inputTokens: number
|
|
||||||
outputTokens: number
|
|
||||||
reasoningTokens?: number
|
|
||||||
cacheReadTokens?: number
|
|
||||||
cacheWrite5mTokens?: number
|
|
||||||
cacheWrite1hTokens?: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommonMessage {
|
export interface CommonMessage {
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js"
|
||||||
|
import { IpTable } from "@opencode-ai/console-core/schema/ip.sql.js"
|
||||||
|
import { UsageInfo } from "./provider/provider"
|
||||||
|
|
||||||
|
export function createTrialLimiter(limit: number | undefined, ip: string) {
|
||||||
|
if (!limit) return
|
||||||
|
if (!ip) return
|
||||||
|
|
||||||
|
let trial: boolean
|
||||||
|
|
||||||
|
return {
|
||||||
|
isTrial: async () => {
|
||||||
|
const data = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
usage: IpTable.usage,
|
||||||
|
})
|
||||||
|
.from(IpTable)
|
||||||
|
.where(eq(IpTable.ip, ip))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
|
||||||
|
trial = (data?.usage ?? 0) < limit
|
||||||
|
return trial
|
||||||
|
},
|
||||||
|
track: async (usageInfo: UsageInfo) => {
|
||||||
|
if (!trial) return
|
||||||
|
const usage =
|
||||||
|
usageInfo.inputTokens +
|
||||||
|
usageInfo.outputTokens +
|
||||||
|
(usageInfo.reasoningTokens ?? 0) +
|
||||||
|
(usageInfo.cacheReadTokens ?? 0) +
|
||||||
|
(usageInfo.cacheWrite5mTokens ?? 0) +
|
||||||
|
(usageInfo.cacheWrite1hTokens ?? 0)
|
||||||
|
await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.insert(IpTable)
|
||||||
|
.values({ ip, usage })
|
||||||
|
.onDuplicateKeyUpdate({ set: { usage: sql`${IpTable.usage} + ${usage}` } }),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"types": ["vinxi/types/client"],
|
"types": ["vite/client"],
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["./src/*"]
|
"~/*": ["./src/*"]
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { defineConfig, PluginOption } from "vite"
|
||||||
|
import { solidStart } from "@solidjs/start/config"
|
||||||
|
import { nitro } from "nitro/vite"
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
solidStart() as PluginOption,
|
||||||
|
nitro({
|
||||||
|
compatibilityDate: "2024-09-19",
|
||||||
|
preset: "cloudflare_module",
|
||||||
|
cloudflare: {
|
||||||
|
nodeCompat: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
server: {
|
||||||
|
allowedHosts: true,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
external: ["cloudflare:workers"],
|
||||||
|
},
|
||||||
|
minify: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE `ip` (
|
||||||
|
`ip` varchar(45) NOT NULL,
|
||||||
|
`time_created` timestamp(3) NOT NULL DEFAULT (now()),
|
||||||
|
`time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
`time_deleted` timestamp(3),
|
||||||
|
`usage` int,
|
||||||
|
CONSTRAINT `ip_ip_pk` PRIMARY KEY(`ip`)
|
||||||
|
);
|
||||||
@@ -0,0 +1,981 @@
|
|||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "mysql",
|
||||||
|
"id": "9d5d9885-7ec5-45f6-ac53-45a8e25dede7",
|
||||||
|
"prevId": "8b7fa839-a088-408e-84a4-1a07325c0290",
|
||||||
|
"tables": {
|
||||||
|
"account": {
|
||||||
|
"name": "account",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"account_id_pk": {
|
||||||
|
"name": "account_id_pk",
|
||||||
|
"columns": ["id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"name": "auth",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"name": "provider",
|
||||||
|
"type": "enum('email','github','google')",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"subject": {
|
||||||
|
"name": "subject",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"account_id": {
|
||||||
|
"name": "account_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"provider": {
|
||||||
|
"name": "provider",
|
||||||
|
"columns": ["provider", "subject"],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"account_id": {
|
||||||
|
"name": "account_id",
|
||||||
|
"columns": ["account_id"],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"auth_id_pk": {
|
||||||
|
"name": "auth_id_pk",
|
||||||
|
"columns": ["id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"billing": {
|
||||||
|
"name": "billing",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"customer_id": {
|
||||||
|
"name": "customer_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"payment_method_id": {
|
||||||
|
"name": "payment_method_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"payment_method_type": {
|
||||||
|
"name": "payment_method_type",
|
||||||
|
"type": "varchar(32)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"payment_method_last4": {
|
||||||
|
"name": "payment_method_last4",
|
||||||
|
"type": "varchar(4)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"balance": {
|
||||||
|
"name": "balance",
|
||||||
|
"type": "bigint",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"monthly_limit": {
|
||||||
|
"name": "monthly_limit",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"monthly_usage": {
|
||||||
|
"name": "monthly_usage",
|
||||||
|
"type": "bigint",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_monthly_usage_updated": {
|
||||||
|
"name": "time_monthly_usage_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"reload": {
|
||||||
|
"name": "reload",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"reload_trigger": {
|
||||||
|
"name": "reload_trigger",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"reload_amount": {
|
||||||
|
"name": "reload_amount",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"reload_error": {
|
||||||
|
"name": "reload_error",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_reload_error": {
|
||||||
|
"name": "time_reload_error",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_reload_locked_till": {
|
||||||
|
"name": "time_reload_locked_till",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"global_customer_id": {
|
||||||
|
"name": "global_customer_id",
|
||||||
|
"columns": ["customer_id"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"billing_workspace_id_id_pk": {
|
||||||
|
"name": "billing_workspace_id_id_pk",
|
||||||
|
"columns": ["workspace_id", "id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"payment": {
|
||||||
|
"name": "payment",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"customer_id": {
|
||||||
|
"name": "customer_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"invoice_id": {
|
||||||
|
"name": "invoice_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"payment_id": {
|
||||||
|
"name": "payment_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"name": "amount",
|
||||||
|
"type": "bigint",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_refunded": {
|
||||||
|
"name": "time_refunded",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"payment_workspace_id_id_pk": {
|
||||||
|
"name": "payment_workspace_id_id_pk",
|
||||||
|
"columns": ["workspace_id", "id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"usage": {
|
||||||
|
"name": "usage",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"name": "model",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"name": "provider",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"input_tokens": {
|
||||||
|
"name": "input_tokens",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"output_tokens": {
|
||||||
|
"name": "output_tokens",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"reasoning_tokens": {
|
||||||
|
"name": "reasoning_tokens",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"cache_read_tokens": {
|
||||||
|
"name": "cache_read_tokens",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"cache_write_5m_tokens": {
|
||||||
|
"name": "cache_write_5m_tokens",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"cache_write_1h_tokens": {
|
||||||
|
"name": "cache_write_1h_tokens",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"cost": {
|
||||||
|
"name": "cost",
|
||||||
|
"type": "bigint",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"key_id": {
|
||||||
|
"name": "key_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"usage_workspace_id_id_pk": {
|
||||||
|
"name": "usage_workspace_id_id_pk",
|
||||||
|
"columns": ["workspace_id", "id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"ip": {
|
||||||
|
"name": "ip",
|
||||||
|
"columns": {
|
||||||
|
"ip": {
|
||||||
|
"name": "ip",
|
||||||
|
"type": "varchar(45)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"usage": {
|
||||||
|
"name": "usage",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"ip_ip_pk": {
|
||||||
|
"name": "ip_ip_pk",
|
||||||
|
"columns": ["ip"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_used": {
|
||||||
|
"name": "time_used",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"global_key": {
|
||||||
|
"name": "global_key",
|
||||||
|
"columns": ["key"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"key_workspace_id_id_pk": {
|
||||||
|
"name": "key_workspace_id_id_pk",
|
||||||
|
"columns": ["workspace_id", "id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"name": "model",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"name": "model",
|
||||||
|
"type": "varchar(64)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"model_workspace_model": {
|
||||||
|
"name": "model_workspace_model",
|
||||||
|
"columns": ["workspace_id", "model"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"model_workspace_id_id_pk": {
|
||||||
|
"name": "model_workspace_id_id_pk",
|
||||||
|
"columns": ["workspace_id", "id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"name": "provider",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"name": "provider",
|
||||||
|
"type": "varchar(64)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"credentials": {
|
||||||
|
"name": "credentials",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"workspace_provider": {
|
||||||
|
"name": "workspace_provider",
|
||||||
|
"columns": ["workspace_id", "provider"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"provider_workspace_id_id_pk": {
|
||||||
|
"name": "provider_workspace_id_id_pk",
|
||||||
|
"columns": ["workspace_id", "id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "user",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"account_id": {
|
||||||
|
"name": "account_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_seen": {
|
||||||
|
"name": "time_seen",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"name": "color",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"name": "role",
|
||||||
|
"type": "enum('admin','member')",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"monthly_limit": {
|
||||||
|
"name": "monthly_limit",
|
||||||
|
"type": "int",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"monthly_usage": {
|
||||||
|
"name": "monthly_usage",
|
||||||
|
"type": "bigint",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_monthly_usage_updated": {
|
||||||
|
"name": "time_monthly_usage_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"user_account_id": {
|
||||||
|
"name": "user_account_id",
|
||||||
|
"columns": ["workspace_id", "account_id"],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"user_email": {
|
||||||
|
"name": "user_email",
|
||||||
|
"columns": ["workspace_id", "email"],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"global_account_id": {
|
||||||
|
"name": "global_account_id",
|
||||||
|
"columns": ["account_id"],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"global_email": {
|
||||||
|
"name": "global_email",
|
||||||
|
"columns": ["email"],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"user_workspace_id_id_pk": {
|
||||||
|
"name": "user_workspace_id_id_pk",
|
||||||
|
"columns": ["workspace_id", "id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"name": "workspace",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(now())"
|
||||||
|
},
|
||||||
|
"time_updated": {
|
||||||
|
"name": "time_updated",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp(3)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"columns": ["slug"],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"columns": ["id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraint": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"tables": {},
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -267,6 +267,13 @@
|
|||||||
"when": 1761928273807,
|
"when": 1761928273807,
|
||||||
"tag": "0037_messy_jackal",
|
"tag": "0037_messy_jackal",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 38,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1764110043942,
|
||||||
|
"tag": "0038_famous_magik",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.0.85",
|
"version": "1.0.130",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
"update-models": "script/update-models.ts",
|
"update-models": "script/update-models.ts",
|
||||||
"promote-models-to-dev": "script/promote-models.ts dev",
|
"promote-models-to-dev": "script/promote-models.ts dev",
|
||||||
"promote-models-to-prod": "script/promote-models.ts production",
|
"promote-models-to-prod": "script/promote-models.ts production",
|
||||||
|
"pull-models-from-dev": "script/pull-models.ts dev",
|
||||||
"typecheck": "tsgo --noEmit"
|
"typecheck": "tsgo --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -11,20 +11,21 @@ const root = path.resolve(process.cwd(), "..", "..", "..")
|
|||||||
|
|
||||||
// read the secret
|
// read the secret
|
||||||
const ret = await $`bun sst secret list`.cwd(root).text()
|
const ret = await $`bun sst secret list`.cwd(root).text()
|
||||||
const value1 = ret
|
const lines = ret.split("\n")
|
||||||
.split("\n")
|
const value1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[1]
|
||||||
.find((line) => line.startsWith("ZEN_MODELS1"))
|
const value2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1]
|
||||||
?.split("=")[1]
|
const value3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1]
|
||||||
const value2 = ret
|
const value4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1]
|
||||||
.split("\n")
|
|
||||||
.find((line) => line.startsWith("ZEN_MODELS2"))
|
|
||||||
?.split("=")[1]
|
|
||||||
if (!value1) throw new Error("ZEN_MODELS1 not found")
|
if (!value1) throw new Error("ZEN_MODELS1 not found")
|
||||||
if (!value2) throw new Error("ZEN_MODELS2 not found")
|
if (!value2) throw new Error("ZEN_MODELS2 not found")
|
||||||
|
if (!value3) throw new Error("ZEN_MODELS3 not found")
|
||||||
|
if (!value4) throw new Error("ZEN_MODELS4 not found")
|
||||||
|
|
||||||
// validate value
|
// validate value
|
||||||
ZenData.validate(JSON.parse(value1 + value2))
|
ZenData.validate(JSON.parse(value1 + value2 + value3 + value4))
|
||||||
|
|
||||||
// update the secret
|
// update the secret
|
||||||
await $`bun sst secret set ZEN_MODELS1 ${value1} --stage ${stage}`
|
await $`bun sst secret set ZEN_MODELS1 ${value1} --stage ${stage}`
|
||||||
await $`bun sst secret set ZEN_MODELS2 ${value2} --stage ${stage}`
|
await $`bun sst secret set ZEN_MODELS2 ${value2} --stage ${stage}`
|
||||||
|
await $`bun sst secret set ZEN_MODELS3 ${value3} --stage ${stage}`
|
||||||
|
await $`bun sst secret set ZEN_MODELS4 ${value4} --stage ${stage}`
|
||||||
|
|||||||
Executable
+31
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
import { $ } from "bun"
|
||||||
|
import path from "path"
|
||||||
|
import { ZenData } from "../src/model"
|
||||||
|
|
||||||
|
const stage = process.argv[2]
|
||||||
|
if (!stage) throw new Error("Stage is required")
|
||||||
|
|
||||||
|
const root = path.resolve(process.cwd(), "..", "..", "..")
|
||||||
|
|
||||||
|
// read the secret
|
||||||
|
const ret = await $`bun sst secret list --stage ${stage}`.cwd(root).text()
|
||||||
|
const lines = ret.split("\n")
|
||||||
|
const value1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[1]
|
||||||
|
const value2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1]
|
||||||
|
const value3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1]
|
||||||
|
const value4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1]
|
||||||
|
if (!value1) throw new Error("ZEN_MODELS1 not found")
|
||||||
|
if (!value2) throw new Error("ZEN_MODELS2 not found")
|
||||||
|
if (!value3) throw new Error("ZEN_MODELS3 not found")
|
||||||
|
if (!value4) throw new Error("ZEN_MODELS4 not found")
|
||||||
|
|
||||||
|
// validate value
|
||||||
|
ZenData.validate(JSON.parse(value1 + value2 + value3 + value4))
|
||||||
|
|
||||||
|
// update the secret
|
||||||
|
await $`bun sst secret set ZEN_MODELS1 ${value1}`
|
||||||
|
await $`bun sst secret set ZEN_MODELS2 ${value2}`
|
||||||
|
await $`bun sst secret set ZEN_MODELS3 ${value3}`
|
||||||
|
await $`bun sst secret set ZEN_MODELS4 ${value4}`
|
||||||
@@ -9,21 +9,20 @@ const root = path.resolve(process.cwd(), "..", "..", "..")
|
|||||||
const models = await $`bun sst secret list`.cwd(root).text()
|
const models = await $`bun sst secret list`.cwd(root).text()
|
||||||
|
|
||||||
// read the line starting with "ZEN_MODELS"
|
// read the line starting with "ZEN_MODELS"
|
||||||
const oldValue1 = models
|
const lines = models.split("\n")
|
||||||
.split("\n")
|
const oldValue1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[1]
|
||||||
.find((line) => line.startsWith("ZEN_MODELS1"))
|
const oldValue2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1]
|
||||||
?.split("=")[1]
|
const oldValue3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1]
|
||||||
const oldValue2 = models
|
const oldValue4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1]
|
||||||
.split("\n")
|
|
||||||
.find((line) => line.startsWith("ZEN_MODELS2"))
|
|
||||||
?.split("=")[1]
|
|
||||||
if (!oldValue1) throw new Error("ZEN_MODELS1 not found")
|
if (!oldValue1) throw new Error("ZEN_MODELS1 not found")
|
||||||
if (!oldValue2) throw new Error("ZEN_MODELS2 not found")
|
if (!oldValue2) throw new Error("ZEN_MODELS2 not found")
|
||||||
|
if (!oldValue3) throw new Error("ZEN_MODELS3 not found")
|
||||||
|
if (!oldValue4) throw new Error("ZEN_MODELS4 not found")
|
||||||
|
|
||||||
// store the prettified json to a temp file
|
// store the prettified json to a temp file
|
||||||
const filename = `models-${Date.now()}.json`
|
const filename = `models-${Date.now()}.json`
|
||||||
const tempFile = Bun.file(path.join(os.tmpdir(), filename))
|
const tempFile = Bun.file(path.join(os.tmpdir(), filename))
|
||||||
await tempFile.write(JSON.stringify(JSON.parse(oldValue1 + oldValue2), null, 2))
|
await tempFile.write(JSON.stringify(JSON.parse(oldValue1 + oldValue2 + oldValue3 + oldValue4), null, 2))
|
||||||
console.log("tempFile", tempFile.name)
|
console.log("tempFile", tempFile.name)
|
||||||
|
|
||||||
// open temp file in vim and read the file on close
|
// open temp file in vim and read the file on close
|
||||||
@@ -32,6 +31,12 @@ const newValue = JSON.stringify(JSON.parse(await tempFile.text()))
|
|||||||
ZenData.validate(JSON.parse(newValue))
|
ZenData.validate(JSON.parse(newValue))
|
||||||
|
|
||||||
// update the secret
|
// update the secret
|
||||||
const mid = Math.floor(newValue.length / 2)
|
const chunk = Math.ceil(newValue.length / 4)
|
||||||
await $`bun sst secret set ZEN_MODELS1 ${newValue.slice(0, mid)}`
|
const newValue1 = newValue.slice(0, chunk)
|
||||||
await $`bun sst secret set ZEN_MODELS2 ${newValue.slice(mid)}`
|
const newValue2 = newValue.slice(chunk, chunk * 2)
|
||||||
|
const newValue3 = newValue.slice(chunk * 2, chunk * 3)
|
||||||
|
const newValue4 = newValue.slice(chunk * 3)
|
||||||
|
await $`bun sst secret set ZEN_MODELS1 ${newValue1}`
|
||||||
|
await $`bun sst secret set ZEN_MODELS2 ${newValue2}`
|
||||||
|
await $`bun sst secret set ZEN_MODELS3 ${newValue3}`
|
||||||
|
await $`bun sst secret set ZEN_MODELS4 ${newValue4}`
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export namespace Account {
|
|||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
async (input) =>
|
async (input) =>
|
||||||
Database.transaction(async (tx) => {
|
Database.use(async (tx) => {
|
||||||
const id = input.id ?? Identifier.create("account")
|
const id = input.id ?? Identifier.create("account")
|
||||||
await tx.insert(AccountTable).values({
|
await tx.insert(AccountTable).values({
|
||||||
id,
|
id,
|
||||||
@@ -21,13 +21,12 @@ export namespace Account {
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const fromID = fn(z.string(), async (id) =>
|
export const fromID = fn(z.string(), async (id) =>
|
||||||
Database.transaction(async (tx) => {
|
Database.use((tx) =>
|
||||||
return tx
|
tx
|
||||||
.select()
|
.select()
|
||||||
.from(AccountTable)
|
.from(AccountTable)
|
||||||
.where(eq(AccountTable.id, id))
|
.where(eq(AccountTable.id, id))
|
||||||
.execute()
|
.then((rows) => rows[0]),
|
||||||
.then((rows) => rows[0])
|
),
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export namespace AWS {
|
|||||||
to: z.string(),
|
to: z.string(),
|
||||||
subject: z.string(),
|
subject: z.string(),
|
||||||
body: z.string(),
|
body: z.string(),
|
||||||
|
replyTo: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
async (input) => {
|
async (input) => {
|
||||||
const res = await createClient().fetch("https://email.us-east-1.amazonaws.com/v2/email/outbound-emails", {
|
const res = await createClient().fetch("https://email.us-east-1.amazonaws.com/v2/email/outbound-emails", {
|
||||||
@@ -35,6 +36,7 @@ export namespace AWS {
|
|||||||
Destination: {
|
Destination: {
|
||||||
ToAddresses: [input.to],
|
ToAddresses: [input.to],
|
||||||
},
|
},
|
||||||
|
...(input.replyTo && { ReplyToAddresses: [input.replyTo] }),
|
||||||
Content: {
|
Content: {
|
||||||
Simple: {
|
Simple: {
|
||||||
Subject: {
|
Subject: {
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ export namespace ZenData {
|
|||||||
cost: ModelCostSchema,
|
cost: ModelCostSchema,
|
||||||
cost200K: ModelCostSchema.optional(),
|
cost200K: ModelCostSchema.optional(),
|
||||||
allowAnonymous: z.boolean().optional(),
|
allowAnonymous: z.boolean().optional(),
|
||||||
|
trial: z
|
||||||
|
.object({
|
||||||
|
limit: z.number(),
|
||||||
|
provider: z.string(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
rateLimit: z.number().optional(),
|
rateLimit: z.number().optional(),
|
||||||
fallbackProvider: z.string().optional(),
|
fallbackProvider: z.string().optional(),
|
||||||
providers: z.array(
|
providers: z.array(
|
||||||
@@ -32,6 +38,7 @@ export namespace ZenData {
|
|||||||
model: z.string(),
|
model: z.string(),
|
||||||
weight: z.number().optional(),
|
weight: z.number().optional(),
|
||||||
disabled: z.boolean().optional(),
|
disabled: z.boolean().optional(),
|
||||||
|
storeModel: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
@@ -53,7 +60,9 @@ export namespace ZenData {
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const list = fn(z.void(), () => {
|
export const list = fn(z.void(), () => {
|
||||||
const json = JSON.parse(Resource.ZEN_MODELS1.value + Resource.ZEN_MODELS2.value)
|
const json = JSON.parse(
|
||||||
|
Resource.ZEN_MODELS1.value + Resource.ZEN_MODELS2.value + Resource.ZEN_MODELS3.value + Resource.ZEN_MODELS4.value,
|
||||||
|
)
|
||||||
return ModelsSchema.parse(json)
|
return ModelsSchema.parse(json)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export namespace Provider {
|
|||||||
}),
|
}),
|
||||||
async ({ provider }) => {
|
async ({ provider }) => {
|
||||||
Actor.assertAdmin()
|
Actor.assertAdmin()
|
||||||
return Database.transaction((tx) =>
|
return Database.use((tx) =>
|
||||||
tx
|
tx
|
||||||
.delete(ProviderTable)
|
.delete(ProviderTable)
|
||||||
.where(and(eq(ProviderTable.provider, provider), eq(ProviderTable.workspaceID, Actor.workspace()))),
|
.where(and(eq(ProviderTable.provider, provider), eq(ProviderTable.workspaceID, Actor.workspace()))),
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { mysqlTable, int, primaryKey, varchar } from "drizzle-orm/mysql-core"
|
||||||
|
import { timestamps } from "../drizzle/types"
|
||||||
|
|
||||||
|
export const IpTable = mysqlTable(
|
||||||
|
"ip",
|
||||||
|
{
|
||||||
|
ip: varchar("ip", { length: 45 }).notNull(),
|
||||||
|
...timestamps,
|
||||||
|
usage: int("usage"),
|
||||||
|
},
|
||||||
|
(table) => [primaryKey({ columns: [table.ip] })],
|
||||||
|
)
|
||||||
Vendored
+96
-74
@@ -6,106 +6,128 @@
|
|||||||
import "sst"
|
import "sst"
|
||||||
declare module "sst" {
|
declare module "sst" {
|
||||||
export interface Resource {
|
export interface Resource {
|
||||||
ADMIN_SECRET: {
|
"ADMIN_SECRET": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
AUTH_API_URL: {
|
"AUTH_API_URL": {
|
||||||
type: "sst.sst.Linkable"
|
"type": "sst.sst.Linkable"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
AWS_SES_ACCESS_KEY_ID: {
|
"AWS_SES_ACCESS_KEY_ID": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
AWS_SES_SECRET_ACCESS_KEY: {
|
"AWS_SES_SECRET_ACCESS_KEY": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
CLOUDFLARE_API_TOKEN: {
|
"CLOUDFLARE_API_TOKEN": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
|
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
Console: {
|
"Console": {
|
||||||
type: "sst.cloudflare.SolidStart"
|
"type": "sst.cloudflare.SolidStart"
|
||||||
url: string
|
"url": string
|
||||||
}
|
}
|
||||||
Database: {
|
"Database": {
|
||||||
database: string
|
"database": string
|
||||||
host: string
|
"host": string
|
||||||
password: string
|
"password": string
|
||||||
port: number
|
"port": number
|
||||||
type: "sst.sst.Linkable"
|
"type": "sst.sst.Linkable"
|
||||||
username: string
|
"username": string
|
||||||
}
|
}
|
||||||
Desktop: {
|
"Desktop": {
|
||||||
type: "sst.cloudflare.StaticSite"
|
"type": "sst.cloudflare.StaticSite"
|
||||||
url: string
|
"url": string
|
||||||
}
|
}
|
||||||
EMAILOCTOPUS_API_KEY: {
|
"EMAILOCTOPUS_API_KEY": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
GITHUB_APP_ID: {
|
"Enterprise": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.cloudflare.SolidStart"
|
||||||
value: string
|
"url": string
|
||||||
}
|
}
|
||||||
GITHUB_APP_PRIVATE_KEY: {
|
"GITHUB_APP_ID": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
GITHUB_CLIENT_ID_CONSOLE: {
|
"GITHUB_APP_PRIVATE_KEY": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
GITHUB_CLIENT_SECRET_CONSOLE: {
|
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
GOOGLE_CLIENT_ID: {
|
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
HONEYCOMB_API_KEY: {
|
"GOOGLE_CLIENT_ID": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
STRIPE_SECRET_KEY: {
|
"HONEYCOMB_API_KEY": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
STRIPE_WEBHOOK_SECRET: {
|
"R2AccessKey": {
|
||||||
type: "sst.sst.Linkable"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
Web: {
|
"R2SecretKey": {
|
||||||
type: "sst.cloudflare.Astro"
|
"type": "sst.sst.Secret"
|
||||||
url: string
|
"value": string
|
||||||
}
|
}
|
||||||
ZEN_MODELS1: {
|
"STRIPE_SECRET_KEY": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
ZEN_MODELS2: {
|
"STRIPE_WEBHOOK_SECRET": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Linkable"
|
||||||
value: string
|
"value": string
|
||||||
|
}
|
||||||
|
"Web": {
|
||||||
|
"type": "sst.cloudflare.Astro"
|
||||||
|
"url": string
|
||||||
|
}
|
||||||
|
"ZEN_MODELS1": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"ZEN_MODELS2": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"ZEN_MODELS3": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"ZEN_MODELS4": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// cloudflare
|
// cloudflare
|
||||||
import * as cloudflare from "@cloudflare/workers-types"
|
import * as cloudflare from "@cloudflare/workers-types";
|
||||||
declare module "sst" {
|
declare module "sst" {
|
||||||
export interface Resource {
|
export interface Resource {
|
||||||
Api: cloudflare.Service
|
"Api": cloudflare.Service
|
||||||
AuthApi: cloudflare.Service
|
"AuthApi": cloudflare.Service
|
||||||
AuthStorage: cloudflare.KVNamespace
|
"AuthStorage": cloudflare.KVNamespace
|
||||||
Bucket: cloudflare.R2Bucket
|
"Bucket": cloudflare.R2Bucket
|
||||||
GatewayKv: cloudflare.KVNamespace
|
"EnterpriseStorage": cloudflare.R2Bucket
|
||||||
LogProcessor: cloudflare.Service
|
"GatewayKv": cloudflare.KVNamespace
|
||||||
|
"LogProcessor": cloudflare.Service
|
||||||
|
"ZenData": cloudflare.R2Bucket
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.0.85",
|
"version": "1.0.130",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export default {
|
|||||||
// Get workspace
|
// Get workspace
|
||||||
await Actor.provide("account", { accountID, email }, async () => {
|
await Actor.provide("account", { accountID, email }, async () => {
|
||||||
await User.joinInvitedWorkspaces()
|
await User.joinInvitedWorkspaces()
|
||||||
const workspaces = await Database.transaction(async (tx) =>
|
const workspaces = await Database.use((tx) =>
|
||||||
tx
|
tx
|
||||||
.select({ id: WorkspaceTable.id })
|
.select({ id: WorkspaceTable.id })
|
||||||
.from(WorkspaceTable)
|
.from(WorkspaceTable)
|
||||||
|
|||||||
+96
-74
@@ -6,106 +6,128 @@
|
|||||||
import "sst"
|
import "sst"
|
||||||
declare module "sst" {
|
declare module "sst" {
|
||||||
export interface Resource {
|
export interface Resource {
|
||||||
ADMIN_SECRET: {
|
"ADMIN_SECRET": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
AUTH_API_URL: {
|
"AUTH_API_URL": {
|
||||||
type: "sst.sst.Linkable"
|
"type": "sst.sst.Linkable"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
AWS_SES_ACCESS_KEY_ID: {
|
"AWS_SES_ACCESS_KEY_ID": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
AWS_SES_SECRET_ACCESS_KEY: {
|
"AWS_SES_SECRET_ACCESS_KEY": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
CLOUDFLARE_API_TOKEN: {
|
"CLOUDFLARE_API_TOKEN": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
|
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
Console: {
|
"Console": {
|
||||||
type: "sst.cloudflare.SolidStart"
|
"type": "sst.cloudflare.SolidStart"
|
||||||
url: string
|
"url": string
|
||||||
}
|
}
|
||||||
Database: {
|
"Database": {
|
||||||
database: string
|
"database": string
|
||||||
host: string
|
"host": string
|
||||||
password: string
|
"password": string
|
||||||
port: number
|
"port": number
|
||||||
type: "sst.sst.Linkable"
|
"type": "sst.sst.Linkable"
|
||||||
username: string
|
"username": string
|
||||||
}
|
}
|
||||||
Desktop: {
|
"Desktop": {
|
||||||
type: "sst.cloudflare.StaticSite"
|
"type": "sst.cloudflare.StaticSite"
|
||||||
url: string
|
"url": string
|
||||||
}
|
}
|
||||||
EMAILOCTOPUS_API_KEY: {
|
"EMAILOCTOPUS_API_KEY": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
GITHUB_APP_ID: {
|
"Enterprise": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.cloudflare.SolidStart"
|
||||||
value: string
|
"url": string
|
||||||
}
|
}
|
||||||
GITHUB_APP_PRIVATE_KEY: {
|
"GITHUB_APP_ID": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
GITHUB_CLIENT_ID_CONSOLE: {
|
"GITHUB_APP_PRIVATE_KEY": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
GITHUB_CLIENT_SECRET_CONSOLE: {
|
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
GOOGLE_CLIENT_ID: {
|
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
HONEYCOMB_API_KEY: {
|
"GOOGLE_CLIENT_ID": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
STRIPE_SECRET_KEY: {
|
"HONEYCOMB_API_KEY": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
STRIPE_WEBHOOK_SECRET: {
|
"R2AccessKey": {
|
||||||
type: "sst.sst.Linkable"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
Web: {
|
"R2SecretKey": {
|
||||||
type: "sst.cloudflare.Astro"
|
"type": "sst.sst.Secret"
|
||||||
url: string
|
"value": string
|
||||||
}
|
}
|
||||||
ZEN_MODELS1: {
|
"STRIPE_SECRET_KEY": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
value: string
|
"value": string
|
||||||
}
|
}
|
||||||
ZEN_MODELS2: {
|
"STRIPE_WEBHOOK_SECRET": {
|
||||||
type: "sst.sst.Secret"
|
"type": "sst.sst.Linkable"
|
||||||
value: string
|
"value": string
|
||||||
|
}
|
||||||
|
"Web": {
|
||||||
|
"type": "sst.cloudflare.Astro"
|
||||||
|
"url": string
|
||||||
|
}
|
||||||
|
"ZEN_MODELS1": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"ZEN_MODELS2": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"ZEN_MODELS3": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
|
"ZEN_MODELS4": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// cloudflare
|
// cloudflare
|
||||||
import * as cloudflare from "@cloudflare/workers-types"
|
import * as cloudflare from "@cloudflare/workers-types";
|
||||||
declare module "sst" {
|
declare module "sst" {
|
||||||
export interface Resource {
|
export interface Resource {
|
||||||
Api: cloudflare.Service
|
"Api": cloudflare.Service
|
||||||
AuthApi: cloudflare.Service
|
"AuthApi": cloudflare.Service
|
||||||
AuthStorage: cloudflare.KVNamespace
|
"AuthStorage": cloudflare.KVNamespace
|
||||||
Bucket: cloudflare.R2Bucket
|
"Bucket": cloudflare.R2Bucket
|
||||||
GatewayKv: cloudflare.KVNamespace
|
"EnterpriseStorage": cloudflare.R2Bucket
|
||||||
LogProcessor: cloudflare.Service
|
"GatewayKv": cloudflare.KVNamespace
|
||||||
|
"LogProcessor": cloudflare.Service
|
||||||
|
"ZenData": cloudflare.R2Bucket
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.0.85",
|
"version": "1.0.130",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { env } from "cloudflare:workers"
|
import { env } from "cloudflare:workers"
|
||||||
|
export { waitUntil } from "cloudflare:workers"
|
||||||
|
|
||||||
export const Resource = new Proxy(
|
export const Resource = new Proxy(
|
||||||
{},
|
{},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user