Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48e12ade76 |
@@ -17,13 +17,11 @@ jobs:
|
|||||||
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
bun-version: 1.2.21
|
bun-version: 1.2.17
|
||||||
|
|
||||||
- run: bun install
|
- run: bun install
|
||||||
|
|
||||||
- run: bun sst deploy --stage=${{ github.ref_name }}
|
- run: bun sst deploy --stage=${{ github.ref_name }}
|
||||||
env:
|
env:
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
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 }}
|
STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }}
|
||||||
|
|||||||
@@ -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."
|
|
||||||
@@ -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
|
|
||||||
@@ -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."
|
|
||||||
@@ -2,7 +2,7 @@ name: discord
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [published] # fires only when a release is published
|
types: [published] # fires only when a release is published
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
notify:
|
notify:
|
||||||
|
|||||||
@@ -8,20 +8,22 @@ jobs:
|
|||||||
opencode:
|
opencode:
|
||||||
if: |
|
if: |
|
||||||
contains(github.event.comment.body, ' /oc') ||
|
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
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
|
||||||
contents: read
|
contents: read
|
||||||
pull-requests: read
|
id-token: write
|
||||||
issues: read
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Run opencode
|
- name: Run opencode
|
||||||
uses: sst/opencode/github@latest
|
uses: sst/opencode/github@latest
|
||||||
env:
|
env:
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
with:
|
with:
|
||||||
model: opencode/sonic
|
model: anthropic/claude-sonnet-4-20250514
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: 1.2.21
|
bun-version: 1.2.17
|
||||||
|
|
||||||
- run: git fetch --force --tags
|
- run: git fetch --force --tags
|
||||||
- run: bun install -g @vscode/vsce
|
- run: bun install -g @vscode/vsce
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
name: publish
|
name: publish
|
||||||
run-name: "${{ format('release {0}', inputs.bump) }}"
|
run-name: "${{ format('v{0}', inputs.version) }}"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
bump:
|
version:
|
||||||
description: "Bump major, minor, or patch"
|
description: "Version to publish"
|
||||||
required: true
|
required: true
|
||||||
type: choice
|
type: string
|
||||||
options:
|
title:
|
||||||
- major
|
description: "Custom title for this run"
|
||||||
- minor
|
required: false
|
||||||
- patch
|
type: string
|
||||||
|
|
||||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
@@ -37,37 +37,38 @@ jobs:
|
|||||||
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: 1.2.21
|
bun-version: 1.2.19
|
||||||
|
|
||||||
- name: Cache ~/.bun
|
- name: Cache ~/.bun
|
||||||
id: cache-bun
|
id: cache-bun
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.bun
|
path: ~/.bun
|
||||||
key: ${{ runner.os }}-bun-1-2-21-${{ hashFiles('bun.lock') }}
|
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-bun-1-2-21-
|
${{ runner.os }}-bun-
|
||||||
|
|
||||||
- name: Install makepkg
|
- name: Install makepkg
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y pacman-package-manager
|
sudo apt-get install -y pacman-package-manager
|
||||||
|
|
||||||
- name: Setup SSH for AUR
|
- name: Setup SSH for AUR
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa
|
echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa
|
||||||
chmod 600 ~/.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.email "opencode@sst.dev"
|
||||||
git config --global user.name "opencode"
|
git config --global user.name "opencode"
|
||||||
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install
|
run: bun install
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: |
|
run: |
|
||||||
./script/publish.ts
|
OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
|
||||||
env:
|
env:
|
||||||
OPENCODE_BUMP: ${{ inputs.bump }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
bun-version: 1.2.21
|
bun-version: 1.2.19
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install
|
run: bun install
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
.worktrees
|
|
||||||
.sst
|
.sst
|
||||||
.env
|
.env
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
openapi.json
|
openapi.json
|
||||||
playground
|
playground
|
||||||
tmp
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
model: openai/gpt-5
|
||||||
|
reasoningEffort: medium
|
||||||
description: ALWAYS use this when writing docs
|
description: ALWAYS use this when writing docs
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -6,26 +8,7 @@ You are an expert technical documentation writer
|
|||||||
|
|
||||||
You are not verbose
|
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
|
Chunks of text should not be more than 2 sentences long.
|
||||||
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:`
|
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
commit and push
|
|
||||||
|
|
||||||
make sure it includes a prefix like
|
|
||||||
docs:
|
|
||||||
tui:
|
|
||||||
core:
|
|
||||||
ci:
|
|
||||||
ignore:
|
|
||||||
wip:
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
description: hello world
|
|
||||||
---
|
|
||||||
|
|
||||||
hey there $ARGUMENTS
|
|
||||||
|
|
||||||
!`ls`
|
|
||||||
check out @README.md
|
|
||||||
@@ -10,7 +10,3 @@
|
|||||||
- AVOID `let` statements
|
- AVOID `let` statements
|
||||||
- PREFER single word variable names where possible
|
- PREFER single word variable names where possible
|
||||||
- Use as many bun apis as possible like Bun.file()
|
- Use as many bun apis as possible like Bun.file()
|
||||||
|
|
||||||
## Debugging
|
|
||||||
|
|
||||||
- To test opencode in the `packages/opencode` directory you can run `bun dev`
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,78 +1,46 @@
|
|||||||
# Download Stats
|
# Download Stats
|
||||||
|
|
||||||
| Date | GitHub Downloads | npm Downloads | Total |
|
| Date | GitHub Downloads | npm Downloads | Total |
|
||||||
| ---------- | ---------------- | ---------------- | ----------------- |
|
| ---------- | ---------------- | ---------------- | ---------------- |
|
||||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
| 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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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) |
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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.
|
|
||||||
@@ -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)
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { defineConfig } from "@solidjs/start/config"
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
middleware: "./src/middleware.ts",
|
|
||||||
vite: {
|
|
||||||
server: {
|
|
||||||
allowedHosts: true,
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
external: ["cloudflare:workers"],
|
|
||||||
},
|
|
||||||
minify: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
compatibilityDate: "2024-09-19",
|
|
||||||
preset: "cloudflare_module",
|
|
||||||
cloudflare: {
|
|
||||||
nodeCompat: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,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.2"
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 |
@@ -1,5 +0,0 @@
|
|||||||
User-agent: *
|
|
||||||
Allow: /
|
|
||||||
|
|
||||||
# Disallow shared content pages
|
|
||||||
Disallow: /s/
|
|
||||||
|
Before Width: | Height: | Size: 17 KiB |
@@ -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 +0,0 @@
|
|||||||
@import "./style/index.css";
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"/></svg>
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 212 B |
@@ -1,2 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512"><rect width="336" height="336" x="128" y="128" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" rx="57" ry="57"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"/></svg>
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 443 B |
|
Before Width: | Height: | Size: 902 KiB |
|
Before Width: | Height: | Size: 456 KiB |
|
Before Width: | Height: | Size: 998 KiB |
|
Before Width: | Height: | Size: 592 KiB |
@@ -1,19 +0,0 @@
|
|||||||
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M8.5 16.5H24.5V33H8.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
|
||||||
<path d="M48.5 16.5H64.5V33H48.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
|
||||||
<path d="M120.5 16.5H136.5V33H120.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
|
||||||
<path d="M160.5 16.5H176.5V33H160.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
|
||||||
<path d="M192.5 16.5H208.5V33H192.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
|
||||||
<path d="M232.5 16.5H248.5V33H232.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
|
||||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="white" fill-opacity="0.95"/>
|
|
||||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="white" fill-opacity="0.95"/>
|
|
||||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="white" fill-opacity="0.95"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="white" fill-opacity="0.95"/>
|
|
||||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
|
||||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="white" fill-opacity="0.5"/>
|
|
||||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="white" fill-opacity="0.5"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
|
||||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="white" fill-opacity="0.95"/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,18 +0,0 @@
|
|||||||
<svg width="288" height="50" viewBox="0 0 288 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M8 16.5H24V33H8V16.5Z" fill="black" fill-opacity="0.15"/>
|
|
||||||
<path d="M48 16.5H64V33H48V16.5Z" fill="black" fill-opacity="0.15"/>
|
|
||||||
<path d="M120 16.5H136V33H120V16.5Z" fill="black" fill-opacity="0.15"/>
|
|
||||||
<path d="M160 16.5H176V33H160V16.5Z" fill="black" fill-opacity="0.15"/>
|
|
||||||
<path d="M192 16.5H208V33H192V16.5Z" fill="black" fill-opacity="0.15"/>
|
|
||||||
<path d="M232 16.5H248V33H232V16.5Z" fill="black" fill-opacity="0.15"/>
|
|
||||||
<path d="M264 0H288V8.5H272V16.5H288V25H272V33H288V41.5H264V0Z" fill="black" fill-opacity="0.95"/>
|
|
||||||
<path d="M248 0H224V41.5H248V33H232V8.5H248V0Z" fill="black" fill-opacity="0.95"/>
|
|
||||||
<path d="M256 8.5H248V33H256V8.5Z" fill="black" fill-opacity="0.95"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184 0H216V41.5H184V0ZM208 8.5H192V33H208V8.5Z" fill="black" fill-opacity="0.95"/>
|
|
||||||
<path d="M144 8.5H136V41.5H144V8.5Z" fill="black" fill-opacity="0.55"/>
|
|
||||||
<path d="M136 0H112V41.5H120V8.5H136V0Z" fill="black" fill-opacity="0.55"/>
|
|
||||||
<path d="M80 0H104V8.5H88V16.5H104V25H88V33H104V41.5H80V0Z" fill="black" fill-opacity="0.55"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40 0H72V41.5H48V49.5H40V0ZM64 8.5H48V33H64V8.5Z" fill="black" fill-opacity="0.55"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="black" fill-opacity="0.55"/>
|
|
||||||
<path d="M152 0H176V8.5H160V33H176V41.5H152V0Z" fill="black" fill-opacity="0.95"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,12 +0,0 @@
|
|||||||
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="black"/>
|
|
||||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="black"/>
|
|
||||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="black"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="black"/>
|
|
||||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="black"/>
|
|
||||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="black"/>
|
|
||||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="black"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="black"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="black"/>
|
|
||||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="black"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 981 B |
@@ -1,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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// @refresh reload
|
|
||||||
import { mount, StartClient } from "@solidjs/start/client"
|
|
||||||
|
|
||||||
mount(() => <StartClient />, document.getElementById("app")!)
|
|
||||||
@@ -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 +0,0 @@
|
|||||||
/// <reference types="@solidjs/start/env" />
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { defineMiddleware } from "vinxi/http"
|
|
||||||
|
|
||||||
export default defineMiddleware({
|
|
||||||
onBeforeResponse() {},
|
|
||||||
})
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server"
|
|
||||||
import { AuthClient } from "~/context/auth"
|
|
||||||
|
|
||||||
export async function GET(input: APIEvent) {
|
|
||||||
const result = await AuthClient.authorize(new URL("./callback", input.request.url).toString(), "code")
|
|
||||||
return Response.redirect(result.url, 302)
|
|
||||||
}
|
|
||||||
@@ -1,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")
|
|
||||||
}
|
|
||||||
@@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { redirect } from "@solidjs/router"
|
|
||||||
|
|
||||||
export async function GET() {
|
|
||||||
return redirect("https://discord.gg/opencode")
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -1,308 +0,0 @@
|
|||||||
/**
|
|
||||||
* @deprecated Use zen/v1/chat/completions instead
|
|
||||||
*/
|
|
||||||
import { Resource } from "@opencode/cloud-resource"
|
|
||||||
import type { APIEvent } from "@solidjs/start/server"
|
|
||||||
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
|
||||||
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
|
|
||||||
import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
|
||||||
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
|
|
||||||
import { Identifier } from "@opencode/cloud-core/identifier.js"
|
|
||||||
|
|
||||||
const MODELS = {
|
|
||||||
// "anthropic/claude-sonnet-4": {
|
|
||||||
// auth: true,
|
|
||||||
// api: "https://api.anthropic.com",
|
|
||||||
// apiKey: Resource.ANTHROPIC_API_KEY.value,
|
|
||||||
// model: "claude-sonnet-4-20250514",
|
|
||||||
// cost: {
|
|
||||||
// input: 0.0000015,
|
|
||||||
// output: 0.000006,
|
|
||||||
// reasoning: 0.0000015,
|
|
||||||
// cacheRead: 0.0000001,
|
|
||||||
// cacheWrite: 0.0000001,
|
|
||||||
// },
|
|
||||||
// headerMappings: {},
|
|
||||||
// },
|
|
||||||
"qwen/qwen3-coder": {
|
|
||||||
id: "qwen/qwen3-coder",
|
|
||||||
auth: true,
|
|
||||||
api: "https://inference.baseten.co",
|
|
||||||
apiKey: Resource.BASETEN_API_KEY.value,
|
|
||||||
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
|
||||||
cost: {
|
|
||||||
input: 0.00000038,
|
|
||||||
output: 0.00000153,
|
|
||||||
reasoning: 0,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite: 0,
|
|
||||||
},
|
|
||||||
headerMappings: {},
|
|
||||||
},
|
|
||||||
"grok-code": {
|
|
||||||
id: "x-ai/grok-code-fast-1",
|
|
||||||
auth: false,
|
|
||||||
api: "https://api.x.ai",
|
|
||||||
apiKey: Resource.XAI_API_KEY.value,
|
|
||||||
model: "grok-code",
|
|
||||||
cost: {
|
|
||||||
input: 0,
|
|
||||||
output: 0,
|
|
||||||
reasoning: 0,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite: 0,
|
|
||||||
},
|
|
||||||
headerMappings: {
|
|
||||||
"x-grok-conv-id": "x-opencode-session",
|
|
||||||
"x-grok-req-id": "x-opencode-request",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuthError extends Error {}
|
|
||||||
class CreditsError extends Error {}
|
|
||||||
class ModelError extends Error {}
|
|
||||||
|
|
||||||
export async function POST(input: APIEvent) {
|
|
||||||
try {
|
|
||||||
const url = new URL(input.request.url)
|
|
||||||
const body = await input.request.json()
|
|
||||||
const MODEL = validateModel()
|
|
||||||
const apiKey = await authenticate()
|
|
||||||
await checkCredits()
|
|
||||||
|
|
||||||
// Request to model provider
|
|
||||||
const res = await fetch(new URL(url.pathname.replace(/^\/gateway/, "") + url.search, MODEL.api), {
|
|
||||||
method: "POST",
|
|
||||||
headers: (() => {
|
|
||||||
const headers = input.request.headers
|
|
||||||
headers.delete("host")
|
|
||||||
headers.delete("content-length")
|
|
||||||
headers.set("authorization", `Bearer ${MODEL.apiKey}`)
|
|
||||||
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
|
|
||||||
headers.set(k, headers.get(v)!)
|
|
||||||
})
|
|
||||||
return headers
|
|
||||||
})(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
...body,
|
|
||||||
model: MODEL.model,
|
|
||||||
stream_options: {
|
|
||||||
include_usage: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Scrub response headers
|
|
||||||
const resHeaders = new Headers()
|
|
||||||
const keepHeaders = ["content-type", "cache-control"]
|
|
||||||
for (const [k, v] of res.headers.entries()) {
|
|
||||||
if (keepHeaders.includes(k.toLowerCase())) {
|
|
||||||
resHeaders.set(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle non-streaming response
|
|
||||||
if (!body.stream) {
|
|
||||||
const body = await res.json()
|
|
||||||
await trackUsage(body)
|
|
||||||
return new Response(JSON.stringify(body), {
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: resHeaders,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle streaming response
|
|
||||||
const stream = new ReadableStream({
|
|
||||||
start(c) {
|
|
||||||
const reader = res.body?.getReader()
|
|
||||||
const decoder = new TextDecoder()
|
|
||||||
let buffer = ""
|
|
||||||
|
|
||||||
function pump(): Promise<void> {
|
|
||||||
return (
|
|
||||||
reader?.read().then(async ({ done, value }) => {
|
|
||||||
if (done) {
|
|
||||||
c.close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer += decoder.decode(value, { stream: true })
|
|
||||||
|
|
||||||
const parts = buffer.split("\n\n")
|
|
||||||
buffer = parts.pop() ?? ""
|
|
||||||
|
|
||||||
const usage = parts
|
|
||||||
.map((part) => part.trim())
|
|
||||||
.filter((part) => part.startsWith("data: "))
|
|
||||||
.map((part) => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(part.slice(6))
|
|
||||||
} catch (e) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.find((part) => part.usage)
|
|
||||||
if (usage) await trackUsage(usage)
|
|
||||||
|
|
||||||
c.enqueue(value)
|
|
||||||
|
|
||||||
return pump()
|
|
||||||
}) || Promise.resolve()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pump()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return new Response(stream, {
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: resHeaders,
|
|
||||||
})
|
|
||||||
|
|
||||||
function validateModel() {
|
|
||||||
if (!(body.model in MODELS)) {
|
|
||||||
throw new ModelError(`Model ${body.model} not supported`)
|
|
||||||
}
|
|
||||||
return MODELS[body.model as keyof typeof MODELS]
|
|
||||||
}
|
|
||||||
|
|
||||||
async function authenticate() {
|
|
||||||
try {
|
|
||||||
const authHeader = input.request.headers.get("authorization")
|
|
||||||
if (!authHeader || !authHeader.startsWith("Bearer ")) throw new AuthError("Missing API key.")
|
|
||||||
|
|
||||||
const apiKey = authHeader.split(" ")[1]
|
|
||||||
const key = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select({
|
|
||||||
id: KeyTable.id,
|
|
||||||
workspaceID: KeyTable.workspaceID,
|
|
||||||
})
|
|
||||||
.from(KeyTable)
|
|
||||||
.where(eq(KeyTable.key, apiKey))
|
|
||||||
.then((rows) => rows[0]),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!key) throw new AuthError("Invalid API key.")
|
|
||||||
return key
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
// ignore error if model does not require authentication
|
|
||||||
if (!MODEL.auth) return
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkCredits() {
|
|
||||||
if (!apiKey || !MODEL.auth) return
|
|
||||||
|
|
||||||
const billing = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select({
|
|
||||||
balance: BillingTable.balance,
|
|
||||||
})
|
|
||||||
.from(BillingTable)
|
|
||||||
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
|
|
||||||
.then((rows) => rows[0]),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
|
|
||||||
}
|
|
||||||
|
|
||||||
async function trackUsage(chunk: any) {
|
|
||||||
console.log(`trackUsage ${apiKey}`)
|
|
||||||
|
|
||||||
if (!apiKey) return
|
|
||||||
|
|
||||||
const usage = chunk.usage
|
|
||||||
const inputTokens = usage.prompt_tokens ?? 0
|
|
||||||
const outputTokens = usage.completion_tokens ?? 0
|
|
||||||
const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? 0
|
|
||||||
const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens ?? 0
|
|
||||||
//const cacheWriteTokens = providerMetadata?.["anthropic"]?.["cacheCreationInputTokens"] ?? 0
|
|
||||||
const cacheWriteTokens = 0
|
|
||||||
|
|
||||||
const inputCost = MODEL.cost.input * inputTokens
|
|
||||||
const outputCost = MODEL.cost.output * outputTokens
|
|
||||||
const reasoningCost = MODEL.cost.reasoning * reasoningTokens
|
|
||||||
const cacheReadCost = MODEL.cost.cacheRead * cacheReadTokens
|
|
||||||
const cacheWriteCost = MODEL.cost.cacheWrite * cacheWriteTokens
|
|
||||||
const costInCents = (inputCost + outputCost + reasoningCost + cacheReadCost + cacheWriteCost) * 100
|
|
||||||
const cost = centsToMicroCents(costInCents)
|
|
||||||
|
|
||||||
await Database.transaction(async (tx) => {
|
|
||||||
await tx.insert(UsageTable).values({
|
|
||||||
workspaceID: apiKey.workspaceID,
|
|
||||||
id: Identifier.create("usage"),
|
|
||||||
model: MODEL.id,
|
|
||||||
inputTokens,
|
|
||||||
outputTokens,
|
|
||||||
reasoningTokens,
|
|
||||||
cacheReadTokens,
|
|
||||||
cacheWriteTokens,
|
|
||||||
cost,
|
|
||||||
})
|
|
||||||
await tx
|
|
||||||
.update(BillingTable)
|
|
||||||
.set({
|
|
||||||
balance: sql`${BillingTable.balance} - ${cost}`,
|
|
||||||
})
|
|
||||||
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
|
|
||||||
})
|
|
||||||
|
|
||||||
await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.update(KeyTable)
|
|
||||||
.set({ timeUsed: sql`now()` })
|
|
||||||
.where(eq(KeyTable.id, apiKey.id)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error instanceof AuthError) {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
error: {
|
|
||||||
message: error.message,
|
|
||||||
type: "invalid_request_error",
|
|
||||||
param: null,
|
|
||||||
code: "unauthorized",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof CreditsError) {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
error: {
|
|
||||||
message: error.message,
|
|
||||||
type: "insufficient_quota",
|
|
||||||
param: null,
|
|
||||||
code: "insufficient_quota",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof ModelError) {
|
|
||||||
return new Response(JSON.stringify({ error: { message: error.message } }), {
|
|
||||||
status: 401,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(error)
|
|
||||||
return new Response(JSON.stringify({ error: { message: error.message } }), {
|
|
||||||
status: 500,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,504 +0,0 @@
|
|||||||
[data-page="home"] {
|
|
||||||
--color-text: hsl(224, 10%, 10%);
|
|
||||||
--color-text-secondary: hsl(224, 7%, 46%);
|
|
||||||
--color-text-dimmed: hsl(224, 6%, 63%);
|
|
||||||
--color-text-inverted: hsl(0, 0%, 100%);
|
|
||||||
|
|
||||||
--color-border: hsl(224, 6%, 77%);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-page="home"] {
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
--color-text: hsl(0, 0%, 100%);
|
|
||||||
--color-text-secondary: hsl(224, 6%, 66%);
|
|
||||||
--color-text-dimmed: hsl(224, 7%, 46%);
|
|
||||||
--color-text-inverted: hsl(224, 10%, 10%);
|
|
||||||
|
|
||||||
--color-border: hsl(224, 6%, 36%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-page="home"] {
|
|
||||||
--padding: 3rem;
|
|
||||||
--vertical-padding: 1.5rem;
|
|
||||||
--heading-font-size: 1.375rem;
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
--padding: 1rem;
|
|
||||||
--vertical-padding: 0.75rem;
|
|
||||||
--heading-font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
gap: var(--vertical-padding);
|
|
||||||
flex-direction: column;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
color: var(--color-text);
|
|
||||||
padding: calc(var(--padding) + 1rem);
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--color-text);
|
|
||||||
text-decoration: underline;
|
|
||||||
text-underline-offset: var(--space-0-75);
|
|
||||||
text-decoration-thickness: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="content"] {
|
|
||||||
max-width: 67.5rem;
|
|
||||||
margin: 0 auto;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="top"] {
|
|
||||||
padding: calc(var(--padding) * 1.5) var(--padding) var(--padding);
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: calc(var(--vertical-padding) / 2);
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: auto;
|
|
||||||
width: clamp(200px, 85vw, 552px);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="logo dark"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
[data-slot="logo light"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
[data-slot="logo dark"] {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="title"] {
|
|
||||||
line-height: 1.25;
|
|
||||||
font-weight: 500;
|
|
||||||
text-align: center;
|
|
||||||
font-size: var(--heading-font-size);
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="login"] {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
border-width: 0 0 1px 1px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: var(--color-border);
|
|
||||||
background-color: var(--color-bg);
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: block;
|
|
||||||
padding: 0.5rem 1rem calc(0.5rem + 4px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="cta"] {
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
& > div + div {
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="left"] {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.4;
|
|
||||||
padding: var(--vertical-padding) 2rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
font-size: 1rem;
|
|
||||||
padding-bottom: calc(var(--vertical-padding) + 4px);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
padding-left: 0.5rem;
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="center"] {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
display: block;
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
padding: var(--vertical-padding) 0.5rem;
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
border-left: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="right"] {
|
|
||||||
flex: 1;
|
|
||||||
padding: var(--vertical-padding) 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 50rem) {
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
[data-slot="right"] {
|
|
||||||
border-left: none;
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="command"] {
|
|
||||||
all: unset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
font-size: 1.125rem;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
gap: var(--space-2);
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
& > span {
|
|
||||||
@media (max-width: 24rem) {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
@media (max-width: 56rem) {
|
|
||||||
[data-slot="protocol"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 38rem) {
|
|
||||||
text-align: center;
|
|
||||||
span:first-child {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="highlight"] {
|
|
||||||
color: var(--color-text);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="features"] {
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
padding: var(--padding);
|
|
||||||
|
|
||||||
[data-slot="list"] {
|
|
||||||
padding-left: var(--space-4);
|
|
||||||
margin: 0;
|
|
||||||
list-style: disc;
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom: var(--space-4);
|
|
||||||
line-height: 1.6;
|
|
||||||
|
|
||||||
strong {
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
line-height: 1;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
letter-spacing: 0.03125rem;
|
|
||||||
background: var(--color-border);
|
|
||||||
padding: 0.125rem 0.375rem;
|
|
||||||
color: var(--color-text-inverted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
li:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="install"] {
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-template-rows: 1fr 1fr;
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-rows: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="method"] {
|
|
||||||
display: flex;
|
|
||||||
padding: calc(var(--vertical-padding) / 2) calc(var(--padding) / 2) calc(var(--vertical-padding) / 2 + 0.125rem);
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: left;
|
|
||||||
gap: var(--space-2-5);
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
gap: 0.3125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
border-left: none;
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(3) {
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(4) {
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
border-left: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="title"] {
|
|
||||||
letter-spacing: -0.03125rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 1rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: var(--color-text-dimmed);
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="button"] {
|
|
||||||
all: unset;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
gap: var(--space-2-5);
|
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
@media (max-width: 24rem) {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
|
||||||
color: var(--color-text);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="screenshots"] {
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
|
|
||||||
figure {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: calc(var(--padding) / 4);
|
|
||||||
padding: calc(var(--padding) / 2);
|
|
||||||
border-width: 0;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: var(--color-border);
|
|
||||||
min-height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
& > div,
|
|
||||||
figcaption {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
figcaption {
|
|
||||||
letter-spacing: -0.03125rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--color-text-dimmed);
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& > [data-slot="left"] figure {
|
|
||||||
height: var(--images-height);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > [data-slot="right"] figure {
|
|
||||||
height: calc(var(--images-height) / 2);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > [data-slot="left"] img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > [data-slot="right"] img {
|
|
||||||
width: 100%;
|
|
||||||
height: calc(100% - 2rem);
|
|
||||||
object-fit: contain;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
& {
|
|
||||||
--images-height: auto;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-rows: auto auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > [data-slot="left"] {
|
|
||||||
grid-row: 1;
|
|
||||||
grid-column: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > [data-slot="right"] {
|
|
||||||
grid-row: 2;
|
|
||||||
grid-column: 1;
|
|
||||||
border-left: none;
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
|
|
||||||
& > [data-slot="row1"],
|
|
||||||
& > [data-slot="row2"] {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& > [data-slot="left"] figure,
|
|
||||||
& > [data-slot="right"] figure {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > [data-slot="left"] img,
|
|
||||||
& > [data-slot="right"] img {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="copy-status"] {
|
|
||||||
@media (max-width: 38rem) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="copy"] {
|
|
||||||
display: block;
|
|
||||||
width: var(--space-4);
|
|
||||||
height: var(--space-4);
|
|
||||||
color: var(--color-text-dimmed);
|
|
||||||
|
|
||||||
[data-copied] & {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="check"] {
|
|
||||||
display: none;
|
|
||||||
width: var(--space-4);
|
|
||||||
height: var(--space-4);
|
|
||||||
color: var(--color-text);
|
|
||||||
|
|
||||||
[data-copied] & {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="footer"] {
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
[data-slot="cell"] {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
text-transform: uppercase;
|
|
||||||
padding: var(--vertical-padding) 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="cell"] + [data-slot="cell"] {
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile: third column on its own row */
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
[data-slot="cell"]:nth-child(1),
|
|
||||||
[data-slot="cell"]:nth-child(2) {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="cell"]:nth-child(3) {
|
|
||||||
flex: 1 0 100%;
|
|
||||||
border-left: none;
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="legal"] {
|
|
||||||
color: var(--color-text-dimmed);
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--color-text-dimmed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import { Billing } from "@opencode/cloud-core/billing.js"
|
|
||||||
import type { APIEvent } from "@solidjs/start/server"
|
|
||||||
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
|
||||||
import { BillingTable, PaymentTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
|
||||||
import { Identifier } from "@opencode/cloud-core/identifier.js"
|
|
||||||
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
|
|
||||||
import { Actor } from "@opencode/cloud-core/actor.js"
|
|
||||||
import { Resource } from "@opencode/cloud-resource"
|
|
||||||
|
|
||||||
export async function POST(input: APIEvent) {
|
|
||||||
const body = await Billing.stripe().webhooks.constructEventAsync(
|
|
||||||
await input.request.text(),
|
|
||||||
input.request.headers.get("stripe-signature")!,
|
|
||||||
Resource.STRIPE_WEBHOOK_SECRET.value,
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(body.type, JSON.stringify(body, null, 2))
|
|
||||||
if (body.type === "checkout.session.completed") {
|
|
||||||
const workspaceID = body.data.object.metadata?.workspaceID
|
|
||||||
const customerID = body.data.object.customer as string
|
|
||||||
const paymentID = body.data.object.payment_intent as string
|
|
||||||
const amount = body.data.object.amount_total
|
|
||||||
|
|
||||||
if (!workspaceID) throw new Error("Workspace ID not found")
|
|
||||||
if (!customerID) throw new Error("Customer ID not found")
|
|
||||||
if (!amount) throw new Error("Amount not found")
|
|
||||||
if (!paymentID) throw new Error("Payment ID not found")
|
|
||||||
|
|
||||||
const chargedAmount = 2000
|
|
||||||
|
|
||||||
await Actor.provide("system", { workspaceID }, async () => {
|
|
||||||
const customer = await Billing.get()
|
|
||||||
if (customer?.customerID && customer.customerID !== customerID) throw new Error("Customer ID mismatch")
|
|
||||||
|
|
||||||
// set customer metadata
|
|
||||||
if (!customer?.customerID) {
|
|
||||||
await Billing.stripe().customers.update(customerID, {
|
|
||||||
metadata: {
|
|
||||||
workspaceID,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// get payment method for the payment intent
|
|
||||||
const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
|
|
||||||
expand: ["payment_method"],
|
|
||||||
})
|
|
||||||
const paymentMethod = paymentIntent.payment_method
|
|
||||||
if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
|
|
||||||
|
|
||||||
await Database.transaction(async (tx) => {
|
|
||||||
await tx
|
|
||||||
.update(BillingTable)
|
|
||||||
.set({
|
|
||||||
balance: sql`${BillingTable.balance} + ${centsToMicroCents(chargedAmount)}`,
|
|
||||||
customerID,
|
|
||||||
paymentMethodID: paymentMethod.id,
|
|
||||||
paymentMethodLast4: paymentMethod.card!.last4,
|
|
||||||
})
|
|
||||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
|
||||||
await tx.insert(PaymentTable).values({
|
|
||||||
workspaceID,
|
|
||||||
id: Identifier.create("payment"),
|
|
||||||
amount: centsToMicroCents(chargedAmount),
|
|
||||||
paymentID,
|
|
||||||
customerID,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("finished handling")
|
|
||||||
|
|
||||||
return Response.json("ok", { status: 200 })
|
|
||||||
}
|
|
||||||
@@ -1,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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,634 +0,0 @@
|
|||||||
[data-page="workspace-[id]"] {
|
|
||||||
max-width: 64rem;
|
|
||||||
padding: var(--space-10) var(--space-4);
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-10);
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
padding-top: var(--space-4);
|
|
||||||
padding-bottom: var(--space-4);
|
|
||||||
|
|
||||||
gap: var(--space-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="sections"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-16);
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
gap: var(--space-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-6);
|
|
||||||
|
|
||||||
/* Section titles */
|
|
||||||
[data-slot="section-title"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-1);
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: var(--font-size-md);
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.2;
|
|
||||||
letter-spacing: -0.03125rem;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
font-size: var(--font-size-md);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
section:not(:last-child) {
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
padding-bottom: var(--space-16);
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
padding-bottom: var(--space-8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="empty-state"] {
|
|
||||||
padding: var(--space-20) var(--space-6);
|
|
||||||
text-align: center;
|
|
||||||
border: 1px dashed var(--color-border);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-2);
|
|
||||||
|
|
||||||
p {
|
|
||||||
line-height: 1.5;
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Title section */
|
|
||||||
[data-component="title-section"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-2);
|
|
||||||
padding-bottom: var(--space-8);
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
padding-bottom: var(--space-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: var(--font-size-2xl);
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1.2;
|
|
||||||
letter-spacing: -0.03125rem;
|
|
||||||
margin: 0;
|
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
font-size: var(--font-size-xl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
line-height: 1.5;
|
|
||||||
font-size: var(--font-size-md);
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* API Keys Section */
|
|
||||||
[data-component="api-keys-section"] {
|
|
||||||
[data-slot="create-form"] {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--space-3);
|
|
||||||
padding: var(--space-4);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
gap: var(--space-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
flex: 1;
|
|
||||||
padding: var(--space-2) var(--space-3);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
background-color: var(--color-bg);
|
|
||||||
color: var(--color-text);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: var(--color-text-disabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="form-actions"] {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--space-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="api-keys-table"] {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="api-keys-table-element"] {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
|
|
||||||
thead {
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
padding: var(--space-3) var(--space-4);
|
|
||||||
text-align: left;
|
|
||||||
font-weight: normal;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: var(--space-3) var(--space-4);
|
|
||||||
border-bottom: 1px solid var(--color-border-muted);
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
|
|
||||||
&[data-slot="key-name"] {
|
|
||||||
color: var(--color-text);
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-slot="key-value"] {
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
|
|
||||||
button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-2);
|
|
||||||
padding: var(--space-2) var(--space-3);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
font-weight: 400;
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.15s ease;
|
|
||||||
text-transform: none;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background-color: var(--color-bg-surface);
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
cursor: default;
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-slot="key-date"] {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-slot="key-actions"] {
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr {
|
|
||||||
&:last-child td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
padding: var(--space-2) var(--space-3);
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
&:nth-child(3) /* Date */ {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
&:nth-child(3) /* Date */ {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Balance Section */
|
|
||||||
[data-component="balance-section"] {
|
|
||||||
[data-slot="balance"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-3);
|
|
||||||
padding: var(--space-4);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
min-width: 14.5rem;
|
|
||||||
width: fit-content;
|
|
||||||
|
|
||||||
[data-slot="amount"] {
|
|
||||||
padding: var(--space-3-5) var(--space-4);
|
|
||||||
background-color: var(--color-bg-surface);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: var(--space-1);
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
&[data-state="danger"] {
|
|
||||||
[data-slot="value"] {
|
|
||||||
color: var(--color-danger);
|
|
||||||
}
|
|
||||||
[data-slot="currency"] {
|
|
||||||
color: var(--color-danger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="currency"] {
|
|
||||||
position: relative;
|
|
||||||
bottom: 2px;
|
|
||||||
font-size: var(--font-size-lg);
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="value"] {
|
|
||||||
font-size: var(--font-size-3xl);
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Payments Section */
|
|
||||||
[data-component="payments-section"] {
|
|
||||||
[data-slot="payments-table"] {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="payments-table-element"] {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
|
|
||||||
thead {
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
padding: var(--space-3) var(--space-4);
|
|
||||||
text-align: left;
|
|
||||||
font-weight: normal;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: var(--space-3) var(--space-4);
|
|
||||||
border-bottom: 1px solid var(--color-border-muted);
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
|
|
||||||
&[data-slot="payment-date"] {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-slot="payment-id"] {
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
max-width: 200px;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-slot="payment-amount"] {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr {
|
|
||||||
&:last-child td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
padding: var(--space-2) var(--space-3);
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
&:nth-child(2) /* Payment ID */ {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
&:nth-child(2) /* Payment ID */ {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Usage Section */
|
|
||||||
[data-component="usage-section"] {
|
|
||||||
[data-slot="usage-table"] {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="usage-table-element"] {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
|
|
||||||
thead {
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
padding: var(--space-3) var(--space-4);
|
|
||||||
text-align: left;
|
|
||||||
font-weight: normal;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: var(--space-3) var(--space-4);
|
|
||||||
border-bottom: 1px solid var(--color-border-muted);
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
|
|
||||||
&[data-slot="usage-date"] {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-slot="usage-model"] {
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
max-width: 200px;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-slot="usage-cost"] {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr {
|
|
||||||
&:last-child td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
padding: var(--space-2) var(--space-3);
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
&:nth-child(2) /* Model */ {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
&:nth-child(2) /* Model */ {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="new-user-sections"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-16);
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
gap: var(--space-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="feature-grid"] {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
||||||
gap: var(--space-6);
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: var(--space-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="feature"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-2);
|
|
||||||
padding: var(--space-4);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
background-color: var(--color-bg-surface);
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--color-text);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: -0.025rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
line-height: 1.5;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="api-key-highlight"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-6);
|
|
||||||
|
|
||||||
[data-slot="section-title"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-1);
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: var(--font-size-md);
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.2;
|
|
||||||
letter-spacing: -0.03125rem;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
font-size: var(--font-size-md);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="key-display"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-3);
|
|
||||||
|
|
||||||
[data-slot="key-container"] {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--space-3);
|
|
||||||
padding: var(--space-4);
|
|
||||||
border: 2px solid var(--color-accent);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
background-color: var(--color-bg-surface);
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-3);
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="key-value"] {
|
|
||||||
flex: 1;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
color: var(--color-text);
|
|
||||||
background-color: var(--color-bg);
|
|
||||||
padding: var(--space-3);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
word-break: break-all;
|
|
||||||
line-height: 1.4;
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
padding: var(--space-2-5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-2);
|
|
||||||
padding: var(--space-3) var(--space-4);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
font-weight: 500;
|
|
||||||
white-space: nowrap;
|
|
||||||
min-width: 130px;
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
justify-content: center;
|
|
||||||
padding: var(--space-2-5) var(--space-3);
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
min-width: 96px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="next-steps"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-6);
|
|
||||||
|
|
||||||
[data-slot="section-title"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-1);
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: var(--font-size-md);
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.2;
|
|
||||||
letter-spacing: -0.03125rem;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
@media (max-width: 30rem) {
|
|
||||||
font-size: var(--font-size-md);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ol {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-2);
|
|
||||||
list-style-position: inside;
|
|
||||||
|
|
||||||
li {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
line-height: 1.5;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
padding: var(--space-1) var(--space-2);
|
|
||||||
background-color: var(--color-bg-surface);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,588 +0,0 @@
|
|||||||
import "./[id].css"
|
|
||||||
import { Billing } from "@opencode/cloud-core/billing.js"
|
|
||||||
import { Key } from "@opencode/cloud-core/key.js"
|
|
||||||
import { json, query, action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router"
|
|
||||||
import { createMemo, createSignal, For, Show } from "solid-js"
|
|
||||||
import { withActor } from "~/context/auth.withActor"
|
|
||||||
import { IconCopy, IconCheck } from "~/component/icon"
|
|
||||||
|
|
||||||
function formatDateForTable(date: Date) {
|
|
||||||
const options: Intl.DateTimeFormatOptions = {
|
|
||||||
day: "numeric",
|
|
||||||
month: "short",
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "2-digit",
|
|
||||||
hour12: true,
|
|
||||||
}
|
|
||||||
return date.toLocaleDateString("en-GB", options).replace(",", ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDateUTC(date: Date) {
|
|
||||||
const options: Intl.DateTimeFormatOptions = {
|
|
||||||
weekday: "short",
|
|
||||||
year: "numeric",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit",
|
|
||||||
timeZoneName: "short",
|
|
||||||
timeZone: "UTC",
|
|
||||||
}
|
|
||||||
return date.toLocaleDateString("en-US", options)
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////
|
|
||||||
// Keys related queries and actions
|
|
||||||
/////////////////////////////////////
|
|
||||||
|
|
||||||
const listKeys = query(async (workspaceID: string) => {
|
|
||||||
"use server"
|
|
||||||
return withActor(() => Key.list(), workspaceID)
|
|
||||||
}, "key.list")
|
|
||||||
|
|
||||||
const createKey = action(async (workspaceID: string, name: string) => {
|
|
||||||
"use server"
|
|
||||||
return json(
|
|
||||||
withActor(() => Key.create({ name }), workspaceID),
|
|
||||||
{ revalidate: listKeys.key },
|
|
||||||
)
|
|
||||||
}, "key.create")
|
|
||||||
|
|
||||||
const removeKey = action(async (workspaceID: string, id: string) => {
|
|
||||||
"use server"
|
|
||||||
return json(
|
|
||||||
withActor(() => Key.remove({ id }), workspaceID),
|
|
||||||
{ revalidate: listKeys.key },
|
|
||||||
)
|
|
||||||
}, "key.remove")
|
|
||||||
|
|
||||||
/////////////////////////////////////
|
|
||||||
// Billing related queries and actions
|
|
||||||
/////////////////////////////////////
|
|
||||||
|
|
||||||
const getBalanceInfo = query(async (workspaceID: string) => {
|
|
||||||
"use server"
|
|
||||||
return withActor(async () => {
|
|
||||||
return await Billing.get()
|
|
||||||
}, workspaceID)
|
|
||||||
}, "balanceInfo")
|
|
||||||
|
|
||||||
const getUsageInfo = query(async (workspaceID: string) => {
|
|
||||||
"use server"
|
|
||||||
return withActor(async () => {
|
|
||||||
return await Billing.usages()
|
|
||||||
}, workspaceID)
|
|
||||||
}, "usageInfo")
|
|
||||||
|
|
||||||
const getPaymentsInfo = query(async (workspaceID: string) => {
|
|
||||||
"use server"
|
|
||||||
return withActor(async () => {
|
|
||||||
return await Billing.payments()
|
|
||||||
}, workspaceID)
|
|
||||||
}, "paymentsInfo")
|
|
||||||
|
|
||||||
const createCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => {
|
|
||||||
"use server"
|
|
||||||
return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID)
|
|
||||||
}, "checkoutUrl")
|
|
||||||
|
|
||||||
// const createPortalUrl = action(async (workspaceID: string, returnUrl: string) => {
|
|
||||||
// "use server"
|
|
||||||
// return withActor(() => Billing.generatePortalUrl({ returnUrl }), workspaceID)
|
|
||||||
// }, "portalUrl")
|
|
||||||
|
|
||||||
function KeysSection() {
|
|
||||||
// Dummy data for testing
|
|
||||||
const dummyKeys = [
|
|
||||||
{
|
|
||||||
id: "key_1",
|
|
||||||
name: "Development API Key",
|
|
||||||
key: "oc_dev_1234567890abcdef1234567890abcdef12345678",
|
|
||||||
timeCreated: new Date("2024-01-15T10:30:00Z"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "key_2",
|
|
||||||
name: "Production API Key",
|
|
||||||
key: "oc_prod_abcdef1234567890abcdef1234567890abcdef12",
|
|
||||||
timeCreated: new Date("2024-02-01T14:22:00Z"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "key_3",
|
|
||||||
name: "Testing Environment",
|
|
||||||
key: "oc_test_9876543210fedcba9876543210fedcba98765432",
|
|
||||||
timeCreated: new Date("2024-02-10T09:15:00Z"),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const params = useParams()
|
|
||||||
const keys = createAsync(() => listKeys(params.id))
|
|
||||||
// const keys = () => dummyKeys
|
|
||||||
const [showForm, setShowForm] = createSignal(false)
|
|
||||||
const [name, setName] = createSignal("")
|
|
||||||
const removeAction = useAction(removeKey)
|
|
||||||
const createAction = useAction(createKey)
|
|
||||||
const createSubmission = useSubmission(createKey)
|
|
||||||
const [copiedId, setCopiedId] = createSignal<string | null>(null)
|
|
||||||
|
|
||||||
function formatKey(key: string) {
|
|
||||||
if (key.length <= 11) return key
|
|
||||||
return `${key.slice(0, 7)}...${key.slice(-4)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCreateKey() {
|
|
||||||
if (!name().trim()) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
await createAction(params.id, name().trim())
|
|
||||||
setName("")
|
|
||||||
setShowForm(false)
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to create API key:", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function copyKeyToClipboard(text: string, keyId: string) {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text)
|
|
||||||
setCopiedId(keyId)
|
|
||||||
setTimeout(() => setCopiedId(null), 1500)
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to copy to clipboard:", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleDeleteKey(keyId: string) {
|
|
||||||
if (!confirm("Are you sure you want to delete this API key?")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await removeAction(params.id, keyId)
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to delete API key:", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section data-component="api-keys-section">
|
|
||||||
<div data-slot="section-title">
|
|
||||||
<h2>API Keys</h2>
|
|
||||||
<p>Manage your API keys for accessing opencode services.</p>
|
|
||||||
</div>
|
|
||||||
<Show
|
|
||||||
when={!showForm()}
|
|
||||||
fallback={
|
|
||||||
<div data-slot="create-form">
|
|
||||||
<input
|
|
||||||
data-component="input"
|
|
||||||
type="text"
|
|
||||||
placeholder="Enter key name"
|
|
||||||
value={name()}
|
|
||||||
onInput={(e) => setName(e.currentTarget.value)}
|
|
||||||
onKeyPress={(e) => e.key === "Enter" && handleCreateKey()}
|
|
||||||
/>
|
|
||||||
<div data-slot="form-actions">
|
|
||||||
<button
|
|
||||||
data-color="ghost"
|
|
||||||
onClick={() => {
|
|
||||||
setShowForm(false)
|
|
||||||
setName("")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
data-color="primary"
|
|
||||||
disabled={createSubmission.pending || !name().trim()}
|
|
||||||
onClick={handleCreateKey}
|
|
||||||
>
|
|
||||||
{createSubmission.pending ? "Creating..." : "Create"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
data-color="primary"
|
|
||||||
onClick={() => {
|
|
||||||
console.log("clicked")
|
|
||||||
setShowForm(true)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Create API Key
|
|
||||||
</button>
|
|
||||||
</Show>
|
|
||||||
<div data-slot="api-keys-table">
|
|
||||||
<Show
|
|
||||||
when={keys()?.length}
|
|
||||||
fallback={
|
|
||||||
<div data-component="empty-state">
|
|
||||||
<p>Create an opencode Gateway API key</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<table data-slot="api-keys-table-element">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Key</th>
|
|
||||||
<th>Created</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<For each={keys()!}>
|
|
||||||
{(key) => (
|
|
||||||
<tr>
|
|
||||||
<td data-slot="key-name">{key.name}</td>
|
|
||||||
<td data-slot="key-value">
|
|
||||||
<button
|
|
||||||
data-color="ghost"
|
|
||||||
disabled={copiedId() === key.id}
|
|
||||||
onClick={() => copyKeyToClipboard(key.key, key.id)}
|
|
||||||
title="Copy API key"
|
|
||||||
>
|
|
||||||
<span>{formatKey(key.key)}</span>
|
|
||||||
<Show
|
|
||||||
when={copiedId() === key.id}
|
|
||||||
fallback={<IconCopy style={{ width: "14px", height: "14px" }} />}
|
|
||||||
>
|
|
||||||
<IconCheck style={{ width: "14px", height: "14px" }} />
|
|
||||||
</Show>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
<td data-slot="key-date" title={formatDateUTC(key.timeCreated)}>
|
|
||||||
{formatDateForTable(key.timeCreated)}
|
|
||||||
</td>
|
|
||||||
<td data-slot="key-actions">
|
|
||||||
<button data-color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key">
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function BalanceSection() {
|
|
||||||
const params = useParams()
|
|
||||||
const dummyBalanceInfo = { balance: 2500000000 } // $25.00 in cents
|
|
||||||
|
|
||||||
const balanceInfo = createAsync(() => getBalanceInfo(params.id))
|
|
||||||
// const balanceInfo = () => dummyBalanceInfo
|
|
||||||
const createCheckoutUrlAction = useAction(createCheckoutUrl)
|
|
||||||
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
|
|
||||||
|
|
||||||
async function handleBuyCredits() {
|
|
||||||
try {
|
|
||||||
const baseUrl = window.location.href
|
|
||||||
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
|
|
||||||
if (checkoutUrl) {
|
|
||||||
window.location.href = checkoutUrl
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to get checkout URL:", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section data-component="balance-section">
|
|
||||||
<div data-slot="section-title">
|
|
||||||
<h2>Balance</h2>
|
|
||||||
<p>Add credits to your account.</p>
|
|
||||||
</div>
|
|
||||||
<div data-slot="balance">
|
|
||||||
<div
|
|
||||||
data-slot="amount"
|
|
||||||
data-state={(() => {
|
|
||||||
const balanceStr = ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
|
|
||||||
return balanceStr === "0.00" || balanceStr === "-0.00" ? "danger" : undefined
|
|
||||||
})()}
|
|
||||||
>
|
|
||||||
<span data-slot="currency">$</span>
|
|
||||||
<span data-slot="value">
|
|
||||||
{(() => {
|
|
||||||
const balanceStr = ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
|
|
||||||
return balanceStr === "-0.00" ? "0.00" : balanceStr
|
|
||||||
})()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button data-color="primary" disabled={createCheckoutUrlSubmission.pending} onClick={handleBuyCredits}>
|
|
||||||
{createCheckoutUrlSubmission.pending ? "Loading..." : "Buy Credits"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function UsageSection() {
|
|
||||||
const params = useParams()
|
|
||||||
const dummyUsage = [
|
|
||||||
{
|
|
||||||
id: "usage_1",
|
|
||||||
model: "claude-3-sonnet-20240229",
|
|
||||||
inputTokens: 1250,
|
|
||||||
outputTokens: 890,
|
|
||||||
cost: 125000000, // $1.25 in cents
|
|
||||||
timeCreated: "2024-02-10T15:30:00Z",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "usage_2",
|
|
||||||
model: "gpt-4-turbo-preview",
|
|
||||||
inputTokens: 2100,
|
|
||||||
outputTokens: 1456,
|
|
||||||
cost: 340000000, // $3.40 in cents
|
|
||||||
timeCreated: "2024-02-09T09:45:00Z",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "usage_3",
|
|
||||||
model: "claude-3-haiku-20240307",
|
|
||||||
inputTokens: 850,
|
|
||||||
outputTokens: 620,
|
|
||||||
cost: 45000000, // $0.45 in cents
|
|
||||||
timeCreated: "2024-02-08T13:22:00Z",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "usage_4",
|
|
||||||
model: "gpt-3.5-turbo",
|
|
||||||
inputTokens: 1800,
|
|
||||||
outputTokens: 1200,
|
|
||||||
cost: 85000000, // $0.85 in cents
|
|
||||||
timeCreated: "2024-02-07T11:15:00Z",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const usage = createAsync(() => getUsageInfo(params.id))
|
|
||||||
// const usage = () => dummyUsage
|
|
||||||
return (
|
|
||||||
<section data-component="usage-section">
|
|
||||||
<div data-slot="section-title">
|
|
||||||
<h2>Usage History</h2>
|
|
||||||
<p>Recent API usage and costs.</p>
|
|
||||||
</div>
|
|
||||||
<div data-slot="usage-table">
|
|
||||||
<Show
|
|
||||||
when={usage() && usage()!.length > 0}
|
|
||||||
fallback={
|
|
||||||
<div data-component="empty-state">
|
|
||||||
<p>Make your first API call to get started.</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<table data-slot="usage-table-element">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Model</th>
|
|
||||||
<th>Input</th>
|
|
||||||
<th>Output</th>
|
|
||||||
<th>Cost</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<For each={usage()!}>
|
|
||||||
{(usage) => {
|
|
||||||
const date = createMemo(() => new Date(usage.timeCreated))
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<td data-slot="usage-date" title={formatDateUTC(date())}>
|
|
||||||
{formatDateForTable(date())}
|
|
||||||
</td>
|
|
||||||
<td data-slot="usage-model">{usage.model}</td>
|
|
||||||
<td data-slot="usage-tokens">{usage.inputTokens}</td>
|
|
||||||
<td data-slot="usage-tokens">{usage.outputTokens}</td>
|
|
||||||
<td data-slot="usage-cost">${((usage.cost ?? 0) / 100000000).toFixed(4)}</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PaymentsSection() {
|
|
||||||
const params = useParams()
|
|
||||||
const dummyPayments = [
|
|
||||||
{
|
|
||||||
id: "pi_1234567890",
|
|
||||||
amount: 5000000000, // $50.00 in cents
|
|
||||||
timeCreated: "2024-02-01T10:00:00Z",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "pi_0987654321",
|
|
||||||
amount: 2500000000, // $25.00 in cents
|
|
||||||
timeCreated: "2024-01-15T14:30:00Z",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const payments = createAsync(() => getPaymentsInfo(params.id))
|
|
||||||
// const payments = () => dummyPayments
|
|
||||||
|
|
||||||
return (
|
|
||||||
payments() &&
|
|
||||||
payments()!.length > 0 && (
|
|
||||||
<section data-component="payments-section">
|
|
||||||
<div data-slot="section-title">
|
|
||||||
<h2>Payments History</h2>
|
|
||||||
<p>Recent payment transactions.</p>
|
|
||||||
</div>
|
|
||||||
<div data-slot="payments-table">
|
|
||||||
<table data-slot="payments-table-element">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Payment ID</th>
|
|
||||||
<th>Amount</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<For each={payments()!}>
|
|
||||||
{(payment) => {
|
|
||||||
const date = new Date(payment.timeCreated)
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<td data-slot="payment-date" title={formatDateUTC(date)}>
|
|
||||||
{formatDateForTable(date)}
|
|
||||||
</td>
|
|
||||||
<td data-slot="payment-id">{payment.id}</td>
|
|
||||||
<td data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function NewUserSection() {
|
|
||||||
const params = useParams()
|
|
||||||
const keys = createAsync(() => listKeys(params.id))
|
|
||||||
const [copiedKey, setCopiedKey] = createSignal(false)
|
|
||||||
|
|
||||||
async function copyKeyToClipboard(text: string) {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text)
|
|
||||||
setCopiedKey(true)
|
|
||||||
setTimeout(() => setCopiedKey(false), 2000)
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to copy to clipboard:", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div data-slot="new-user-sections">
|
|
||||||
<div data-component="feature-grid">
|
|
||||||
<div data-slot="feature">
|
|
||||||
<h3>Tested & Verified Models</h3>
|
|
||||||
<p>We've benchmarked and tested models specifically for coding agents to ensure the best performance.</p>
|
|
||||||
</div>
|
|
||||||
<div data-slot="feature">
|
|
||||||
<h3>Highest Quality</h3>
|
|
||||||
<p>Access models configured for optimal performance - no downgrades or routing to cheaper providers.</p>
|
|
||||||
</div>
|
|
||||||
<div data-slot="feature">
|
|
||||||
<h3>No Lock-in</h3>
|
|
||||||
<p>Use Zen with any coding agent, and continue using other providers with opencode whenever you want.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-component="api-key-highlight">
|
|
||||||
<div data-slot="section-title">
|
|
||||||
<h2>Your API Key</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Show when={keys()?.length}>
|
|
||||||
<div data-slot="key-display">
|
|
||||||
<div data-slot="key-container">
|
|
||||||
<code data-slot="key-value">{keys()![0].key}</code>
|
|
||||||
<button
|
|
||||||
data-color="primary"
|
|
||||||
disabled={copiedKey()}
|
|
||||||
onClick={() => copyKeyToClipboard(keys()![0].key)}
|
|
||||||
title="Copy API key"
|
|
||||||
>
|
|
||||||
<Show
|
|
||||||
when={copiedKey()}
|
|
||||||
fallback={
|
|
||||||
<>
|
|
||||||
<IconCopy style={{ width: "16px", height: "16px" }} /> Copy Key
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<IconCheck style={{ width: "16px", height: "16px" }} /> Copied!
|
|
||||||
</Show>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-component="next-steps">
|
|
||||||
<div data-slot="section-title">
|
|
||||||
<h2>Next Steps</h2>
|
|
||||||
</div>
|
|
||||||
<ol>
|
|
||||||
<li>Copy your API key above</li>
|
|
||||||
<li>
|
|
||||||
Run <code>opencode auth login</code> and select opencode
|
|
||||||
</li>
|
|
||||||
<li>Paste your API key when prompted</li>
|
|
||||||
<li>
|
|
||||||
Run <code>/models</code> to see available models
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
const params = useParams()
|
|
||||||
const keys = createAsync(() => listKeys(params.id))
|
|
||||||
const usage = createAsync(() => getUsageInfo(params.id))
|
|
||||||
|
|
||||||
const isNewUser = createMemo(() => {
|
|
||||||
const keysList = keys()
|
|
||||||
const usageList = usage()
|
|
||||||
return keysList?.length === 1 && (!usageList || usageList.length === 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div data-page="workspace-[id]">
|
|
||||||
<section data-component="title-section">
|
|
||||||
<h1>Zen</h1>
|
|
||||||
<p>
|
|
||||||
Curated list of models provided by opencode.{" "}
|
|
||||||
<a target="_blank" href="/docs/zen">
|
|
||||||
Learn more
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<Show when={!isNewUser()} fallback={<NewUserSection />}>
|
|
||||||
<div data-slot="sections">
|
|
||||||
<KeysSection />
|
|
||||||
<BalanceSection />
|
|
||||||
<UsageSection />
|
|
||||||
<PaymentsSection />
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,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,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
html {
|
|
||||||
line-height: 1;
|
|
||||||
background-color: var(--color-bg);
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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";
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,526 +0,0 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server"
|
|
||||||
import path from "node:path"
|
|
||||||
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
|
|
||||||
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
|
|
||||||
import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
|
|
||||||
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
|
|
||||||
import { Identifier } from "@opencode/cloud-core/identifier.js"
|
|
||||||
import { Resource } from "@opencode/cloud-resource"
|
|
||||||
|
|
||||||
type ModelCost = {
|
|
||||||
input: number
|
|
||||||
output: number
|
|
||||||
cacheRead: number
|
|
||||||
cacheWrite5m: number
|
|
||||||
cacheWrite1h: number
|
|
||||||
}
|
|
||||||
type Model = {
|
|
||||||
id: string
|
|
||||||
auth: boolean
|
|
||||||
cost: ModelCost | ((usage: any) => ModelCost)
|
|
||||||
headerMappings: Record<string, string>
|
|
||||||
providers: Record<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
api: string
|
|
||||||
apiKey: string
|
|
||||||
model: string
|
|
||||||
weight?: number
|
|
||||||
}
|
|
||||||
>
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handler(
|
|
||||||
input: APIEvent,
|
|
||||||
opts: {
|
|
||||||
modifyBody?: (body: any) => any
|
|
||||||
setAuthHeader: (headers: Headers, apiKey: string) => void
|
|
||||||
parseApiKey: (headers: Headers) => string | undefined
|
|
||||||
onStreamPart: (chunk: string) => void
|
|
||||||
getStreamUsage: () => any
|
|
||||||
normalizeUsage: (body: any) => {
|
|
||||||
inputTokens: number
|
|
||||||
outputTokens: number
|
|
||||||
reasoningTokens?: number
|
|
||||||
cacheReadTokens: number
|
|
||||||
cacheWrite5mTokens?: number
|
|
||||||
cacheWrite1hTokens?: number
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
class AuthError extends Error {}
|
|
||||||
class CreditsError extends Error {}
|
|
||||||
class ModelError extends Error {}
|
|
||||||
|
|
||||||
const MODELS: Record<string, Model> = {
|
|
||||||
"claude-opus-4-1": {
|
|
||||||
id: "claude-opus-4-1" as const,
|
|
||||||
auth: true,
|
|
||||||
cost: {
|
|
||||||
input: 0.000015,
|
|
||||||
output: 0.000075,
|
|
||||||
cacheRead: 0.0000015,
|
|
||||||
cacheWrite5m: 0.00001875,
|
|
||||||
cacheWrite1h: 0.00003,
|
|
||||||
},
|
|
||||||
headerMappings: {},
|
|
||||||
providers: {
|
|
||||||
anthropic: {
|
|
||||||
api: "https://api.anthropic.com",
|
|
||||||
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
|
||||||
model: "claude-opus-4-1-20250805",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"claude-sonnet-4": {
|
|
||||||
id: "claude-sonnet-4" as const,
|
|
||||||
auth: true,
|
|
||||||
cost: (usage: any) => {
|
|
||||||
const totalInputTokens =
|
|
||||||
usage.inputTokens + usage.cacheReadTokens + usage.cacheWrite5mTokens + usage.cacheWrite1hTokens
|
|
||||||
return totalInputTokens <= 200_000
|
|
||||||
? {
|
|
||||||
input: 0.000003,
|
|
||||||
output: 0.000015,
|
|
||||||
cacheRead: 0.0000003,
|
|
||||||
cacheWrite5m: 0.00000375,
|
|
||||||
cacheWrite1h: 0.000006,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
input: 0.000006,
|
|
||||||
output: 0.0000225,
|
|
||||||
cacheRead: 0.0000006,
|
|
||||||
cacheWrite5m: 0.0000075,
|
|
||||||
cacheWrite1h: 0.000012,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
headerMappings: {},
|
|
||||||
providers: {
|
|
||||||
anthropic: {
|
|
||||||
api: "https://api.anthropic.com",
|
|
||||||
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
|
||||||
model: "claude-sonnet-4-20250514",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"claude-3-5-haiku": {
|
|
||||||
id: "claude-3-5-haiku" as const,
|
|
||||||
auth: true,
|
|
||||||
cost: {
|
|
||||||
input: 0.0000008,
|
|
||||||
output: 0.000004,
|
|
||||||
cacheRead: 0.00000008,
|
|
||||||
cacheWrite5m: 0.000001,
|
|
||||||
cacheWrite1h: 0.0000016,
|
|
||||||
},
|
|
||||||
headerMappings: {},
|
|
||||||
providers: {
|
|
||||||
anthropic: {
|
|
||||||
api: "https://api.anthropic.com",
|
|
||||||
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
|
||||||
model: "claude-3-5-haiku-20241022",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"gpt-5": {
|
|
||||||
id: "gpt-5" as const,
|
|
||||||
auth: true,
|
|
||||||
cost: {
|
|
||||||
input: 0.00000125,
|
|
||||||
output: 0.00001,
|
|
||||||
cacheRead: 0.000000125,
|
|
||||||
cacheWrite5m: 0,
|
|
||||||
cacheWrite1h: 0,
|
|
||||||
},
|
|
||||||
headerMappings: {},
|
|
||||||
providers: {
|
|
||||||
openai: {
|
|
||||||
api: "https://api.openai.com",
|
|
||||||
apiKey: Resource.OPENAI_API_KEY.value,
|
|
||||||
model: "gpt-5",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"qwen3-coder": {
|
|
||||||
id: "qwen3-coder" as const,
|
|
||||||
auth: true,
|
|
||||||
cost: {
|
|
||||||
input: 0.00000045,
|
|
||||||
output: 0.0000018,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite5m: 0,
|
|
||||||
cacheWrite1h: 0,
|
|
||||||
},
|
|
||||||
headerMappings: {},
|
|
||||||
providers: {
|
|
||||||
baseten: {
|
|
||||||
api: "https://inference.baseten.co",
|
|
||||||
apiKey: Resource.BASETEN_API_KEY.value,
|
|
||||||
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
|
||||||
weight: 4,
|
|
||||||
},
|
|
||||||
fireworks: {
|
|
||||||
api: "https://api.fireworks.ai/inference",
|
|
||||||
apiKey: Resource.FIREWORKS_API_KEY.value,
|
|
||||||
model: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
|
|
||||||
weight: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"kimi-k2": {
|
|
||||||
id: "kimi-k2" as const,
|
|
||||||
auth: true,
|
|
||||||
cost: {
|
|
||||||
input: 0.0000006,
|
|
||||||
output: 0.0000025,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite5m: 0,
|
|
||||||
cacheWrite1h: 0,
|
|
||||||
},
|
|
||||||
headerMappings: {},
|
|
||||||
providers: {
|
|
||||||
baseten: {
|
|
||||||
api: "https://inference.baseten.co",
|
|
||||||
apiKey: Resource.BASETEN_API_KEY.value,
|
|
||||||
model: "moonshotai/Kimi-K2-Instruct-0905",
|
|
||||||
weight: 4,
|
|
||||||
},
|
|
||||||
fireworks: {
|
|
||||||
api: "https://api.fireworks.ai/inference",
|
|
||||||
apiKey: Resource.FIREWORKS_API_KEY.value,
|
|
||||||
model: "accounts/fireworks/models/kimi-k2-instruct-0905",
|
|
||||||
weight: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"grok-code": {
|
|
||||||
id: "grok-code" as const,
|
|
||||||
auth: false,
|
|
||||||
cost: {
|
|
||||||
input: 0,
|
|
||||||
output: 0,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite5m: 0,
|
|
||||||
cacheWrite1h: 0,
|
|
||||||
},
|
|
||||||
headerMappings: {
|
|
||||||
"x-grok-conv-id": "x-opencode-session",
|
|
||||||
"x-grok-req-id": "x-opencode-request",
|
|
||||||
},
|
|
||||||
providers: {
|
|
||||||
xai: {
|
|
||||||
api: "https://api.x.ai",
|
|
||||||
apiKey: Resource.XAI_API_KEY.value,
|
|
||||||
model: "grok-code",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// deprecated
|
|
||||||
"qwen/qwen3-coder": {
|
|
||||||
id: "qwen/qwen3-coder" as const,
|
|
||||||
auth: true,
|
|
||||||
cost: {
|
|
||||||
input: 0.00000038,
|
|
||||||
output: 0.00000153,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite5m: 0,
|
|
||||||
cacheWrite1h: 0,
|
|
||||||
},
|
|
||||||
headerMappings: {},
|
|
||||||
providers: {
|
|
||||||
baseten: {
|
|
||||||
api: "https://inference.baseten.co",
|
|
||||||
apiKey: Resource.BASETEN_API_KEY.value,
|
|
||||||
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
|
||||||
weight: 5,
|
|
||||||
},
|
|
||||||
fireworks: {
|
|
||||||
api: "https://api.fireworks.ai/inference",
|
|
||||||
apiKey: Resource.FIREWORKS_API_KEY.value,
|
|
||||||
model: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
|
|
||||||
weight: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const FREE_WORKSPACES = [
|
|
||||||
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
|
|
||||||
]
|
|
||||||
|
|
||||||
const logger = {
|
|
||||||
metric: (values: Record<string, any>) => {
|
|
||||||
console.log(`_metric:${JSON.stringify(values)}`)
|
|
||||||
},
|
|
||||||
log: console.log,
|
|
||||||
debug: (message: string) => {
|
|
||||||
if (Resource.App.stage === "production") return
|
|
||||||
console.debug(message)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const url = new URL(input.request.url)
|
|
||||||
const body = await input.request.json()
|
|
||||||
logger.debug(JSON.stringify(body))
|
|
||||||
logger.metric({
|
|
||||||
is_tream: !!body.stream,
|
|
||||||
session: input.request.headers.get("x-opencode-session"),
|
|
||||||
request: input.request.headers.get("x-opencode-request"),
|
|
||||||
})
|
|
||||||
const MODEL = validateModel()
|
|
||||||
const apiKey = await authenticate()
|
|
||||||
const isFree = FREE_WORKSPACES.includes(apiKey?.workspaceID ?? "")
|
|
||||||
await checkCredits()
|
|
||||||
const providerName = selectProvider()
|
|
||||||
const providerData = MODEL.providers[providerName]
|
|
||||||
logger.metric({ provider: providerName })
|
|
||||||
|
|
||||||
// Request to model provider
|
|
||||||
const startTimestamp = Date.now()
|
|
||||||
const res = await fetch(path.posix.join(providerData.api, url.pathname.replace(/^\/zen/, "") + url.search), {
|
|
||||||
method: "POST",
|
|
||||||
headers: (() => {
|
|
||||||
const headers = input.request.headers
|
|
||||||
headers.delete("host")
|
|
||||||
headers.delete("content-length")
|
|
||||||
opts.setAuthHeader(headers, providerData.apiKey)
|
|
||||||
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
|
|
||||||
headers.set(k, headers.get(v)!)
|
|
||||||
})
|
|
||||||
return headers
|
|
||||||
})(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
...(opts.modifyBody?.(body) ?? body),
|
|
||||||
model: providerData.model,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Scrub response headers
|
|
||||||
const resHeaders = new Headers()
|
|
||||||
const keepHeaders = ["content-type", "cache-control"]
|
|
||||||
for (const [k, v] of res.headers.entries()) {
|
|
||||||
if (keepHeaders.includes(k.toLowerCase())) {
|
|
||||||
resHeaders.set(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle non-streaming response
|
|
||||||
if (!body.stream) {
|
|
||||||
const json = await res.json()
|
|
||||||
const body = JSON.stringify(json)
|
|
||||||
logger.metric({ response_length: body.length })
|
|
||||||
logger.debug(body)
|
|
||||||
await trackUsage(json.usage)
|
|
||||||
return new Response(body, {
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: resHeaders,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle streaming response
|
|
||||||
const stream = new ReadableStream({
|
|
||||||
start(c) {
|
|
||||||
const reader = res.body?.getReader()
|
|
||||||
const decoder = new TextDecoder()
|
|
||||||
let buffer = ""
|
|
||||||
let responseLength = 0
|
|
||||||
|
|
||||||
function pump(): Promise<void> {
|
|
||||||
return (
|
|
||||||
reader?.read().then(async ({ done, value }) => {
|
|
||||||
if (done) {
|
|
||||||
logger.metric({ response_length: responseLength })
|
|
||||||
const usage = opts.getStreamUsage()
|
|
||||||
if (usage) await trackUsage(usage)
|
|
||||||
c.close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseLength === 0) {
|
|
||||||
logger.metric({ time_to_first_byte: Date.now() - startTimestamp })
|
|
||||||
}
|
|
||||||
responseLength += value.length
|
|
||||||
buffer += decoder.decode(value, { stream: true })
|
|
||||||
|
|
||||||
const parts = buffer.split("\n\n")
|
|
||||||
buffer = parts.pop() ?? ""
|
|
||||||
|
|
||||||
for (const part of parts) {
|
|
||||||
logger.debug(part)
|
|
||||||
opts.onStreamPart(part.trim())
|
|
||||||
}
|
|
||||||
|
|
||||||
c.enqueue(value)
|
|
||||||
|
|
||||||
return pump()
|
|
||||||
}) || Promise.resolve()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pump()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return new Response(stream, {
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: resHeaders,
|
|
||||||
})
|
|
||||||
|
|
||||||
function validateModel() {
|
|
||||||
if (!(body.model in MODELS)) {
|
|
||||||
throw new ModelError(`Model ${body.model} not supported`)
|
|
||||||
}
|
|
||||||
const model = MODELS[body.model as keyof typeof MODELS]
|
|
||||||
logger.metric({ model: model.id })
|
|
||||||
return model
|
|
||||||
}
|
|
||||||
|
|
||||||
async function authenticate() {
|
|
||||||
try {
|
|
||||||
const apiKey = opts.parseApiKey(input.request.headers)
|
|
||||||
if (!apiKey) throw new AuthError("Missing API key.")
|
|
||||||
|
|
||||||
const key = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select({
|
|
||||||
id: KeyTable.id,
|
|
||||||
workspaceID: KeyTable.workspaceID,
|
|
||||||
})
|
|
||||||
.from(KeyTable)
|
|
||||||
.where(eq(KeyTable.key, apiKey))
|
|
||||||
.then((rows) => rows[0]),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!key) throw new AuthError("Invalid API key.")
|
|
||||||
logger.metric({
|
|
||||||
api_key: key.id,
|
|
||||||
workspace: key.workspaceID,
|
|
||||||
})
|
|
||||||
return key
|
|
||||||
} catch (e) {
|
|
||||||
// ignore error if model does not require authentication
|
|
||||||
if (!MODEL.auth) return
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkCredits() {
|
|
||||||
if (!apiKey || !MODEL.auth || isFree) return
|
|
||||||
|
|
||||||
const billing = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select({
|
|
||||||
balance: BillingTable.balance,
|
|
||||||
})
|
|
||||||
.from(BillingTable)
|
|
||||||
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
|
|
||||||
.then((rows) => rows[0]),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectProvider() {
|
|
||||||
const picks = Object.entries(MODEL.providers).flatMap(([name, provider]) =>
|
|
||||||
Array<string>(provider.weight ?? 1).fill(name),
|
|
||||||
)
|
|
||||||
return picks[Math.floor(Math.random() * picks.length)]
|
|
||||||
}
|
|
||||||
|
|
||||||
async function trackUsage(usage: any) {
|
|
||||||
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
|
|
||||||
opts.normalizeUsage(usage)
|
|
||||||
|
|
||||||
const modelCost = typeof MODEL.cost === "function" ? MODEL.cost(usage) : MODEL.cost
|
|
||||||
|
|
||||||
const inputCost = modelCost.input * inputTokens * 100
|
|
||||||
const outputCost = modelCost.output * outputTokens * 100
|
|
||||||
const reasoningCost = reasoningTokens ? modelCost.output * reasoningTokens * 100 : undefined
|
|
||||||
const cacheReadCost = modelCost.cacheRead * cacheReadTokens * 100
|
|
||||||
const cacheWrite5mCost = cacheWrite5mTokens ? modelCost.cacheWrite5m * cacheWrite5mTokens * 100 : undefined
|
|
||||||
const cacheWrite1hCost = cacheWrite1hTokens ? modelCost.cacheWrite1h * cacheWrite1hTokens * 100 : undefined
|
|
||||||
const totalCostInCent =
|
|
||||||
inputCost +
|
|
||||||
outputCost +
|
|
||||||
(reasoningCost ?? 0) +
|
|
||||||
cacheReadCost +
|
|
||||||
(cacheWrite5mCost ?? 0) +
|
|
||||||
(cacheWrite1hCost ?? 0)
|
|
||||||
|
|
||||||
logger.metric({
|
|
||||||
"tokens.input": inputTokens,
|
|
||||||
"tokens.output": outputTokens,
|
|
||||||
"tokens.reasoning": reasoningTokens,
|
|
||||||
"tokens.cache_read": cacheReadTokens,
|
|
||||||
"tokens.cache_write_5m": cacheWrite5mTokens,
|
|
||||||
"tokens.cache_write_1h": cacheWrite1hTokens,
|
|
||||||
"cost.input": Math.round(inputCost),
|
|
||||||
"cost.output": Math.round(outputCost),
|
|
||||||
"cost.reasoning": reasoningCost ? Math.round(reasoningCost) : undefined,
|
|
||||||
"cost.cache_read": Math.round(cacheReadCost),
|
|
||||||
"cost.cache_write_5m": cacheWrite5mCost ? Math.round(cacheWrite5mCost) : undefined,
|
|
||||||
"cost.cache_write_1h": cacheWrite1hCost ? Math.round(cacheWrite1hCost) : undefined,
|
|
||||||
"cost.total": Math.round(totalCostInCent),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!apiKey) return
|
|
||||||
|
|
||||||
const cost = isFree ? 0 : centsToMicroCents(totalCostInCent)
|
|
||||||
await Database.transaction(async (tx) => {
|
|
||||||
await tx.insert(UsageTable).values({
|
|
||||||
workspaceID: apiKey.workspaceID,
|
|
||||||
id: Identifier.create("usage"),
|
|
||||||
model: MODEL.id,
|
|
||||||
inputTokens,
|
|
||||||
outputTokens,
|
|
||||||
reasoningTokens,
|
|
||||||
cacheReadTokens,
|
|
||||||
cacheWriteTokens: (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0),
|
|
||||||
cost,
|
|
||||||
})
|
|
||||||
await tx
|
|
||||||
.update(BillingTable)
|
|
||||||
.set({
|
|
||||||
balance: sql`${BillingTable.balance} - ${cost}`,
|
|
||||||
})
|
|
||||||
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
|
|
||||||
})
|
|
||||||
|
|
||||||
await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.update(KeyTable)
|
|
||||||
.set({ timeUsed: sql`now()` })
|
|
||||||
.where(eq(KeyTable.id, apiKey.id)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.metric({
|
|
||||||
"error.type": error.constructor.name,
|
|
||||||
"error.message": error.message,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Note: both top level "type" and "error.type" fields are used by the @ai-sdk/anthropic client to render the error message.
|
|
||||||
if (error instanceof AuthError || error instanceof CreditsError || error instanceof ModelError)
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
type: "error",
|
|
||||||
error: { type: error.constructor.name, message: error.message },
|
|
||||||
}),
|
|
||||||
{ status: 401 },
|
|
||||||
)
|
|
||||||
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
type: "error",
|
|
||||||
error: {
|
|
||||||
type: "error",
|
|
||||||
message: error.message,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ status: 500 },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,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/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Resource } from "sst"
|
|
||||||
import { defineConfig } from "drizzle-kit"
|
import { defineConfig } from "drizzle-kit"
|
||||||
|
import { Resource } from "sst"
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
out: "./migrations/",
|
out: "./migrations/",
|
||||||
strict: true,
|
strict: true,
|
||||||
schema: ["./src/**/*.sql.ts"],
|
schema: ["./src/**/*.sql.ts"],
|
||||||
verbose: true,
|
verbose: true,
|
||||||
dialect: "mysql",
|
dialect: "postgresql",
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
database: Resource.Database.database,
|
database: Resource.Database.database,
|
||||||
host: Resource.Database.host,
|
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");
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "usage" DROP COLUMN "request_id";
|
||||||
@@ -1,569 +1,461 @@
|
|||||||
{
|
{
|
||||||
"version": "5",
|
"id": "9b5cec8c-8b59-4d7a-bb5c-76ade1c83d6f",
|
||||||
"dialect": "mysql",
|
|
||||||
"id": "aee779c5-db1d-4655-95ec-6451c18455be",
|
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
"tables": {
|
"tables": {
|
||||||
"account": {
|
"public.billing": {
|
||||||
"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",
|
"name": "billing",
|
||||||
|
"schema": "",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false,
|
"default": "now()"
|
||||||
"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": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"customer_id": {
|
"customer_id": {
|
||||||
"name": "customer_id",
|
"name": "customer_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"payment_method_id": {
|
"payment_method_id": {
|
||||||
"name": "payment_method_id",
|
"name": "payment_method_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"payment_method_last4": {
|
"payment_method_last4": {
|
||||||
"name": "payment_method_last4",
|
"name": "payment_method_last4",
|
||||||
"type": "varchar(4)",
|
"type": "varchar(4)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"balance": {
|
"balance": {
|
||||||
"name": "balance",
|
"name": "balance",
|
||||||
"type": "bigint",
|
"type": "bigint",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"reload": {
|
"reload": {
|
||||||
"name": "reload",
|
"name": "reload",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"billing_workspace_id_id_pk": {
|
"billing_workspace_id_id_pk": {
|
||||||
"name": "billing_workspace_id_id_pk",
|
"name": "billing_workspace_id_id_pk",
|
||||||
"columns": ["workspace_id", "id"]
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraint": {}
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"payment": {
|
"public.payment": {
|
||||||
"name": "payment",
|
"name": "payment",
|
||||||
|
"schema": "",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false,
|
"default": "now()"
|
||||||
"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": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"customer_id": {
|
"customer_id": {
|
||||||
"name": "customer_id",
|
"name": "customer_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"payment_id": {
|
"payment_id": {
|
||||||
"name": "payment_id",
|
"name": "payment_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"amount": {
|
"amount": {
|
||||||
"name": "amount",
|
"name": "amount",
|
||||||
"type": "bigint",
|
"type": "bigint",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"payment_workspace_id_id_pk": {
|
"payment_workspace_id_id_pk": {
|
||||||
"name": "payment_workspace_id_id_pk",
|
"name": "payment_workspace_id_id_pk",
|
||||||
"columns": ["workspace_id", "id"]
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraint": {}
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"usage": {
|
"public.usage": {
|
||||||
"name": "usage",
|
"name": "usage",
|
||||||
|
"schema": "",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false,
|
"default": "now()"
|
||||||
"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": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
},
|
||||||
|
"request_id": {
|
||||||
|
"name": "request_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"name": "model",
|
"name": "model",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"input_tokens": {
|
"input_tokens": {
|
||||||
"name": "input_tokens",
|
"name": "input_tokens",
|
||||||
"type": "int",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"output_tokens": {
|
"output_tokens": {
|
||||||
"name": "output_tokens",
|
"name": "output_tokens",
|
||||||
"type": "int",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"reasoning_tokens": {
|
"reasoning_tokens": {
|
||||||
"name": "reasoning_tokens",
|
"name": "reasoning_tokens",
|
||||||
"type": "int",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"cache_read_tokens": {
|
"cache_read_tokens": {
|
||||||
"name": "cache_read_tokens",
|
"name": "cache_read_tokens",
|
||||||
"type": "int",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"cache_write_tokens": {
|
"cache_write_tokens": {
|
||||||
"name": "cache_write_tokens",
|
"name": "cache_write_tokens",
|
||||||
"type": "int",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"cost": {
|
"cost": {
|
||||||
"name": "cost",
|
"name": "cost",
|
||||||
"type": "bigint",
|
"type": "bigint",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"usage_workspace_id_id_pk": {
|
"usage_workspace_id_id_pk": {
|
||||||
"name": "usage_workspace_id_id_pk",
|
"name": "usage_workspace_id_id_pk",
|
||||||
"columns": ["workspace_id", "id"]
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraint": {}
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"key": {
|
"public.user": {
|
||||||
"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": {
|
|
||||||
"name": "user",
|
"name": "user",
|
||||||
|
"schema": "",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false,
|
"default": "now()"
|
||||||
"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": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"type": "varchar(255)",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_seen": {
|
"time_seen": {
|
||||||
"name": "time_seen",
|
"name": "time_seen",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"name": "color",
|
"name": "color",
|
||||||
"type": "int",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"user_email": {
|
"user_email": {
|
||||||
"name": "user_email",
|
"name": "user_email",
|
||||||
"columns": ["workspace_id", "email"],
|
"columns": [
|
||||||
"isUnique": true
|
{
|
||||||
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"user_workspace_id_id_pk": {
|
"user_workspace_id_id_pk": {
|
||||||
"name": "user_workspace_id_id_pk",
|
"name": "user_workspace_id_id_pk",
|
||||||
"columns": ["workspace_id", "id"]
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraint": {}
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"workspace": {
|
"public.workspace": {
|
||||||
"name": "workspace",
|
"name": "workspace",
|
||||||
|
"schema": "",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": true,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"slug": {
|
"slug": {
|
||||||
"name": "slug",
|
"name": "slug",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false,
|
"default": "now()"
|
||||||
"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": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"slug": {
|
"slug": {
|
||||||
"name": "slug",
|
"name": "slug",
|
||||||
"columns": ["slug"],
|
"columns": [
|
||||||
"isUnique": true
|
{
|
||||||
|
"expression": "slug",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": true,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"foreignKeys": {},
|
"foreignKeys": {},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {},
|
||||||
"workspace_id": {
|
|
||||||
"name": "workspace_id",
|
|
||||||
"columns": ["id"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraint": {}
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
"views": {},
|
"views": {},
|
||||||
"_meta": {
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
"schemas": {},
|
"schemas": {},
|
||||||
"tables": {},
|
"tables": {}
|
||||||
"columns": {}
|
|
||||||
},
|
|
||||||
"internal": {
|
|
||||||
"tables": {},
|
|
||||||
"indexes": {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,569 +1,515 @@
|
|||||||
{
|
{
|
||||||
"version": "5",
|
"id": "bf9e9084-4073-4ecb-8e56-5610816c9589",
|
||||||
"dialect": "mysql",
|
"prevId": "9b5cec8c-8b59-4d7a-bb5c-76ade1c83d6f",
|
||||||
"id": "79b7ee25-1c1c-41ff-9bbf-754af257102b",
|
"version": "7",
|
||||||
"prevId": "aee779c5-db1d-4655-95ec-6451c18455be",
|
"dialect": "postgresql",
|
||||||
"tables": {
|
"tables": {
|
||||||
"account": {
|
"public.account": {
|
||||||
"name": "account",
|
"name": "account",
|
||||||
|
"schema": "",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false,
|
"default": "now()"
|
||||||
"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": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"email": {
|
"email": {
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"columns": ["email"],
|
"columns": [
|
||||||
"isUnique": true
|
{
|
||||||
|
"expression": "email",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": true,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"foreignKeys": {},
|
"foreignKeys": {},
|
||||||
"compositePrimaryKeys": {},
|
"compositePrimaryKeys": {},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraint": {}
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"billing": {
|
"public.billing": {
|
||||||
"name": "billing",
|
"name": "billing",
|
||||||
|
"schema": "",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false,
|
"default": "now()"
|
||||||
"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": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"customer_id": {
|
"customer_id": {
|
||||||
"name": "customer_id",
|
"name": "customer_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"payment_method_id": {
|
"payment_method_id": {
|
||||||
"name": "payment_method_id",
|
"name": "payment_method_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"payment_method_last4": {
|
"payment_method_last4": {
|
||||||
"name": "payment_method_last4",
|
"name": "payment_method_last4",
|
||||||
"type": "varchar(4)",
|
"type": "varchar(4)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"balance": {
|
"balance": {
|
||||||
"name": "balance",
|
"name": "balance",
|
||||||
"type": "bigint",
|
"type": "bigint",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"reload": {
|
"reload": {
|
||||||
"name": "reload",
|
"name": "reload",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"billing_workspace_id_id_pk": {
|
"billing_workspace_id_id_pk": {
|
||||||
"name": "billing_workspace_id_id_pk",
|
"name": "billing_workspace_id_id_pk",
|
||||||
"columns": ["workspace_id", "id"]
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraint": {}
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"payment": {
|
"public.payment": {
|
||||||
"name": "payment",
|
"name": "payment",
|
||||||
|
"schema": "",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false,
|
"default": "now()"
|
||||||
"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": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"customer_id": {
|
"customer_id": {
|
||||||
"name": "customer_id",
|
"name": "customer_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"payment_id": {
|
"payment_id": {
|
||||||
"name": "payment_id",
|
"name": "payment_id",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"amount": {
|
"amount": {
|
||||||
"name": "amount",
|
"name": "amount",
|
||||||
"type": "bigint",
|
"type": "bigint",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"payment_workspace_id_id_pk": {
|
"payment_workspace_id_id_pk": {
|
||||||
"name": "payment_workspace_id_id_pk",
|
"name": "payment_workspace_id_id_pk",
|
||||||
"columns": ["workspace_id", "id"]
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraint": {}
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"usage": {
|
"public.usage": {
|
||||||
"name": "usage",
|
"name": "usage",
|
||||||
|
"schema": "",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false,
|
"default": "now()"
|
||||||
"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": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
},
|
||||||
|
"request_id": {
|
||||||
|
"name": "request_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"name": "model",
|
"name": "model",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"input_tokens": {
|
"input_tokens": {
|
||||||
"name": "input_tokens",
|
"name": "input_tokens",
|
||||||
"type": "int",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"output_tokens": {
|
"output_tokens": {
|
||||||
"name": "output_tokens",
|
"name": "output_tokens",
|
||||||
"type": "int",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"reasoning_tokens": {
|
"reasoning_tokens": {
|
||||||
"name": "reasoning_tokens",
|
"name": "reasoning_tokens",
|
||||||
"type": "int",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"cache_read_tokens": {
|
"cache_read_tokens": {
|
||||||
"name": "cache_read_tokens",
|
"name": "cache_read_tokens",
|
||||||
"type": "int",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"cache_write_tokens": {
|
"cache_write_tokens": {
|
||||||
"name": "cache_write_tokens",
|
"name": "cache_write_tokens",
|
||||||
"type": "int",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"cost": {
|
"cost": {
|
||||||
"name": "cost",
|
"name": "cost",
|
||||||
"type": "bigint",
|
"type": "bigint",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"usage_workspace_id_id_pk": {
|
"usage_workspace_id_id_pk": {
|
||||||
"name": "usage_workspace_id_id_pk",
|
"name": "usage_workspace_id_id_pk",
|
||||||
"columns": ["workspace_id", "id"]
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraint": {}
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"key": {
|
"public.user": {
|
||||||
"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": {
|
|
||||||
"name": "user",
|
"name": "user",
|
||||||
|
"schema": "",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"workspace_id": {
|
"workspace_id": {
|
||||||
"name": "workspace_id",
|
"name": "workspace_id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false,
|
"default": "now()"
|
||||||
"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": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"type": "varchar(255)",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_seen": {
|
"time_seen": {
|
||||||
"name": "time_seen",
|
"name": "time_seen",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"name": "color",
|
"name": "color",
|
||||||
"type": "int",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"user_email": {
|
"user_email": {
|
||||||
"name": "user_email",
|
"name": "user_email",
|
||||||
"columns": ["workspace_id", "email"],
|
"columns": [
|
||||||
"isUnique": true
|
{
|
||||||
|
"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": {
|
"compositePrimaryKeys": {
|
||||||
"user_workspace_id_id_pk": {
|
"user_workspace_id_id_pk": {
|
||||||
"name": "user_workspace_id_id_pk",
|
"name": "user_workspace_id_id_pk",
|
||||||
"columns": ["workspace_id", "id"]
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraint": {}
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"workspace": {
|
"public.workspace": {
|
||||||
"name": "workspace",
|
"name": "workspace",
|
||||||
|
"schema": "",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(30)",
|
"type": "varchar(30)",
|
||||||
"primaryKey": false,
|
"primaryKey": true,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"slug": {
|
"slug": {
|
||||||
"name": "slug",
|
"name": "slug",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"type": "varchar(255)",
|
"type": "varchar(255)",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
},
|
},
|
||||||
"time_created": {
|
"time_created": {
|
||||||
"name": "time_created",
|
"name": "time_created",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false,
|
"default": "now()"
|
||||||
"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": {
|
"time_deleted": {
|
||||||
"name": "time_deleted",
|
"name": "time_deleted",
|
||||||
"type": "timestamp(3)",
|
"type": "timestamp with time zone",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false
|
||||||
"autoincrement": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"slug": {
|
"slug": {
|
||||||
"name": "slug",
|
"name": "slug",
|
||||||
"columns": ["slug"],
|
"columns": [
|
||||||
"isUnique": true
|
{
|
||||||
|
"expression": "slug",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": true,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"foreignKeys": {},
|
"foreignKeys": {},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {},
|
||||||
"workspace_id": {
|
|
||||||
"name": "workspace_id",
|
|
||||||
"columns": ["id"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraint": {}
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
"views": {},
|
"views": {},
|
||||||
"_meta": {
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
"schemas": {},
|
"schemas": {},
|
||||||
"tables": {},
|
"tables": {}
|
||||||
"columns": {}
|
|
||||||
},
|
|
||||||
"internal": {
|
|
||||||
"tables": {},
|
|
||||||
"indexes": {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,615 @@
|
|||||||
|
{
|
||||||
|
"id": "351e4956-74e0-4282-a23b-02f1a73fa38c",
|
||||||
|
"prevId": "bf9e9084-4073-4ecb-8e56-5610816c9589",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.account": {
|
||||||
|
"name": "account",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "email",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": true,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.billing": {
|
||||||
|
"name": "billing",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"customer_id": {
|
||||||
|
"name": "customer_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"payment_method_id": {
|
||||||
|
"name": "payment_method_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"payment_method_last4": {
|
||||||
|
"name": "payment_method_last4",
|
||||||
|
"type": "varchar(4)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"balance": {
|
||||||
|
"name": "balance",
|
||||||
|
"type": "bigint",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"reload": {
|
||||||
|
"name": "reload",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"billing_workspace_id_workspace_id_fk": {
|
||||||
|
"name": "billing_workspace_id_workspace_id_fk",
|
||||||
|
"tableFrom": "billing",
|
||||||
|
"tableTo": "workspace",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workspace_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"billing_workspace_id_id_pk": {
|
||||||
|
"name": "billing_workspace_id_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.payment": {
|
||||||
|
"name": "payment",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"customer_id": {
|
||||||
|
"name": "customer_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"payment_id": {
|
||||||
|
"name": "payment_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"name": "amount",
|
||||||
|
"type": "bigint",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"payment_workspace_id_workspace_id_fk": {
|
||||||
|
"name": "payment_workspace_id_workspace_id_fk",
|
||||||
|
"tableFrom": "payment",
|
||||||
|
"tableTo": "workspace",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workspace_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"payment_workspace_id_id_pk": {
|
||||||
|
"name": "payment_workspace_id_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.usage": {
|
||||||
|
"name": "usage",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"request_id": {
|
||||||
|
"name": "request_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"name": "model",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"input_tokens": {
|
||||||
|
"name": "input_tokens",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"output_tokens": {
|
||||||
|
"name": "output_tokens",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"reasoning_tokens": {
|
||||||
|
"name": "reasoning_tokens",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cache_read_tokens": {
|
||||||
|
"name": "cache_read_tokens",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cache_write_tokens": {
|
||||||
|
"name": "cache_write_tokens",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cost": {
|
||||||
|
"name": "cost",
|
||||||
|
"type": "bigint",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"usage_workspace_id_workspace_id_fk": {
|
||||||
|
"name": "usage_workspace_id_workspace_id_fk",
|
||||||
|
"tableFrom": "usage",
|
||||||
|
"tableTo": "workspace",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workspace_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"usage_workspace_id_id_pk": {
|
||||||
|
"name": "usage_workspace_id_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.key": {
|
||||||
|
"name": "key",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_used": {
|
||||||
|
"name": "time_used",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"global_key": {
|
||||||
|
"name": "global_key",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"key_workspace_id_id_pk": {
|
||||||
|
"name": "key_workspace_id_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.user": {
|
||||||
|
"name": "user",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_seen": {
|
||||||
|
"name": "time_seen",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"name": "color",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"user_email": {
|
||||||
|
"name": "user_email",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "workspace_id",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "email",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": true,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"user_workspace_id_workspace_id_fk": {
|
||||||
|
"name": "user_workspace_id_workspace_id_fk",
|
||||||
|
"tableFrom": "user",
|
||||||
|
"tableTo": "workspace",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workspace_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"user_workspace_id_id_pk": {
|
||||||
|
"name": "user_workspace_id_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.workspace": {
|
||||||
|
"name": "workspace",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "slug",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": true,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,609 @@
|
|||||||
|
{
|
||||||
|
"id": "fa935883-9e51-4811-90c7-8967eefe458c",
|
||||||
|
"prevId": "351e4956-74e0-4282-a23b-02f1a73fa38c",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.account": {
|
||||||
|
"name": "account",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "email",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": true,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.billing": {
|
||||||
|
"name": "billing",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"customer_id": {
|
||||||
|
"name": "customer_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"payment_method_id": {
|
||||||
|
"name": "payment_method_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"payment_method_last4": {
|
||||||
|
"name": "payment_method_last4",
|
||||||
|
"type": "varchar(4)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"balance": {
|
||||||
|
"name": "balance",
|
||||||
|
"type": "bigint",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"reload": {
|
||||||
|
"name": "reload",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"billing_workspace_id_workspace_id_fk": {
|
||||||
|
"name": "billing_workspace_id_workspace_id_fk",
|
||||||
|
"tableFrom": "billing",
|
||||||
|
"tableTo": "workspace",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workspace_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"billing_workspace_id_id_pk": {
|
||||||
|
"name": "billing_workspace_id_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.payment": {
|
||||||
|
"name": "payment",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"customer_id": {
|
||||||
|
"name": "customer_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"payment_id": {
|
||||||
|
"name": "payment_id",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"name": "amount",
|
||||||
|
"type": "bigint",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"payment_workspace_id_workspace_id_fk": {
|
||||||
|
"name": "payment_workspace_id_workspace_id_fk",
|
||||||
|
"tableFrom": "payment",
|
||||||
|
"tableTo": "workspace",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workspace_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"payment_workspace_id_id_pk": {
|
||||||
|
"name": "payment_workspace_id_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.usage": {
|
||||||
|
"name": "usage",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"name": "model",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"input_tokens": {
|
||||||
|
"name": "input_tokens",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"output_tokens": {
|
||||||
|
"name": "output_tokens",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"reasoning_tokens": {
|
||||||
|
"name": "reasoning_tokens",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cache_read_tokens": {
|
||||||
|
"name": "cache_read_tokens",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cache_write_tokens": {
|
||||||
|
"name": "cache_write_tokens",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cost": {
|
||||||
|
"name": "cost",
|
||||||
|
"type": "bigint",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"usage_workspace_id_workspace_id_fk": {
|
||||||
|
"name": "usage_workspace_id_workspace_id_fk",
|
||||||
|
"tableFrom": "usage",
|
||||||
|
"tableTo": "workspace",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workspace_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"usage_workspace_id_id_pk": {
|
||||||
|
"name": "usage_workspace_id_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.key": {
|
||||||
|
"name": "key",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_used": {
|
||||||
|
"name": "time_used",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"global_key": {
|
||||||
|
"name": "global_key",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"key_workspace_id_id_pk": {
|
||||||
|
"name": "key_workspace_id_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.user": {
|
||||||
|
"name": "user",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"time_seen": {
|
||||||
|
"name": "time_seen",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"name": "color",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"user_email": {
|
||||||
|
"name": "user_email",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "workspace_id",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "email",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": true,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"user_workspace_id_workspace_id_fk": {
|
||||||
|
"name": "user_workspace_id_workspace_id_fk",
|
||||||
|
"tableFrom": "user",
|
||||||
|
"tableTo": "workspace",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workspace_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"user_workspace_id_id_pk": {
|
||||||
|
"name": "user_workspace_id_id_pk",
|
||||||
|
"columns": [
|
||||||
|
"workspace_id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.workspace": {
|
||||||
|
"name": "workspace",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "varchar(30)",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"time_created": {
|
||||||
|
"name": "time_created",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"time_deleted": {
|
||||||
|
"name": "time_deleted",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "slug",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": true,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,34 @@
|
|||||||
{
|
{
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"dialect": "mysql",
|
"dialect": "postgresql",
|
||||||
"entries": [
|
"entries": [
|
||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "5",
|
"version": "7",
|
||||||
"when": 1756796050935,
|
"when": 1754518198186,
|
||||||
"tag": "0000_fluffy_raza",
|
"tag": "0000_amused_mojo",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"version": "5",
|
"version": "7",
|
||||||
"when": 1756871639102,
|
"when": 1754609655262,
|
||||||
"tag": "0001_serious_whistler",
|
"tag": "0001_thankful_chat",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1754627626945,
|
||||||
|
"tag": "0002_stale_jackal",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1754672464106,
|
||||||
|
"tag": "0003_tranquil_spencer_smythe",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode/cloud-core",
|
"name": "@opencode/cloud-core",
|
||||||
"version": "0.7.2",
|
"version": "0.4.13",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@opencode/cloud-resource": "workspace:*",
|
|
||||||
"@planetscale/database": "1.19.0",
|
|
||||||
"drizzle-orm": "0.41.0",
|
"drizzle-orm": "0.41.0",
|
||||||
"postgres": "3.4.7",
|
"postgres": "3.4.7",
|
||||||
"stripe": "18.0.0",
|
"stripe": "18.0.0",
|
||||||
@@ -17,11 +15,9 @@
|
|||||||
"./*": "./src/*"
|
"./*": "./src/*"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"db": "sst shell drizzle-kit",
|
"db": "sst shell drizzle-kit"
|
||||||
"typecheck": "tsc --noEmit"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"drizzle-kit": "0.30.5",
|
"drizzle-kit": "0.30.5"
|
||||||
"mysql2": "3.14.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export namespace Actor {
|
|||||||
properties: {
|
properties: {
|
||||||
userID: string
|
userID: string
|
||||||
workspaceID: string
|
workspaceID: string
|
||||||
|
email: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import { Resource } from "sst"
|
||||||
import { Stripe } from "stripe"
|
import { Stripe } from "stripe"
|
||||||
import { Database, eq, sql } from "./drizzle"
|
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 { Actor } from "./actor"
|
||||||
import { fn } from "./util/fn"
|
import { fn } from "./util/fn"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { User } from "./user"
|
import { Identifier } from "./identifier"
|
||||||
import { Resource } from "@opencode/cloud-resource"
|
import { centsToMicroCents } from "./util/price"
|
||||||
|
|
||||||
export namespace Billing {
|
export namespace Billing {
|
||||||
export const stripe = () =>
|
export const stripe = () =>
|
||||||
@@ -28,93 +29,43 @@ export namespace Billing {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const payments = async () => {
|
export const consume = fn(
|
||||||
return await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(PaymentTable)
|
|
||||||
.where(eq(PaymentTable.workspaceID, Actor.workspace()))
|
|
||||||
.orderBy(sql`${PaymentTable.timeCreated} DESC`)
|
|
||||||
.limit(100),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usages = async () => {
|
|
||||||
return await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(UsageTable)
|
|
||||||
.where(eq(UsageTable.workspaceID, Actor.workspace()))
|
|
||||||
.orderBy(sql`${UsageTable.timeCreated} DESC`)
|
|
||||||
.limit(100),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generateCheckoutUrl = fn(
|
|
||||||
z.object({
|
z.object({
|
||||||
successUrl: z.string(),
|
requestID: z.string().optional(),
|
||||||
cancelUrl: z.string(),
|
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) => {
|
async (input) => {
|
||||||
const account = Actor.assert("user")
|
const workspaceID = Actor.workspace()
|
||||||
const { successUrl, cancelUrl } = input
|
const cost = centsToMicroCents(input.costInCents)
|
||||||
|
|
||||||
const user = await User.fromID(account.properties.userID)
|
return await Database.transaction(async (tx) => {
|
||||||
const customer = await Billing.get()
|
await tx.insert(UsageTable).values({
|
||||||
const session = await Billing.stripe().checkout.sessions.create({
|
workspaceID,
|
||||||
mode: "payment",
|
id: Identifier.create("usage"),
|
||||||
line_items: [
|
requestID: input.requestID,
|
||||||
{
|
model: input.model,
|
||||||
price_data: {
|
inputTokens: input.inputTokens,
|
||||||
currency: "usd",
|
outputTokens: input.outputTokens,
|
||||||
product_data: {
|
reasoningTokens: input.reasoningTokens,
|
||||||
name: "opencode credits",
|
cacheReadTokens: input.cacheReadTokens,
|
||||||
},
|
cacheWriteTokens: input.cacheWriteTokens,
|
||||||
unit_amount: 2123, // $20 minimum + Stripe fee 4.4% + $0.30
|
cost,
|
||||||
},
|
})
|
||||||
quantity: 1,
|
const [updated] = await tx
|
||||||
},
|
.update(BillingTable)
|
||||||
],
|
.set({
|
||||||
payment_intent_data: {
|
balance: sql`${BillingTable.balance} - ${cost}`,
|
||||||
setup_future_usage: "on_session",
|
})
|
||||||
},
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
...(customer.customerID
|
.returning()
|
||||||
? { customer: customer.customerID }
|
return updated.balance
|
||||||
: {
|
|
||||||
customer_email: user.email,
|
|
||||||
customer_creation: "always",
|
|
||||||
}),
|
|
||||||
metadata: {
|
|
||||||
workspaceID: Actor.workspace(),
|
|
||||||
},
|
|
||||||
currency: "usd",
|
|
||||||
payment_method_types: ["card"],
|
|
||||||
success_url: successUrl,
|
|
||||||
cancel_url: cancelUrl,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return session.url
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
export const generatePortalUrl = fn(
|
|
||||||
z.object({
|
|
||||||
returnUrl: z.string(),
|
|
||||||
}),
|
|
||||||
async (input) => {
|
|
||||||
const { returnUrl } = input
|
|
||||||
|
|
||||||
const customer = await Billing.get()
|
|
||||||
if (!customer?.customerID) {
|
|
||||||
throw new Error("No stripe customer ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = await Billing.stripe().billingPortal.sessions.create({
|
|
||||||
customer: customer.customerID,
|
|
||||||
return_url: returnUrl,
|
|
||||||
})
|
|
||||||
|
|
||||||
return session.url
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,39 @@
|
|||||||
import { drizzle } from "drizzle-orm/planetscale-serverless"
|
import { drizzle } from "drizzle-orm/postgres-js"
|
||||||
import { Resource } from "@opencode/cloud-resource"
|
import { Resource } from "sst"
|
||||||
export * from "drizzle-orm"
|
export * from "drizzle-orm"
|
||||||
import { Client } from "@planetscale/database"
|
import postgres from "postgres"
|
||||||
|
|
||||||
import { MySqlTransaction, type MySqlTransactionConfig } from "drizzle-orm/mysql-core"
|
function createClient() {
|
||||||
import type { ExtractTablesWithRelations } from "drizzle-orm"
|
const client = postgres({
|
||||||
import type { PlanetScalePreparedQueryHKT, PlanetscaleQueryResultHKT } from "drizzle-orm/planetscale-serverless"
|
idle_timeout: 30000,
|
||||||
import { Context } from "../context"
|
connect_timeout: 30000,
|
||||||
import { memo } from "../util/memo"
|
host: Resource.Database.host,
|
||||||
|
database: Resource.Database.database,
|
||||||
export namespace Database {
|
user: Resource.Database.username,
|
||||||
export type Transaction = MySqlTransaction<
|
password: Resource.Database.password,
|
||||||
PlanetscaleQueryResultHKT,
|
port: Resource.Database.port,
|
||||||
PlanetScalePreparedQueryHKT,
|
ssl: {
|
||||||
Record<string, never>,
|
rejectUnauthorized: false,
|
||||||
ExtractTablesWithRelations<Record<string, never>>
|
},
|
||||||
>
|
max: 1,
|
||||||
|
|
||||||
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
|
|
||||||
})
|
})
|
||||||
|
|
||||||
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<{
|
const TransactionContext = Context.create<{
|
||||||
tx: TxOrDb
|
tx: TxOrDb
|
||||||
@@ -40,13 +46,14 @@ export namespace Database {
|
|||||||
return tx.transaction(callback)
|
return tx.transaction(callback)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Context.NotFound) {
|
if (err instanceof Context.NotFound) {
|
||||||
|
const client = createClient()
|
||||||
const effects: (() => void | Promise<void>)[] = []
|
const effects: (() => void | Promise<void>)[] = []
|
||||||
const result = await TransactionContext.provide(
|
const result = await TransactionContext.provide(
|
||||||
{
|
{
|
||||||
effects,
|
effects,
|
||||||
tx: client(),
|
tx: client,
|
||||||
},
|
},
|
||||||
() => callback(client()),
|
() => callback(client),
|
||||||
)
|
)
|
||||||
await Promise.all(effects.map((x) => x()))
|
await Promise.all(effects.map((x) => x()))
|
||||||
return result
|
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 {
|
try {
|
||||||
const { tx } = TransactionContext.use()
|
const { tx } = TransactionContext.use()
|
||||||
return callback(tx)
|
return callback(tx)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Context.NotFound) {
|
if (err instanceof Context.NotFound) {
|
||||||
|
const client = createClient()
|
||||||
const effects: (() => void | Promise<void>)[] = []
|
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))
|
return TransactionContext.provide({ tx, effects }, () => callback(tx))
|
||||||
}, config)
|
}, config)
|
||||||
await Promise.all(effects.map((x) => x()))
|
await Promise.all(effects.map((x) => x()))
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { sql } from "drizzle-orm"
|
import { bigint, timestamp, varchar } from "drizzle-orm/pg-core"
|
||||||
import { bigint, timestamp, varchar } from "drizzle-orm/mysql-core"
|
|
||||||
|
|
||||||
export const ulid = (name: string) => varchar(name, { length: 30 })
|
export const ulid = (name: string) => varchar(name, { length: 30 })
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ export const id = () => ulid("id").notNull()
|
|||||||
|
|
||||||
export const utc = (name: string) =>
|
export const utc = (name: string) =>
|
||||||
timestamp(name, {
|
timestamp(name, {
|
||||||
fsp: 3,
|
withTimezone: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const currency = (name: string) =>
|
export const currency = (name: string) =>
|
||||||
@@ -26,8 +25,5 @@ export const currency = (name: string) =>
|
|||||||
|
|
||||||
export const timestamps = {
|
export const timestamps = {
|
||||||
timeCreated: utc("time_created").notNull().defaultNow(),
|
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"),
|
timeDeleted: utc("time_deleted"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
import { z } from "zod"
|
|
||||||
import { fn } from "./util/fn"
|
|
||||||
import { Actor } from "./actor"
|
|
||||||
import { and, Database, eq, sql } from "./drizzle"
|
|
||||||
import { Identifier } from "./identifier"
|
|
||||||
import { KeyTable } from "./schema/key.sql"
|
|
||||||
|
|
||||||
export namespace Key {
|
|
||||||
export const list = async () => {
|
|
||||||
const workspace = Actor.workspace()
|
|
||||||
const keys = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(KeyTable)
|
|
||||||
.where(eq(KeyTable.workspaceID, workspace))
|
|
||||||
.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.use((tx) =>
|
|
||||||
tx.delete(KeyTable).where(and(eq(KeyTable.id, input.id), eq(KeyTable.workspaceID, workspace))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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"
|
import { id, timestamps } from "../drizzle/types"
|
||||||
|
|
||||||
export const AccountTable = mysqlTable(
|
export const AccountTable = pgTable(
|
||||||
"account",
|
"account",
|
||||||
{
|
{
|
||||||
id: id(),
|
id: id(),
|
||||||
|
|||||||
@@ -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 { timestamps, workspaceColumns } from "../drizzle/types"
|
||||||
import { workspaceIndexes } from "./workspace.sql"
|
import { workspaceIndexes } from "./workspace.sql"
|
||||||
|
|
||||||
export const BillingTable = mysqlTable(
|
export const BillingTable = pgTable(
|
||||||
"billing",
|
"billing",
|
||||||
{
|
{
|
||||||
...workspaceColumns,
|
...workspaceColumns,
|
||||||
@@ -16,7 +16,7 @@ export const BillingTable = mysqlTable(
|
|||||||
(table) => [...workspaceIndexes(table)],
|
(table) => [...workspaceIndexes(table)],
|
||||||
)
|
)
|
||||||
|
|
||||||
export const PaymentTable = mysqlTable(
|
export const PaymentTable = pgTable(
|
||||||
"payment",
|
"payment",
|
||||||
{
|
{
|
||||||
...workspaceColumns,
|
...workspaceColumns,
|
||||||
@@ -28,17 +28,17 @@ export const PaymentTable = mysqlTable(
|
|||||||
(table) => [...workspaceIndexes(table)],
|
(table) => [...workspaceIndexes(table)],
|
||||||
)
|
)
|
||||||
|
|
||||||
export const UsageTable = mysqlTable(
|
export const UsageTable = pgTable(
|
||||||
"usage",
|
"usage",
|
||||||
{
|
{
|
||||||
...workspaceColumns,
|
...workspaceColumns,
|
||||||
...timestamps,
|
...timestamps,
|
||||||
model: varchar("model", { length: 255 }).notNull(),
|
model: varchar("model", { length: 255 }).notNull(),
|
||||||
inputTokens: int("input_tokens").notNull(),
|
inputTokens: integer("input_tokens").notNull(),
|
||||||
outputTokens: int("output_tokens").notNull(),
|
outputTokens: integer("output_tokens").notNull(),
|
||||||
reasoningTokens: int("reasoning_tokens"),
|
reasoningTokens: integer("reasoning_tokens"),
|
||||||
cacheReadTokens: int("cache_read_tokens"),
|
cacheReadTokens: integer("cache_read_tokens"),
|
||||||
cacheWriteTokens: int("cache_write_tokens"),
|
cacheWriteTokens: integer("cache_write_tokens"),
|
||||||
cost: bigint("cost", { mode: "number" }).notNull(),
|
cost: bigint("cost", { mode: "number" }).notNull(),
|
||||||
},
|
},
|
||||||
(table) => [...workspaceIndexes(table)],
|
(table) => [...workspaceIndexes(table)],
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
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 { timestamps, utc, workspaceColumns } from "../drizzle/types"
|
||||||
import { workspaceIndexes } from "./workspace.sql"
|
import { workspaceIndexes } from "./workspace.sql"
|
||||||
import { Actor } from "../actor"
|
|
||||||
|
|
||||||
export const KeyTable = mysqlTable(
|
export const KeyTable = pgTable(
|
||||||
"key",
|
"key",
|
||||||
{
|
{
|
||||||
...workspaceColumns,
|
...workspaceColumns,
|
||||||
...timestamps,
|
...timestamps,
|
||||||
actor: json("actor").$type<Actor.Info>(),
|
userID: text("user_id").notNull(),
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
key: varchar("key", { length: 255 }).notNull(),
|
key: varchar("key", { length: 255 }).notNull(),
|
||||||
timeUsed: utc("time_used"),
|
timeUsed: utc("time_used"),
|
||||||
|
|||||||
@@ -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 { timestamps, utc, workspaceColumns } from "../drizzle/types"
|
||||||
import { workspaceIndexes } from "./workspace.sql"
|
import { workspaceIndexes } from "./workspace.sql"
|
||||||
|
|
||||||
export const UserTable = mysqlTable(
|
export const UserTable = pgTable(
|
||||||
"user",
|
"user",
|
||||||
{
|
{
|
||||||
...workspaceColumns,
|
...workspaceColumns,
|
||||||
...timestamps,
|
...timestamps,
|
||||||
email: varchar("email", { length: 255 }).notNull(),
|
email: text("email").notNull(),
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
timeSeen: utc("time_seen"),
|
timeSeen: utc("time_seen"),
|
||||||
color: int("color"),
|
color: integer("color"),
|
||||||
},
|
},
|
||||||
(table) => [...workspaceIndexes(table), uniqueIndex("user_email").on(table.workspaceID, table.email)],
|
(table) => [...workspaceIndexes(table), uniqueIndex("user_email").on(table.workspaceID, table.email)],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { primaryKey, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
|
import { primaryKey, foreignKey, pgTable, uniqueIndex, varchar } from "drizzle-orm/pg-core"
|
||||||
import { timestamps, ulid } from "../drizzle/types"
|
import { timestamps, ulid } from "../drizzle/types"
|
||||||
|
|
||||||
export const WorkspaceTable = mysqlTable(
|
export const WorkspaceTable = pgTable(
|
||||||
"workspace",
|
"workspace",
|
||||||
{
|
{
|
||||||
id: ulid("id").notNull().primaryKey(),
|
id: ulid("id").notNull().primaryKey(),
|
||||||
@@ -17,5 +17,9 @@ export function workspaceIndexes(table: any) {
|
|||||||
primaryKey({
|
primaryKey({
|
||||||
columns: [table.workspaceID, table.id],
|
columns: [table.workspaceID, table.id],
|
||||||
}),
|
}),
|
||||||
|
foreignKey({
|
||||||
|
foreignColumns: [WorkspaceTable.id],
|
||||||
|
columns: [table.workspaceID],
|
||||||
|
}),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import { z } from "zod"
|
|
||||||
import { eq } from "drizzle-orm"
|
|
||||||
import { fn } from "./util/fn"
|
|
||||||
import { Database } from "./drizzle"
|
|
||||||
import { UserTable } from "./schema/user.sql"
|
|
||||||
|
|
||||||
export namespace User {
|
|
||||||
export const fromID = fn(z.string(), async (id) =>
|
|
||||||
Database.transaction(async (tx) => {
|
|
||||||
return tx
|
|
||||||
.select()
|
|
||||||
.from(UserTable)
|
|
||||||
.where(eq(UserTable.id, id))
|
|
||||||
.execute()
|
|
||||||
.then((rows) => rows[0])
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||