Compare commits

..
1 Commits
Author SHA1 Message Date
opencode 48e12ade76 release: v0.4.13 2025-08-11 05:51:06 +00:00
450 changed files with 16464 additions and 26180 deletions
+1 -3
View File
@@ -17,13 +17,11 @@ jobs:
- uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.21
bun-version: 1.2.17
- run: bun install
- run: bun sst deploy --stage=${{ github.ref_name }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }}
-58
View File
@@ -1,58 +0,0 @@
name: Duplicate Issue Detection
on:
issues:
types: [opened]
jobs:
check-duplicates:
runs-on: ubuntu-latest
permissions:
contents: read
issues: 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 for duplicate issues
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENCODE_PERMISSION: |
{
"bash": {
"gh issue*": "allow",
"*": "deny"
},
"webfetch": "deny"
}
run: |
opencode run -m anthropic/claude-sonnet-4-20250514 "A new issue has been created:'
Issue number:
${{ github.event.issue.number }}
Lookup this issue and search through existing issues (excluding #${{ github.event.issue.number }}) in this repository to find any potential duplicates of this new issue.
Consider:
1. Similar titles or descriptions
2. Same error messages or symptoms
3. Related functionality or components
4. Similar feature requests
If you find any potential duplicates, please comment on the new issue with:
- A brief explanation of why it might be a duplicate
- Links to the potentially duplicate issues
- A suggestion to check those issues first
Use this format for the comment:
'This issue might be a duplicate of existing issues. Please check:
- #[issue_number]: [brief description of similarity]
Feel free to ignore if none of these address your specific case.'
If no clear duplicates are found, do not comment."
-28
View File
@@ -1,28 +0,0 @@
name: Format
on:
push:
pull_request:
workflow_dispatch:
jobs:
format:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.21
- name: run
run: |
bun install
./script/format.ts
env:
CI: true
-53
View File
@@ -1,53 +0,0 @@
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."
+1 -1
View File
@@ -2,7 +2,7 @@ name: discord
on:
release:
types: [published] # fires only when a release is published
types: [published] # fires only when a release is published
jobs:
notify:
+8 -6
View File
@@ -8,20 +8,22 @@ jobs:
opencode:
if: |
contains(github.event.comment.body, ' /oc') ||
contains(github.event.comment.body, ' /opencode')
startsWith(github.event.comment.body, '/oc') ||
contains(github.event.comment.body, ' /opencode') ||
startsWith(github.event.comment.body, '/opencode')
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run opencode
uses: sst/opencode/github@latest
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
with:
model: opencode/sonic
model: anthropic/claude-sonnet-4-20250514
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.21
bun-version: 1.2.17
- run: git fetch --force --tags
- run: bun install -g @vscode/vsce
+15 -14
View File
@@ -1,17 +1,17 @@
name: publish
run-name: "${{ format('release {0}', inputs.bump) }}"
run-name: "${{ format('v{0}', inputs.version) }}"
on:
workflow_dispatch:
inputs:
bump:
description: "Bump major, minor, or patch"
version:
description: "Version to publish"
required: true
type: choice
options:
- major
- minor
- patch
type: string
title:
description: "Custom title for this run"
required: false
type: string
concurrency: ${{ github.workflow }}-${{ github.ref }}
@@ -37,37 +37,38 @@ jobs:
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.21
bun-version: 1.2.19
- name: Cache ~/.bun
id: cache-bun
uses: actions/cache@v3
with:
path: ~/.bun
key: ${{ runner.os }}-bun-1-2-21-${{ hashFiles('bun.lock') }}
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1-2-21-
${{ runner.os }}-bun-
- name: Install makepkg
run: |
sudo apt-get update
sudo apt-get install -y pacman-package-manager
- name: Setup SSH for AUR
run: |
mkdir -p ~/.ssh
echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts
git config --global user.email "opencode@sst.dev"
git config --global user.name "opencode"
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
- name: Install dependencies
run: bun install
- name: Publish
run: |
./script/publish.ts
OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
env:
OPENCODE_BUMP: ${{ inputs.bump }}
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }}
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.21
bun-version: 1.2.19
- name: Install dependencies
run: bun install
-2
View File
@@ -1,10 +1,8 @@
.DS_Store
node_modules
.worktrees
.sst
.env
.idea
.vscode
openapi.json
playground
tmp
+5 -22
View File
@@ -1,4 +1,6 @@
---
model: openai/gpt-5
reasoningEffort: medium
description: ALWAYS use this when writing docs
---
@@ -6,26 +8,7 @@ You are an expert technical documentation writer
You are not verbose
The title of the page should be a word or a 2-3 word phrase
Every chunk of text should be followed by an example or something besides text
to look at.
The description should be one short line, should not start with "The", should
avoid repeating the title of the page, should be 5-10 words long
Chunks of text should not be more than 2 sentences long
Each section is spearated by a divider of 3 dashes
The section titles are short with only the first letter of the word capitalized
The section titles are in the imperative mood
The section titles should not repeat the term used in the page title, for
example, if the page title is "Models", avoid using a section title like "Add
new models". This might be unavoidable in some cases, but try to avoid it.
Check out the /packages/web/src/content/docs/docs/index.mdx as an example.
For JS or TS code snippets remove trailing semicolons and any trailing commas
that might not be needed.
If you are making a commit prefix the commit message with `docs:`
Chunks of text should not be more than 2 sentences long.
-10
View File
@@ -1,10 +0,0 @@
---
description: Use this agent when you are asked to commit and push code changes to a git repository.
mode: subagent
---
You commit and push to git
Commit messages should be brief since they are used to generate release notes.
Messages should say WHY the change was made and not WHAT was changed.
-9
View File
@@ -1,9 +0,0 @@
commit and push
make sure it includes a prefix like
docs:
tui:
core:
ci:
ignore:
wip:
-8
View File
@@ -1,8 +0,0 @@
---
description: hello world
---
hey there $ARGUMENTS
!`ls`
check out @README.md
-4
View File
@@ -10,7 +10,3 @@
- AVOID `let` statements
- PREFER single word variable names where possible
- Use as many bun apis as possible like Bun.file()
## Debugging
- To test opencode in the `packages/opencode` directory you can run `bun dev`
+1 -1
View File
@@ -107,4 +107,4 @@ The other confusingly named repo has no relation to this one. You can [read the
---
**Join our community** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
**Join our community** [Discord](https://discord.gg/opencode) | [YouTube](https://www.youtube.com/c/sst-dev) | [X.com](https://x.com/SST_dev)
+44 -76
View File
@@ -1,78 +1,46 @@
# Download Stats
| Date | GitHub Downloads | npm Downloads | Total |
| ---------- | ---------------- | ---------------- | ----------------- |
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
| Date | GitHub Downloads | npm Downloads | Total |
| ---------- | ---------------- | ---------------- | ---------------- |
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
+642 -1139
View File
File diff suppressed because it is too large Load Diff
-28
View File
@@ -1,28 +0,0 @@
dist
.wrangler
.output
.vercel
.netlify
.vinxi
app.config.timestamp_*.js
# Environment
.env
.env*.local
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
*.launch
.settings/
# Temp
gitignore
# System Files
.DS_Store
Thumbs.db
-149
View File
@@ -1,149 +0,0 @@
---
description: use whenever you are styling a ui with css
---
you are very good at writing clean maintainable css using modern techniques
css is structured like this
```css
[data-page="home"] {
[data-component="header"] {
[data-slot="logo"] {
}
}
}
```
top level pages are scoped using `data-page`
pages can break down into components using `data-component`
components can break down into slots using `data-slot`
structure things so that this hierarchy is followed IN YOUR CSS - you should rarely need to
nest components inside other components. you should NEVER nest components inside
slots. you should NEVER nest slots inside other slots.
**IMPORTANT: This hierarchy rule applies to CSS structure, NOT JSX/DOM structure.**
The hierarchy in css file does NOT have to match the hierarchy in the dom - you
can put components or slots at the same level in CSS even if one goes inside another in the DOM.
Your JSX can nest however makes semantic sense - components can be inside slots,
slots can contain components, etc. The DOM structure should be whatever makes the most
semantic and functional sense.
It is more important to follow the pages -> components -> slots structure IN YOUR CSS,
while keeping your JSX/DOM structure logical and semantic.
use data attributes to represent different states of the component
```css
[data-component="modal"] {
opacity: 0;
&[data-state="open"] {
opacity: 1;
}
}
```
this will allow jsx to control the syling
avoid selectors that just target an element type like `> span` you should assign
it a slot name. it's ok to do this sometimes where it makes sense semantically
like targeting `li` elements in a list
in terms of file structure `./src/style/` contains all universal styling rules.
these should not contain anything specific to a page
`./src/style/token` contains all the tokens used in the project
`./src/style/component` is for reusable components like buttons or inputs
page specific styles should go next to the page they are styling so
`./src/routes/about.tsx` should have its styles in `./src/routes/about.css`
`about.css` should be scoped using `data-page="about"`
## Example of correct implementation
JSX can nest however makes sense semantically:
```jsx
<div data-slot="left">
<div data-component="title">Section Title</div>
<div data-slot="content">Content here</div>
</div>
```
CSS maintains clean hierarchy regardless of DOM nesting:
```css
[data-page="home"] {
[data-component="screenshots"] {
[data-slot="left"] {
/* styles */
}
[data-slot="content"] {
/* styles */
}
}
[data-component="title"] {
/* can be at same level even though nested in DOM */
}
}
```
## Reusable Components
If a component is reused across multiple sections of the same page, define it at the page level:
```jsx
<!-- Used in multiple places on the same page -->
<section data-component="install">
<div data-component="method">
<h3 data-component="title">npm</h3>
</div>
<div data-component="method">
<h3 data-component="title">bun</h3>
</div>
</section>
<section data-component="screenshots">
<div data-slot="left">
<div data-component="title">Screenshot Title</div>
</div>
</section>
```
```css
[data-page="home"] {
/* Reusable title component defined at page level since it's used in multiple components */
[data-component="title"] {
text-transform: uppercase;
font-weight: 400;
}
[data-component="install"] {
/* install-specific styles */
}
[data-component="screenshots"] {
/* screenshots-specific styles */
}
}
```
This is correct because the `title` component has consistent styling and behavior across the page.
## Key Clarifications
1. **JSX Nesting is Flexible**: Components can be nested inside slots, slots can contain components - whatever makes semantic sense
2. **CSS Hierarchy is Strict**: Follow pages → components → slots structure in CSS
3. **Reusable Components**: Define at the appropriate level where they're shared (page level if used across the page, component level if only used within that component)
4. **DOM vs CSS Structure**: These don't need to match - optimize each for its purpose
See ./src/routes/index.css and ./src/routes/index.tsx for a complete example.
-32
View File
@@ -1,32 +0,0 @@
# SolidStart
Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com);
## Creating a project
```bash
# create a new project in the current directory
npm init solid@latest
# create a new project in my-app
npm init solid@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
Solid apps are built with _presets_, which optimise your project for deployment to different environments.
By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`.
## This project was created with the [Solid CLI](https://github.com/solidjs-community/solid-cli)
-23
View File
@@ -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,
},
},
})
-25
View File
@@ -1,25 +0,0 @@
{
"name": "@opencode/cloud-app",
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit",
"dev": "vinxi dev --host 0.0.0.0",
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
"build": "vinxi build && ../../packages/opencode/script/schema.ts ./.output/public/config.json",
"start": "vinxi start",
"version": "0.7.4"
},
"dependencies": {
"@ibm/plex": "6.4.1",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.0",
"@solidjs/start": "^1.1.0",
"solid-js": "catalog:",
"vinxi": "^0.5.7",
"@opencode/cloud-core": "workspace:*"
},
"engines": {
"node": ">=22"
}
}
-5
View File
@@ -1,5 +0,0 @@
<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="600" height="600" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M115 180H300V420H115V180ZM253.75 229.044H161.25V370.405H253.75V229.044Z" fill="white"/>
<path d="M346.25 180H485V229.044H392.5V370.405H485V419.449H346.25V180Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 377 B

-5
View File
@@ -1,5 +0,0 @@
User-agent: *
Allow: /
# Disallow shared content pages
Disallow: /s/
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

-182
View File
@@ -1,182 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "JSON schema reference for configuration validation"
},
"defs": {
"type": "object",
"description": "Color definitions that can be referenced in the theme",
"patternProperties": {
"^[a-zA-Z][a-zA-Z0-9_]*$": {
"oneOf": [
{
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"description": "Hex color value"
},
{
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "ANSI color code (0-255)"
},
{
"type": "string",
"enum": ["none"],
"description": "No color (uses terminal default)"
}
]
}
},
"additionalProperties": false
},
"theme": {
"type": "object",
"description": "Theme color definitions",
"properties": {
"primary": { "$ref": "#/definitions/colorValue" },
"secondary": { "$ref": "#/definitions/colorValue" },
"accent": { "$ref": "#/definitions/colorValue" },
"error": { "$ref": "#/definitions/colorValue" },
"warning": { "$ref": "#/definitions/colorValue" },
"success": { "$ref": "#/definitions/colorValue" },
"info": { "$ref": "#/definitions/colorValue" },
"text": { "$ref": "#/definitions/colorValue" },
"textMuted": { "$ref": "#/definitions/colorValue" },
"background": { "$ref": "#/definitions/colorValue" },
"backgroundPanel": { "$ref": "#/definitions/colorValue" },
"backgroundElement": { "$ref": "#/definitions/colorValue" },
"border": { "$ref": "#/definitions/colorValue" },
"borderActive": { "$ref": "#/definitions/colorValue" },
"borderSubtle": { "$ref": "#/definitions/colorValue" },
"diffAdded": { "$ref": "#/definitions/colorValue" },
"diffRemoved": { "$ref": "#/definitions/colorValue" },
"diffContext": { "$ref": "#/definitions/colorValue" },
"diffHunkHeader": { "$ref": "#/definitions/colorValue" },
"diffHighlightAdded": { "$ref": "#/definitions/colorValue" },
"diffHighlightRemoved": { "$ref": "#/definitions/colorValue" },
"diffAddedBg": { "$ref": "#/definitions/colorValue" },
"diffRemovedBg": { "$ref": "#/definitions/colorValue" },
"diffContextBg": { "$ref": "#/definitions/colorValue" },
"diffLineNumber": { "$ref": "#/definitions/colorValue" },
"diffAddedLineNumberBg": { "$ref": "#/definitions/colorValue" },
"diffRemovedLineNumberBg": { "$ref": "#/definitions/colorValue" },
"markdownText": { "$ref": "#/definitions/colorValue" },
"markdownHeading": { "$ref": "#/definitions/colorValue" },
"markdownLink": { "$ref": "#/definitions/colorValue" },
"markdownLinkText": { "$ref": "#/definitions/colorValue" },
"markdownCode": { "$ref": "#/definitions/colorValue" },
"markdownBlockQuote": { "$ref": "#/definitions/colorValue" },
"markdownEmph": { "$ref": "#/definitions/colorValue" },
"markdownStrong": { "$ref": "#/definitions/colorValue" },
"markdownHorizontalRule": { "$ref": "#/definitions/colorValue" },
"markdownListItem": { "$ref": "#/definitions/colorValue" },
"markdownListEnumeration": { "$ref": "#/definitions/colorValue" },
"markdownImage": { "$ref": "#/definitions/colorValue" },
"markdownImageText": { "$ref": "#/definitions/colorValue" },
"markdownCodeBlock": { "$ref": "#/definitions/colorValue" },
"syntaxComment": { "$ref": "#/definitions/colorValue" },
"syntaxKeyword": { "$ref": "#/definitions/colorValue" },
"syntaxFunction": { "$ref": "#/definitions/colorValue" },
"syntaxVariable": { "$ref": "#/definitions/colorValue" },
"syntaxString": { "$ref": "#/definitions/colorValue" },
"syntaxNumber": { "$ref": "#/definitions/colorValue" },
"syntaxType": { "$ref": "#/definitions/colorValue" },
"syntaxOperator": { "$ref": "#/definitions/colorValue" },
"syntaxPunctuation": { "$ref": "#/definitions/colorValue" }
},
"required": ["primary", "secondary", "accent", "text", "textMuted", "background"],
"additionalProperties": false
}
},
"required": ["theme"],
"additionalProperties": false,
"definitions": {
"colorValue": {
"oneOf": [
{
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"description": "Hex color value (same for dark and light)"
},
{
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "ANSI color code (0-255, same for dark and light)"
},
{
"type": "string",
"enum": ["none"],
"description": "No color (uses terminal default)"
},
{
"type": "string",
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
"description": "Reference to another color in the theme or defs"
},
{
"type": "object",
"properties": {
"dark": {
"oneOf": [
{
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"description": "Hex color value for dark mode"
},
{
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "ANSI color code for dark mode"
},
{
"type": "string",
"enum": ["none"],
"description": "No color (uses terminal default)"
},
{
"type": "string",
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
"description": "Reference to another color for dark mode"
}
]
},
"light": {
"oneOf": [
{
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"description": "Hex color value for light mode"
},
{
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "ANSI color code for light mode"
},
{
"type": "string",
"enum": ["none"],
"description": "No color (uses terminal default)"
},
{
"type": "string",
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
"description": "Reference to another color for light mode"
}
]
}
},
"required": ["dark", "light"],
"additionalProperties": false,
"description": "Separate colors for dark and light modes"
}
]
}
}
}
-1
View File
@@ -1 +0,0 @@
@import "./style/index.css";
-23
View File
@@ -1,23 +0,0 @@
import { MetaProvider, Title, Meta } from "@solidjs/meta"
import { Router } from "@solidjs/router"
import { FileRoutes } from "@solidjs/start/router"
import { ErrorBoundary, Suspense } from "solid-js"
import "@ibm/plex/css/ibm-plex.css"
import "./app.css"
export default function App() {
return (
<Router
explicitLinks={true}
root={(props) => (
<MetaProvider>
<Title>opencode</Title>
<Meta name="description" content="opencode - The AI coding agent built for the terminal." />
<Suspense>{props.children}</Suspense>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
)
}
-2
View File
@@ -1,2 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"/></svg>

Before

Width:  |  Height:  |  Size: 212 B

-2
View File
@@ -1,2 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512"><rect width="336" height="336" x="128" y="128" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" rx="57" ry="57"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"/></svg>

Before

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 902 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 998 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 592 KiB

-19
View File
@@ -1,19 +0,0 @@
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.5 16.5H24.5V33H8.5V16.5Z" fill="white" fill-opacity="0.2"/>
<path d="M48.5 16.5H64.5V33H48.5V16.5Z" fill="white" fill-opacity="0.2"/>
<path d="M120.5 16.5H136.5V33H120.5V16.5Z" fill="white" fill-opacity="0.2"/>
<path d="M160.5 16.5H176.5V33H160.5V16.5Z" fill="white" fill-opacity="0.2"/>
<path d="M192.5 16.5H208.5V33H192.5V16.5Z" fill="white" fill-opacity="0.2"/>
<path d="M232.5 16.5H248.5V33H232.5V16.5Z" fill="white" fill-opacity="0.2"/>
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="white" fill-opacity="0.95"/>
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="white" fill-opacity="0.95"/>
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="white" fill-opacity="0.95"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="white" fill-opacity="0.95"/>
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="white" fill-opacity="0.5"/>
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="white" fill-opacity="0.5"/>
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="white" fill-opacity="0.5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="white" fill-opacity="0.5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="white" fill-opacity="0.5"/>
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="white" fill-opacity="0.95"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

-18
View File
@@ -1,18 +0,0 @@
<svg width="288" height="50" viewBox="0 0 288 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 16.5H24V33H8V16.5Z" fill="black" fill-opacity="0.15"/>
<path d="M48 16.5H64V33H48V16.5Z" fill="black" fill-opacity="0.15"/>
<path d="M120 16.5H136V33H120V16.5Z" fill="black" fill-opacity="0.15"/>
<path d="M160 16.5H176V33H160V16.5Z" fill="black" fill-opacity="0.15"/>
<path d="M192 16.5H208V33H192V16.5Z" fill="black" fill-opacity="0.15"/>
<path d="M232 16.5H248V33H232V16.5Z" fill="black" fill-opacity="0.15"/>
<path d="M264 0H288V8.5H272V16.5H288V25H272V33H288V41.5H264V0Z" fill="black" fill-opacity="0.95"/>
<path d="M248 0H224V41.5H248V33H232V8.5H248V0Z" fill="black" fill-opacity="0.95"/>
<path d="M256 8.5H248V33H256V8.5Z" fill="black" fill-opacity="0.95"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M184 0H216V41.5H184V0ZM208 8.5H192V33H208V8.5Z" fill="black" fill-opacity="0.95"/>
<path d="M144 8.5H136V41.5H144V8.5Z" fill="black" fill-opacity="0.55"/>
<path d="M136 0H112V41.5H120V8.5H136V0Z" fill="black" fill-opacity="0.55"/>
<path d="M80 0H104V8.5H88V16.5H104V25H88V33H104V41.5H80V0Z" fill="black" fill-opacity="0.55"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M40 0H72V41.5H48V49.5H40V0ZM64 8.5H48V33H64V8.5Z" fill="black" fill-opacity="0.55"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="black" fill-opacity="0.55"/>
<path d="M152 0H176V8.5H160V33H176V41.5H152V0Z" fill="black" fill-opacity="0.95"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-12
View File
@@ -1,12 +0,0 @@
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="black"/>
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="black"/>
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="black"/>
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="black"/>
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="black"/>
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="black"/>
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 981 B

-71
View File
@@ -1,71 +0,0 @@
import { JSX } from "solid-js"
export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="currentColor" />
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="currentColor" />
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="currentColor" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z"
fill="currentColor"
/>
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="currentColor" />
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="currentColor" />
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="currentColor" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z"
fill="currentColor"
/>
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="currentColor" />
</svg>
)
}
export function IconCopy(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 512 512">
<rect
width="336"
height="336"
x="128"
y="128"
fill="none"
stroke="currentColor"
stroke-linejoin="round"
stroke-width="32"
rx="57"
ry="57"
></rect>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="32"
d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"
></path>
</svg>
)
}
export function IconCheck(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24">
<path
fill="currentColor"
d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"
></path>
</svg>
)
}
-23
View File
@@ -1,23 +0,0 @@
import { useSession } from "vinxi/http"
export interface AuthSession {
account?: Record<
string,
{
id: string
email: string
}
>
current?: string
}
export function useAuthSession() {
return useSession<AuthSession>({
password: "0".repeat(32),
name: "auth",
cookie: {
secure: false,
httpOnly: true,
},
})
}
-83
View File
@@ -1,83 +0,0 @@
import { getRequestEvent } from "solid-js/web"
import { and, Database, eq, inArray } from "@opencode/cloud-core/drizzle/index.js"
import { WorkspaceTable } from "@opencode/cloud-core/schema/workspace.sql.js"
import { UserTable } from "@opencode/cloud-core/schema/user.sql.js"
import { redirect } from "@solidjs/router"
import { AccountTable } from "@opencode/cloud-core/schema/account.sql.js"
import { Actor } from "@opencode/cloud-core/actor.js"
import { createClient } from "@openauthjs/openauth/client"
import { useAuthSession } from "./auth.session"
export const AuthClient = createClient({
clientID: "app",
issuer: import.meta.env.VITE_AUTH_URL,
})
export const getActor = async (workspace?: string): Promise<Actor.Info> => {
"use server"
const evt = getRequestEvent()
if (!evt) throw new Error("No request event")
if (evt.locals.actor) return evt.locals.actor
evt.locals.actor = (async () => {
const auth = await useAuthSession()
if (!workspace) {
const account = auth.data.account ?? {}
const current = account[auth.data.current ?? ""]
if (current) {
return {
type: "account",
properties: {
email: current.email,
accountID: current.id,
},
}
}
if (Object.keys(account).length > 0) {
const current = Object.values(account)[0]
await auth.update((val) => ({
...val,
current: current.id,
}))
return {
type: "account",
properties: {
email: current.email,
accountID: current.id,
},
}
}
return {
type: "public",
properties: {},
}
}
const accounts = Object.keys(auth.data.account ?? {})
if (accounts.length) {
const result = await Database.transaction(async (tx) => {
return await tx
.select({
user: UserTable,
})
.from(AccountTable)
.innerJoin(UserTable, and(eq(UserTable.email, AccountTable.email)))
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
.where(and(inArray(AccountTable.id, accounts), eq(WorkspaceTable.id, workspace)))
.limit(1)
.execute()
.then((x) => x[0])
})
if (result) {
return {
type: "user",
properties: {
userID: result.user.id,
workspaceID: result.user.workspaceID,
},
}
}
}
throw redirect("/auth/authorize")
})()
return evt.locals.actor
}
-7
View File
@@ -1,7 +0,0 @@
import { Actor } from "@opencode/cloud-core/actor.js"
import { getActor } from "./auth"
export async function withActor<T>(fn: () => T, workspace?: string) {
const actor = await getActor(workspace)
return Actor.provide(actor.type, actor.properties, fn)
}
-4
View File
@@ -1,4 +0,0 @@
// @refresh reload
import { mount, StartClient } from "@solidjs/start/client"
mount(() => <StartClient />, document.getElementById("app")!)
-28
View File
@@ -1,28 +0,0 @@
// @refresh reload
import { createHandler, StartServer } from "@solidjs/start/server"
export default createHandler(
() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.svg" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
),
{
mode: "async",
},
)
-1
View File
@@ -1 +0,0 @@
/// <reference types="@solidjs/start/env" />
-5
View File
@@ -1,5 +0,0 @@
import { defineMiddleware } from "vinxi/http"
export default defineMiddleware({
onBeforeResponse() {},
})
-130
View File
@@ -1,130 +0,0 @@
[data-page="not-found"] {
--color-text: hsl(224, 10%, 10%);
--color-text-secondary: hsl(224, 7%, 46%);
--color-text-dimmed: hsl(224, 6%, 63%);
--color-text-inverted: hsl(0, 0%, 100%);
--color-border: hsl(224, 6%, 77%);
}
[data-page="not-found"] {
@media (prefers-color-scheme: dark) {
--color-text: hsl(0, 0%, 100%);
--color-text-secondary: hsl(224, 6%, 66%);
--color-text-dimmed: hsl(224, 7%, 46%);
--color-text-inverted: hsl(224, 10%, 10%);
--color-border: hsl(224, 6%, 36%);
}
}
[data-page="not-found"] {
--padding: 3rem;
--vertical-padding: 1.5rem;
--heading-font-size: 1.375rem;
@media (max-width: 30rem) {
--padding: 1rem;
--vertical-padding: 0.75rem;
--heading-font-size: 1rem;
}
font-family: var(--font-mono);
color: var(--color-text);
padding: calc(var(--padding) + 1rem);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
a {
color: var(--color-text);
text-decoration: underline;
text-underline-offset: var(--space-0-75);
text-decoration-thickness: 1px;
}
[data-component="content"] {
max-width: 40rem;
width: 100%;
border: 1px solid var(--color-border);
}
[data-component="top"] {
padding: var(--padding);
display: flex;
flex-direction: column;
align-items: center;
gap: calc(var(--vertical-padding) / 2);
text-align: center;
[data-slot="logo-link"] {
text-decoration: none;
}
img {
height: auto;
width: clamp(200px, 85vw, 400px);
}
[data-slot="logo dark"] {
display: none;
}
@media (prefers-color-scheme: dark) {
[data-slot="logo light"] {
display: none;
}
[data-slot="logo dark"] {
display: block;
}
}
[data-slot="title"] {
line-height: 1.25;
font-weight: 500;
text-align: center;
font-size: var(--heading-font-size);
color: var(--color-text);
text-transform: uppercase;
margin: 0;
}
}
[data-component="actions"] {
border-top: 1px solid var(--color-border);
display: flex;
[data-slot="action"] {
flex: 1;
text-align: center;
line-height: 1.4;
padding: var(--vertical-padding) 1rem;
text-transform: uppercase;
font-size: 1rem;
a {
display: block;
width: 100%;
height: 100%;
color: var(--color-text);
text-decoration: underline;
text-underline-offset: var(--space-0-75);
text-decoration-thickness: 1px;
}
}
[data-slot="action"] + [data-slot="action"] {
border-left: 1px solid var(--color-border);
}
@media (max-width: 40rem) {
flex-direction: column;
[data-slot="action"] + [data-slot="action"] {
border-left: none;
border-top: 1px solid var(--color-border);
}
}
}
}
-38
View File
@@ -1,38 +0,0 @@
import "./[...404].css"
import { Title } from "@solidjs/meta"
import { HttpStatusCode } from "@solidjs/start"
import logoLight from "../asset/logo-ornate-light.svg"
import logoDark from "../asset/logo-ornate-dark.svg"
export default function NotFound() {
return (
<main data-page="not-found">
<Title>Not Found | opencode</Title>
<HttpStatusCode code={404} />
<div data-component="content">
<section data-component="top">
<a href="/" data-slot="logo-link">
<img data-slot="logo light" src={logoLight} alt="opencode logo light" />
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
</a>
<h1 data-slot="title">404 - Page Not Found</h1>
</section>
<section data-component="actions">
<div data-slot="action">
<a href="/">Home</a>
</div>
<div data-slot="action">
<a href="/docs">Docs</a>
</div>
<div data-slot="action">
<a href="https://github.com/sst/opencode">GitHub</a>
</div>
<div data-slot="action">
<a href="/discord">Discord</a>
</div>
</section>
</div>
</main>
)
}
-7
View File
@@ -1,7 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { AuthClient } from "~/context/auth"
export async function GET(input: APIEvent) {
const result = await AuthClient.authorize(new URL("./callback", input.request.url).toString(), "code")
return Response.redirect(result.url, 302)
}
-31
View File
@@ -1,31 +0,0 @@
import { redirect } from "@solidjs/router"
import type { APIEvent } from "@solidjs/start/server"
import { AuthClient } from "~/context/auth"
import { useAuthSession } from "~/context/auth.session"
export async function GET(input: APIEvent) {
const url = new URL(input.request.url)
const code = url.searchParams.get("code")
if (!code) throw new Error("No code found")
const result = await AuthClient.exchange(code, `${url.origin}${url.pathname}`)
if (result.err) {
throw new Error(result.err.message)
}
const decoded = AuthClient.decode(result.tokens.access, {} as any)
if (decoded.err) throw new Error(decoded.err.message)
const session = await useAuthSession()
const id = decoded.subject.properties.accountID
await session.update((value) => {
return {
...value,
account: {
[id]: {
id,
email: decoded.subject.properties.email,
},
},
current: id,
}
})
return redirect("/auth")
}
-13
View File
@@ -1,13 +0,0 @@
import { Account } from "@opencode/cloud-core/account.js"
import { redirect } from "@solidjs/router"
import type { APIEvent } from "@solidjs/start/server"
import { withActor } from "~/context/auth.withActor"
export async function GET(input: APIEvent) {
try {
const workspaces = await withActor(async () => Account.workspaces())
return redirect(`/workspace/${workspaces[0].id}`)
} catch {
return redirect("/auth/authorize")
}
}
-13
View File
@@ -1,13 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { json } from "@solidjs/router"
import { Database } from "@opencode/cloud-core/drizzle/index.js"
import { UserTable } from "@opencode/cloud-core/schema/user.sql.js"
export async function GET(evt: APIEvent) {
return json({
data: await Database.use(async (tx) => {
const result = await tx.$count(UserTable)
return result
}),
})
}
-5
View File
@@ -1,5 +0,0 @@
import { redirect } from "@solidjs/router"
export async function GET() {
return redirect("https://discord.gg/opencode")
}
-20
View File
@@ -1,20 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
async function handler(evt: APIEvent) {
const req = evt.request.clone()
const url = new URL(req.url)
const targetUrl = `https://docs.opencode.ai${url.pathname}${url.search}`
const response = await fetch(targetUrl, {
method: req.method,
headers: req.headers,
body: req.body,
})
return response
}
export const GET = handler
export const POST = handler
export const PUT = handler
export const DELETE = handler
export const OPTIONS = handler
export const PATCH = handler
-20
View File
@@ -1,20 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
async function handler(evt: APIEvent) {
const req = evt.request.clone()
const url = new URL(req.url)
const targetUrl = `https://docs.opencode.ai${url.pathname}${url.search}`
const response = await fetch(targetUrl, {
method: req.method,
headers: req.headers,
body: req.body,
})
return response
}
export const GET = handler
export const POST = handler
export const PUT = handler
export const DELETE = handler
export const OPTIONS = handler
export const PATCH = handler
-504
View File
@@ -1,504 +0,0 @@
[data-page="home"] {
--color-text: hsl(224, 10%, 10%);
--color-text-secondary: hsl(224, 7%, 46%);
--color-text-dimmed: hsl(224, 6%, 63%);
--color-text-inverted: hsl(0, 0%, 100%);
--color-border: hsl(224, 6%, 77%);
}
[data-page="home"] {
@media (prefers-color-scheme: dark) {
--color-text: hsl(0, 0%, 100%);
--color-text-secondary: hsl(224, 6%, 66%);
--color-text-dimmed: hsl(224, 7%, 46%);
--color-text-inverted: hsl(224, 10%, 10%);
--color-border: hsl(224, 6%, 36%);
}
}
[data-page="home"] {
--padding: 3rem;
--vertical-padding: 1.5rem;
--heading-font-size: 1.375rem;
@media (max-width: 30rem) {
--padding: 1rem;
--vertical-padding: 0.75rem;
--heading-font-size: 1rem;
}
display: flex;
gap: var(--vertical-padding);
flex-direction: column;
font-family: var(--font-mono);
color: var(--color-text);
padding: calc(var(--padding) + 1rem);
a {
color: var(--color-text);
text-decoration: underline;
text-underline-offset: var(--space-0-75);
text-decoration-thickness: 1px;
}
[data-component="content"] {
max-width: 67.5rem;
margin: 0 auto;
border: 1px solid var(--color-border);
}
[data-component="top"] {
padding: calc(var(--padding) * 1.5) var(--padding) var(--padding);
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: calc(var(--vertical-padding) / 2);
img {
height: auto;
width: clamp(200px, 85vw, 552px);
}
[data-slot="logo dark"] {
display: none;
}
@media (prefers-color-scheme: dark) {
[data-slot="logo light"] {
display: none;
}
[data-slot="logo dark"] {
display: block;
}
}
[data-slot="title"] {
line-height: 1.25;
font-weight: 500;
text-align: center;
font-size: var(--heading-font-size);
color: var(--color-text-secondary);
text-transform: uppercase;
}
[data-slot="login"] {
position: absolute;
top: 0;
right: 0;
border-width: 0 0 1px 1px;
border-style: solid;
border-color: var(--color-border);
background-color: var(--color-bg);
@media (max-width: 30rem) {
display: none;
}
a {
display: block;
padding: 0.5rem 1rem calc(0.5rem + 4px);
}
}
}
[data-component="cta"] {
border-top: 1px solid var(--color-border);
display: flex;
& > div + div {
border-left: 1px solid var(--color-border);
}
[data-slot="left"] {
flex: 0 0 auto;
text-align: center;
line-height: 1.4;
padding: var(--vertical-padding) 2rem;
text-transform: uppercase;
font-size: 1.125rem;
@media (max-width: 30rem) {
font-size: 1rem;
padding-bottom: calc(var(--vertical-padding) + 4px);
}
@media (max-width: 30rem) {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
}
[data-slot="center"] {
display: none;
@media (max-width: 30rem) {
display: block;
flex: 1;
text-align: center;
padding: var(--vertical-padding) 0.5rem;
border-top: 1px solid var(--color-border);
border-left: none;
}
}
[data-slot="right"] {
flex: 1;
padding: var(--vertical-padding) 1rem;
}
@media (max-width: 50rem) {
flex-direction: column;
[data-slot="right"] {
border-left: none;
border-top: 1px solid var(--color-border);
}
}
[data-slot="command"] {
all: unset;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--color-text-secondary);
font-size: 1.125rem;
font-family: var(--font-mono);
gap: var(--space-2);
width: 100%;
& > span {
@media (max-width: 24rem) {
font-size: 0.875rem;
}
@media (max-width: 56rem) {
[data-slot="protocol"] {
display: none;
}
}
@media (max-width: 38rem) {
text-align: center;
span:first-child {
display: block;
}
}
}
}
[data-slot="highlight"] {
color: var(--color-text);
font-weight: 500;
}
}
[data-component="features"] {
border-top: 1px solid var(--color-border);
padding: var(--padding);
[data-slot="list"] {
padding-left: var(--space-4);
margin: 0;
list-style: disc;
li {
margin-bottom: var(--space-4);
line-height: 1.6;
strong {
text-transform: uppercase;
font-weight: 600;
}
label {
line-height: 1;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.03125rem;
background: var(--color-border);
padding: 0.125rem 0.375rem;
color: var(--color-text-inverted);
}
}
li:last-child {
margin-bottom: 0;
}
}
}
[data-component="install"] {
border-top: 1px solid var(--color-border);
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
@media (max-width: 40rem) {
grid-template-columns: 1fr;
grid-template-rows: auto;
}
}
[data-component="method"] {
display: flex;
padding: calc(var(--vertical-padding) / 2) calc(var(--padding) / 2) calc(var(--vertical-padding) / 2 + 0.125rem);
flex-direction: column;
text-align: left;
gap: var(--space-2-5);
@media (max-width: 30rem) {
gap: 0.3125rem;
}
@media (max-width: 40rem) {
text-align: left;
}
&:nth-child(2) {
border-left: 1px solid var(--color-border);
@media (max-width: 40rem) {
border-left: none;
border-top: 1px solid var(--color-border);
}
}
&:nth-child(3) {
border-top: 1px solid var(--color-border);
}
&:nth-child(4) {
border-top: 1px solid var(--color-border);
border-left: 1px solid var(--color-border);
@media (max-width: 40rem) {
border-left: none;
}
}
[data-component="title"] {
letter-spacing: -0.03125rem;
text-transform: uppercase;
font-weight: normal;
font-size: 1rem;
flex-shrink: 0;
color: var(--color-text-dimmed);
@media (max-width: 30rem) {
font-size: 0.75rem;
}
}
[data-slot="button"] {
all: unset;
cursor: pointer;
display: flex;
align-items: center;
color: var(--color-text-secondary);
gap: var(--space-2-5);
font-size: 1rem;
@media (max-width: 24rem) {
font-size: 0.875rem;
}
strong {
color: var(--color-text);
font-weight: 500;
}
@media (max-width: 40rem) {
justify-content: flex-start;
}
@media (max-width: 30rem) {
justify-content: center;
}
}
}
[data-component="screenshots"] {
border-top: 1px solid var(--color-border);
figure {
flex: 1;
display: flex;
flex-direction: column;
gap: calc(var(--padding) / 4);
padding: calc(var(--padding) / 2);
border-width: 0;
border-style: solid;
border-color: var(--color-border);
min-height: 0;
overflow: hidden;
& > div,
figcaption {
display: flex;
align-items: center;
}
& > div {
flex: 1;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
}
a {
display: flex;
flex: 1;
min-height: 0;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
figcaption {
letter-spacing: -0.03125rem;
text-transform: uppercase;
color: var(--color-text-dimmed);
flex-shrink: 0;
@media (max-width: 30rem) {
font-size: 0.75rem;
}
}
}
& > [data-slot="left"] figure {
height: var(--images-height);
box-sizing: border-box;
}
& > [data-slot="right"] figure {
height: calc(var(--images-height) / 2);
box-sizing: border-box;
}
& > [data-slot="left"] img {
width: 100%;
height: 100%;
min-width: 0;
object-fit: contain;
}
& > [data-slot="right"] img {
width: 100%;
height: calc(100% - 2rem);
object-fit: contain;
display: block;
}
@media (max-width: 30rem) {
& {
--images-height: auto;
grid-template-columns: 1fr;
grid-template-rows: auto auto;
}
& > [data-slot="left"] {
grid-row: 1;
grid-column: 1;
}
& > [data-slot="right"] {
grid-row: 2;
grid-column: 1;
border-left: none;
border-top: 1px solid var(--color-border);
& > [data-slot="row1"],
& > [data-slot="row2"] {
height: auto;
}
}
& > [data-slot="left"] figure,
& > [data-slot="right"] figure {
height: auto;
}
& > [data-slot="left"] img,
& > [data-slot="right"] img {
width: 100%;
height: auto;
max-height: none;
}
}
}
[data-component="copy-status"] {
@media (max-width: 38rem) {
display: none;
}
[data-slot="copy"] {
display: block;
width: var(--space-4);
height: var(--space-4);
color: var(--color-text-dimmed);
[data-copied] & {
display: none;
}
}
[data-slot="check"] {
display: none;
width: var(--space-4);
height: var(--space-4);
color: var(--color-text);
[data-copied] & {
display: block;
}
}
}
[data-component="footer"] {
border-top: 1px solid var(--color-border);
display: flex;
flex-direction: row;
[data-slot="cell"] {
flex: 1;
text-align: center;
text-transform: uppercase;
padding: var(--vertical-padding) 0.5rem;
}
[data-slot="cell"] + [data-slot="cell"] {
border-left: 1px solid var(--color-border);
}
/* Mobile: third column on its own row */
@media (max-width: 30rem) {
flex-wrap: wrap;
[data-slot="cell"]:nth-child(1),
[data-slot="cell"]:nth-child(2) {
flex: 1;
}
[data-slot="cell"]:nth-child(3) {
flex: 1 0 100%;
border-left: none;
border-top: 1px solid var(--color-border);
}
}
}
[data-component="legal"] {
color: var(--color-text-dimmed);
text-align: center;
a {
color: var(--color-text-dimmed);
}
}
}
-183
View File
@@ -1,183 +0,0 @@
import "./index.css"
import { Title } from "@solidjs/meta"
import { onCleanup, onMount } from "solid-js"
import logoLight from "../asset/logo-ornate-light.svg"
import logoDark from "../asset/logo-ornate-dark.svg"
import IMG_SPLASH from "../asset/lander/screenshot-splash.png"
import { IconCopy, IconCheck } from "../component/icon"
import { createAsync, query } from "@solidjs/router"
import { getActor } from "~/context/auth"
import { withActor } from "~/context/auth.withActor"
import { Account } from "@opencode/cloud-core/account.js"
function CopyStatus() {
return (
<div data-component="copy-status">
<IconCopy data-slot="copy" />
<IconCheck data-slot="check" />
</div>
)
}
const defaultWorkspace = query(async () => {
"use server"
const actor = await getActor()
if (actor.type === "account") {
const workspaces = await withActor(() => Account.workspaces())
return workspaces[0].id
}
}, "defaultWorkspace")
export default function Home() {
const workspace = createAsync(() => defaultWorkspace())
onMount(() => {
const commands = document.querySelectorAll("[data-copy]")
for (const button of commands) {
const callback = () => {
const text = button.textContent
if (text) {
navigator.clipboard.writeText(text)
button.setAttribute("data-copied", "")
setTimeout(() => {
button.removeAttribute("data-copied")
}, 1500)
}
}
button.addEventListener("click", callback)
onCleanup(() => {
button.removeEventListener("click", callback)
})
}
})
return (
<main data-page="home">
<Title>opencode | AI coding agent built for the terminal</Title>
<div data-component="content">
<section data-component="top">
<img data-slot="logo light" src={logoLight} alt="opencode logo light" />
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
<h1 data-slot="title">The AI coding agent built for the terminal</h1>
<div data-slot="login">
<a href="/auth">opencode zen</a>
</div>
</section>
<section data-component="cta">
<div data-slot="left">
<a href="/docs">Get Started</a>
</div>
<div data-slot="center">
<a href="/auth">opencode zen</a>
</div>
<div data-slot="right">
<button data-copy data-slot="command">
<span>
<span>curl -fsSL </span>
<span data-slot="protocol">https://</span>
<span data-slot="highlight">opencode.ai/install</span>
<span> | bash</span>
</span>
<CopyStatus />
</button>
</div>
</section>
<section data-component="features">
<ul data-slot="list">
<li>
<strong>Native TUI</strong> A responsive, native, themeable terminal UI
</li>
<li>
<strong>LSP enabled</strong> Automatically loads the right LSPs for the LLM
</li>
<li>
<strong>opencode zen</strong> A <a href="/docs/zen">curated list of models</a> provided by opencode{" "}
<label>New</label>
</li>
<li>
<strong>Multi-session</strong> Start multiple agents in parallel on the same project
</li>
<li>
<strong>Shareable links</strong> Share a link to any sessions for reference or to debug
</li>
<li>
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
</li>
<li>
<strong>Use any model</strong> Supports 75+ LLM providers through{" "}
<a href="https://models.dev">Models.dev</a>, including local models
</li>
</ul>
</section>
<section data-component="install">
<div data-component="method">
<h3 data-component="title">npm</h3>
<button data-copy data-slot="button">
<span>
npm install -g <strong>opencode-ai</strong>
</span>
<CopyStatus />
</button>
</div>
<div data-component="method">
<h3 data-component="title">bun</h3>
<button data-copy data-slot="button">
<span>
bun install -g <strong>opencode-ai</strong>
</span>
<CopyStatus />
</button>
</div>
<div data-component="method">
<h3 data-component="title">homebrew</h3>
<button data-copy data-slot="button">
<span>
brew install <strong>sst/tap/opencode</strong>
</span>
<CopyStatus />
</button>
</div>
<div data-component="method">
<h3 data-component="title">paru</h3>
<button data-copy data-slot="button">
<span>
paru -S <strong>opencode-bin</strong>
</span>
<CopyStatus />
</button>
</div>
</section>
<section data-component="screenshots">
<figure>
<figcaption>opencode TUI with the tokyonight theme</figcaption>
<a href="/docs/cli">
<img src={IMG_SPLASH} alt="opencode TUI with tokyonight theme" />
</a>
</figure>
</section>
<footer data-component="footer">
<div data-slot="cell">
<a href="https://x.com/opencode">X.com</a>
</div>
<div data-slot="cell">
<a href="https://github.com/sst/opencode">GitHub</a>
</div>
<div data-slot="cell">
<a href="https://opencode.ai/discord">Discord</a>
</div>
</footer>
</div>
<div data-component="legal">
<span>
©2025 <a href="https://anoma.ly">Anomaly Innovations</a>
</span>
</div>
</main>
)
}
-20
View File
@@ -1,20 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
async function handler(evt: APIEvent) {
const req = evt.request.clone()
const url = new URL(req.url)
const targetUrl = `https://docs.opencode.ai/docs${url.pathname}${url.search}`
const response = await fetch(targetUrl, {
method: req.method,
headers: req.headers,
body: req.body,
})
return response
}
export const GET = handler
export const POST = handler
export const PUT = handler
export const DELETE = handler
export const OPTIONS = handler
export const PATCH = handler
-75
View File
@@ -1,75 +0,0 @@
import { Billing } from "@opencode/cloud-core/billing.js"
import type { APIEvent } from "@solidjs/start/server"
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
import { BillingTable, PaymentTable } from "@opencode/cloud-core/schema/billing.sql.js"
import { Identifier } from "@opencode/cloud-core/identifier.js"
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
import { Actor } from "@opencode/cloud-core/actor.js"
import { Resource } from "@opencode/cloud-resource"
export async function POST(input: APIEvent) {
const body = await Billing.stripe().webhooks.constructEventAsync(
await input.request.text(),
input.request.headers.get("stripe-signature")!,
Resource.STRIPE_WEBHOOK_SECRET.value,
)
console.log(body.type, JSON.stringify(body, null, 2))
if (body.type === "checkout.session.completed") {
const workspaceID = body.data.object.metadata?.workspaceID
const customerID = body.data.object.customer as string
const paymentID = body.data.object.payment_intent as string
const amount = body.data.object.amount_total
if (!workspaceID) throw new Error("Workspace ID not found")
if (!customerID) throw new Error("Customer ID not found")
if (!amount) throw new Error("Amount not found")
if (!paymentID) throw new Error("Payment ID not found")
const chargedAmount = 2000
await Actor.provide("system", { workspaceID }, async () => {
const customer = await Billing.get()
if (customer?.customerID && customer.customerID !== customerID) throw new Error("Customer ID mismatch")
// set customer metadata
if (!customer?.customerID) {
await Billing.stripe().customers.update(customerID, {
metadata: {
workspaceID,
},
})
}
// get payment method for the payment intent
const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
expand: ["payment_method"],
})
const paymentMethod = paymentIntent.payment_method
if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
await Database.transaction(async (tx) => {
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} + ${centsToMicroCents(chargedAmount)}`,
customerID,
paymentMethodID: paymentMethod.id,
paymentMethodLast4: paymentMethod.card!.last4,
})
.where(eq(BillingTable.workspaceID, workspaceID))
await tx.insert(PaymentTable).values({
workspaceID,
id: Identifier.create("payment"),
amount: centsToMicroCents(chargedAmount),
paymentID,
customerID,
})
})
})
}
console.log("finished handling")
return Response.json("ok", { status: 200 })
}
-127
View File
@@ -1,127 +0,0 @@
[data-page="workspace"] {
line-height: 1;
/* Common elements */
button {
padding: var(--space-3) var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
font-family: var(--font-sans);
font-weight: 500;
text-transform: uppercase;
cursor: pointer;
transition: all 0.15s ease;
&:hover:not(:disabled) {
background-color: var(--color-surface-hover);
border-color: var(--color-accent);
}
&:active {
transform: translateY(1px);
}
&:disabled {
opacity: 0.5;
transform: none;
}
&[data-color="primary"] {
background-color: var(--color-primary);
border-color: var(--color-primary);
color: var(--color-primary-text);
&:hover:not(:disabled) {
background-color: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
}
&[data-color="ghost"] {
background-color: transparent;
border-color: transparent;
color: var(--color-text-muted);
&:hover:not(:disabled) {
background-color: var(--color-surface-hover);
border-color: var(--color-border);
color: var(--color-text);
}
}
}
a {
color: var(--color-text);
text-decoration: underline;
text-underline-offset: var(--space-0-75);
text-decoration-thickness: 1px;
}
/* Workspace Header */
[data-component="workspace-header"] {
position: sticky;
top: 0;
z-index: 100;
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-4) var(--space-4);
border-bottom: 1px solid var(--color-border);
background-color: var(--color-bg);
@media (max-width: 30rem) {
padding: var(--space-4) var(--space-4);
}
}
[data-slot="header-brand"] {
flex: 0 0 auto;
padding-top: 4px;
svg {
width: 138px;
}
[data-component="site-title"] {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--color-text);
text-decoration: none;
letter-spacing: -0.02em;
}
}
[data-slot="header-actions"] {
display: flex;
gap: var(--space-4);
align-items: center;
font-size: var(--font-size-sm);
[data-slot="user"] {
color: var(--color-text-muted);
}
@media (max-width: 30rem) {
[data-slot="user"] {
display: none;
}
}
a,
button {
appearance: none;
background: none;
border: none;
cursor: pointer;
padding: 0;
color: var(--color-text);
text-decoration: underline;
text-underline-offset: var(--space-0-75);
text-decoration-thickness: 1px;
text-transform: uppercase;
}
}
}
-67
View File
@@ -1,67 +0,0 @@
import "./workspace.css"
import { useAuthSession } from "~/context/auth.session"
import { IconLogo } from "../component/icon"
import { withActor } from "~/context/auth.withActor"
import {
query,
action,
redirect,
createAsync,
RouteSectionProps,
Navigate,
useNavigate,
useParams,
A,
} from "@solidjs/router"
import { User } from "@opencode/cloud-core/user.js"
import { Actor } from "@opencode/cloud-core/actor.js"
import { getRequestEvent } from "solid-js/web"
const getUserInfo = query(async (workspaceID: string) => {
"use server"
return withActor(async () => {
const actor = Actor.assert("user")
return await User.fromID(actor.properties.userID)
}, workspaceID)
}, "userInfo")
const logout = action(async () => {
"use server"
const auth = await useAuthSession()
const event = getRequestEvent()
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
})
throw redirect("/")
})
export default function WorkspaceLayout(props: RouteSectionProps) {
const params = useParams()
const userInfo = createAsync(() => getUserInfo(params.id))
return (
<main data-page="workspace">
<header data-component="workspace-header">
<div data-slot="header-brand">
<A href="/" data-component="site-title">
<IconLogo />
</A>
</div>
<div data-slot="header-actions">
<span data-slot="user">{userInfo()?.email}</span>
<form action={logout} method="post">
<button type="submit" formaction={logout}>
Logout
</button>
</form>
</div>
</header>
<div>{props.children}</div>
</main>
)
}
-634
View File
@@ -1,634 +0,0 @@
[data-page="workspace-[id]"] {
max-width: 64rem;
padding: var(--space-10) var(--space-4);
margin: 0 auto;
width: 100%;
display: flex;
flex-direction: column;
gap: var(--space-10);
@media (max-width: 30rem) {
padding-top: var(--space-4);
padding-bottom: var(--space-4);
gap: var(--space-8);
}
[data-slot="sections"] {
display: flex;
flex-direction: column;
gap: var(--space-16);
@media (max-width: 30rem) {
gap: var(--space-8);
}
section {
display: flex;
flex-direction: column;
gap: var(--space-6);
/* Section titles */
[data-slot="section-title"] {
display: flex;
flex-direction: column;
gap: var(--space-1);
h2 {
font-size: var(--font-size-md);
font-weight: 600;
line-height: 1.2;
letter-spacing: -0.03125rem;
margin: 0;
color: var(--color-text-secondary);
text-transform: uppercase;
@media (max-width: 30rem) {
font-size: var(--font-size-md);
}
}
}
}
section:not(:last-child) {
border-bottom: 1px solid var(--color-border);
padding-bottom: var(--space-16);
@media (max-width: 30rem) {
padding-bottom: var(--space-8);
}
}
}
[data-component="empty-state"] {
padding: var(--space-20) var(--space-6);
text-align: center;
border: 1px dashed var(--color-border);
border-radius: var(--border-radius-sm);
display: flex;
flex-direction: column;
gap: var(--space-2);
p {
line-height: 1.5;
font-size: var(--font-size-sm);
color: var(--color-text-muted);
}
}
/* Title section */
[data-component="title-section"] {
display: flex;
flex-direction: column;
gap: var(--space-2);
padding-bottom: var(--space-8);
border-bottom: 1px solid var(--color-border);
@media (max-width: 30rem) {
padding-bottom: var(--space-6);
}
h1 {
font-size: var(--font-size-2xl);
font-weight: 500;
line-height: 1.2;
letter-spacing: -0.03125rem;
margin: 0;
text-transform: uppercase;
@media (max-width: 30rem) {
font-size: var(--font-size-xl);
}
}
p {
line-height: 1.5;
font-size: var(--font-size-md);
color: var(--color-text-muted);
a {
color: var(--color-text-muted);
}
}
}
/* API Keys Section */
[data-component="api-keys-section"] {
[data-slot="create-form"] {
display: flex;
gap: var(--space-3);
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
@media (max-width: 30rem) {
gap: var(--space-2);
}
input {
flex: 1;
padding: var(--space-2) var(--space-3);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
font-family: var(--font-mono);
&:focus {
outline: none;
border-color: var(--color-accent);
}
&::placeholder {
color: var(--color-text-disabled);
}
}
[data-slot="form-actions"] {
display: flex;
gap: var(--space-2);
}
}
[data-slot="api-keys-table"] {
overflow-x: auto;
}
[data-slot="api-keys-table-element"] {
width: 100%;
border-collapse: collapse;
font-size: var(--font-size-sm);
thead {
border-bottom: 1px solid var(--color-border);
}
th {
padding: var(--space-3) var(--space-4);
text-align: left;
font-weight: normal;
color: var(--color-text-muted);
text-transform: uppercase;
}
td {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border-muted);
color: var(--color-text-muted);
font-family: var(--font-mono);
&[data-slot="key-name"] {
color: var(--color-text);
font-family: var(--font-sans);
font-weight: 500;
}
&[data-slot="key-value"] {
font-family: var(--font-mono);
button {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
font-size: var(--font-size-sm);
font-weight: 400;
border: none;
background-color: transparent;
color: var(--color-text-muted);
font-family: var(--font-mono);
border-radius: var(--border-radius-sm);
cursor: pointer;
transition: all 0.15s ease;
text-transform: none;
&:hover:not(:disabled) {
background-color: var(--color-bg-surface);
color: var(--color-text);
}
&:disabled {
cursor: default;
color: var(--color-text);
}
span {
font-family: inherit;
}
}
}
&[data-slot="key-date"] {
color: var(--color-text);
}
&[data-slot="key-actions"] {
font-family: var(--font-sans);
}
}
tbody tr {
&:last-child td {
border-bottom: none;
}
}
@media (max-width: 40rem) {
th,
td {
padding: var(--space-2) var(--space-3);
font-size: var(--font-size-xs);
}
th {
&:nth-child(3) /* Date */ {
display: none;
}
}
td {
&:nth-child(3) /* Date */ {
display: none;
}
}
}
}
}
/* Balance Section */
[data-component="balance-section"] {
[data-slot="balance"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
min-width: 14.5rem;
width: fit-content;
[data-slot="amount"] {
padding: var(--space-3-5) var(--space-4);
background-color: var(--color-bg-surface);
border-radius: var(--border-radius-sm);
display: flex;
align-items: baseline;
gap: var(--space-1);
justify-content: flex-end;
&[data-state="danger"] {
[data-slot="value"] {
color: var(--color-danger);
}
[data-slot="currency"] {
color: var(--color-danger);
}
}
[data-slot="currency"] {
position: relative;
bottom: 2px;
font-size: var(--font-size-lg);
color: var(--color-text-muted);
font-weight: 400;
}
[data-slot="value"] {
font-size: var(--font-size-3xl);
font-weight: 500;
color: var(--color-text);
}
}
}
}
/* Payments Section */
[data-component="payments-section"] {
[data-slot="payments-table"] {
overflow-x: auto;
}
[data-slot="payments-table-element"] {
width: 100%;
border-collapse: collapse;
font-size: var(--font-size-sm);
thead {
border-bottom: 1px solid var(--color-border);
}
th {
padding: var(--space-3) var(--space-4);
text-align: left;
font-weight: normal;
color: var(--color-text-muted);
text-transform: uppercase;
}
td {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border-muted);
color: var(--color-text-muted);
font-family: var(--font-mono);
&[data-slot="payment-date"] {
color: var(--color-text);
}
&[data-slot="payment-id"] {
font-family: var(--font-mono);
font-weight: 400;
color: var(--color-text-muted);
max-width: 200px;
word-break: break-word;
}
&[data-slot="payment-amount"] {
color: var(--color-text);
}
}
tbody tr {
&:last-child td {
border-bottom: none;
}
}
@media (max-width: 40rem) {
th,
td {
padding: var(--space-2) var(--space-3);
font-size: var(--font-size-xs);
}
th {
&:nth-child(2) /* Payment ID */ {
display: none;
}
}
td {
&:nth-child(2) /* Payment ID */ {
display: none;
}
}
}
}
}
/* Usage Section */
[data-component="usage-section"] {
[data-slot="usage-table"] {
overflow-x: auto;
}
[data-slot="usage-table-element"] {
width: 100%;
border-collapse: collapse;
font-size: var(--font-size-sm);
thead {
border-bottom: 1px solid var(--color-border);
}
th {
padding: var(--space-3) var(--space-4);
text-align: left;
font-weight: normal;
color: var(--color-text-muted);
text-transform: uppercase;
}
td {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border-muted);
color: var(--color-text-muted);
font-family: var(--font-mono);
&[data-slot="usage-date"] {
color: var(--color-text);
}
&[data-slot="usage-model"] {
font-family: var(--font-sans);
font-weight: 400;
color: var(--color-text-secondary);
max-width: 200px;
word-break: break-word;
}
&[data-slot="usage-cost"] {
color: var(--color-text);
}
}
tbody tr {
&:last-child td {
border-bottom: none;
}
}
@media (max-width: 40rem) {
th,
td {
padding: var(--space-2) var(--space-3);
font-size: var(--font-size-xs);
}
th {
&:nth-child(2) /* Model */ {
display: none;
}
}
td {
&:nth-child(2) /* Model */ {
display: none;
}
}
}
}
}
[data-slot="new-user-sections"] {
display: flex;
flex-direction: column;
gap: var(--space-16);
@media (max-width: 30rem) {
gap: var(--space-8);
}
[data-component="feature-grid"] {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--space-6);
@media (max-width: 30rem) {
grid-template-columns: 1fr;
gap: var(--space-4);
}
[data-slot="feature"] {
display: flex;
flex-direction: column;
gap: var(--space-2);
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg-surface);
h3 {
font-size: var(--font-size-sm);
font-weight: 600;
margin: 0;
color: var(--color-text);
text-transform: uppercase;
letter-spacing: -0.025rem;
}
p {
font-size: var(--font-size-sm);
line-height: 1.5;
margin: 0;
color: var(--color-text-muted);
}
}
}
[data-component="api-key-highlight"] {
display: flex;
flex-direction: column;
gap: var(--space-6);
[data-slot="section-title"] {
display: flex;
flex-direction: column;
gap: var(--space-1);
h2 {
font-size: var(--font-size-md);
font-weight: 600;
line-height: 1.2;
letter-spacing: -0.03125rem;
margin: 0;
color: var(--color-text-secondary);
text-transform: uppercase;
@media (max-width: 30rem) {
font-size: var(--font-size-md);
}
}
}
[data-slot="key-display"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
[data-slot="key-container"] {
display: flex;
gap: var(--space-3);
padding: var(--space-4);
border: 2px solid var(--color-accent);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg-surface);
align-items: center;
@media (max-width: 40rem) {
flex-direction: column;
gap: var(--space-3);
align-items: stretch;
}
[data-slot="key-value"] {
flex: 1;
font-family: var(--font-mono);
font-size: var(--font-size-sm);
color: var(--color-text);
background-color: var(--color-bg);
padding: var(--space-3);
border-radius: var(--border-radius-sm);
border: 1px solid var(--color-border);
word-break: break-all;
line-height: 1.4;
@media (max-width: 40rem) {
font-size: var(--font-size-xs);
padding: var(--space-2-5);
}
}
button {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-4);
font-size: var(--font-size-sm);
font-weight: 500;
white-space: nowrap;
min-width: 130px;
@media (max-width: 40rem) {
justify-content: center;
padding: var(--space-2-5) var(--space-3);
font-size: var(--font-size-xs);
min-width: 96px;
}
}
}
}
}
[data-component="next-steps"] {
display: flex;
flex-direction: column;
gap: var(--space-6);
[data-slot="section-title"] {
display: flex;
flex-direction: column;
gap: var(--space-1);
h2 {
font-size: var(--font-size-md);
font-weight: 600;
line-height: 1.2;
letter-spacing: -0.03125rem;
margin: 0;
color: var(--color-text-secondary);
text-transform: uppercase;
@media (max-width: 30rem) {
font-size: var(--font-size-md);
}
}
}
ol {
margin: 0;
padding-left: 0;
display: flex;
flex-direction: column;
gap: var(--space-2);
list-style-position: inside;
li {
font-size: var(--font-size-sm);
line-height: 1.5;
color: var(--color-text-muted);
code {
font-family: var(--font-mono);
font-size: var(--font-size-xs);
padding: var(--space-1) var(--space-2);
background-color: var(--color-bg-surface);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
color: var(--color-text);
}
}
}
}
}
}
-486
View File
@@ -1,486 +0,0 @@
import "./[id].css"
import { Billing } from "@opencode/cloud-core/billing.js"
import { Key } from "@opencode/cloud-core/key.js"
import { json, query, action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router"
import { createEffect, createMemo, createSignal, For, Show } from "solid-js"
import { withActor } from "~/context/auth.withActor"
import { IconCopy, IconCheck } from "~/component/icon"
import { createStore } from "solid-js/store"
function formatDateForTable(date: Date) {
const options: Intl.DateTimeFormatOptions = {
day: "numeric",
month: "short",
hour: "numeric",
minute: "2-digit",
hour12: true,
}
return date.toLocaleDateString("en-GB", options).replace(",", ",")
}
function formatDateUTC(date: Date) {
const options: Intl.DateTimeFormatOptions = {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
timeZoneName: "short",
timeZone: "UTC",
}
return date.toLocaleDateString("en-US", options)
}
/////////////////////////////////////
// Keys related queries and actions
/////////////////////////////////////
const listKeys = query(async (workspaceID: string) => {
"use server"
return withActor(() => Key.list(), workspaceID)
}, "key.list")
const createKey = action(async (form: FormData) => {
"use server"
const name = form.get("name")?.toString().trim()
if (!name) return { error: "Name is required" }
const workspaceID = form.get("workspaceID")?.toString()
if (!workspaceID) return { error: "Workspace ID is required" }
return json(
withActor(() => Key.create({ name }), workspaceID),
{ revalidate: listKeys.key },
)
}, "key.create")
const removeKey = action(async (form: FormData) => {
"use server"
const id = form.get("id")?.toString()
if (!id) return { error: "ID is required" }
const workspaceID = form.get("workspaceID")?.toString()
if (!workspaceID) return { error: "Workspace ID is required" }
return json(
withActor(() => Key.remove({ id }), workspaceID),
{ revalidate: listKeys.key },
)
}, "key.remove")
/////////////////////////////////////
// Billing related queries and actions
/////////////////////////////////////
const getBalanceInfo = query(async (workspaceID: string) => {
"use server"
return withActor(async () => {
return await Billing.get()
}, workspaceID)
}, "balanceInfo")
const getUsageInfo = query(async (workspaceID: string) => {
"use server"
return withActor(async () => {
return await Billing.usages()
}, workspaceID)
}, "usageInfo")
const getPaymentsInfo = query(async (workspaceID: string) => {
"use server"
return withActor(async () => {
return await Billing.payments()
}, workspaceID)
}, "paymentsInfo")
const createCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => {
"use server"
return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID)
}, "checkoutUrl")
// const createPortalUrl = action(async (workspaceID: string, returnUrl: string) => {
// "use server"
// return withActor(() => Billing.generatePortalUrl({ returnUrl }), workspaceID)
// }, "portalUrl")
function KeySection() {
const params = useParams()
const keys = createAsync(() => listKeys(params.id))
function formatKey(key: string) {
if (key.length <= 11) return key
return `${key.slice(0, 7)}...${key.slice(-4)}`
}
return (
<section data-component="api-keys-section">
<div data-slot="section-title">
<h2>API Keys</h2>
<p>Manage your API keys for accessing opencode services.</p>
</div>
<KeyCreateForm />
<div data-slot="api-keys-table">
<Show
when={keys()?.length}
fallback={
<div data-component="empty-state">
<p>Create an opencode Gateway API key</p>
</div>
}
>
<table data-slot="api-keys-table-element">
<thead>
<tr>
<th>Name</th>
<th>Key</th>
<th>Created</th>
<th></th>
</tr>
</thead>
<tbody>
<For each={keys()!}>
{(key) => {
const [copied, setCopied] = createSignal(false)
// const submission = useSubmission(removeKey, ([fd]) => fd.get("id")?.toString() === key.id)
return (
<tr>
<td data-slot="key-name">{key.name}</td>
<td data-slot="key-value">
<button
data-color="ghost"
disabled={copied()}
onClick={async () => {
await navigator.clipboard.writeText(key.key)
setCopied(true)
setTimeout(() => setCopied(false), 1000)
}}
title="Copy API key"
>
<span>{formatKey(key.key)}</span>
<Show when={copied()} fallback={<IconCopy style={{ width: "14px", height: "14px" }} />}>
<IconCheck style={{ width: "14px", height: "14px" }} />
</Show>
</button>
</td>
<td data-slot="key-date" title={formatDateUTC(key.timeCreated)}>
{formatDateForTable(key.timeCreated)}
</td>
<td data-slot="key-actions">
<form action={removeKey} method="post">
<input type="hidden" name="id" value={key.id} />
<input type="hidden" name="workspaceID" value={params.id} />
<button data-color="ghost">Delete</button>
</form>
</td>
</tr>
)
}}
</For>
</tbody>
</table>
</Show>
</div>
</section>
)
}
function KeyCreateForm() {
const params = useParams()
const submission = useSubmission(createKey)
const [store, setStore] = createStore({
show: false,
})
let input: HTMLInputElement
createEffect(() => {
if (!submission.pending && submission.result) {
hide()
}
})
function show() {
setStore("show", true)
input.focus()
}
function hide() {
setStore("show", false)
}
return (
<Show
when={store.show}
fallback={
<button data-color="primary" onClick={() => show()}>
Create API Key
</button>
}
>
<form action={createKey} method="post" data-slot="create-form">
<input ref={(r) => (input = r)} data-component="input" name="name" type="text" placeholder="Enter key name" />
<input type="hidden" name="workspaceID" value={params.id} />
<div data-slot="form-actions">
<button type="reset" data-color="ghost" onClick={() => hide()}>
Cancel
</button>
<button type="submit" data-color="primary" disabled={submission.pending}>
{submission.pending ? "Creating..." : "Create"}
</button>
</div>
</form>
</Show>
)
}
function BalanceSection() {
const params = useParams()
const balanceInfo = createAsync(() => getBalanceInfo(params.id))
const createCheckoutUrlAction = useAction(createCheckoutUrl)
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
return (
<section data-component="balance-section">
<div data-slot="section-title">
<h2>Balance</h2>
<p>Add credits to your account.</p>
</div>
<div data-slot="balance">
<div
data-slot="amount"
data-state={(() => {
const balanceStr = ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
return balanceStr === "0.00" || balanceStr === "-0.00" ? "danger" : undefined
})()}
>
<span data-slot="currency">$</span>
<span data-slot="value">
{(() => {
const balanceStr = ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
return balanceStr === "-0.00" ? "0.00" : balanceStr
})()}
</span>
</div>
<button
data-color="primary"
disabled={createCheckoutUrlSubmission.pending}
onClick={async () => {
const baseUrl = window.location.href
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
if (checkoutUrl) {
window.location.href = checkoutUrl
}
}}
>
{createCheckoutUrlSubmission.pending ? "Loading..." : "Buy Credits"}
</button>
</div>
</section>
)
}
function UsageSection() {
const params = useParams()
const usage = createAsync(() => getUsageInfo(params.id))
return (
<section data-component="usage-section">
<div data-slot="section-title">
<h2>Usage History</h2>
<p>Recent API usage and costs.</p>
</div>
<div data-slot="usage-table">
<Show
when={usage() && usage()!.length > 0}
fallback={
<div data-component="empty-state">
<p>Make your first API call to get started.</p>
</div>
}
>
<table data-slot="usage-table-element">
<thead>
<tr>
<th>Date</th>
<th>Model</th>
<th>Input</th>
<th>Output</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<For each={usage()!}>
{(usage) => {
const date = createMemo(() => new Date(usage.timeCreated))
return (
<tr>
<td data-slot="usage-date" title={formatDateUTC(date())}>
{formatDateForTable(date())}
</td>
<td data-slot="usage-model">{usage.model}</td>
<td data-slot="usage-tokens">{usage.inputTokens}</td>
<td data-slot="usage-tokens">{usage.outputTokens}</td>
<td data-slot="usage-cost">${((usage.cost ?? 0) / 100000000).toFixed(4)}</td>
</tr>
)
}}
</For>
</tbody>
</table>
</Show>
</div>
</section>
)
}
function PaymentSection() {
const params = useParams()
const payments = createAsync(() => getPaymentsInfo(params.id))
return (
payments() &&
payments()!.length > 0 && (
<section data-component="payments-section">
<div data-slot="section-title">
<h2>Payments History</h2>
<p>Recent payment transactions.</p>
</div>
<div data-slot="payments-table">
<table data-slot="payments-table-element">
<thead>
<tr>
<th>Date</th>
<th>Payment ID</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<For each={payments()!}>
{(payment) => {
const date = new Date(payment.timeCreated)
return (
<tr>
<td data-slot="payment-date" title={formatDateUTC(date)}>
{formatDateForTable(date)}
</td>
<td data-slot="payment-id">{payment.id}</td>
<td data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</td>
</tr>
)
}}
</For>
</tbody>
</table>
</div>
</section>
)
)
}
function NewUserSection() {
const params = useParams()
const [copiedKey, setCopiedKey] = createSignal(false)
const keys = createAsync(() => listKeys(params.id))
const usage = createAsync(() => getUsageInfo(params.id))
const isNew = createMemo(() => {
const keysList = keys()
const usageList = usage()
return keysList?.length === 1 && (!usageList || usageList.length === 0)
})
const defaultKey = createMemo(() => keys()?.at(-1)?.key)
return (
<Show when={isNew()}>
<div data-slot="new-user-sections">
<div data-component="feature-grid">
<div data-slot="feature">
<h3>Tested & Verified Models</h3>
<p>We've benchmarked and tested models specifically for coding agents to ensure the best performance.</p>
</div>
<div data-slot="feature">
<h3>Highest Quality</h3>
<p>Access models configured for optimal performance - no downgrades or routing to cheaper providers.</p>
</div>
<div data-slot="feature">
<h3>No Lock-in</h3>
<p>Use Zen with any coding agent, and continue using other providers with opencode whenever you want.</p>
</div>
</div>
<div data-component="api-key-highlight">
<div data-slot="section-title">
<h2>Your API Key</h2>
</div>
<Show when={defaultKey()}>
<div data-slot="key-display">
<div data-slot="key-container">
<code data-slot="key-value">{defaultKey()}</code>
<button
data-color="primary"
disabled={copiedKey()}
onClick={async () => {
await navigator.clipboard.writeText(defaultKey() ?? "")
setCopiedKey(true)
setTimeout(() => setCopiedKey(false), 2000)
}}
title="Copy API key"
>
<Show
when={copiedKey()}
fallback={
<>
<IconCopy style={{ width: "16px", height: "16px" }} /> Copy Key
</>
}
>
<IconCheck style={{ width: "16px", height: "16px" }} /> Copied!
</Show>
</button>
</div>
</div>
</Show>
</div>
<div data-component="next-steps">
<div data-slot="section-title">
<h2>Next Steps</h2>
</div>
<ol>
<li>Copy your API key above</li>
<li>
Run <code>opencode auth login</code> and select opencode
</li>
<li>Paste your API key when prompted</li>
<li>
Run <code>/models</code> to see available models
</li>
</ol>
</div>
</div>
</Show>
)
}
export default function () {
return (
<div data-page="workspace-[id]">
<section data-component="title-section">
<h1>Zen</h1>
<p>
Curated list of models provided by opencode.{" "}
<a target="_blank" href="/docs/zen">
Learn more
</a>
.
</p>
</section>
<div data-slot="sections">
<NewUserSection />
<KeySection />
<BalanceSection />
<UsageSection />
<PaymentSection />
</div>
</div>
)
}
@@ -1,36 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { handler } from "~/util/zen"
export function POST(input: APIEvent) {
let usage: any
return handler(input, {
modifyBody: (body: any) => ({
...body,
...(body.stream ? { stream_options: { include_usage: true } } : {}),
}),
setAuthHeader: (headers: Headers, apiKey: string) => {
headers.set("authorization", `Bearer ${apiKey}`)
},
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
onStreamPart: (chunk: string) => {
if (!chunk.startsWith("data: ")) return
let json
try {
json = JSON.parse(chunk.slice(6))
} catch (e) {
return
}
if (!json.usage) return
usage = json.usage
},
getStreamUsage: () => usage,
normalizeUsage: (usage: any) => ({
inputTokens: usage.prompt_tokens ?? 0,
outputTokens: usage.completion_tokens ?? 0,
reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0,
cacheReadTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,
}),
})
}
-61
View File
@@ -1,61 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { handler } from "~/util/zen"
type Usage = {
cache_creation?: {
ephemeral_5m_input_tokens?: number
ephemeral_1h_input_tokens?: number
}
cache_creation_input_tokens?: number
cache_read_input_tokens?: number
input_tokens?: number
output_tokens?: number
server_tool_use?: {
web_search_requests?: number
}
}
export function POST(input: APIEvent) {
let usage: Usage
return handler(input, {
modifyBody: (body: any) => ({
...body,
service_tier: "standard_only",
}),
setAuthHeader: (headers: Headers, apiKey: string) => headers.set("x-api-key", apiKey),
parseApiKey: (headers: Headers) => headers.get("x-api-key") ?? undefined,
onStreamPart: (chunk: string) => {
const data = chunk.split("\n")[1]
if (!data.startsWith("data: ")) return
let json
try {
json = JSON.parse(data.slice(6)) as { usage?: Usage }
} catch (e) {
return
}
if (!json.usage) return
usage = {
...usage,
...json.usage,
cache_creation: {
...usage?.cache_creation,
...json.usage.cache_creation,
},
server_tool_use: {
...usage?.server_tool_use,
...json.usage.server_tool_use,
},
}
},
getStreamUsage: () => usage,
normalizeUsage: (usage: Usage) => ({
inputTokens: usage.input_tokens ?? 0,
outputTokens: usage.output_tokens ?? 0,
cacheReadTokens: usage.cache_read_input_tokens ?? 0,
cacheWrite5mTokens: usage.cache_creation?.ephemeral_5m_input_tokens,
cacheWrite1hTokens: usage.cache_creation?.ephemeral_1h_input_tokens,
}),
})
}
-40
View File
@@ -1,40 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { handler } from "~/util/zen"
export function POST(input: APIEvent) {
let usage: any
return handler(input, {
setAuthHeader: (headers: Headers, apiKey: string) => {
headers.set("authorization", `Bearer ${apiKey}`)
},
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
onStreamPart: (chunk: string) => {
const [event, data] = chunk.split("\n")
if (event !== "event: response.completed") return
if (!data.startsWith("data: ")) return
let json
try {
json = JSON.parse(data.slice(6))
} catch (e) {
return
}
if (!json.response?.usage) return
usage = json.response.usage
},
getStreamUsage: () => usage,
normalizeUsage: (usage: any) => {
const inputTokens = usage.input_tokens ?? 0
const outputTokens = usage.output_tokens ?? 0
const reasoningTokens = usage.output_tokens_details?.reasoning_tokens ?? 0
const cacheReadTokens = usage.input_tokens_details?.cached_tokens ?? 0
return {
inputTokens: inputTokens - cacheReadTokens,
outputTokens: outputTokens - reasoningTokens,
reasoningTokens,
cacheReadTokens,
}
},
})
}
-9
View File
@@ -1,9 +0,0 @@
html {
line-height: 1;
background-color: var(--color-bg);
color: var(--color-text);
}
body {
font-family: var(--font-sans);
}
-102
View File
@@ -1,102 +0,0 @@
[data-component="button"] {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-4);
border: 1px solid transparent;
border-radius: var(--space-2);
font-family: var(--font-sans);
font-size: var(--font-size-md);
font-weight: 500;
line-height: 1.25;
cursor: pointer;
transition: all 0.2s ease-in-out;
text-decoration: none;
user-select: none;
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
&:focus {
outline: none;
box-shadow: 0 0 0 2px var(--color-primary);
}
&[data-color="primary"] {
background-color: var(--color-primary);
color: var(--color-primary-text);
border-color: var(--color-primary);
&:hover:not(:disabled) {
background-color: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
&:active:not(:disabled) {
background-color: var(--color-primary-active);
border-color: var(--color-primary-active);
}
}
&[data-color="danger"] {
background-color: var(--color-danger);
color: var(--color-danger-text);
border-color: var(--color-danger);
&:hover:not(:disabled) {
background-color: var(--color-danger-hover);
border-color: var(--color-danger-hover);
}
&:active:not(:disabled) {
background-color: var(--color-danger-active);
border-color: var(--color-danger-active);
}
&:focus {
box-shadow: 0 0 0 2px var(--color-danger);
}
}
&[data-color="warning"] {
background-color: var(--color-warning);
color: var(--color-warning-text);
border-color: var(--color-warning);
&:hover:not(:disabled) {
background-color: var(--color-warning-hover);
border-color: var(--color-warning-hover);
}
&:active:not(:disabled) {
background-color: var(--color-warning-active);
border-color: var(--color-warning-active);
}
&:focus {
box-shadow: 0 0 0 2px var(--color-warning);
}
}
&[data-size="small"] {
padding: var(--space-2) var(--space-3);
font-size: var(--font-size-sm);
gap: var(--space-1-5);
}
&[data-size="large"] {
padding: var(--space-4) var(--space-6);
font-size: var(--font-size-lg);
gap: var(--space-3);
}
[data-slot="icon"] {
display: flex;
align-items: center;
width: 1em;
height: 1em;
}
}
-8
View File
@@ -1,8 +0,0 @@
@import "./token/color.css";
@import "./token/font.css";
@import "./token/space.css";
@import "./component/button.css";
@import "./reset.css";
@import "./base.css";
-76
View File
@@ -1,76 +0,0 @@
/* 1. Use a more-intuitive box-sizing model */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* 2. Remove default margin */
* {
margin: 0;
}
/* 3. Enable keyword animations */
@media (prefers-reduced-motion: no-preference) {
html {
interpolate-size: allow-keywords;
}
}
body {
/* 4. Add accessible line-height */
line-height: 1.5;
/* 5. Improve text rendering */
-webkit-font-smoothing: antialiased;
}
/* 6. Improve media defaults */
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
/* 7. Inherit fonts for form controls */
input,
button,
textarea,
select {
font: inherit;
}
/* 8. Avoid text overflows */
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
/* 9. Improve line wrapping */
p {
text-wrap: pretty;
}
h1,
h2,
h3,
h4,
h5,
h6 {
text-wrap: balance;
}
/*
10. Create a root stacking context
*/
#root,
#__next {
isolation: isolate;
}
-91
View File
@@ -1,91 +0,0 @@
:root {
--color-white: #ffffff;
--color-black: #000000;
/* Default light theme colors */
--color-bg: #ffffff;
--color-bg-surface: #f5f5f7;
--color-bg-elevated: #ffffff;
--color-text: #1d1d1f;
--color-text-secondary: #424245;
--color-text-muted: #6e6e73;
--color-text-disabled: #86868b;
--color-accent: #007aff;
--color-accent-hover: #0056b3;
--color-accent-active: #004085;
--color-success: #30d158;
--color-warning: #ff9f0a;
--color-danger: #ff3b30;
--color-border: #d2d2d7;
--color-border-muted: #e5e5ea;
/* Button colors */
--color-primary: var(--color-accent);
--color-primary-hover: var(--color-accent-hover);
--color-primary-active: var(--color-accent-active);
--color-primary-text: #ffffff;
--color-danger: #ff3b30;
--color-danger-hover: #d70015;
--color-danger-active: #a50011;
--color-danger-text: #ffffff;
--color-warning: #ff9f0a;
--color-warning-hover: #cc7f08;
--color-warning-active: #995f06;
--color-warning-text: #000000;
/* Surface colors */
--color-surface: var(--color-bg-surface);
--color-surface-hover: var(--color-bg-elevated);
--color-surface-border: var(--color-border);
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0c0c0e;
--color-bg-surface: #161618;
--color-bg-elevated: #1c1c1f;
--color-text: #ffffff;
--color-text-secondary: #c7c7cc;
--color-text-muted: #a1a1a6;
--color-text-disabled: #68686f;
--color-accent: #007aff;
--color-accent-hover: #0056b3;
--color-accent-active: #004085;
--color-success: #30d158;
--color-warning: #ff9f0a;
--color-danger: #ff453a;
--color-border: #38383a;
--color-border-muted: #2c2c2e;
/* Button colors */
--color-primary: var(--color-accent);
--color-primary-hover: var(--color-accent-hover);
--color-primary-active: var(--color-accent-active);
--color-primary-text: #ffffff;
--color-danger: #ff453a;
--color-danger-hover: #d70015;
--color-danger-active: #a50011;
--color-danger-text: #ffffff;
--color-warning: #ff9f0a;
--color-warning-hover: #cc7f08;
--color-warning-active: #995f06;
--color-warning-text: #000000;
/* Surface colors */
--color-surface: var(--color-bg-surface);
--color-surface-hover: var(--color-bg-elevated);
--color-surface-border: var(--color-border);
}
}
-527
View File
@@ -1,527 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import path from "node:path"
import { and, Database, eq, isNull, sql } from "@opencode/cloud-core/drizzle/index.js"
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
import { Identifier } from "@opencode/cloud-core/identifier.js"
import { Resource } from "@opencode/cloud-resource"
type ModelCost = {
input: number
output: number
cacheRead: number
cacheWrite5m: number
cacheWrite1h: number
}
type Model = {
id: string
auth: boolean
cost: ModelCost | ((usage: any) => ModelCost)
headerMappings: Record<string, string>
providers: Record<
string,
{
api: string
apiKey: string
model: string
weight?: number
}
>
}
export async function handler(
input: APIEvent,
opts: {
modifyBody?: (body: any) => any
setAuthHeader: (headers: Headers, apiKey: string) => void
parseApiKey: (headers: Headers) => string | undefined
onStreamPart: (chunk: string) => void
getStreamUsage: () => any
normalizeUsage: (body: any) => {
inputTokens: number
outputTokens: number
reasoningTokens?: number
cacheReadTokens: number
cacheWrite5mTokens?: number
cacheWrite1hTokens?: number
}
},
) {
class AuthError extends Error {}
class CreditsError extends Error {}
class ModelError extends Error {}
const MODELS: Record<string, Model> = {
"claude-opus-4-1": {
id: "claude-opus-4-1" as const,
auth: true,
cost: {
input: 0.000015,
output: 0.000075,
cacheRead: 0.0000015,
cacheWrite5m: 0.00001875,
cacheWrite1h: 0.00003,
},
headerMappings: {},
providers: {
anthropic: {
api: "https://api.anthropic.com",
apiKey: Resource.ANTHROPIC_API_KEY.value,
model: "claude-opus-4-1-20250805",
},
},
},
"claude-sonnet-4": {
id: "claude-sonnet-4" as const,
auth: true,
cost: (usage: any) => {
const totalInputTokens =
usage.inputTokens + usage.cacheReadTokens + usage.cacheWrite5mTokens + usage.cacheWrite1hTokens
return totalInputTokens <= 200_000
? {
input: 0.000003,
output: 0.000015,
cacheRead: 0.0000003,
cacheWrite5m: 0.00000375,
cacheWrite1h: 0.000006,
}
: {
input: 0.000006,
output: 0.0000225,
cacheRead: 0.0000006,
cacheWrite5m: 0.0000075,
cacheWrite1h: 0.000012,
}
},
headerMappings: {},
providers: {
anthropic: {
api: "https://api.anthropic.com",
apiKey: Resource.ANTHROPIC_API_KEY.value,
model: "claude-sonnet-4-20250514",
},
},
},
"claude-3-5-haiku": {
id: "claude-3-5-haiku" as const,
auth: true,
cost: {
input: 0.0000008,
output: 0.000004,
cacheRead: 0.00000008,
cacheWrite5m: 0.000001,
cacheWrite1h: 0.0000016,
},
headerMappings: {},
providers: {
anthropic: {
api: "https://api.anthropic.com",
apiKey: Resource.ANTHROPIC_API_KEY.value,
model: "claude-3-5-haiku-20241022",
},
},
},
"gpt-5": {
id: "gpt-5" as const,
auth: true,
cost: {
input: 0.00000125,
output: 0.00001,
cacheRead: 0.000000125,
cacheWrite5m: 0,
cacheWrite1h: 0,
},
headerMappings: {},
providers: {
openai: {
api: "https://api.openai.com",
apiKey: Resource.OPENAI_API_KEY.value,
model: "gpt-5",
},
},
},
"qwen3-coder": {
id: "qwen3-coder" as const,
auth: true,
cost: {
input: 0.00000045,
output: 0.0000018,
cacheRead: 0,
cacheWrite5m: 0,
cacheWrite1h: 0,
},
headerMappings: {},
providers: {
baseten: {
api: "https://inference.baseten.co",
apiKey: Resource.BASETEN_API_KEY.value,
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
weight: 4,
},
fireworks: {
api: "https://api.fireworks.ai/inference",
apiKey: Resource.FIREWORKS_API_KEY.value,
model: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
weight: 1,
},
},
},
"kimi-k2": {
id: "kimi-k2" as const,
auth: true,
cost: {
input: 0.0000006,
output: 0.0000025,
cacheRead: 0,
cacheWrite5m: 0,
cacheWrite1h: 0,
},
headerMappings: {},
providers: {
baseten: {
api: "https://inference.baseten.co",
apiKey: Resource.BASETEN_API_KEY.value,
model: "moonshotai/Kimi-K2-Instruct-0905",
weight: 4,
},
fireworks: {
api: "https://api.fireworks.ai/inference",
apiKey: Resource.FIREWORKS_API_KEY.value,
model: "accounts/fireworks/models/kimi-k2-instruct-0905",
weight: 1,
},
},
},
"grok-code": {
id: "grok-code" as const,
auth: false,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite5m: 0,
cacheWrite1h: 0,
},
headerMappings: {
"x-grok-conv-id": "x-opencode-session",
"x-grok-req-id": "x-opencode-request",
},
providers: {
xai: {
api: "https://api.x.ai",
apiKey: Resource.XAI_API_KEY.value,
model: "grok-code",
},
},
},
// deprecated
"qwen/qwen3-coder": {
id: "qwen/qwen3-coder" as const,
auth: true,
cost: {
input: 0.00000038,
output: 0.00000153,
cacheRead: 0,
cacheWrite5m: 0,
cacheWrite1h: 0,
},
headerMappings: {},
providers: {
baseten: {
api: "https://inference.baseten.co",
apiKey: Resource.BASETEN_API_KEY.value,
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
weight: 5,
},
fireworks: {
api: "https://api.fireworks.ai/inference",
apiKey: Resource.FIREWORKS_API_KEY.value,
model: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
weight: 1,
},
},
},
}
const FREE_WORKSPACES = [
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
]
const logger = {
metric: (values: Record<string, any>) => {
console.log(`_metric:${JSON.stringify(values)}`)
},
log: console.log,
debug: (message: string) => {
if (Resource.App.stage === "production") return
console.debug(message)
},
}
try {
const url = new URL(input.request.url)
const body = await input.request.json()
logger.debug(JSON.stringify(body))
logger.metric({
is_tream: !!body.stream,
session: input.request.headers.get("x-opencode-session"),
request: input.request.headers.get("x-opencode-request"),
})
const MODEL = validateModel()
const apiKey = await authenticate()
const isFree = FREE_WORKSPACES.includes(apiKey?.workspaceID ?? "")
await checkCredits()
const providerName = selectProvider()
const providerData = MODEL.providers[providerName]
logger.metric({ provider: providerName })
// Request to model provider
const startTimestamp = Date.now()
const res = await fetch(path.posix.join(providerData.api, url.pathname.replace(/^\/zen/, "") + url.search), {
method: "POST",
headers: (() => {
const headers = input.request.headers
headers.delete("host")
headers.delete("content-length")
opts.setAuthHeader(headers, providerData.apiKey)
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
headers.set(k, headers.get(v)!)
})
return headers
})(),
body: JSON.stringify({
...(opts.modifyBody?.(body) ?? body),
model: providerData.model,
}),
})
// Scrub response headers
const resHeaders = new Headers()
const keepHeaders = ["content-type", "cache-control"]
for (const [k, v] of res.headers.entries()) {
if (keepHeaders.includes(k.toLowerCase())) {
resHeaders.set(k, v)
}
}
// Handle non-streaming response
if (!body.stream) {
const json = await res.json()
const body = JSON.stringify(json)
logger.metric({ response_length: body.length })
logger.debug(body)
await trackUsage(json.usage)
return new Response(body, {
status: res.status,
statusText: res.statusText,
headers: resHeaders,
})
}
// Handle streaming response
const stream = new ReadableStream({
start(c) {
const reader = res.body?.getReader()
const decoder = new TextDecoder()
let buffer = ""
let responseLength = 0
function pump(): Promise<void> {
return (
reader?.read().then(async ({ done, value }) => {
if (done) {
logger.metric({ response_length: responseLength })
const usage = opts.getStreamUsage()
if (usage) await trackUsage(usage)
c.close()
return
}
if (responseLength === 0) {
logger.metric({ time_to_first_byte: Date.now() - startTimestamp })
}
responseLength += value.length
buffer += decoder.decode(value, { stream: true })
const parts = buffer.split("\n\n")
buffer = parts.pop() ?? ""
for (const part of parts) {
logger.debug(part)
opts.onStreamPart(part.trim())
}
c.enqueue(value)
return pump()
}) || Promise.resolve()
)
}
return pump()
},
})
return new Response(stream, {
status: res.status,
statusText: res.statusText,
headers: resHeaders,
})
function validateModel() {
if (!(body.model in MODELS)) {
throw new ModelError(`Model ${body.model} not supported`)
}
const model = MODELS[body.model as keyof typeof MODELS]
logger.metric({ model: model.id })
return model
}
async function authenticate() {
try {
const apiKey = opts.parseApiKey(input.request.headers)
if (!apiKey) throw new AuthError("Missing API key.")
const key = await Database.use((tx) =>
tx
.select({
id: KeyTable.id,
workspaceID: KeyTable.workspaceID,
})
.from(KeyTable)
.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
.then((rows) => rows[0]),
)
if (!key) throw new AuthError("Invalid API key.")
logger.metric({
api_key: key.id,
workspace: key.workspaceID,
})
return key
} catch (e) {
// ignore error if model does not require authentication
if (!MODEL.auth) return
throw e
}
}
async function checkCredits() {
if (!apiKey || !MODEL.auth || isFree) return
const billing = await Database.use((tx) =>
tx
.select({
balance: BillingTable.balance,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
.then((rows) => rows[0]),
)
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
}
function selectProvider() {
const picks = Object.entries(MODEL.providers).flatMap(([name, provider]) =>
Array<string>(provider.weight ?? 1).fill(name),
)
return picks[Math.floor(Math.random() * picks.length)]
}
async function trackUsage(usage: any) {
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
opts.normalizeUsage(usage)
const modelCost = typeof MODEL.cost === "function" ? MODEL.cost(usage) : MODEL.cost
const inputCost = modelCost.input * inputTokens * 100
const outputCost = modelCost.output * outputTokens * 100
const reasoningCost = reasoningTokens ? modelCost.output * reasoningTokens * 100 : undefined
const cacheReadCost = modelCost.cacheRead * cacheReadTokens * 100
const cacheWrite5mCost = cacheWrite5mTokens ? modelCost.cacheWrite5m * cacheWrite5mTokens * 100 : undefined
const cacheWrite1hCost = cacheWrite1hTokens ? modelCost.cacheWrite1h * cacheWrite1hTokens * 100 : undefined
const totalCostInCent =
inputCost +
outputCost +
(reasoningCost ?? 0) +
cacheReadCost +
(cacheWrite5mCost ?? 0) +
(cacheWrite1hCost ?? 0)
logger.metric({
"tokens.input": inputTokens,
"tokens.output": outputTokens,
"tokens.reasoning": reasoningTokens,
"tokens.cache_read": cacheReadTokens,
"tokens.cache_write_5m": cacheWrite5mTokens,
"tokens.cache_write_1h": cacheWrite1hTokens,
"cost.input": Math.round(inputCost),
"cost.output": Math.round(outputCost),
"cost.reasoning": reasoningCost ? Math.round(reasoningCost) : undefined,
"cost.cache_read": Math.round(cacheReadCost),
"cost.cache_write_5m": cacheWrite5mCost ? Math.round(cacheWrite5mCost) : undefined,
"cost.cache_write_1h": cacheWrite1hCost ? Math.round(cacheWrite1hCost) : undefined,
"cost.total": Math.round(totalCostInCent),
})
if (!apiKey) return
const cost = isFree ? 0 : centsToMicroCents(totalCostInCent)
await Database.transaction(async (tx) => {
await tx.insert(UsageTable).values({
workspaceID: apiKey.workspaceID,
id: Identifier.create("usage"),
model: MODEL.id,
provider: providerName,
inputTokens,
outputTokens,
reasoningTokens,
cacheReadTokens,
cacheWriteTokens: (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0),
cost,
})
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} - ${cost}`,
})
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
})
await Database.use((tx) =>
tx
.update(KeyTable)
.set({ timeUsed: sql`now()` })
.where(eq(KeyTable.id, apiKey.id)),
)
}
} catch (error: any) {
logger.metric({
"error.type": error.constructor.name,
"error.message": error.message,
})
// Note: both top level "type" and "error.type" fields are used by the @ai-sdk/anthropic client to render the error message.
if (error instanceof AuthError || error instanceof CreditsError || error instanceof ModelError)
return new Response(
JSON.stringify({
type: "error",
error: { type: error.constructor.name, message: error.message },
}),
{ status: 401 },
)
return new Response(
JSON.stringify({
type: "error",
error: {
type: "error",
message: error.message,
},
}),
{ status: 500 },
)
}
}
-21
View File
@@ -1,21 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"allowJs": true,
"strict": true,
"noEmit": true,
"types": ["vinxi/types/client"],
"isolatedModules": true,
"paths": {
"~/*": ["./src/*"]
}
}
}
+2 -2
View File
@@ -1,12 +1,12 @@
import { Resource } from "sst"
import { defineConfig } from "drizzle-kit"
import { Resource } from "sst"
export default defineConfig({
out: "./migrations/",
strict: true,
schema: ["./src/**/*.sql.ts"],
verbose: true,
dialect: "mysql",
dialect: "postgresql",
dbCredentials: {
database: Resource.Database.database,
host: Resource.Database.host,
@@ -0,0 +1,66 @@
CREATE TABLE "billing" (
"id" varchar(30) NOT NULL,
"workspace_id" varchar(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"customer_id" varchar(255),
"payment_method_id" varchar(255),
"payment_method_last4" varchar(4),
"balance" bigint NOT NULL,
"reload" boolean,
CONSTRAINT "billing_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
);
--> statement-breakpoint
CREATE TABLE "payment" (
"id" varchar(30) NOT NULL,
"workspace_id" varchar(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"customer_id" varchar(255),
"payment_id" varchar(255),
"amount" bigint NOT NULL,
CONSTRAINT "payment_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
);
--> statement-breakpoint
CREATE TABLE "usage" (
"id" varchar(30) NOT NULL,
"workspace_id" varchar(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"request_id" varchar(255),
"model" varchar(255) NOT NULL,
"input_tokens" integer NOT NULL,
"output_tokens" integer NOT NULL,
"reasoning_tokens" integer,
"cache_read_tokens" integer,
"cache_write_tokens" integer,
"cost" bigint NOT NULL,
CONSTRAINT "usage_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
);
--> statement-breakpoint
CREATE TABLE "user" (
"id" varchar(30) NOT NULL,
"workspace_id" varchar(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"email" text NOT NULL,
"name" varchar(255) NOT NULL,
"time_seen" timestamp with time zone,
"color" integer,
CONSTRAINT "user_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
);
--> statement-breakpoint
CREATE TABLE "workspace" (
"id" varchar(30) PRIMARY KEY NOT NULL,
"slug" varchar(255),
"name" varchar(255),
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone
);
--> statement-breakpoint
ALTER TABLE "billing" ADD CONSTRAINT "billing_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "payment" ADD CONSTRAINT "payment_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "usage" ADD CONSTRAINT "usage_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user" ADD CONSTRAINT "user_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "user_email" ON "user" USING btree ("workspace_id","email");--> statement-breakpoint
CREATE UNIQUE INDEX "slug" ON "workspace" USING btree ("slug");
@@ -1,89 +0,0 @@
CREATE TABLE `account` (
`id` varchar(30) 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),
`email` varchar(255) NOT NULL,
CONSTRAINT `email` UNIQUE(`email`)
);
--> statement-breakpoint
CREATE TABLE `billing` (
`id` varchar(30) NOT NULL,
`workspace_id` varchar(30) 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),
`customer_id` varchar(255),
`payment_method_id` varchar(255),
`payment_method_last4` varchar(4),
`balance` bigint NOT NULL,
`reload` boolean,
CONSTRAINT `billing_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`)
);
--> statement-breakpoint
CREATE TABLE `payment` (
`id` varchar(30) NOT NULL,
`workspace_id` varchar(30) NOT NULL,
`time_created` timestamp(3) NOT NULL DEFAULT (now()),
`time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
`time_deleted` timestamp(3),
`customer_id` varchar(255),
`payment_id` varchar(255),
`amount` bigint NOT NULL,
CONSTRAINT `payment_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`)
);
--> statement-breakpoint
CREATE TABLE `usage` (
`id` varchar(30) NOT NULL,
`workspace_id` varchar(30) NOT NULL,
`time_created` timestamp(3) NOT NULL DEFAULT (now()),
`time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
`time_deleted` timestamp(3),
`model` varchar(255) NOT NULL,
`input_tokens` int NOT NULL,
`output_tokens` int NOT NULL,
`reasoning_tokens` int,
`cache_read_tokens` int,
`cache_write_tokens` int,
`cost` bigint NOT NULL,
CONSTRAINT `usage_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`)
);
--> statement-breakpoint
CREATE TABLE `key` (
`id` varchar(30) NOT NULL,
`workspace_id` varchar(30) 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),
`user_id` text NOT NULL,
`name` varchar(255) NOT NULL,
`key` varchar(255) NOT NULL,
`time_used` timestamp(3),
CONSTRAINT `key_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`),
CONSTRAINT `global_key` UNIQUE(`key`)
);
--> statement-breakpoint
CREATE TABLE `user` (
`id` varchar(30) NOT NULL,
`workspace_id` varchar(30) 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),
`email` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`time_seen` timestamp(3),
`color` int,
CONSTRAINT `user_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`),
CONSTRAINT `user_email` UNIQUE(`workspace_id`,`email`)
);
--> statement-breakpoint
CREATE TABLE `workspace` (
`id` varchar(30) NOT NULL,
`slug` varchar(255),
`name` varchar(255),
`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),
CONSTRAINT `workspace_id` PRIMARY KEY(`id`),
CONSTRAINT `slug` UNIQUE(`slug`)
);
@@ -1,2 +0,0 @@
ALTER TABLE `key` ADD `actor` json;--> statement-breakpoint
ALTER TABLE `key` DROP COLUMN `user_id`;
@@ -0,0 +1,8 @@
CREATE TABLE "account" (
"id" varchar(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"email" varchar(255) NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX "email" ON "account" USING btree ("email");
@@ -0,0 +1,14 @@
CREATE TABLE "key" (
"id" varchar(30) NOT NULL,
"workspace_id" varchar(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"user_id" text NOT NULL,
"name" varchar(255) NOT NULL,
"key" varchar(255) NOT NULL,
"time_used" timestamp with time zone,
CONSTRAINT "key_workspace_id_id_pk" PRIMARY KEY("workspace_id","id")
);
--> statement-breakpoint
ALTER TABLE "key" ADD CONSTRAINT "key_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "global_key" ON "key" USING btree ("key");
@@ -1 +0,0 @@
ALTER TABLE `key` ADD `old_name` varchar(255);
@@ -1 +0,0 @@
ALTER TABLE `key` ADD CONSTRAINT `name` UNIQUE(`workspace_id`,`name`);
@@ -0,0 +1 @@
ALTER TABLE "usage" DROP COLUMN "request_id";
@@ -1 +0,0 @@
ALTER TABLE `usage` ADD `provider` varchar(255);
+208 -316
View File
@@ -1,569 +1,461 @@
{
"version": "5",
"dialect": "mysql",
"id": "aee779c5-db1d-4655-95ec-6451c18455be",
"id": "9b5cec8c-8b59-4d7a-bb5c-76ade1c83d6f",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"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
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"email": {
"name": "email",
"columns": ["email"],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraint": {}
},
"billing": {
"public.billing": {
"name": "billing",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"payment_method_id": {
"name": "payment_method_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"payment_method_last4": {
"name": "payment_method_last4",
"type": "varchar(4)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"balance": {
"name": "balance",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"reload": {
"name": "reload",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"foreignKeys": {
"billing_workspace_id_workspace_id_fk": {
"name": "billing_workspace_id_workspace_id_fk",
"tableFrom": "billing",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"payment": {
"public.payment": {
"name": "payment",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"payment_id": {
"name": "payment_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"amount": {
"name": "amount",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"foreignKeys": {
"payment_workspace_id_workspace_id_fk": {
"name": "payment_workspace_id_workspace_id_fk",
"tableFrom": "payment",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"usage": {
"public.usage": {
"name": "usage",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"request_id": {
"name": "request_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"model": {
"name": "model",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"input_tokens": {
"name": "input_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"output_tokens": {
"name": "output_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"reasoning_tokens": {
"name": "reasoning_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"cache_read_tokens": {
"name": "cache_read_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"cache_write_tokens": {
"name": "cache_write_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"cost": {
"name": "cost",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"foreignKeys": {
"usage_workspace_id_workspace_id_fk": {
"name": "usage_workspace_id_workspace_id_fk",
"tableFrom": "usage",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"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
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"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
},
"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": {}
},
"user": {
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_seen": {
"name": "time_seen",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"color": {
"name": "color",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": ["workspace_id", "email"],
"isUnique": true
"columns": [
{
"expression": "workspace_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"user_workspace_id_workspace_id_fk": {
"name": "user_workspace_id_workspace_id_fk",
"tableFrom": "user",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"workspace": {
"public.workspace": {
"name": "workspace",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"primaryKey": true,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": ["slug"],
"isUnique": true
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": ["id"]
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
"tables": {}
}
}
}
+232 -286
View File
@@ -1,569 +1,515 @@
{
"version": "5",
"dialect": "mysql",
"id": "79b7ee25-1c1c-41ff-9bbf-754af257102b",
"prevId": "aee779c5-db1d-4655-95ec-6451c18455be",
"id": "bf9e9084-4073-4ecb-8e56-5610816c9589",
"prevId": "9b5cec8c-8b59-4d7a-bb5c-76ade1c83d6f",
"version": "7",
"dialect": "postgresql",
"tables": {
"account": {
"public.account": {
"name": "account",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {
"email": {
"name": "email",
"columns": ["email"],
"isUnique": true
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"billing": {
"public.billing": {
"name": "billing",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"payment_method_id": {
"name": "payment_method_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"payment_method_last4": {
"name": "payment_method_last4",
"type": "varchar(4)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"balance": {
"name": "balance",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"reload": {
"name": "reload",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"foreignKeys": {
"billing_workspace_id_workspace_id_fk": {
"name": "billing_workspace_id_workspace_id_fk",
"tableFrom": "billing",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"payment": {
"public.payment": {
"name": "payment",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"payment_id": {
"name": "payment_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"amount": {
"name": "amount",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"foreignKeys": {
"payment_workspace_id_workspace_id_fk": {
"name": "payment_workspace_id_workspace_id_fk",
"tableFrom": "payment",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"usage": {
"public.usage": {
"name": "usage",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"request_id": {
"name": "request_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"model": {
"name": "model",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"input_tokens": {
"name": "input_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"output_tokens": {
"name": "output_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"reasoning_tokens": {
"name": "reasoning_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"cache_read_tokens": {
"name": "cache_read_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"cache_write_tokens": {
"name": "cache_write_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"cost": {
"name": "cost",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"foreignKeys": {
"usage_workspace_id_workspace_id_fk": {
"name": "usage_workspace_id_workspace_id_fk",
"tableFrom": "usage",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"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
},
"actor": {
"name": "actor",
"type": "json",
"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
},
"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": {}
},
"user": {
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_seen": {
"name": "time_seen",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"color": {
"name": "color",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": ["workspace_id", "email"],
"isUnique": true
"columns": [
{
"expression": "workspace_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"user_workspace_id_workspace_id_fk": {
"name": "user_workspace_id_workspace_id_fk",
"tableFrom": "user",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"workspace": {
"public.workspace": {
"name": "workspace",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"primaryKey": true,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": ["slug"],
"isUnique": true
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": ["id"]
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
"tables": {}
}
}
}
+282 -243
View File
@@ -1,576 +1,615 @@
{
"version": "5",
"dialect": "mysql",
"id": "9f51ef52-31ac-4ace-8b6d-39b35efe9c81",
"prevId": "79b7ee25-1c1c-41ff-9bbf-754af257102b",
"id": "351e4956-74e0-4282-a23b-02f1a73fa38c",
"prevId": "bf9e9084-4073-4ecb-8e56-5610816c9589",
"version": "7",
"dialect": "postgresql",
"tables": {
"account": {
"public.account": {
"name": "account",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {
"email": {
"name": "email",
"columns": ["email"],
"isUnique": true
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"billing": {
"public.billing": {
"name": "billing",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"payment_method_id": {
"name": "payment_method_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"payment_method_last4": {
"name": "payment_method_last4",
"type": "varchar(4)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"balance": {
"name": "balance",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"reload": {
"name": "reload",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"foreignKeys": {
"billing_workspace_id_workspace_id_fk": {
"name": "billing_workspace_id_workspace_id_fk",
"tableFrom": "billing",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"payment": {
"public.payment": {
"name": "payment",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"payment_id": {
"name": "payment_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"amount": {
"name": "amount",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"foreignKeys": {
"payment_workspace_id_workspace_id_fk": {
"name": "payment_workspace_id_workspace_id_fk",
"tableFrom": "payment",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"usage": {
"public.usage": {
"name": "usage",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"request_id": {
"name": "request_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"model": {
"name": "model",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"input_tokens": {
"name": "input_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"output_tokens": {
"name": "output_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"reasoning_tokens": {
"name": "reasoning_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"cache_read_tokens": {
"name": "cache_read_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"cache_write_tokens": {
"name": "cache_write_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"cost": {
"name": "cost",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"foreignKeys": {
"usage_workspace_id_workspace_id_fk": {
"name": "usage_workspace_id_workspace_id_fk",
"tableFrom": "usage",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"key": {
"public.key": {
"name": "key",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"actor": {
"name": "actor",
"type": "json",
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"old_name": {
"name": "old_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": true
},
"key": {
"name": "key",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_used": {
"name": "time_used",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {
"global_key": {
"name": "global_key",
"columns": ["key"],
"isUnique": true
"columns": [
{
"expression": "key",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"key_workspace_id_workspace_id_fk": {
"name": "key_workspace_id_workspace_id_fk",
"tableFrom": "key",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"user": {
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_seen": {
"name": "time_seen",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"color": {
"name": "color",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": ["workspace_id", "email"],
"isUnique": true
"columns": [
{
"expression": "workspace_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"user_workspace_id_workspace_id_fk": {
"name": "user_workspace_id_workspace_id_fk",
"tableFrom": "user",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"workspace": {
"public.workspace": {
"name": "workspace",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"primaryKey": true,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": ["slug"],
"isUnique": true
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": ["id"]
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
"tables": {}
}
}
}
+276 -248
View File
@@ -1,581 +1,609 @@
{
"version": "5",
"dialect": "mysql",
"id": "26cebd59-f553-441c-a2b2-2f9578a0ad2b",
"prevId": "9f51ef52-31ac-4ace-8b6d-39b35efe9c81",
"id": "fa935883-9e51-4811-90c7-8967eefe458c",
"prevId": "351e4956-74e0-4282-a23b-02f1a73fa38c",
"version": "7",
"dialect": "postgresql",
"tables": {
"account": {
"public.account": {
"name": "account",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {
"email": {
"name": "email",
"columns": ["email"],
"isUnique": true
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"billing": {
"public.billing": {
"name": "billing",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"payment_method_id": {
"name": "payment_method_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"payment_method_last4": {
"name": "payment_method_last4",
"type": "varchar(4)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"balance": {
"name": "balance",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"reload": {
"name": "reload",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"foreignKeys": {
"billing_workspace_id_workspace_id_fk": {
"name": "billing_workspace_id_workspace_id_fk",
"tableFrom": "billing",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"payment": {
"public.payment": {
"name": "payment",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"payment_id": {
"name": "payment_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"amount": {
"name": "amount",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"foreignKeys": {
"payment_workspace_id_workspace_id_fk": {
"name": "payment_workspace_id_workspace_id_fk",
"tableFrom": "payment",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"usage": {
"public.usage": {
"name": "usage",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"model": {
"name": "model",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"input_tokens": {
"name": "input_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"output_tokens": {
"name": "output_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"reasoning_tokens": {
"name": "reasoning_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"cache_read_tokens": {
"name": "cache_read_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"cache_write_tokens": {
"name": "cache_write_tokens",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"cost": {
"name": "cost",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"foreignKeys": {
"usage_workspace_id_workspace_id_fk": {
"name": "usage_workspace_id_workspace_id_fk",
"tableFrom": "usage",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"key": {
"public.key": {
"name": "key",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"actor": {
"name": "actor",
"type": "json",
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"old_name": {
"name": "old_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": true
},
"key": {
"name": "key",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_used": {
"name": "time_used",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {
"global_key": {
"name": "global_key",
"columns": ["key"],
"isUnique": true
},
"name": {
"name": "name",
"columns": ["workspace_id", "name"],
"isUnique": true
"columns": [
{
"expression": "key",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"key_workspace_id_workspace_id_fk": {
"name": "key_workspace_id_workspace_id_fk",
"tableFrom": "key",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"user": {
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"time_seen": {
"name": "time_seen",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"color": {
"name": "color",
"type": "int",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": ["workspace_id", "email"],
"isUnique": true
"columns": [
{
"expression": "workspace_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"user_workspace_id_workspace_id_fk": {
"name": "user_workspace_id_workspace_id_fk",
"tableFrom": "user",
"tableTo": "workspace",
"columnsFrom": [
"workspace_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
"columns": [
"workspace_id",
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"workspace": {
"public.workspace": {
"name": "workspace",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"primaryKey": true,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"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)"
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": ["slug"],
"isUnique": true
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": ["id"]
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraint": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
"tables": {}
}
}
}
@@ -1,588 +0,0 @@
{
"version": "5",
"dialect": "mysql",
"id": "06dc6226-bfbb-4ccc-b4bc-f26070c3bed5",
"prevId": "26cebd59-f553-441c-a2b2-2f9578a0ad2b",
"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
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"email": {
"name": "email",
"columns": ["email"],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"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_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
},
"reload": {
"name": "reload",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"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
},
"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
}
},
"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": false,
"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_tokens": {
"name": "cache_write_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cost": {
"name": "cost",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
}
},
"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
},
"actor": {
"name": "actor",
"type": "json",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"old_name": {
"name": "old_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"key": {
"name": "key",
"type": "varchar(255)",
"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
},
"name": {
"name": "name",
"columns": ["workspace_id", "name"],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_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
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"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
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": ["workspace_id", "email"],
"isUnique": true
}
},
"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": false,
"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": {}
}
}
+14 -21
View File
@@ -1,41 +1,34 @@
{
"version": "7",
"dialect": "mysql",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "5",
"when": 1756796050935,
"tag": "0000_fluffy_raza",
"version": "7",
"when": 1754518198186,
"tag": "0000_amused_mojo",
"breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1756871639102,
"tag": "0001_serious_whistler",
"version": "7",
"when": 1754609655262,
"tag": "0001_thankful_chat",
"breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1757597611832,
"tag": "0002_violet_loners",
"version": "7",
"when": 1754627626945,
"tag": "0002_stale_jackal",
"breakpoints": true
},
{
"idx": 3,
"version": "5",
"when": 1757600397194,
"tag": "0003_dusty_clint_barton",
"breakpoints": true
},
{
"idx": 4,
"version": "5",
"when": 1757627357232,
"tag": "0004_first_mockingbird",
"version": "7",
"when": 1754672464106,
"tag": "0003_tranquil_spencer_smythe",
"breakpoints": true
}
]
}
}
+3 -9
View File
@@ -1,13 +1,11 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/cloud-core",
"version": "0.7.4",
"version": "0.4.13",
"private": true,
"type": "module",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@opencode/cloud-resource": "workspace:*",
"@planetscale/database": "1.19.0",
"drizzle-orm": "0.41.0",
"postgres": "3.4.7",
"stripe": "18.0.0",
@@ -17,13 +15,9 @@
"./*": "./src/*"
},
"scripts": {
"db": "sst shell drizzle-kit",
"db-dev": "sst shell --stage dev -- drizzle-kit",
"db-prod": "sst shell --stage production -- drizzle-kit",
"typecheck": "tsc --noEmit"
"db": "sst shell drizzle-kit"
},
"devDependencies": {
"drizzle-kit": "0.30.5",
"mysql2": "3.14.4"
"drizzle-kit": "0.30.5"
}
}
+1
View File
@@ -20,6 +20,7 @@ export namespace Actor {
properties: {
userID: string
workspaceID: string
email: string
}
}
+36 -85
View File
@@ -1,11 +1,12 @@
import { Resource } from "sst"
import { Stripe } from "stripe"
import { Database, eq, sql } from "./drizzle"
import { BillingTable, PaymentTable, UsageTable } from "./schema/billing.sql"
import { BillingTable, UsageTable } from "./schema/billing.sql"
import { Actor } from "./actor"
import { fn } from "./util/fn"
import { z } from "zod"
import { User } from "./user"
import { Resource } from "@opencode/cloud-resource"
import { Identifier } from "./identifier"
import { centsToMicroCents } from "./util/price"
export namespace Billing {
export const stripe = () =>
@@ -28,93 +29,43 @@ export namespace Billing {
)
}
export const payments = async () => {
return await Database.use((tx) =>
tx
.select()
.from(PaymentTable)
.where(eq(PaymentTable.workspaceID, Actor.workspace()))
.orderBy(sql`${PaymentTable.timeCreated} DESC`)
.limit(100),
)
}
export const usages = async () => {
return await Database.use((tx) =>
tx
.select()
.from(UsageTable)
.where(eq(UsageTable.workspaceID, Actor.workspace()))
.orderBy(sql`${UsageTable.timeCreated} DESC`)
.limit(100),
)
}
export const generateCheckoutUrl = fn(
export const consume = fn(
z.object({
successUrl: z.string(),
cancelUrl: z.string(),
requestID: z.string().optional(),
model: z.string(),
inputTokens: z.number(),
outputTokens: z.number(),
reasoningTokens: z.number().optional(),
cacheReadTokens: z.number().optional(),
cacheWriteTokens: z.number().optional(),
costInCents: z.number(),
}),
async (input) => {
const account = Actor.assert("user")
const { successUrl, cancelUrl } = input
const workspaceID = Actor.workspace()
const cost = centsToMicroCents(input.costInCents)
const user = await User.fromID(account.properties.userID)
const customer = await Billing.get()
const session = await Billing.stripe().checkout.sessions.create({
mode: "payment",
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: "opencode credits",
},
unit_amount: 2123, // $20 minimum + Stripe fee 4.4% + $0.30
},
quantity: 1,
},
],
payment_intent_data: {
setup_future_usage: "on_session",
},
...(customer.customerID
? { customer: customer.customerID }
: {
customer_email: user.email,
customer_creation: "always",
}),
metadata: {
workspaceID: Actor.workspace(),
},
currency: "usd",
payment_method_types: ["card"],
success_url: successUrl,
cancel_url: cancelUrl,
return await Database.transaction(async (tx) => {
await tx.insert(UsageTable).values({
workspaceID,
id: Identifier.create("usage"),
requestID: input.requestID,
model: input.model,
inputTokens: input.inputTokens,
outputTokens: input.outputTokens,
reasoningTokens: input.reasoningTokens,
cacheReadTokens: input.cacheReadTokens,
cacheWriteTokens: input.cacheWriteTokens,
cost,
})
const [updated] = await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} - ${cost}`,
})
.where(eq(BillingTable.workspaceID, workspaceID))
.returning()
return updated.balance
})
return session.url
},
)
export const generatePortalUrl = fn(
z.object({
returnUrl: z.string(),
}),
async (input) => {
const { returnUrl } = input
const customer = await Billing.get()
if (!customer?.customerID) {
throw new Error("No stripe customer ID")
}
const session = await Billing.stripe().billingPortal.sessions.create({
customer: customer.customerID,
return_url: returnUrl,
})
return session.url
},
)
}
+38 -30
View File
@@ -1,33 +1,39 @@
import { drizzle } from "drizzle-orm/planetscale-serverless"
import { Resource } from "@opencode/cloud-resource"
import { drizzle } from "drizzle-orm/postgres-js"
import { Resource } from "sst"
export * from "drizzle-orm"
import { Client } from "@planetscale/database"
import postgres from "postgres"
import { MySqlTransaction, type MySqlTransactionConfig } from "drizzle-orm/mysql-core"
import type { ExtractTablesWithRelations } from "drizzle-orm"
import type { PlanetScalePreparedQueryHKT, PlanetscaleQueryResultHKT } from "drizzle-orm/planetscale-serverless"
import { Context } from "../context"
import { memo } from "../util/memo"
export namespace Database {
export type Transaction = MySqlTransaction<
PlanetscaleQueryResultHKT,
PlanetScalePreparedQueryHKT,
Record<string, never>,
ExtractTablesWithRelations<Record<string, never>>
>
const client = memo(() => {
const result = new Client({
host: Resource.Database.host,
username: Resource.Database.username,
password: Resource.Database.password,
})
const db = drizzle(result, {})
return db
function createClient() {
const client = postgres({
idle_timeout: 30000,
connect_timeout: 30000,
host: Resource.Database.host,
database: Resource.Database.database,
user: Resource.Database.username,
password: Resource.Database.password,
port: Resource.Database.port,
ssl: {
rejectUnauthorized: false,
},
max: 1,
})
export type TxOrDb = Transaction | ReturnType<typeof client>
return drizzle(client, {})
}
import { PgTransaction, type PgTransactionConfig } from "drizzle-orm/pg-core"
import type { ExtractTablesWithRelations } from "drizzle-orm"
import type { PostgresJsQueryResultHKT } from "drizzle-orm/postgres-js"
import { Context } from "../context"
export namespace Database {
export type Transaction = PgTransaction<
PostgresJsQueryResultHKT,
Record<string, unknown>,
ExtractTablesWithRelations<Record<string, unknown>>
>
export type TxOrDb = Transaction | ReturnType<typeof createClient>
const TransactionContext = Context.create<{
tx: TxOrDb
@@ -40,13 +46,14 @@ export namespace Database {
return tx.transaction(callback)
} catch (err) {
if (err instanceof Context.NotFound) {
const client = createClient()
const effects: (() => void | Promise<void>)[] = []
const result = await TransactionContext.provide(
{
effects,
tx: client(),
tx: client,
},
() => callback(client()),
() => callback(client),
)
await Promise.all(effects.map((x) => x()))
return result
@@ -67,14 +74,15 @@ export namespace Database {
}
}
export async function transaction<T>(callback: (tx: TxOrDb) => Promise<T>, config?: MySqlTransactionConfig) {
export async function transaction<T>(callback: (tx: TxOrDb) => Promise<T>, config?: PgTransactionConfig) {
try {
const { tx } = TransactionContext.use()
return callback(tx)
} catch (err) {
if (err instanceof Context.NotFound) {
const client = createClient()
const effects: (() => void | Promise<void>)[] = []
const result = await client().transaction(async (tx) => {
const result = await client.transaction(async (tx) => {
return TransactionContext.provide({ tx, effects }, () => callback(tx))
}, config)
await Promise.all(effects.map((x) => x()))
+2 -6
View File
@@ -1,5 +1,4 @@
import { sql } from "drizzle-orm"
import { bigint, timestamp, varchar } from "drizzle-orm/mysql-core"
import { bigint, timestamp, varchar } from "drizzle-orm/pg-core"
export const ulid = (name: string) => varchar(name, { length: 30 })
@@ -16,7 +15,7 @@ export const id = () => ulid("id").notNull()
export const utc = (name: string) =>
timestamp(name, {
fsp: 3,
withTimezone: true,
})
export const currency = (name: string) =>
@@ -26,8 +25,5 @@ export const currency = (name: string) =>
export const timestamps = {
timeCreated: utc("time_created").notNull().defaultNow(),
timeUpdated: utc("time_updated")
.notNull()
.default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`),
timeDeleted: utc("time_deleted"),
}
-71
View File
@@ -1,71 +0,0 @@
import { z } from "zod"
import { fn } from "./util/fn"
import { Actor } from "./actor"
import { and, Database, eq, isNull, sql } from "./drizzle"
import { Identifier } from "./identifier"
import { KeyTable } from "./schema/key.sql"
export namespace Key {
export const list = async () => {
const workspace = Actor.workspace()
const keys = await Database.use((tx) =>
tx
.select()
.from(KeyTable)
.where(and(eq(KeyTable.workspaceID, workspace), isNull(KeyTable.timeDeleted)))
.orderBy(sql`${KeyTable.timeCreated} DESC`),
)
return keys
}
export const create = fn(z.object({ name: z.string().min(1).max(255) }), async (input) => {
const workspaceID = Actor.workspace()
const { name } = input
// Generate secret key: sk- + 64 random characters (upper, lower, numbers)
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
let secretKey = "sk-"
const array = new Uint32Array(64)
crypto.getRandomValues(array)
for (let i = 0, l = array.length; i < l; i++) {
secretKey += chars[array[i] % chars.length]
}
const keyID = Identifier.create("key")
await Database.use((tx) =>
tx.insert(KeyTable).values({
id: keyID,
workspaceID,
actor: Actor.use(),
name,
key: secretKey,
timeUsed: null,
}),
)
return keyID
})
export const remove = fn(z.object({ id: z.string() }), async (input) => {
const workspace = Actor.workspace()
await Database.transaction(async (tx) => {
const row = await tx
.select({
name: KeyTable.name,
})
.from(KeyTable)
.where(and(eq(KeyTable.id, input.id), eq(KeyTable.workspaceID, workspace)))
.then((rows) => rows[0])
if (!row) return
await tx
.update(KeyTable)
.set({
timeDeleted: sql`now()`,
oldName: row.name,
name: input.id, // Use the key ID as the name
})
.where(and(eq(KeyTable.id, input.id), eq(KeyTable.workspaceID, workspace)))
})
})
}
+2 -2
View File
@@ -1,7 +1,7 @@
import { mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
import { pgTable, uniqueIndex, varchar } from "drizzle-orm/pg-core"
import { id, timestamps } from "../drizzle/types"
export const AccountTable = mysqlTable(
export const AccountTable = pgTable(
"account",
{
id: id(),
+9 -10
View File
@@ -1,8 +1,8 @@
import { bigint, boolean, int, mysqlTable, varchar } from "drizzle-orm/mysql-core"
import { bigint, boolean, integer, pgTable, varchar } from "drizzle-orm/pg-core"
import { timestamps, workspaceColumns } from "../drizzle/types"
import { workspaceIndexes } from "./workspace.sql"
export const BillingTable = mysqlTable(
export const BillingTable = pgTable(
"billing",
{
...workspaceColumns,
@@ -16,7 +16,7 @@ export const BillingTable = mysqlTable(
(table) => [...workspaceIndexes(table)],
)
export const PaymentTable = mysqlTable(
export const PaymentTable = pgTable(
"payment",
{
...workspaceColumns,
@@ -28,18 +28,17 @@ export const PaymentTable = mysqlTable(
(table) => [...workspaceIndexes(table)],
)
export const UsageTable = mysqlTable(
export const UsageTable = pgTable(
"usage",
{
...workspaceColumns,
...timestamps,
model: varchar("model", { length: 255 }).notNull(),
provider: varchar("provider", { length: 255 }),
inputTokens: int("input_tokens").notNull(),
outputTokens: int("output_tokens").notNull(),
reasoningTokens: int("reasoning_tokens"),
cacheReadTokens: int("cache_read_tokens"),
cacheWriteTokens: int("cache_write_tokens"),
inputTokens: integer("input_tokens").notNull(),
outputTokens: integer("output_tokens").notNull(),
reasoningTokens: integer("reasoning_tokens"),
cacheReadTokens: integer("cache_read_tokens"),
cacheWriteTokens: integer("cache_write_tokens"),
cost: bigint("cost", { mode: "number" }).notNull(),
},
(table) => [...workspaceIndexes(table)],
+4 -10
View File
@@ -1,22 +1,16 @@
import { mysqlTable, varchar, uniqueIndex, json } from "drizzle-orm/mysql-core"
import { text, pgTable, varchar, uniqueIndex } from "drizzle-orm/pg-core"
import { timestamps, utc, workspaceColumns } from "../drizzle/types"
import { workspaceIndexes } from "./workspace.sql"
import { Actor } from "../actor"
export const KeyTable = mysqlTable(
export const KeyTable = pgTable(
"key",
{
...workspaceColumns,
...timestamps,
actor: json("actor").$type<Actor.Info>(),
userID: text("user_id").notNull(),
name: varchar("name", { length: 255 }).notNull(),
oldName: varchar("old_name", { length: 255 }),
key: varchar("key", { length: 255 }).notNull(),
timeUsed: utc("time_used"),
},
(table) => [
...workspaceIndexes(table),
uniqueIndex("global_key").on(table.key),
uniqueIndex("name").on(table.workspaceID, table.name),
],
(table) => [...workspaceIndexes(table), uniqueIndex("global_key").on(table.key)],
)
+4 -4
View File
@@ -1,16 +1,16 @@
import { text, mysqlTable, uniqueIndex, varchar, int } from "drizzle-orm/mysql-core"
import { text, pgTable, uniqueIndex, varchar, integer } from "drizzle-orm/pg-core"
import { timestamps, utc, workspaceColumns } from "../drizzle/types"
import { workspaceIndexes } from "./workspace.sql"
export const UserTable = mysqlTable(
export const UserTable = pgTable(
"user",
{
...workspaceColumns,
...timestamps,
email: varchar("email", { length: 255 }).notNull(),
email: text("email").notNull(),
name: varchar("name", { length: 255 }).notNull(),
timeSeen: utc("time_seen"),
color: int("color"),
color: integer("color"),
},
(table) => [...workspaceIndexes(table), uniqueIndex("user_email").on(table.workspaceID, table.email)],
)

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