Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09ff3b9bb9 | ||
|
|
2256362ba2 | ||
|
|
077ca4454f | ||
|
|
05cbb11709 | ||
|
|
fcc561ebb7 | ||
|
|
ee6ca104e5 | ||
|
|
4347a77d89 | ||
|
|
76b10d85ee | ||
|
|
45a770cdb1 | ||
|
|
a57c8669b6 | ||
|
|
f9fcdead55 | ||
|
|
ff669d4414 | ||
|
|
9b2d595cfc | ||
|
|
3839d70a94 | ||
|
|
6fe265e7d8 | ||
|
|
2aed4d263b | ||
|
|
e2ac588c84 | ||
|
|
8917dfdf5e | ||
|
|
86900d71f5 | ||
|
|
adcc661798 | ||
|
|
f4a28b2659 | ||
|
|
a160a35d0c | ||
|
|
90eaf9b3fc | ||
|
|
16d516dbdb | ||
|
|
0026bc5815 | ||
|
|
bcdaf7e779 | ||
|
|
874e22a045 | ||
|
|
905226c01e | ||
|
|
73adf7e86f | ||
|
|
4c37e17ac2 | ||
|
|
cd6e07355b | ||
|
|
29703aee9a | ||
|
|
3997d3f2d7 | ||
|
|
1fccb3bda4 | ||
|
|
16b2bfa8ef | ||
|
|
4eb6b57503 | ||
|
|
7599396162 | ||
|
|
d99d1315ee | ||
|
|
d831432f93 | ||
|
|
0ddf8e6c6e | ||
|
|
a520c4ff98 | ||
|
|
a184714f67 | ||
|
|
9b76337236 | ||
|
|
4dc72669e5 | ||
|
|
dfa59dd21d | ||
|
|
f642a6c5b9 | ||
|
|
e37104cb10 | ||
|
|
dc654c93d2 | ||
|
|
c67b0a9ba4 | ||
|
|
5b699a0d9b | ||
|
|
bc557e828d | ||
|
|
fcaa041ef9 | ||
|
|
3c9d80d75f | ||
|
|
a761f66a16 | ||
|
|
15e80fca69 | ||
|
|
43680534df | ||
|
|
aa522aad62 | ||
|
|
82319bbd83 | ||
|
|
45fa4eda15 | ||
|
|
f242541ef3 | ||
|
|
562f067131 | ||
|
|
1ff46c75fa | ||
|
|
73d5cacc06 | ||
|
|
b8828f2609 | ||
|
|
2f7b2cf603 | ||
|
|
eaf18d9915 | ||
|
|
7aa7dd3690 | ||
|
|
bee4b6801e | ||
|
|
3565d8e44d | ||
|
|
0187b6bb72 | ||
|
|
0eb898abcf | ||
|
|
5a309c2dbf | ||
|
|
452f11ff77 | ||
|
|
774c24e76e | ||
|
|
ec4a44087b | ||
|
|
501347cda5 | ||
|
|
3f3816c0f2 | ||
|
|
0a3c72d678 | ||
|
|
66b7a4991e | ||
|
|
1550ae47c0 | ||
|
|
33ba064c40 | ||
|
|
96ae5925c3 | ||
|
|
3a750b0809 | ||
|
|
1258f7aeea | ||
|
|
797a56873d | ||
|
|
05867f9318 | ||
|
|
5947fe72e4 | ||
|
|
f3d4dd5099 | ||
|
|
b68a4a8838 | ||
|
|
b7a1d8f2f5 | ||
|
|
80e1173ef7 | ||
|
|
8ae10f1c94 | ||
|
|
f24251f89e | ||
|
|
3600bd27f4 | ||
|
|
92089bb295 | ||
|
|
a70932f742 | ||
|
|
217cf24c3c | ||
|
|
c87939ad12 | ||
|
|
a092f567b7 | ||
|
|
883a6577d5 | ||
|
|
20b52cad2a | ||
|
|
528291532b | ||
|
|
2b77a84c4f | ||
|
|
7d0b52dc29 | ||
|
|
c86c2acf4c | ||
|
|
29bf731d47 | ||
|
|
3c5a256f0f | ||
|
|
067338bc25 | ||
|
|
736cd10847 | ||
|
|
a03daa4252 | ||
|
|
b01eec38d1 | ||
|
|
35cb06e0e4 | ||
|
|
f3b7d2f786 | ||
|
|
1facf7d8e4 | ||
|
|
ddd9c71cca | ||
|
|
520a814fc2 | ||
|
|
2072c8681a | ||
|
|
21990621e2 | ||
|
|
68a0947292 | ||
|
|
f1f44644e2 | ||
|
|
afb8a0d28a | ||
|
|
f05f175842 | ||
|
|
c0b214232d | ||
|
|
f4f8f2d151 | ||
|
|
b4ad5c138e | ||
|
|
789e111a0f | ||
|
|
eaa76dad0c | ||
|
|
66f9bdab32 | ||
|
|
efaa9166fb | ||
|
|
eb2044989e | ||
|
|
5d37e58d34 | ||
|
|
20088a87b0 | ||
|
|
05019dae76 | ||
|
|
bf37a88f7f | ||
|
|
d839f70834 | ||
|
|
498a4ab408 | ||
|
|
835e48cd28 | ||
|
|
1a5a63843e | ||
|
|
d954e1e3b6 | ||
|
|
8f22a6b69d | ||
|
|
fd37d5b54e | ||
|
|
e146083b73 | ||
|
|
d7a1c268d9 | ||
|
|
5c4345da4f | ||
|
|
0be37cc2c6 | ||
|
|
64f0205f97 | ||
|
|
b3a1360ad9 | ||
|
|
08d4d6d4af | ||
|
|
9c69c1de9f | ||
|
|
735f3d17bc | ||
|
|
db7243c364 | ||
|
|
f0912ee838 | ||
|
|
983f8ffeca | ||
|
|
c474380684 | ||
|
|
7ca767de55 | ||
|
|
1954c1255e | ||
|
|
b4f33485a7 | ||
|
|
f2504d8eb2 | ||
|
|
e47f383137 | ||
|
|
cd56845dce | ||
|
|
1c24dd02a6 | ||
|
|
b7b09fdfc2 | ||
|
|
62702fbd11 | ||
|
|
22c68a6992 | ||
|
|
ca1b597b01 | ||
|
|
d527ceeb2b | ||
|
|
2e9c22d911 | ||
|
|
762c58b756 | ||
|
|
61d0b3e4dc | ||
|
|
71a7ad1a4e | ||
|
|
20399bbdfe | ||
|
|
547a975707 | ||
|
|
c009cab15b | ||
|
|
e6bc3b253b | ||
|
|
f1a13f25a4 | ||
|
|
4695e685cf | ||
|
|
c6092e4ad9 | ||
|
|
e6045ca925 | ||
|
|
ebbb4dd88a | ||
|
|
087473be6e | ||
|
|
e0eb460fc8 | ||
|
|
7c6b3f981e | ||
|
|
a5b6c57a76 | ||
|
|
65724b693b | ||
|
|
8b9a85b7e7 | ||
|
|
7cbec9a1a7 | ||
|
|
c4ba5961c8 | ||
|
|
82b432349e | ||
|
|
68ed664a3f | ||
|
|
3a30773874 | ||
|
|
0c0057a7de | ||
|
|
fa79736b87 | ||
|
|
2e0c2c9db7 | ||
|
|
3e9366487a | ||
|
|
025ed04da0 | ||
|
|
b81eca4ebc | ||
|
|
20c18689c0 | ||
|
|
a803cf8aee | ||
|
|
c526e2d908 | ||
|
|
bdbbcd8a0c | ||
|
|
43c2da24d0 | ||
|
|
3205db9c16 | ||
|
|
efbab087df | ||
|
|
9280db3297 | ||
|
|
0cc3c3bc78 | ||
|
|
ee8b38ab26 | ||
|
|
44fa3d5392 | ||
|
|
a457828a67 | ||
|
|
4752c83155 | ||
|
|
f94ee5ce90 | ||
|
|
76386f5cfc | ||
|
|
a9275def43 | ||
|
|
50ed4c6b5d | ||
|
|
f882cca98a | ||
|
|
2d2a044961 | ||
|
|
b41fbda68f | ||
|
|
794c5981a5 | ||
|
|
d5738f542c | ||
|
|
02b7eb59f8 | ||
|
|
a8f23fb548 | ||
|
|
1a642a79a6 | ||
|
|
b6b0097755 | ||
|
|
afb1cad26d | ||
|
|
58186004db | ||
|
|
a593ed4c9b | ||
|
|
449270aacc | ||
|
|
e20535655b | ||
|
|
1662e149b3 | ||
|
|
dfe3e79304 | ||
|
|
e92a2ec9db | ||
|
|
7cba1ff793 | ||
|
|
8c3cc0d447 | ||
|
|
e30562d5f4 | ||
|
|
7c06ef2477 | ||
|
|
58eccf7f53 | ||
|
|
6da60bd5d9 | ||
|
|
8a43c24934 | ||
|
|
b03172d723 | ||
|
|
e342795bd0 | ||
|
|
a44d4acb3f | ||
|
|
8b8a358de1 | ||
|
|
075fa2c0e8 | ||
|
|
8b287caaad | ||
|
|
b3e6b7a985 | ||
|
|
5c74bff8e1 | ||
|
|
172bbdaced | ||
|
|
563b4c33f2 | ||
|
|
982b71e861 | ||
|
|
75df5040ea | ||
|
|
f2b2940298 | ||
|
|
22f51c6b47 | ||
|
|
de286b08f6 | ||
|
|
0f2124db32 | ||
|
|
e30a159264 | ||
|
|
a5edf3a311 | ||
|
|
7c2907cbb4 | ||
|
|
bce9dc040f | ||
|
|
44297ffe78 | ||
|
|
cbb3141130 | ||
|
|
18cf4df6c6 | ||
|
|
f3e8a275b8 | ||
|
|
7df36cf0fa | ||
|
|
e82b112759 | ||
|
|
030b14ac4e | ||
|
|
b1e381cff7 | ||
|
|
0433d4d064 | ||
|
|
d34fdac854 | ||
|
|
ec828619ca | ||
|
|
559013e124 | ||
|
|
8e3ab4afa7 | ||
|
|
13305966e5 | ||
|
|
a618fbe8cf | ||
|
|
07dc1f8ecc | ||
|
|
445c8631a2 | ||
|
|
c4eacd0ccf | ||
|
|
dd5ec26c8c | ||
|
|
8b062ed621 | ||
|
|
ab97a95032 | ||
|
|
a98d108d2e | ||
|
|
2e875b2d65 | ||
|
|
790baec41f | ||
|
|
52fbd16e08 | ||
|
|
cf97633d7d | ||
|
|
3fe2e89d55 | ||
|
|
eb5c113cff | ||
|
|
bd9c13bb27 | ||
|
|
3019b1f825 | ||
|
|
119cc8c795 | ||
|
|
be9b2bab15 | ||
|
|
c949e5b390 | ||
|
|
1c717d62e4 | ||
|
|
27675dfd70 | ||
|
|
374275eeb6 | ||
|
|
faa848cfb1 | ||
|
|
80772e5ac2 | ||
|
|
be34747526 | ||
|
|
49d9f99924 | ||
|
|
1f9e195cd8 | ||
|
|
539d6baa8c | ||
|
|
f6fc693c1f | ||
|
|
50d8396c9a | ||
|
|
22dd70b75b | ||
|
|
b4f8de0c0a | ||
|
|
768e0553bd | ||
|
|
f3e79235fb | ||
|
|
eacf3ad361 | ||
|
|
72062d22a0 | ||
|
|
9f90f0e8ed | ||
|
|
c74bc323b6 | ||
|
|
c7b825a42a | ||
|
|
b1a613b3b9 | ||
|
|
958f1edfef | ||
|
|
2bb299d741 | ||
|
|
9930ac6929 | ||
|
|
1906a347f3 | ||
|
|
e5d0c63b29 | ||
|
|
970796b832 | ||
|
|
3c5043497c | ||
|
|
4d09c5618e | ||
|
|
adae0d1853 | ||
|
|
61aeb2a2a7 | ||
|
|
4b0f7b82ba | ||
|
|
9fb24074c8 | ||
|
|
542c9d5346 | ||
|
|
d5f0e3fccc | ||
|
|
7d2bb5cb2b | ||
|
|
ca7a70b628 | ||
|
|
b3a2f9fb4e | ||
|
|
8be5a29870 | ||
|
|
68092f22e1 | ||
|
|
83f3c729e9 | ||
|
|
e37fd9c105 | ||
|
|
2e4fe973c9 | ||
|
|
1b82511fbd | ||
|
|
f24314438b | ||
|
|
361a962673 | ||
|
|
fa9c283fcf | ||
|
|
947b864d96 | ||
|
|
03eabb10e4 | ||
|
|
34c9d106ee | ||
|
|
fe57d7bb38 | ||
|
|
68cf6b04a0 | ||
|
|
9ffaf81fb3 | ||
|
|
50530b1ea7 | ||
|
|
a160eee499 | ||
|
|
d9aef1d73d | ||
|
|
4ba0b22b04 | ||
|
|
662d2b205a | ||
|
|
75960ae00c | ||
|
|
528f198c39 |
@@ -0,0 +1,3 @@
|
|||||||
|
### What does this PR do?
|
||||||
|
|
||||||
|
### How did you verify your code works?
|
||||||
@@ -9,6 +9,13 @@ on:
|
|||||||
- "nix/**"
|
- "nix/**"
|
||||||
- "packages/app/**"
|
- "packages/app/**"
|
||||||
- "packages/desktop/**"
|
- "packages/desktop/**"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "flake.nix"
|
||||||
|
- "flake.lock"
|
||||||
|
- "nix/**"
|
||||||
|
- "packages/app/**"
|
||||||
|
- "packages/desktop/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
name: PR Standards
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, edited, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-standards:
|
||||||
|
if: |
|
||||||
|
github.event.pull_request.user.login != 'actions-user' &&
|
||||||
|
github.event.pull_request.user.login != 'opencode' &&
|
||||||
|
github.event.pull_request.user.login != 'rekram1-node' &&
|
||||||
|
github.event.pull_request.user.login != 'thdxr' &&
|
||||||
|
github.event.pull_request.user.login != 'kommander' &&
|
||||||
|
github.event.pull_request.user.login != 'jayair' &&
|
||||||
|
github.event.pull_request.user.login != 'fwang' &&
|
||||||
|
github.event.pull_request.user.login != 'adamdotdevin' &&
|
||||||
|
github.event.pull_request.user.login != 'iamdavidhill' &&
|
||||||
|
github.event.pull_request.user.login != 'opencode-agent[bot]'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Check PR standards
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const pr = context.payload.pull_request;
|
||||||
|
const title = pr.title;
|
||||||
|
|
||||||
|
async function addLabel(label) {
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: pr.number,
|
||||||
|
labels: [label]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeLabel(label) {
|
||||||
|
try {
|
||||||
|
await github.rest.issues.removeLabel({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: pr.number,
|
||||||
|
name: label
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Label wasn't present, ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function comment(marker, body) {
|
||||||
|
const markerText = `<!-- pr-standards:${marker} -->`;
|
||||||
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: pr.number
|
||||||
|
});
|
||||||
|
|
||||||
|
const existing = comments.find(c => c.body.includes(markerText));
|
||||||
|
if (existing) return;
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: pr.number,
|
||||||
|
body: markerText + '\n' + body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Check title format
|
||||||
|
// Matches: feat:, feat(scope):, feat (scope):, etc.
|
||||||
|
const titlePattern = /^(feat|fix|docs|chore|refactor|test)\s*(\([a-zA-Z0-9-]+\))?\s*:/;
|
||||||
|
const hasValidTitle = titlePattern.test(title);
|
||||||
|
|
||||||
|
if (!hasValidTitle) {
|
||||||
|
await addLabel('needs:title');
|
||||||
|
await comment('title', `Hey! Your PR title \`${title}\` doesn't follow conventional commit format.
|
||||||
|
|
||||||
|
Please update it to start with one of:
|
||||||
|
- \`feat:\` or \`feat(scope):\` new feature
|
||||||
|
- \`fix:\` or \`fix(scope):\` bug fix
|
||||||
|
- \`docs:\` or \`docs(scope):\` documentation changes
|
||||||
|
- \`chore:\` or \`chore(scope):\` maintenance tasks
|
||||||
|
- \`refactor:\` or \`refactor(scope):\` code refactoring
|
||||||
|
- \`test:\` or \`test(scope):\` adding or updating tests
|
||||||
|
|
||||||
|
Where \`scope\` is the package name (e.g., \`app\`, \`desktop\`, \`opencode\`).
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#pr-titles) for details.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await removeLabel('needs:title');
|
||||||
|
|
||||||
|
// Step 2: Check for linked issue (skip for docs/refactor PRs)
|
||||||
|
const skipIssueCheck = /^(docs|refactor)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
|
||||||
|
if (skipIssueCheck) {
|
||||||
|
await removeLabel('needs:issue');
|
||||||
|
console.log('Skipping issue check for docs/refactor PR');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const query = `
|
||||||
|
query($owner: String!, $repo: String!, $number: Int!) {
|
||||||
|
repository(owner: $owner, name: $repo) {
|
||||||
|
pullRequest(number: $number) {
|
||||||
|
closingIssuesReferences(first: 1) {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await github.graphql(query, {
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
number: pr.number
|
||||||
|
});
|
||||||
|
|
||||||
|
const linkedIssues = result.repository.pullRequest.closingIssuesReferences.totalCount;
|
||||||
|
|
||||||
|
if (linkedIssues === 0) {
|
||||||
|
await addLabel('needs:issue');
|
||||||
|
await comment('issue', `Thanks for your contribution!
|
||||||
|
|
||||||
|
This PR doesn't have a linked issue. All PRs must reference an existing issue.
|
||||||
|
|
||||||
|
Please:
|
||||||
|
1. Open an issue describing the bug/feature (if one doesn't exist)
|
||||||
|
2. Add \`Fixes #<number>\` or \`Closes #<number>\` to this PR description
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#issue-first-policy) for details.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await removeLabel('needs:issue');
|
||||||
|
console.log('PR meets all standards');
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
|
|
||||||
publish-tauri:
|
publish-tauri:
|
||||||
needs: publish
|
needs: publish
|
||||||
continue-on-error: true
|
continue-on-error: false
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -177,8 +177,22 @@ jobs:
|
|||||||
cargo tauri --version
|
cargo tauri --version
|
||||||
|
|
||||||
- name: Build and upload artifacts
|
- name: Build and upload artifacts
|
||||||
timeout-minutes: 20
|
uses: Wandalen/wretry.action@v3
|
||||||
uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
|
timeout-minutes: 60
|
||||||
|
with:
|
||||||
|
attempt_limit: 3
|
||||||
|
attempt_delay: 10000
|
||||||
|
action: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
|
||||||
|
with: |
|
||||||
|
projectPath: packages/desktop
|
||||||
|
uploadWorkflowArtifacts: true
|
||||||
|
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
|
||||||
|
args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose
|
||||||
|
updaterJsonPreferNsis: true
|
||||||
|
releaseId: ${{ needs.publish.outputs.release }}
|
||||||
|
tagName: ${{ needs.publish.outputs.tag }}
|
||||||
|
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
|
||||||
|
releaseDraft: true
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
|
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
|
||||||
@@ -190,16 +204,6 @@ jobs:
|
|||||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||||
APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
|
APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
|
||||||
with:
|
|
||||||
projectPath: packages/desktop
|
|
||||||
uploadWorkflowArtifacts: true
|
|
||||||
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
|
|
||||||
args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose
|
|
||||||
updaterJsonPreferNsis: true
|
|
||||||
releaseId: ${{ needs.publish.outputs.release }}
|
|
||||||
tagName: ${{ needs.publish.outputs.tag }}
|
|
||||||
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
|
|
||||||
releaseDraft: true
|
|
||||||
|
|
||||||
publish-release:
|
publish-release:
|
||||||
needs:
|
needs:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ on:
|
|||||||
- "packages/*/package.json"
|
- "packages/*/package.json"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update:
|
update-linux:
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
env:
|
env:
|
||||||
@@ -47,14 +47,14 @@ jobs:
|
|||||||
nix flake update
|
nix flake update
|
||||||
echo "✅ flake.lock updated successfully"
|
echo "✅ flake.lock updated successfully"
|
||||||
|
|
||||||
- name: Update node_modules hash
|
- name: Update node_modules hash for x86_64-linux
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
echo "🔄 Updating node_modules hash..."
|
echo "🔄 Updating node_modules hash for x86_64-linux..."
|
||||||
nix/scripts/update-hashes.sh
|
nix/scripts/update-hashes.sh
|
||||||
echo "✅ node_modules hash updated successfully"
|
echo "✅ node_modules hash for x86_64-linux updated successfully"
|
||||||
|
|
||||||
- name: Commit hash changes
|
- name: Commit Linux hash changes
|
||||||
env:
|
env:
|
||||||
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
summarize() {
|
summarize() {
|
||||||
local status="$1"
|
local status="$1"
|
||||||
{
|
{
|
||||||
echo "### Nix Hash Update"
|
echo "### Nix Hash Update (x86_64-linux)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "- ref: ${GITHUB_REF_NAME}"
|
echo "- ref: ${GITHUB_REF_NAME}"
|
||||||
echo "- status: ${status}"
|
echo "- status: ${status}"
|
||||||
@@ -89,7 +89,92 @@ jobs:
|
|||||||
echo "🔗 Staging files..."
|
echo "🔗 Staging files..."
|
||||||
git add "${FILES[@]}"
|
git add "${FILES[@]}"
|
||||||
echo "💾 Committing changes..."
|
echo "💾 Committing changes..."
|
||||||
git commit -m "Update Nix flake.lock and hashes"
|
git commit -m "Update Nix flake.lock and x86_64-linux hash"
|
||||||
|
echo "✅ Changes committed"
|
||||||
|
|
||||||
|
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
||||||
|
echo "🌳 Pulling latest from branch: $BRANCH"
|
||||||
|
git pull --rebase origin "$BRANCH"
|
||||||
|
echo "🚀 Pushing changes to branch: $BRANCH"
|
||||||
|
git push origin HEAD:"$BRANCH"
|
||||||
|
echo "✅ Changes pushed successfully"
|
||||||
|
|
||||||
|
summarize "committed $(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
|
update-macos:
|
||||||
|
needs: update-linux
|
||||||
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
|
runs-on: macos-latest
|
||||||
|
env:
|
||||||
|
SYSTEM: aarch64-darwin
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ github.head_ref || github.ref_name }}
|
||||||
|
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||||
|
|
||||||
|
- name: Setup Nix
|
||||||
|
uses: DeterminateSystems/nix-installer-action@v20
|
||||||
|
|
||||||
|
- name: Configure git
|
||||||
|
run: |
|
||||||
|
git config --global user.email "action@github.com"
|
||||||
|
git config --global user.name "Github Action"
|
||||||
|
|
||||||
|
- name: Pull latest changes
|
||||||
|
env:
|
||||||
|
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||||
|
run: |
|
||||||
|
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
||||||
|
git pull origin "$BRANCH"
|
||||||
|
|
||||||
|
- name: Update node_modules hash for aarch64-darwin
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
echo "🔄 Updating node_modules hash for aarch64-darwin..."
|
||||||
|
nix/scripts/update-hashes.sh
|
||||||
|
echo "✅ node_modules hash for aarch64-darwin updated successfully"
|
||||||
|
|
||||||
|
- name: Commit macOS hash changes
|
||||||
|
env:
|
||||||
|
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "🔍 Checking for changes in tracked Nix files..."
|
||||||
|
|
||||||
|
summarize() {
|
||||||
|
local status="$1"
|
||||||
|
{
|
||||||
|
echo "### Nix Hash Update (aarch64-darwin)"
|
||||||
|
echo ""
|
||||||
|
echo "- ref: ${GITHUB_REF_NAME}"
|
||||||
|
echo "- status: ${status}"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
if [ -n "${GITHUB_SERVER_URL:-}" ] && [ -n "${GITHUB_REPOSITORY:-}" ] && [ -n "${GITHUB_RUN_ID:-}" ]; then
|
||||||
|
echo "- run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
fi
|
||||||
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
}
|
||||||
|
|
||||||
|
FILES=(nix/hashes.json)
|
||||||
|
STATUS="$(git status --short -- "${FILES[@]}" || true)"
|
||||||
|
if [ -z "$STATUS" ]; then
|
||||||
|
echo "✅ No changes detected. Hash is already up to date."
|
||||||
|
summarize "no changes"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "📝 Changes detected:"
|
||||||
|
echo "$STATUS"
|
||||||
|
echo "🔗 Staging files..."
|
||||||
|
git add "${FILES[@]}"
|
||||||
|
echo "💾 Committing changes..."
|
||||||
|
git commit -m "Update aarch64-darwin hash"
|
||||||
echo "✅ Changes committed"
|
echo "✅ Changes committed"
|
||||||
|
|
||||||
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
||||||
|
|||||||
@@ -24,3 +24,4 @@ target
|
|||||||
# Local dev files
|
# Local dev files
|
||||||
opencode-dev
|
opencode-dev
|
||||||
logs/
|
logs/
|
||||||
|
*.bun-build
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ Desktop app issues:
|
|||||||
|
|
||||||
#### zen
|
#### zen
|
||||||
|
|
||||||
**Only** add if the issue mentions "zen" or "opencode zen". Zen is our gateway for coding models. **Do not** add for other gateways or inference providers.
|
**Only** add if the issue mentions "zen" or "opencode zen" or "opencode black".
|
||||||
|
|
||||||
If the issue doesn't have "zen" in it then don't add zen label
|
If the issue doesn't have "zen" or "opencode black" in it then don't add zen label
|
||||||
|
|
||||||
#### docs
|
#### docs
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
- To test opencode in the `packages/opencode` directory you can run `bun dev`
|
- To test opencode in `packages/opencode`, run `bun dev`.
|
||||||
- To regenerate the javascript SDK, run ./packages/sdk/js/script/build.ts
|
- To regenerate the JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
|
||||||
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
|
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
|
||||||
- the default branch in this repo is `dev`
|
- The default branch in this repo is `dev`.
|
||||||
|
|||||||
+56
-4
@@ -149,11 +149,63 @@ With that said, you may want to try these methods, as they might work for you.
|
|||||||
|
|
||||||
## Pull Request Expectations
|
## Pull Request Expectations
|
||||||
|
|
||||||
- Try to keep pull requests small and focused.
|
### Issue First Policy
|
||||||
- Link relevant issue(s) in the description
|
|
||||||
|
**All PRs must reference an existing issue.** Before opening a PR, open an issue describing the bug or feature. This helps maintainers triage and prevents duplicate work. PRs without a linked issue may be closed without review.
|
||||||
|
|
||||||
|
- Use `Fixes #123` or `Closes #123` in your PR description to link the issue
|
||||||
|
- For small fixes, a brief issue is fine - just enough context for maintainers to understand the problem
|
||||||
|
|
||||||
|
### General Requirements
|
||||||
|
|
||||||
|
- Keep pull requests small and focused
|
||||||
- Explain the issue and why your change fixes it
|
- Explain the issue and why your change fixes it
|
||||||
- Avoid having verbose LLM generated PR descriptions
|
- Before adding new functionality, ensure it doesn't already exist elsewhere in the codebase
|
||||||
- Before adding new functions or functionality, ensure that such behavior doesn't already exist elsewhere in the codebase.
|
|
||||||
|
### UI Changes
|
||||||
|
|
||||||
|
If your PR includes UI changes, please include screenshots or videos showing the before and after. This helps maintainers review faster and gives you quicker feedback.
|
||||||
|
|
||||||
|
### Logic Changes
|
||||||
|
|
||||||
|
For non-UI changes (bug fixes, new features, refactors), explain **how you verified it works**:
|
||||||
|
|
||||||
|
- What did you test?
|
||||||
|
- How can a reviewer reproduce/confirm the fix?
|
||||||
|
|
||||||
|
### No AI-Generated Walls of Text
|
||||||
|
|
||||||
|
Long, AI-generated PR descriptions and issues are not acceptable and may be ignored. Respect the maintainers' time:
|
||||||
|
|
||||||
|
- Write short, focused descriptions
|
||||||
|
- Explain what changed and why in your own words
|
||||||
|
- If you can't explain it briefly, your PR might be too large
|
||||||
|
|
||||||
|
### PR Titles
|
||||||
|
|
||||||
|
PR titles should follow conventional commit standards:
|
||||||
|
|
||||||
|
- `feat:` new feature or functionality
|
||||||
|
- `fix:` bug fix
|
||||||
|
- `docs:` documentation or README changes
|
||||||
|
- `chore:` maintenance tasks, dependency updates, etc.
|
||||||
|
- `refactor:` code refactoring without changing behavior
|
||||||
|
- `test:` adding or updating tests
|
||||||
|
|
||||||
|
You can optionally include a scope to indicate which package is affected:
|
||||||
|
|
||||||
|
- `feat(app):` feature in the app package
|
||||||
|
- `fix(desktop):` bug fix in the desktop package
|
||||||
|
- `chore(opencode):` maintenance in the opencode package
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- `docs: update contributing guidelines`
|
||||||
|
- `fix: resolve crash on startup`
|
||||||
|
- `feat: add dark mode support`
|
||||||
|
- `feat(app): add dark mode support`
|
||||||
|
- `fix(desktop): resolve crash on startup`
|
||||||
|
- `chore: bump dependency versions`
|
||||||
|
|
||||||
### Style Preferences
|
### Style Preferences
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ curl -fsSL https://opencode.ai/install | bash
|
|||||||
npm i -g opencode-ai@latest # or bun/pnpm/yarn
|
npm i -g opencode-ai@latest # or bun/pnpm/yarn
|
||||||
scoop bucket add extras; scoop install extras/opencode # Windows
|
scoop bucket add extras; scoop install extras/opencode # Windows
|
||||||
choco install opencode # Windows
|
choco install opencode # Windows
|
||||||
brew install opencode # macOS and Linux
|
brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)
|
||||||
|
brew install opencode # macOS and Linux (official brew formula, updated less)
|
||||||
paru -S opencode-bin # Arch Linux
|
paru -S opencode-bin # Arch Linux
|
||||||
mise use -g opencode # Any OS
|
mise use -g opencode # Any OS
|
||||||
nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch
|
nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch
|
||||||
|
|||||||
+2
-1
@@ -28,7 +28,8 @@ curl -fsSL https://opencode.ai/install | bash
|
|||||||
npm i -g opencode-ai@latest # 也可使用 bun/pnpm/yarn
|
npm i -g opencode-ai@latest # 也可使用 bun/pnpm/yarn
|
||||||
scoop bucket add extras; scoop install extras/opencode # Windows
|
scoop bucket add extras; scoop install extras/opencode # Windows
|
||||||
choco install opencode # Windows
|
choco install opencode # Windows
|
||||||
brew install opencode # macOS 和 Linux
|
brew install anomalyco/tap/opencode # macOS 和 Linux(推荐,始终保持最新)
|
||||||
|
brew install opencode # macOS 和 Linux(官方 brew formula,更新频率较低)
|
||||||
paru -S opencode-bin # Arch Linux
|
paru -S opencode-bin # Arch Linux
|
||||||
mise use -g opencode # 任意系统
|
mise use -g opencode # 任意系统
|
||||||
nix run nixpkgs#opencode # 或用 github:anomalyco/opencode 获取最新 dev 分支
|
nix run nixpkgs#opencode # 或用 github:anomalyco/opencode 获取最新 dev 分支
|
||||||
|
|||||||
+2
-1
@@ -28,7 +28,8 @@ curl -fsSL https://opencode.ai/install | bash
|
|||||||
npm i -g opencode-ai@latest # 也可使用 bun/pnpm/yarn
|
npm i -g opencode-ai@latest # 也可使用 bun/pnpm/yarn
|
||||||
scoop bucket add extras; scoop install extras/opencode # Windows
|
scoop bucket add extras; scoop install extras/opencode # Windows
|
||||||
choco install opencode # Windows
|
choco install opencode # Windows
|
||||||
brew install opencode # macOS 與 Linux
|
brew install anomalyco/tap/opencode # macOS 與 Linux(推薦,始終保持最新)
|
||||||
|
brew install opencode # macOS 與 Linux(官方 brew formula,更新頻率較低)
|
||||||
paru -S opencode-bin # Arch Linux
|
paru -S opencode-bin # Arch Linux
|
||||||
mise use -g github:anomalyco/opencode # 任何作業系統
|
mise use -g github:anomalyco/opencode # 任何作業系統
|
||||||
nix run nixpkgs#opencode # 或使用 github:anomalyco/opencode 以取得最新開發分支
|
nix run nixpkgs#opencode # 或使用 github:anomalyco/opencode 以取得最新開發分支
|
||||||
|
|||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
# Reporting Security Issues
|
||||||
|
|
||||||
|
We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
|
||||||
|
|
||||||
|
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/anomalyco/opencode/security/advisories/new) tab.
|
||||||
|
|
||||||
|
The team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
|
||||||
|
|
||||||
|
## Escalation
|
||||||
|
|
||||||
|
If you do not receive an acknowledgement of your report within 6 business days, you may send an email to security@anoma.ly
|
||||||
@@ -194,3 +194,10 @@
|
|||||||
| 2026-01-05 | 1,738,171 (+65,515) | 1,353,043 (+13,160) | 3,091,214 (+78,675) |
|
| 2026-01-05 | 1,738,171 (+65,515) | 1,353,043 (+13,160) | 3,091,214 (+78,675) |
|
||||||
| 2026-01-06 | 1,960,988 (+222,817) | 1,377,377 (+24,334) | 3,338,365 (+247,151) |
|
| 2026-01-06 | 1,960,988 (+222,817) | 1,377,377 (+24,334) | 3,338,365 (+247,151) |
|
||||||
| 2026-01-07 | 2,123,239 (+162,251) | 1,398,648 (+21,271) | 3,521,887 (+183,522) |
|
| 2026-01-07 | 2,123,239 (+162,251) | 1,398,648 (+21,271) | 3,521,887 (+183,522) |
|
||||||
|
| 2026-01-08 | 2,272,630 (+149,391) | 1,432,480 (+33,832) | 3,705,110 (+183,223) |
|
||||||
|
| 2026-01-09 | 2,443,565 (+170,935) | 1,469,451 (+36,971) | 3,913,016 (+207,906) |
|
||||||
|
| 2026-01-10 | 2,632,023 (+188,458) | 1,503,670 (+34,219) | 4,135,693 (+222,677) |
|
||||||
|
| 2026-01-11 | 2,836,394 (+204,371) | 1,530,479 (+26,809) | 4,366,873 (+231,180) |
|
||||||
|
| 2026-01-12 | 3,053,594 (+217,200) | 1,553,671 (+23,192) | 4,607,265 (+240,392) |
|
||||||
|
| 2026-01-13 | 3,297,078 (+243,484) | 1,595,062 (+41,391) | 4,892,140 (+284,875) |
|
||||||
|
| 2026-01-14 | 3,568,928 (+271,850) | 1,645,362 (+50,300) | 5,214,290 (+322,150) |
|
||||||
|
|||||||
+69
-8
@@ -1,10 +1,71 @@
|
|||||||
## Style Guide
|
## Style Guide
|
||||||
|
|
||||||
- Try to keep things in one function unless composable or reusable
|
- Keep things in one function unless composable or reusable
|
||||||
- AVOID unnecessary destructuring of variables
|
- Avoid unnecessary destructuring. Instead of `const { a, b } = obj`, use `obj.a` and `obj.b` to preserve context
|
||||||
- AVOID `try`/`catch` where possible
|
- Avoid `try`/`catch` where possible
|
||||||
- AVOID `else` statements
|
- Avoid using the `any` type
|
||||||
- AVOID using `any` type
|
- Prefer single word variable names where possible
|
||||||
- AVOID `let` statements
|
- Use Bun APIs when possible, like `Bun.file()`
|
||||||
- PREFER single word variable names where possible
|
|
||||||
- Use as many bun apis as possible like Bun.file()
|
# Avoid let statements
|
||||||
|
|
||||||
|
We don't like `let` statements, especially combined with if/else statements.
|
||||||
|
Prefer `const`.
|
||||||
|
|
||||||
|
Good:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const foo = condition ? 1 : 2
|
||||||
|
```
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
let foo
|
||||||
|
|
||||||
|
if (condition) foo = 1
|
||||||
|
else foo = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
# Avoid else statements
|
||||||
|
|
||||||
|
Prefer early returns or using an `iife` to avoid else statements.
|
||||||
|
|
||||||
|
Good:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function foo() {
|
||||||
|
if (condition) return 1
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function foo() {
|
||||||
|
if (condition) return 1
|
||||||
|
else return 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Prefer single word naming
|
||||||
|
|
||||||
|
Try your best to find a single word name for your variables, functions, etc.
|
||||||
|
Only use multiple words if you cannot.
|
||||||
|
|
||||||
|
Good:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const foo = 1
|
||||||
|
const bar = 2
|
||||||
|
const baz = 3
|
||||||
|
```
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const fooBar = 1
|
||||||
|
const barBaz = 2
|
||||||
|
const bazFoo = 3
|
||||||
|
```
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -84,9 +84,12 @@
|
|||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
"@solidjs/router": "catalog:",
|
"@solidjs/router": "catalog:",
|
||||||
"@solidjs/start": "catalog:",
|
"@solidjs/start": "catalog:",
|
||||||
|
"@stripe/stripe-js": "8.6.1",
|
||||||
"chart.js": "4.5.1",
|
"chart.js": "4.5.1",
|
||||||
"nitro": "3.0.1-alpha.1",
|
"nitro": "3.0.1-alpha.1",
|
||||||
"solid-js": "catalog:",
|
"solid-js": "catalog:",
|
||||||
|
"solid-list": "0.3.0",
|
||||||
|
"solid-stripe": "0.8.1",
|
||||||
"vite": "catalog:",
|
"vite": "catalog:",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -98,7 +101,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -125,7 +128,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "2.0.0",
|
"@ai-sdk/anthropic": "2.0.0",
|
||||||
"@ai-sdk/openai": "2.0.2",
|
"@ai-sdk/openai": "2.0.2",
|
||||||
@@ -149,7 +152,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -173,7 +176,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -202,7 +205,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
@@ -231,7 +234,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -247,7 +250,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -255,29 +258,30 @@
|
|||||||
"@actions/core": "1.11.1",
|
"@actions/core": "1.11.1",
|
||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@agentclientprotocol/sdk": "0.5.1",
|
"@agentclientprotocol/sdk": "0.5.1",
|
||||||
"@ai-sdk/amazon-bedrock": "3.0.57",
|
"@ai-sdk/amazon-bedrock": "3.0.73",
|
||||||
"@ai-sdk/anthropic": "2.0.56",
|
"@ai-sdk/anthropic": "2.0.57",
|
||||||
"@ai-sdk/azure": "2.0.82",
|
"@ai-sdk/azure": "2.0.91",
|
||||||
"@ai-sdk/cerebras": "1.0.33",
|
"@ai-sdk/cerebras": "1.0.34",
|
||||||
"@ai-sdk/cohere": "2.0.21",
|
"@ai-sdk/cohere": "2.0.22",
|
||||||
"@ai-sdk/deepinfra": "1.0.30",
|
"@ai-sdk/deepinfra": "1.0.31",
|
||||||
"@ai-sdk/gateway": "2.0.23",
|
"@ai-sdk/gateway": "2.0.25",
|
||||||
"@ai-sdk/google": "2.0.49",
|
"@ai-sdk/google": "2.0.52",
|
||||||
"@ai-sdk/google-vertex": "3.0.81",
|
"@ai-sdk/google-vertex": "3.0.97",
|
||||||
"@ai-sdk/groq": "2.0.33",
|
"@ai-sdk/groq": "2.0.34",
|
||||||
"@ai-sdk/mistral": "2.0.26",
|
"@ai-sdk/mistral": "2.0.27",
|
||||||
"@ai-sdk/openai": "2.0.71",
|
"@ai-sdk/openai": "2.0.89",
|
||||||
"@ai-sdk/openai-compatible": "1.0.29",
|
"@ai-sdk/openai-compatible": "1.0.30",
|
||||||
"@ai-sdk/perplexity": "2.0.22",
|
"@ai-sdk/perplexity": "2.0.23",
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "2.0.1",
|
||||||
"@ai-sdk/provider-utils": "3.0.19",
|
"@ai-sdk/provider-utils": "3.0.20",
|
||||||
"@ai-sdk/togetherai": "1.0.30",
|
"@ai-sdk/togetherai": "1.0.31",
|
||||||
"@ai-sdk/vercel": "1.0.31",
|
"@ai-sdk/vercel": "1.0.31",
|
||||||
"@ai-sdk/xai": "2.0.42",
|
"@ai-sdk/xai": "2.0.51",
|
||||||
"@clack/prompts": "1.0.0-alpha.1",
|
"@clack/prompts": "1.0.0-alpha.1",
|
||||||
|
"@gitlab/gitlab-ai-provider": "3.1.0",
|
||||||
"@hono/standard-validator": "0.1.5",
|
"@hono/standard-validator": "0.1.5",
|
||||||
"@hono/zod-validator": "catalog:",
|
"@hono/zod-validator": "catalog:",
|
||||||
"@modelcontextprotocol/sdk": "1.15.1",
|
"@modelcontextprotocol/sdk": "1.25.2",
|
||||||
"@octokit/graphql": "9.0.2",
|
"@octokit/graphql": "9.0.2",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
"@openauthjs/openauth": "catalog:",
|
"@openauthjs/openauth": "catalog:",
|
||||||
@@ -286,8 +290,8 @@
|
|||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
"@openrouter/ai-sdk-provider": "1.5.2",
|
"@openrouter/ai-sdk-provider": "1.5.2",
|
||||||
"@opentui/core": "0.1.69",
|
"@opentui/core": "0.1.72",
|
||||||
"@opentui/solid": "0.1.69",
|
"@opentui/solid": "0.1.72",
|
||||||
"@parcel/watcher": "2.5.1",
|
"@parcel/watcher": "2.5.1",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
"@solid-primitives/event-bus": "1.1.2",
|
"@solid-primitives/event-bus": "1.1.2",
|
||||||
@@ -350,7 +354,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
@@ -370,7 +374,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.88.1",
|
"@hey-api/openapi-ts": "0.88.1",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
@@ -381,7 +385,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -394,7 +398,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -406,6 +410,7 @@
|
|||||||
"@solid-primitives/resize-observer": "2.1.3",
|
"@solid-primitives/resize-observer": "2.1.3",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
|
"dompurify": "catalog:",
|
||||||
"fuzzysort": "catalog:",
|
"fuzzysort": "catalog:",
|
||||||
"katex": "0.16.27",
|
"katex": "0.16.27",
|
||||||
"luxon": "catalog:",
|
"luxon": "catalog:",
|
||||||
@@ -433,7 +438,7 @@
|
|||||||
},
|
},
|
||||||
"packages/util": {
|
"packages/util": {
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -444,7 +449,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -504,8 +509,9 @@
|
|||||||
"@types/luxon": "3.7.1",
|
"@types/luxon": "3.7.1",
|
||||||
"@types/node": "22.13.9",
|
"@types/node": "22.13.9",
|
||||||
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
||||||
"ai": "5.0.97",
|
"ai": "5.0.119",
|
||||||
"diff": "8.0.2",
|
"diff": "8.0.2",
|
||||||
|
"dompurify": "3.3.1",
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
"hono": "4.10.7",
|
"hono": "4.10.7",
|
||||||
"hono-openapi": "1.1.2",
|
"hono-openapi": "1.1.2",
|
||||||
@@ -541,48 +547,52 @@
|
|||||||
|
|
||||||
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.5.1", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg=="],
|
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.5.1", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.57", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.45", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-mOUSLe+RgZzx0rtL1p9QXmSd/08z1EkBR+vQ1ydpd1t5P0Nx2kB8afiukEgM8nuDvmO9eYQlp7VTy1n5ffPs2g=="],
|
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.73", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.57", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EAAGJ/dfbAZaqIhK3w52hq6cftSLZwXdC6uHKh8Cls1T0N4MxS6ykDf54UyFO3bZWkQxR+Mdw1B3qireGOxtJQ=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
|
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
|
||||||
|
|
||||||
"@ai-sdk/azure": ["@ai-sdk/azure@2.0.82", "", { "dependencies": { "@ai-sdk/openai": "2.0.80", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Bpab51ETBB4adZC1xGMYsryL/CB8j1sA+t5aDqhRv3t3WRLTxhaBDcFKtQTIuxiEQTFosz9Q2xQqdfBvQm5jHw=="],
|
"@ai-sdk/azure": ["@ai-sdk/azure@2.0.91", "", { "dependencies": { "@ai-sdk/openai": "2.0.89", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9tznVSs6LGQNKKxb8pKd7CkBV9yk+a/ENpFicHCj2CmBUKefxzwJ9JbUqrlK3VF6dGZw3LXq0dWxt7/Yekaj1w=="],
|
||||||
|
|
||||||
"@ai-sdk/cerebras": ["@ai-sdk/cerebras@1.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2gSSS/7kunIwMdC4td5oWsUAzoLw84ccGpz6wQbxVnrb1iWnrEnKa5tRBduaP6IXpzLWsu8wME3+dQhZy+gT7w=="],
|
"@ai-sdk/cerebras": ["@ai-sdk/cerebras@1.0.34", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XOK0dJsAGoPYi/lfR4KFBi8xhvJ46oCpAxUD6FmJAuJ4eh0qlj5zDt+myvzM8gvN7S6K7zHD+mdWlOPKGQT8Vg=="],
|
||||||
|
|
||||||
"@ai-sdk/cohere": ["@ai-sdk/cohere@2.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZjaZFvJlc5XOPi3QwTLEFZbHIgTJc6YGvxz+8zIMGVZi/hdynR8/f/C1A9x6mhzmBtAqi/dZ2h11oouAQH5z4g=="],
|
"@ai-sdk/cohere": ["@ai-sdk/cohere@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yJ9kP5cEDJwo8qpITq5TQFD8YNfNtW+HbyvWwrKMbFzmiMvIZuk95HIaFXE7PCTuZsqMA05yYu+qX/vQ3rNKjA=="],
|
||||||
|
|
||||||
"@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.30", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XK8oRZFApzo6xnS5C+FhWUUkB2itA5Nfon3pU9dJVM0goViq8GwdleZTBRqhu4DE4KJURo5DGWpJr2hfV54cEg=="],
|
"@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.31", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-87qFcYNvDF/89hB//MQjYTb3tlsAfmgeZrZ34RESeBTZpSgs0EzYOMqPMwFTHUNp4wteoifikDJbaS/9Da8cfw=="],
|
||||||
|
|
||||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-qmX7afPRszUqG5hryHF3UN8ITPIRSGmDW6VYCmByzjoUkgm3MekzSx2hMV1wr0P+llDeuXb378SjqUfpvWJulg=="],
|
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.25", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Rq+FX55ne7lMiqai7NcvvDZj4HLsr+hg77WayqmySqc6zhw3tIOLxd4Ty6OpwNj0C0bVMi3iCl2zvJIEirh9XA=="],
|
||||||
|
|
||||||
"@ai-sdk/google": ["@ai-sdk/google@2.0.49", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-efwKk4mOV0SpumUaQskeYABk37FJPmEYwoDJQEjyLRmGSjtHRe9P5Cwof5ffLvaFav2IaJpBGEz98pyTs7oNWA=="],
|
"@ai-sdk/google": ["@ai-sdk/google@2.0.52", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2XUnGi3f7TV4ujoAhA+Fg3idUoG/+Y2xjCRg70a1/m0DH1KSQqYaCboJ1C19y6ZHGdf5KNT20eJdswP6TvrY2g=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.81", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.50", "@ai-sdk/google": "2.0.44", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18", "google-auth-library": "^9.15.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yrl5Ug0Mqwo9ya45oxczgy2RWgpEA/XQQCSFYP+3NZMQ4yA3Iim1vkOjVCsGaZZ8rjVk395abi1ZMZV0/6rqVA=="],
|
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.97", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.57", "@ai-sdk/google": "2.0.52", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-s4tI7Z15i6FlbtCvS4SBRal8wRfkOXJzKxlS6cU4mJW/QfUfoVy4b22836NVNJwDvkG/HkDSfzwm/X8mn46MhA=="],
|
||||||
|
|
||||||
"@ai-sdk/groq": ["@ai-sdk/groq@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FWGl7xNr88NBveao3y9EcVWYUt9ABPrwLFY7pIutSNgaTf32vgvyhREobaMrLU4Scr5G/2tlNqOPZ5wkYMaZig=="],
|
"@ai-sdk/groq": ["@ai-sdk/groq@2.0.34", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wfCYkVgmVjxNA32T57KbLabVnv9aFUflJ4urJ7eWgTwbnmGQHElCTu+rJ3ydxkXSqxOkXPwMOttDm7XNrvPjmg=="],
|
||||||
|
|
||||||
"@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.26", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jxDB++4WI1wEx5ONNBI+VbkmYJOYIuS8UQY13/83UGRaiW7oB/WHiH4ETe6KzbKpQPB3XruwTJQjUMsMfKyTXA=="],
|
"@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.27", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-gaptHgaXjMw3+eA0Q4FABcsj5nQNP6EpFaGUR+Pj5WJy7Kn6mApl975/x57224MfeJIShNpt8wFKK3tvh5ewKg=="],
|
||||||
|
|
||||||
"@ai-sdk/openai": ["@ai-sdk/openai@2.0.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="],
|
"@ai-sdk/openai": ["@ai-sdk/openai@2.0.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="],
|
||||||
|
|
||||||
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-luHVcU+yKzwv3ekKgbP3v+elUVxb2Rt+8c6w9qi7g2NYG2/pEL21oIrnaEnc6UtTZLLZX9EFBcpq2N1FQKDIMw=="],
|
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-luHVcU+yKzwv3ekKgbP3v+elUVxb2Rt+8c6w9qi7g2NYG2/pEL21oIrnaEnc6UtTZLLZX9EFBcpq2N1FQKDIMw=="],
|
||||||
|
|
||||||
"@ai-sdk/perplexity": ["@ai-sdk/perplexity@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zwzcnk08R2J3mZcQPn4Ifl4wYGrvANR7jsBB0hCTUSbb+Rx3ybpikSWiGuXQXxdiRc1I5MWXgj70m+bZaLPvHw=="],
|
"@ai-sdk/perplexity": ["@ai-sdk/perplexity@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-aiaRvnc6mhQZKhTTSXPCjPH8Iqr5D/PfCN1hgVP/3RGTBbJtsd9HemIBSABeSdAKbsMH/PwJxgnqH75HEamcBA=="],
|
||||||
|
|
||||||
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="],
|
||||||
|
|
||||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="],
|
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
"@ai-sdk/togetherai": ["@ai-sdk/togetherai@1.0.30", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9bxQbIXnWSN4bNismrza3NvIo+ui/Y3pj3UN6e9vCszCWFCN45RgISi4oDe10RqmzaJ/X8cfO/Tem+K8MT3wGQ=="],
|
"@ai-sdk/togetherai": ["@ai-sdk/togetherai@1.0.31", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RlYubjStoZQxna4Ng91Vvo8YskvL7lW9zj68IwZfCnaDBSAp1u6Nhc5BR4ZtKnY6PA3XEtu4bATIQl7yiiQ+Lw=="],
|
||||||
|
|
||||||
"@ai-sdk/vercel": ["@ai-sdk/vercel@1.0.31", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ggvwAMt/KsbqcdR6ILQrjwrRONLV/8aG6rOLbjcOGvV0Ai+WdZRRKQj5nOeQ06PvwVQtKdkp7S4IinpXIhCiHg=="],
|
"@ai-sdk/vercel": ["@ai-sdk/vercel@1.0.31", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ggvwAMt/KsbqcdR6ILQrjwrRONLV/8aG6rOLbjcOGvV0Ai+WdZRRKQj5nOeQ06PvwVQtKdkp7S4IinpXIhCiHg=="],
|
||||||
|
|
||||||
"@ai-sdk/xai": ["@ai-sdk/xai@2.0.42", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wlwO4yRoZ/d+ca29vN8SDzxus7POdnL7GBTyRdSrt6icUF0hooLesauC8qRUC4aLxtqvMEc1YHtJOU7ZnLWbTQ=="],
|
"@ai-sdk/xai": ["@ai-sdk/xai@2.0.51", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-AI3le03qiegkZvn9hpnpDwez49lOvQLj4QUBT8H41SMbrdTYOxn3ktTwrsSu90cNDdzKGMvoH0u2GHju1EdnCg=="],
|
||||||
|
|
||||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||||
|
|
||||||
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||||
|
|
||||||
|
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.71.2", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ=="],
|
||||||
|
|
||||||
|
"@anycable/core": ["@anycable/core@0.9.2", "", { "dependencies": { "nanoevents": "^7.0.1" } }, "sha512-x5ZXDcW/N4cxWl93CnbHs/u7qq4793jS2kNPWm+duPrXlrva+ml2ZGT7X9tuOBKzyIHf60zWCdIK7TUgMPAwXA=="],
|
||||||
|
|
||||||
"@astrojs/cloudflare": ["@astrojs/cloudflare@12.6.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "@astrojs/underscore-redirects": "1.0.0", "@cloudflare/workers-types": "^4.20250507.0", "tinyglobby": "^0.2.13", "vite": "^6.3.5", "wrangler": "^4.14.1" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-xhJptF5tU2k5eo70nIMyL1Udma0CqmUEnGSlGyFflLqSY82CRQI6nWZ/xZt0ZvmXuErUjIx0YYQNfZsz5CNjLQ=="],
|
"@astrojs/cloudflare": ["@astrojs/cloudflare@12.6.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "@astrojs/underscore-redirects": "1.0.0", "@cloudflare/workers-types": "^4.20250507.0", "tinyglobby": "^0.2.13", "vite": "^6.3.5", "wrangler": "^4.14.1" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-xhJptF5tU2k5eo70nIMyL1Udma0CqmUEnGSlGyFflLqSY82CRQI6nWZ/xZt0ZvmXuErUjIx0YYQNfZsz5CNjLQ=="],
|
||||||
|
|
||||||
"@astrojs/compiler": ["@astrojs/compiler@2.13.0", "", {}, "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw=="],
|
"@astrojs/compiler": ["@astrojs/compiler@2.13.0", "", {}, "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw=="],
|
||||||
@@ -903,6 +913,10 @@
|
|||||||
|
|
||||||
"@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
|
"@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
|
||||||
|
|
||||||
|
"@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.1.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-S0MVXsogrwbOboA/8L0CY5sBXg2HrrO8gdeUeHd9yLZDPsggFD0FzcSuzO5vBO6geUOpruRa8Hqrbb6WWu7Frw=="],
|
||||||
|
|
||||||
|
"@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="],
|
||||||
|
|
||||||
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
|
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
|
||||||
|
|
||||||
"@hey-api/codegen-core": ["@hey-api/codegen-core@0.3.3", "", { "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-vArVDtrvdzFewu1hnjUm4jX1NBITlSCeO81EdWq676MxQbyxsGcDPAgohaSA+Wvr4HjPSvsg2/1s2zYxUtXebg=="],
|
"@hey-api/codegen-core": ["@hey-api/codegen-core@0.3.3", "", { "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-vArVDtrvdzFewu1hnjUm4jX1NBITlSCeO81EdWq676MxQbyxsGcDPAgohaSA+Wvr4HjPSvsg2/1s2zYxUtXebg=="],
|
||||||
@@ -911,6 +925,8 @@
|
|||||||
|
|
||||||
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.88.1", "", { "dependencies": { "@hey-api/codegen-core": "^0.3.3", "@hey-api/json-schema-ref-parser": "1.2.2", "ansi-colors": "4.1.3", "c12": "3.3.2", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.2" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-x/nDTupOnV9VuSeNIiJpgIpc915GHduhyseJeMTnI0JMsXaObmpa0rgPr3ASVEYMLgpvqozIEG1RTOOnal6zLQ=="],
|
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.88.1", "", { "dependencies": { "@hey-api/codegen-core": "^0.3.3", "@hey-api/json-schema-ref-parser": "1.2.2", "ansi-colors": "4.1.3", "c12": "3.3.2", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.2" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-x/nDTupOnV9VuSeNIiJpgIpc915GHduhyseJeMTnI0JMsXaObmpa0rgPr3ASVEYMLgpvqozIEG1RTOOnal6zLQ=="],
|
||||||
|
|
||||||
|
"@hono/node-server": ["@hono/node-server@1.19.7", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw=="],
|
||||||
|
|
||||||
"@hono/standard-validator": ["@hono/standard-validator@0.1.5", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="],
|
"@hono/standard-validator": ["@hono/standard-validator@0.1.5", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="],
|
||||||
|
|
||||||
"@hono/zod-validator": ["@hono/zod-validator@0.4.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-1rrlBg+EpDPhzOV4hT9pxr5+xDVmKuz6YJl+la7VCwK6ass5ldyKm5fD+umJdV2zhHD6jROoCCv8NbTwyfhT0g=="],
|
"@hono/zod-validator": ["@hono/zod-validator@0.4.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-1rrlBg+EpDPhzOV4hT9pxr5+xDVmKuz6YJl+la7VCwK6ass5ldyKm5fD+umJdV2zhHD6jROoCCv8NbTwyfhT0g=="],
|
||||||
@@ -1095,7 +1111,7 @@
|
|||||||
|
|
||||||
"@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
|
"@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.15.1", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-W/XlN9c528yYn+9MQkVjxiTPgPxoxt+oczfjHBDsJx0+59+O7B75Zhsp0B16Xbwbz8ANISDajh6+V7nIcPMc5w=="],
|
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.2", "", { "dependencies": { "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww=="],
|
||||||
|
|
||||||
"@motionone/animation": ["@motionone/animation@10.18.0", "", { "dependencies": { "@motionone/easing": "^10.18.0", "@motionone/types": "^10.17.1", "@motionone/utils": "^10.18.0", "tslib": "^2.3.1" } }, "sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw=="],
|
"@motionone/animation": ["@motionone/animation@10.18.0", "", { "dependencies": { "@motionone/easing": "^10.18.0", "@motionone/types": "^10.17.1", "@motionone/utils": "^10.18.0", "tslib": "^2.3.1" } }, "sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw=="],
|
||||||
|
|
||||||
@@ -1199,21 +1215,21 @@
|
|||||||
|
|
||||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||||
|
|
||||||
"@opentui/core": ["@opentui/core@0.1.69", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.69", "@opentui/core-darwin-x64": "0.1.69", "@opentui/core-linux-arm64": "0.1.69", "@opentui/core-linux-x64": "0.1.69", "@opentui/core-win32-arm64": "0.1.69", "@opentui/core-win32-x64": "0.1.69", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-BcEFnAuMq4vgfb+zxOP/l+NO1AS3fVHkYjn+E8Wpmaxr0AzWNTi2NPAMtQf+Wqufxo0NYh0gY4c9B6n8OxTjGw=="],
|
"@opentui/core": ["@opentui/core@0.1.72", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.72", "@opentui/core-darwin-x64": "0.1.72", "@opentui/core-linux-arm64": "0.1.72", "@opentui/core-linux-x64": "0.1.72", "@opentui/core-win32-arm64": "0.1.72", "@opentui/core-win32-x64": "0.1.72", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-l4WQzubBJ80Q0n77Lxuodjwwm8qj/sOa7IXxEAzzDDXY/7bsIhdSpVhRTt+KevBRlok5J+w/KMKYr8UzkA4/hA=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.69", "", { "os": "darwin", "cpu": "arm64" }, "sha512-d9RPAh84O2XIyMw+7+X0fEyi+4KH5sPk9AxLze8GHRBGOzkRunqagFCLBrN5VFs2e2nbhIYtjMszo7gcpWyh7g=="],
|
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.72", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RoU48kOrhLZYDBiXaDu1LXS2bwRdlJlFle8eUQiqJjLRbMIY34J/srBuL0JnAS3qKW4J34NepUQa0l0/S43Q3w=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.69", "", { "os": "darwin", "cpu": "x64" }, "sha512-41K9zkL2IG0ahL+8Gd+e9ulMrnJF6lArPzG7grjWzo+FWEZwvw0WLCO1/Gn5K85G8Yx7gQXkZOUaw1BmHjxoRw=="],
|
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.72", "", { "os": "darwin", "cpu": "x64" }, "sha512-hHUQw8i2LWPToRW1rjAiRqmNf34iJPS9ve9CJDygvFs5JOqUxN5yrfLfKfE+1bQjfFDHnpqW1HUk96iLhkPj8Q=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.69", "", { "os": "linux", "cpu": "arm64" }, "sha512-IcUjwjuIpX3BBG1a9kjMqWrHYCFHAVfjh5nIRozWZZoqaczLzJb3nJeF2eg8aDeIoGhXvERWB1r1gmqPW8u3vQ=="],
|
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.72", "", { "os": "linux", "cpu": "arm64" }, "sha512-63yml0OQ8tVa0JuDF9lBAWiChX6Q+iDO7lKv7c2n0352n/WyPr3iAgq4uSoH49HXuKeAXY/VwHGjvPzjXD/SDA=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.69", "", { "os": "linux", "cpu": "x64" }, "sha512-5S9vqEIq7q+MEdp4cT0HLegBWu0pWLcletHZL80bsLbJt9OT8en3sQmL5bvas9sIuyeBFru9bfCmrQ/gnVTTiA=="],
|
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.72", "", { "os": "linux", "cpu": "x64" }, "sha512-51veiQXNLvzDsFzsEvt71uK7WhiRe2DnvlJSGBSe6aRRHHxjCFYHzYi7t6bitJqtDTUj+EaMPbH81oZ6xy7tyg=="],
|
||||||
|
|
||||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.69", "", { "os": "win32", "cpu": "arm64" }, "sha512-eSKcGwbcnJJPtrTFJI7STZ7inSYeedHS0swwjZhh9SADAruEz08intamunOslffv5+mnlvRp7UBGK35cMjbv/w=="],
|
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.72", "", { "os": "win32", "cpu": "arm64" }, "sha512-1Ep6OcaYTy1RlLOln+LNN7DL1iNyLwLjG2M8aO0pVJKFvxeD5P7rdRzY065E4uhkHeJIHuduUqxvUjD0dyuwbw=="],
|
||||||
|
|
||||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.69", "", { "os": "win32", "cpu": "x64" }, "sha512-OjG/0jqYXURqbbUwNgSPrBA6yuKF3OOFh8JSG7VvzoYHJFJRmwVWY0fztWv/hgGHe354ti37c7JDJBQ44HOCdA=="],
|
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.72", "", { "os": "win32", "cpu": "x64" }, "sha512-5QUv91UkOINlkEaPky3kaxmJvshcJMBAX7LZtIroduaKBGpWRA1aogNhPZzp+30WkvgOU7aOtUktAZuFXb9WdQ=="],
|
||||||
|
|
||||||
"@opentui/solid": ["@opentui/solid@0.1.69", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.69", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-ls589N8P9gvcNW8uF+Il4xisF5Uouk0RRmSaLdzmItNJSW5J9Y0nPtMELta6hBp0yIRAurWUO1wtkKXVF+eaxg=="],
|
"@opentui/solid": ["@opentui/solid@0.1.72", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.72", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-hytoLPboL/MTY/BQUnf/HlBuNXTVONney0X+PIQI82wT7kMx7+HHI2wnowpM3dyvA7l6NfORSud2cs9kIUBFBw=="],
|
||||||
|
|
||||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||||
|
|
||||||
@@ -1595,6 +1611,8 @@
|
|||||||
|
|
||||||
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
|
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
|
||||||
|
|
||||||
|
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
||||||
|
|
||||||
"@solid-primitives/active-element": ["@solid-primitives/active-element@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9t5K4aR2naVDj950XU8OjnLgOg94a8k5wr6JNOPK+N5ESLsJDq42c1ZP8UKpewi1R+wplMMxiM6OPKRzbxJY7A=="],
|
"@solid-primitives/active-element": ["@solid-primitives/active-element@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9t5K4aR2naVDj950XU8OjnLgOg94a8k5wr6JNOPK+N5ESLsJDq42c1ZP8UKpewi1R+wplMMxiM6OPKRzbxJY7A=="],
|
||||||
|
|
||||||
"@solid-primitives/audio": ["@solid-primitives/audio@1.4.2", "", { "dependencies": { "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-UMD3ORQfI5Ky8yuKPxidDiEazsjv/dsoiKK5yZxLnsgaeNR1Aym3/77h/qT1jBYeXUgj4DX6t7NMpFUSVr14OQ=="],
|
"@solid-primitives/audio": ["@solid-primitives/audio@1.4.2", "", { "dependencies": { "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-UMD3ORQfI5Ky8yuKPxidDiEazsjv/dsoiKK5yZxLnsgaeNR1Aym3/77h/qT1jBYeXUgj4DX6t7NMpFUSVr14OQ=="],
|
||||||
@@ -1647,6 +1665,8 @@
|
|||||||
|
|
||||||
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
||||||
|
|
||||||
|
"@stripe/stripe-js": ["@stripe/stripe-js@8.6.1", "", {}, "sha512-UJ05U2062XDgydbUcETH1AoRQLNhigQ2KmDn1BG8sC3xfzu6JKg95Qt6YozdzFpxl1Npii/02m2LEWFt1RYjVA=="],
|
||||||
|
|
||||||
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
|
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
|
||||||
|
|
||||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
|
"@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
|
||||||
@@ -1825,6 +1845,8 @@
|
|||||||
|
|
||||||
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
|
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
|
||||||
|
|
||||||
|
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
||||||
|
|
||||||
"@types/tsscmp": ["@types/tsscmp@1.0.2", "", {}, "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g=="],
|
"@types/tsscmp": ["@types/tsscmp@1.0.2", "", {}, "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g=="],
|
||||||
|
|
||||||
"@types/tunnel": ["@types/tunnel@0.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA=="],
|
"@types/tunnel": ["@types/tunnel@0.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA=="],
|
||||||
@@ -1901,9 +1923,11 @@
|
|||||||
|
|
||||||
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
|
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
|
||||||
|
|
||||||
"ai": ["ai@5.0.97", "", { "dependencies": { "@ai-sdk/gateway": "2.0.12", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8zBx0b/owis4eJI2tAlV8a1Rv0BANmLxontcAelkLNwEHhgfgXeKpDkhNB6OgV+BJSwboIUDkgd9312DdJnCOQ=="],
|
"ai": ["ai@5.0.119", "", { "dependencies": { "@ai-sdk/gateway": "2.0.25", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HUOwhc17fl2SZTJGZyA/99aNu706qKfXaUBCy9vgZiXBwrxg2eTzn2BCz7kmYDsfx6Fg2ACBy2icm41bsDXCTw=="],
|
||||||
|
|
||||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
"ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
|
||||||
|
|
||||||
|
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
||||||
|
|
||||||
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
|
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
|
||||||
|
|
||||||
@@ -2201,6 +2225,8 @@
|
|||||||
|
|
||||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
|
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||||
|
|
||||||
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
||||||
|
|
||||||
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
|
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
|
||||||
@@ -2273,6 +2299,8 @@
|
|||||||
|
|
||||||
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||||
|
|
||||||
|
"dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="],
|
||||||
|
|
||||||
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||||
|
|
||||||
"dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
|
"dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
|
||||||
@@ -2305,6 +2333,10 @@
|
|||||||
|
|
||||||
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||||
|
|
||||||
|
"engine.io-client": ["engine.io-client@6.6.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw=="],
|
||||||
|
|
||||||
|
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
|
||||||
|
|
||||||
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
|
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
|
||||||
|
|
||||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||||
@@ -2407,7 +2439,7 @@
|
|||||||
|
|
||||||
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||||
|
|
||||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
|
||||||
|
|
||||||
"fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="],
|
"fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="],
|
||||||
|
|
||||||
@@ -2415,6 +2447,8 @@
|
|||||||
|
|
||||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
|
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
||||||
|
|
||||||
"file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
|
"file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
|
||||||
|
|
||||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||||
@@ -2445,6 +2479,8 @@
|
|||||||
|
|
||||||
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
|
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
|
||||||
|
|
||||||
|
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
||||||
|
|
||||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||||
|
|
||||||
"fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
|
"fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
|
||||||
@@ -2467,9 +2503,9 @@
|
|||||||
|
|
||||||
"fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="],
|
"fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="],
|
||||||
|
|
||||||
"gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="],
|
"gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="],
|
||||||
|
|
||||||
"gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="],
|
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
|
||||||
|
|
||||||
"gel": ["gel@2.2.0", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ=="],
|
"gel": ["gel@2.2.0", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ=="],
|
||||||
|
|
||||||
@@ -2515,17 +2551,21 @@
|
|||||||
|
|
||||||
"globby": ["globby@11.0.4", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.1.1", "ignore": "^5.1.4", "merge2": "^1.3.0", "slash": "^3.0.0" } }, "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg=="],
|
"globby": ["globby@11.0.4", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.1.1", "ignore": "^5.1.4", "merge2": "^1.3.0", "slash": "^3.0.0" } }, "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg=="],
|
||||||
|
|
||||||
"google-auth-library": ["google-auth-library@9.15.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", "jws": "^4.0.0" } }, "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng=="],
|
"google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="],
|
||||||
|
|
||||||
"google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
|
"google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
|
||||||
|
|
||||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||||
|
|
||||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
|
"graphql": ["graphql@16.12.0", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="],
|
||||||
|
|
||||||
|
"graphql-request": ["graphql-request@6.1.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", "cross-fetch": "^3.1.5" }, "peerDependencies": { "graphql": "14 - 16" } }, "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw=="],
|
||||||
|
|
||||||
"gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
|
"gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
|
||||||
|
|
||||||
"gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="],
|
"gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="],
|
||||||
|
|
||||||
"h3": ["h3@2.0.1-rc.4", "", { "dependencies": { "rou3": "^0.7.8", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-vZq8pEUp6THsXKXrUXX44eOqfChic2wVQ1GlSzQCBr7DeFBkfIZAo2WyNND4GSv54TAa0E4LYIK73WSPdgKUgw=="],
|
"h3": ["h3@2.0.1-rc.4", "", { "dependencies": { "rou3": "^0.7.8", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-vZq8pEUp6THsXKXrUXX44eOqfChic2wVQ1GlSzQCBr7DeFBkfIZAo2WyNND4GSv54TAa0E4LYIK73WSPdgKUgw=="],
|
||||||
|
|
||||||
@@ -2751,6 +2791,8 @@
|
|||||||
|
|
||||||
"isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
|
"isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
|
||||||
|
|
||||||
|
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
|
||||||
|
|
||||||
"iterate-iterator": ["iterate-iterator@1.0.2", "", {}, "sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw=="],
|
"iterate-iterator": ["iterate-iterator@1.0.2", "", {}, "sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw=="],
|
||||||
|
|
||||||
"iterate-value": ["iterate-value@1.0.2", "", { "dependencies": { "es-get-iterator": "^1.0.2", "iterate-iterator": "^1.0.1" } }, "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ=="],
|
"iterate-value": ["iterate-value@1.0.2", "", { "dependencies": { "es-get-iterator": "^1.0.2", "iterate-iterator": "^1.0.1" } }, "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ=="],
|
||||||
@@ -2783,7 +2825,11 @@
|
|||||||
|
|
||||||
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
||||||
|
|
||||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
|
||||||
|
|
||||||
|
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
||||||
|
"json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
|
||||||
|
|
||||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||||
|
|
||||||
@@ -3057,6 +3103,8 @@
|
|||||||
|
|
||||||
"named-placeholders": ["named-placeholders@1.1.3", "", { "dependencies": { "lru-cache": "^7.14.1" } }, "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w=="],
|
"named-placeholders": ["named-placeholders@1.1.3", "", { "dependencies": { "lru-cache": "^7.14.1" } }, "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w=="],
|
||||||
|
|
||||||
|
"nanoevents": ["nanoevents@7.0.1", "", {}, "sha512-o6lpKiCxLeijK4hgsqfR6CNToPyRU3keKyyI6uwuHRvpRTbZ0wXw51WRgyldVugZqoJfkGFrjrIenYH3bfEO3Q=="],
|
||||||
|
|
||||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||||
|
|
||||||
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
||||||
@@ -3385,6 +3433,8 @@
|
|||||||
|
|
||||||
"remeda": ["remeda@2.26.0", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-lmNNwtaC6Co4m0WTTNoZ/JlpjEqAjPZO0+czC9YVRQUpkbS4x8Hmh+Mn9HPfJfiXqUQ5IXXgSXSOB2pBKAytdA=="],
|
"remeda": ["remeda@2.26.0", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-lmNNwtaC6Co4m0WTTNoZ/JlpjEqAjPZO0+czC9YVRQUpkbS4x8Hmh+Mn9HPfJfiXqUQ5IXXgSXSOB2pBKAytdA=="],
|
||||||
|
|
||||||
|
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||||
|
|
||||||
"reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="],
|
"reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="],
|
||||||
|
|
||||||
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
||||||
@@ -3409,6 +3459,8 @@
|
|||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
|
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
|
||||||
|
|
||||||
"rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="],
|
"rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="],
|
||||||
|
|
||||||
"rou3": ["rou3@0.7.10", "", {}, "sha512-aoFj6f7MJZ5muJ+Of79nrhs9N3oLGqi2VEMe94Zbkjb6Wupha46EuoYgpWSOZlXww3bbd8ojgXTAA2mzimX5Ww=="],
|
"rou3": ["rou3@0.7.10", "", {}, "sha512-aoFj6f7MJZ5muJ+Of79nrhs9N3oLGqi2VEMe94Zbkjb6Wupha46EuoYgpWSOZlXww3bbd8ojgXTAA2mzimX5Ww=="],
|
||||||
@@ -3495,6 +3547,10 @@
|
|||||||
|
|
||||||
"smol-toml": ["smol-toml@1.5.2", "", {}, "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ=="],
|
"smol-toml": ["smol-toml@1.5.2", "", {}, "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ=="],
|
||||||
|
|
||||||
|
"socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="],
|
||||||
|
|
||||||
|
"socket.io-parser": ["socket.io-parser@4.2.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ=="],
|
||||||
|
|
||||||
"solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="],
|
"solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="],
|
||||||
|
|
||||||
"solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="],
|
"solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="],
|
||||||
@@ -3505,6 +3561,8 @@
|
|||||||
|
|
||||||
"solid-refresh": ["solid-refresh@0.6.3", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="],
|
"solid-refresh": ["solid-refresh@0.6.3", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="],
|
||||||
|
|
||||||
|
"solid-stripe": ["solid-stripe@0.8.1", "", { "peerDependencies": { "@stripe/stripe-js": ">=1.44.1 <8.0.0", "solid-js": "^1.6.0" } }, "sha512-l2SkWoe51rsvk9u1ILBRWyCHODZebChSGMR6zHYJTivTRC0XWrRnNNKs5x1PYXsaIU71KYI6ov5CZB5cOtGLWw=="],
|
||||||
|
|
||||||
"solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="],
|
"solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="],
|
||||||
|
|
||||||
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||||
@@ -3659,6 +3717,8 @@
|
|||||||
|
|
||||||
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
||||||
|
|
||||||
|
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
|
||||||
|
|
||||||
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
|
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
|
||||||
|
|
||||||
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
|
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
|
||||||
@@ -3759,8 +3819,6 @@
|
|||||||
|
|
||||||
"update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="],
|
"update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="],
|
||||||
|
|
||||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
|
||||||
|
|
||||||
"url": ["url@0.10.3", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="],
|
"url": ["url@0.10.3", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="],
|
||||||
|
|
||||||
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
|
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
|
||||||
@@ -3853,6 +3911,8 @@
|
|||||||
|
|
||||||
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
|
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
|
||||||
|
|
||||||
|
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
|
||||||
|
|
||||||
"xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
|
"xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
|
||||||
|
|
||||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||||
@@ -3903,35 +3963,33 @@
|
|||||||
|
|
||||||
"@agentclientprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
"@agentclientprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.45", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Ipv62vavDCmrV/oE/lXehL9FzwQuZOnnlhPEftWizx464Wb6lvnBTJx8uhmEYruFSzOWTI95Z33ncZ4tA8E6RQ=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.57", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
|
"@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
||||||
|
|
||||||
"@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.80", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tNHuraF11db+8xJEDBoU9E3vMcpnHFKRhnLQ3DQX2LnEzfPB9DksZ8rE+yVuDN1WRW9cm2OWAhgHFgVKs7ICuw=="],
|
"@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
|
||||||
|
|
||||||
"@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
"@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
||||||
|
|
||||||
"@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
"@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="],
|
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.57", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="],
|
"@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
||||||
|
|
||||||
"@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
"@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
||||||
|
|
||||||
"@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
"@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
||||||
|
|
||||||
"@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
"@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
||||||
|
|
||||||
"@ai-sdk/vercel/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
"@ai-sdk/vercel/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
||||||
|
|
||||||
"@ai-sdk/vercel/@ai-sdk/provider": ["@ai-sdk/provider@2.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="],
|
"@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
||||||
|
|
||||||
"@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
|
||||||
|
|
||||||
"@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
|
||||||
|
|
||||||
"@astrojs/cloudflare/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
|
"@astrojs/cloudflare/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
|
||||||
|
|
||||||
@@ -4005,6 +4063,8 @@
|
|||||||
|
|
||||||
"@expressive-code/plugin-shiki/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
|
"@expressive-code/plugin-shiki/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
|
||||||
|
|
||||||
|
"@gitlab/gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
|
|
||||||
"@hey-api/json-schema-ref-parser/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
"@hey-api/json-schema-ref-parser/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||||
|
|
||||||
"@hey-api/openapi-ts/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
|
"@hey-api/openapi-ts/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
|
||||||
@@ -4059,9 +4119,11 @@
|
|||||||
|
|
||||||
"@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
"@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
||||||
|
|
||||||
|
"@modelcontextprotocol/sdk/jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
"@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
"@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
|
||||||
|
|
||||||
"@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="],
|
"@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="],
|
||||||
|
|
||||||
@@ -4197,10 +4259,6 @@
|
|||||||
|
|
||||||
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
"ai/@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.12", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W+cB1sOWvPcz9qiIsNtD+HxUrBUva2vWv2K1EFukuImX+HA0uZx3EyyOjhYQ9gtf/teqEG80M6OvJ7xx/VLV2A=="],
|
|
||||||
|
|
||||||
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
|
|
||||||
|
|
||||||
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
@@ -4249,6 +4307,8 @@
|
|||||||
|
|
||||||
"editorconfig/minimatch": ["minimatch@9.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="],
|
"editorconfig/minimatch": ["minimatch@9.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="],
|
||||||
|
|
||||||
|
"engine.io-client/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
||||||
|
|
||||||
"es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
"es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||||
|
|
||||||
"esbuild-plugin-copy/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"esbuild-plugin-copy/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
@@ -4265,13 +4325,13 @@
|
|||||||
|
|
||||||
"express/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
|
"express/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
|
||||||
|
|
||||||
|
"fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||||
|
|
||||||
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||||
|
|
||||||
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
"gaxios/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
"gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||||
|
|
||||||
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
|
||||||
|
|
||||||
"glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
|
"glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
|
||||||
|
|
||||||
@@ -4313,11 +4373,11 @@
|
|||||||
|
|
||||||
"nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
"nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||||
|
|
||||||
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ=="],
|
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.57", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
|
||||||
|
|
||||||
"opencode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.71", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tg+gj+R0z/On9P4V7hy7/7o04cQPjKGayMCL3gzWD/aNGjAKkhEnaocuNDidSnghizt8g2zJn16cAuAolnW+qQ=="],
|
"opencode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
|
||||||
|
|
||||||
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
|
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
|
||||||
|
|
||||||
@@ -4355,6 +4415,8 @@
|
|||||||
|
|
||||||
"readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
|
"readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
|
||||||
|
|
||||||
|
"rimraf/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||||
|
|
||||||
"router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
"router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
||||||
|
|
||||||
"safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
"safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||||
@@ -4405,8 +4467,6 @@
|
|||||||
|
|
||||||
"unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
"unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
||||||
|
|
||||||
"uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
|
||||||
|
|
||||||
"utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
"utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
||||||
|
|
||||||
"vitest/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
"vitest/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||||
@@ -4919,8 +4979,6 @@
|
|||||||
|
|
||||||
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||||
|
|
||||||
"opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
|
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
||||||
@@ -4937,6 +4995,12 @@
|
|||||||
|
|
||||||
"readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
"readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||||
|
|
||||||
|
"rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||||
|
|
||||||
|
"rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
|
"rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||||
|
|
||||||
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||||
|
|
||||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
@@ -5119,6 +5183,8 @@
|
|||||||
|
|
||||||
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
|
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
|
||||||
|
|
||||||
|
"rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||||
|
|
||||||
"tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
"tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
"tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
"tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||||
|
|||||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767364772,
|
"lastModified": 1768302833,
|
||||||
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
"narHash": "sha256-h5bRFy9bco+8QcK7rGoOiqMxMbmn21moTACofNLRMP4=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
"rev": "61db79b0c6b838d9894923920b612048e1201926",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -27,11 +27,28 @@
|
|||||||
"aarch64-darwin" = "bun-darwin-arm64";
|
"aarch64-darwin" = "bun-darwin-arm64";
|
||||||
"x86_64-darwin" = "bun-darwin-x64";
|
"x86_64-darwin" = "bun-darwin-x64";
|
||||||
};
|
};
|
||||||
defaultNodeModules = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
|
||||||
|
# Parse "bun-{os}-{cpu}" to {os, cpu}
|
||||||
|
parseBunTarget =
|
||||||
|
target:
|
||||||
|
let
|
||||||
|
parts = lib.splitString "-" target;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
os = builtins.elemAt parts 1;
|
||||||
|
cpu = builtins.elemAt parts 2;
|
||||||
|
};
|
||||||
|
|
||||||
hashesFile = "${./nix}/hashes.json";
|
hashesFile = "${./nix}/hashes.json";
|
||||||
hashesData =
|
hashesData =
|
||||||
if builtins.pathExists hashesFile then builtins.fromJSON (builtins.readFile hashesFile) else { };
|
if builtins.pathExists hashesFile then builtins.fromJSON (builtins.readFile hashesFile) else { };
|
||||||
nodeModulesHash = hashesData.nodeModules or defaultNodeModules;
|
# Lookup hash: supports per-system ({system: hash}) or legacy single hash
|
||||||
|
nodeModulesHashFor =
|
||||||
|
system:
|
||||||
|
if builtins.isAttrs hashesData.nodeModules then
|
||||||
|
hashesData.nodeModules.${system}
|
||||||
|
else
|
||||||
|
hashesData.nodeModules;
|
||||||
modelsDev = forEachSystem (
|
modelsDev = forEachSystem (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
@@ -63,8 +80,11 @@
|
|||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
pkgs = pkgsFor system;
|
pkgs = pkgsFor system;
|
||||||
|
bunPlatform = parseBunTarget bunTarget.${system};
|
||||||
mkNodeModules = pkgs.callPackage ./nix/node-modules.nix {
|
mkNodeModules = pkgs.callPackage ./nix/node-modules.nix {
|
||||||
hash = nodeModulesHash;
|
hash = nodeModulesHashFor system;
|
||||||
|
bunCpu = bunPlatform.cpu;
|
||||||
|
bunOs = bunPlatform.os;
|
||||||
};
|
};
|
||||||
mkOpencode = pkgs.callPackage ./nix/opencode.nix { };
|
mkOpencode = pkgs.callPackage ./nix/opencode.nix { };
|
||||||
mkDesktop = pkgs.callPackage ./nix/desktop.nix { };
|
mkDesktop = pkgs.callPackage ./nix/desktop.nix { };
|
||||||
|
|||||||
+6
-5
@@ -81,12 +81,13 @@ This will walk you through installing the GitHub app, creating the workflow, and
|
|||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Run opencode
|
- name: Run opencode
|
||||||
uses: anomalyco/opencode/github@latest
|
uses: anomalyco/opencode/github@latest
|
||||||
env:
|
env:
|
||||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ const ZEN_MODELS = [
|
|||||||
]
|
]
|
||||||
const ZEN_BLACK = new sst.Secret("ZEN_BLACK")
|
const ZEN_BLACK = new sst.Secret("ZEN_BLACK")
|
||||||
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
|
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
|
||||||
|
const STRIPE_PUBLISHABLE_KEY = new sst.Secret("STRIPE_PUBLISHABLE_KEY")
|
||||||
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
||||||
properties: { value: auth.url.apply((url) => url!) },
|
properties: { value: auth.url.apply((url) => url!) },
|
||||||
})
|
})
|
||||||
@@ -163,6 +164,7 @@ new sst.cloudflare.x.SolidStart("Console", {
|
|||||||
AWS_SES_ACCESS_KEY_ID,
|
AWS_SES_ACCESS_KEY_ID,
|
||||||
AWS_SES_SECRET_ACCESS_KEY,
|
AWS_SES_SECRET_ACCESS_KEY,
|
||||||
ZEN_BLACK,
|
ZEN_BLACK,
|
||||||
|
new sst.Secret("ZEN_SESSION_SECRET"),
|
||||||
...ZEN_MODELS,
|
...ZEN_MODELS,
|
||||||
...($dev
|
...($dev
|
||||||
? [
|
? [
|
||||||
@@ -176,6 +178,7 @@ new sst.cloudflare.x.SolidStart("Console", {
|
|||||||
//VITE_DOCS_URL: web.url.apply((url) => url!),
|
//VITE_DOCS_URL: web.url.apply((url) => url!),
|
||||||
//VITE_API_URL: gateway.url.apply((url) => url!),
|
//VITE_API_URL: gateway.url.apply((url) => url!),
|
||||||
VITE_AUTH_URL: auth.url.apply((url) => url!),
|
VITE_AUTH_URL: auth.url.apply((url) => url!),
|
||||||
|
VITE_STRIPE_PUBLISHABLE_KEY: STRIPE_PUBLISHABLE_KEY.value,
|
||||||
},
|
},
|
||||||
transform: {
|
transform: {
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
+4
-1
@@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": "sha256-WHqX159BYPSHBFmxxkTrWPytBzTSTcWkoEywAxP58kI="
|
"nodeModules": {
|
||||||
|
"x86_64-linux": "sha256-wENwhwRVfgoVyA9YNGcG+fAfu46JxK4xvNgiPbRt//s=",
|
||||||
|
"aarch64-darwin": "sha256-vm1DYl1erlbaqz5NHHlnZEMuFmidr/UkS84nIqLJ96Q="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
bun,
|
bun,
|
||||||
cacert,
|
cacert,
|
||||||
curl,
|
curl,
|
||||||
|
bunCpu,
|
||||||
|
bunOs,
|
||||||
}:
|
}:
|
||||||
args:
|
args:
|
||||||
stdenvNoCC.mkDerivation {
|
stdenvNoCC.mkDerivation {
|
||||||
@@ -29,8 +31,8 @@ stdenvNoCC.mkDerivation {
|
|||||||
export HOME=$(mktemp -d)
|
export HOME=$(mktemp -d)
|
||||||
export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
|
export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
|
||||||
bun install \
|
bun install \
|
||||||
--cpu="*" \
|
--cpu="${bunCpu}" \
|
||||||
--os="*" \
|
--os="${bunOs}" \
|
||||||
--frozen-lockfile \
|
--frozen-lockfile \
|
||||||
--ignore-scripts \
|
--ignore-scripts \
|
||||||
--no-progress \
|
--no-progress \
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ HASH_FILE=${HASH_FILE:-$DEFAULT_HASH_FILE}
|
|||||||
if [ ! -f "$HASH_FILE" ]; then
|
if [ ! -f "$HASH_FILE" ]; then
|
||||||
cat >"$HASH_FILE" <<EOF
|
cat >"$HASH_FILE" <<EOF
|
||||||
{
|
{
|
||||||
"nodeModules": "$DUMMY"
|
"nodeModules": {}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
@@ -33,9 +33,16 @@ trap cleanup EXIT
|
|||||||
|
|
||||||
write_node_modules_hash() {
|
write_node_modules_hash() {
|
||||||
local value="$1"
|
local value="$1"
|
||||||
|
local system="${2:-$SYSTEM}"
|
||||||
local temp
|
local temp
|
||||||
temp=$(mktemp)
|
temp=$(mktemp)
|
||||||
jq --arg value "$value" '.nodeModules = $value' "$HASH_FILE" >"$temp"
|
|
||||||
|
if jq -e '.nodeModules | type == "object"' "$HASH_FILE" >/dev/null 2>&1; then
|
||||||
|
jq --arg system "$system" --arg value "$value" '.nodeModules[$system] = $value' "$HASH_FILE" >"$temp"
|
||||||
|
else
|
||||||
|
jq --arg system "$system" --arg value "$value" '.nodeModules = {($system): $value}' "$HASH_FILE" >"$temp"
|
||||||
|
fi
|
||||||
|
|
||||||
mv "$temp" "$HASH_FILE"
|
mv "$temp" "$HASH_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +111,7 @@ fi
|
|||||||
|
|
||||||
write_node_modules_hash "$CORRECT_HASH"
|
write_node_modules_hash "$CORRECT_HASH"
|
||||||
|
|
||||||
jq -e --arg hash "$CORRECT_HASH" '.nodeModules == $hash' "$HASH_FILE" >/dev/null
|
jq -e --arg system "$SYSTEM" --arg hash "$CORRECT_HASH" '.nodeModules[$system] == $hash' "$HASH_FILE" >/dev/null
|
||||||
|
|
||||||
echo "node_modules hash updated for ${SYSTEM}: $CORRECT_HASH"
|
echo "node_modules hash updated for ${SYSTEM}: $CORRECT_HASH"
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -36,7 +36,8 @@
|
|||||||
"@solid-primitives/storage": "4.3.3",
|
"@solid-primitives/storage": "4.3.3",
|
||||||
"@tailwindcss/vite": "4.1.11",
|
"@tailwindcss/vite": "4.1.11",
|
||||||
"diff": "8.0.2",
|
"diff": "8.0.2",
|
||||||
"ai": "5.0.97",
|
"dompurify": "3.3.1",
|
||||||
|
"ai": "5.0.119",
|
||||||
"hono": "4.10.7",
|
"hono": "4.10.7",
|
||||||
"hono-openapi": "1.1.2",
|
"hono-openapi": "1.1.2",
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
|
|||||||
+1
-30
@@ -14,36 +14,7 @@
|
|||||||
<meta property="og:image" content="/social-share.png" />
|
<meta property="og:image" content="/social-share.png" />
|
||||||
<meta property="twitter:image" content="/social-share.png" />
|
<meta property="twitter:image" content="/social-share.png" />
|
||||||
<!-- Theme preload script - applies cached theme to avoid FOUC -->
|
<!-- Theme preload script - applies cached theme to avoid FOUC -->
|
||||||
<script id="oc-theme-preload-script">
|
<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
|
||||||
;(function () {
|
|
||||||
var themeId = localStorage.getItem("opencode-theme-id")
|
|
||||||
if (!themeId) return
|
|
||||||
|
|
||||||
var scheme = localStorage.getItem("opencode-color-scheme") || "system"
|
|
||||||
var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)
|
|
||||||
var mode = isDark ? "dark" : "light"
|
|
||||||
|
|
||||||
document.documentElement.dataset.theme = themeId
|
|
||||||
document.documentElement.dataset.colorScheme = mode
|
|
||||||
|
|
||||||
if (themeId === "oc-1") return
|
|
||||||
|
|
||||||
var css = localStorage.getItem("opencode-theme-css-" + themeId + "-" + mode)
|
|
||||||
if (css) {
|
|
||||||
var style = document.createElement("style")
|
|
||||||
style.id = "oc-theme-preload"
|
|
||||||
style.textContent =
|
|
||||||
":root{color-scheme:" +
|
|
||||||
mode +
|
|
||||||
";--text-mix-blend-mode:" +
|
|
||||||
(isDark ? "plus-lighter" : "multiply") +
|
|
||||||
";" +
|
|
||||||
css +
|
|
||||||
"}"
|
|
||||||
document.head.appendChild(style)
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
|
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
;(function () {
|
||||||
|
var themeId = localStorage.getItem("opencode-theme-id")
|
||||||
|
if (!themeId) return
|
||||||
|
|
||||||
|
var scheme = localStorage.getItem("opencode-color-scheme") || "system"
|
||||||
|
var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)
|
||||||
|
var mode = isDark ? "dark" : "light"
|
||||||
|
|
||||||
|
document.documentElement.dataset.theme = themeId
|
||||||
|
document.documentElement.dataset.colorScheme = mode
|
||||||
|
|
||||||
|
if (themeId === "oc-1") return
|
||||||
|
|
||||||
|
var css = localStorage.getItem("opencode-theme-css-" + themeId + "-" + mode)
|
||||||
|
if (css) {
|
||||||
|
var style = document.createElement("style")
|
||||||
|
style.id = "oc-theme-preload"
|
||||||
|
style.textContent =
|
||||||
|
":root{color-scheme:" +
|
||||||
|
mode +
|
||||||
|
";--text-mix-blend-mode:" +
|
||||||
|
(isDark ? "plus-lighter" : "multiply") +
|
||||||
|
";" +
|
||||||
|
css +
|
||||||
|
"}"
|
||||||
|
document.head.appendChild(style)
|
||||||
|
}
|
||||||
|
})()
|
||||||
+22
-27
@@ -33,22 +33,10 @@ const Loading = () => <div class="size-full flex items-center justify-center tex
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
__OPENCODE__?: { updaterEnabled?: boolean; port?: number; serverReady?: boolean }
|
__OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultServerUrl = iife(() => {
|
|
||||||
const param = new URLSearchParams(document.location.search).get("url")
|
|
||||||
if (param) return param
|
|
||||||
|
|
||||||
if (location.hostname.includes("opencode.ai")) return "http://localhost:4096"
|
|
||||||
if (window.__OPENCODE__) return `http://127.0.0.1:${window.__OPENCODE__.port}`
|
|
||||||
if (import.meta.env.DEV)
|
|
||||||
return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}`
|
|
||||||
|
|
||||||
return window.location.origin
|
|
||||||
})
|
|
||||||
|
|
||||||
export function AppBaseProviders(props: ParentProps) {
|
export function AppBaseProviders(props: ParentProps) {
|
||||||
return (
|
return (
|
||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
@@ -77,9 +65,18 @@ function ServerKey(props: ParentProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AppInterface() {
|
export function AppInterface(props: { defaultUrl?: string }) {
|
||||||
|
const defaultServerUrl = () => {
|
||||||
|
if (props.defaultUrl) return props.defaultUrl
|
||||||
|
if (location.hostname.includes("opencode.ai")) return "http://localhost:4096"
|
||||||
|
if (import.meta.env.DEV)
|
||||||
|
return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}`
|
||||||
|
|
||||||
|
return window.location.origin
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServerProvider defaultUrl={defaultServerUrl}>
|
<ServerProvider defaultUrl={defaultServerUrl()}>
|
||||||
<ServerKey>
|
<ServerKey>
|
||||||
<GlobalSDKProvider>
|
<GlobalSDKProvider>
|
||||||
<GlobalSyncProvider>
|
<GlobalSyncProvider>
|
||||||
@@ -108,18 +105,16 @@ export function AppInterface() {
|
|||||||
<Route path="/" component={() => <Navigate href="session" />} />
|
<Route path="/" component={() => <Navigate href="session" />} />
|
||||||
<Route
|
<Route
|
||||||
path="/session/:id?"
|
path="/session/:id?"
|
||||||
component={(p) => (
|
component={() => (
|
||||||
<Show when={p.params.id ?? "new"} keyed>
|
<TerminalProvider>
|
||||||
<TerminalProvider>
|
<FileProvider>
|
||||||
<FileProvider>
|
<PromptProvider>
|
||||||
<PromptProvider>
|
<Suspense fallback={<Loading />}>
|
||||||
<Suspense fallback={<Loading />}>
|
<Session />
|
||||||
<Session />
|
</Suspense>
|
||||||
</Suspense>
|
</PromptProvider>
|
||||||
</PromptProvider>
|
</FileProvider>
|
||||||
</FileProvider>
|
</TerminalProvider>
|
||||||
</TerminalProvider>
|
|
||||||
</Show>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@@ -7,15 +7,11 @@ import { createMemo, createSignal, For, Show } from "solid-js"
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useGlobalSDK } from "@/context/global-sdk"
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
import { type LocalProject, getAvatarColors } from "@/context/layout"
|
import { type LocalProject, getAvatarColors } from "@/context/layout"
|
||||||
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { Avatar } from "@opencode-ai/ui/avatar"
|
import { Avatar } from "@opencode-ai/ui/avatar"
|
||||||
|
|
||||||
const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
|
const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
|
||||||
|
|
||||||
function getFilename(input: string) {
|
|
||||||
const parts = input.split("/")
|
|
||||||
return parts[parts.length - 1] || input
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DialogEditProject(props: { project: LocalProject }) {
|
export function DialogEditProject(props: { project: LocalProject }) {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { Component, createMemo } from "solid-js"
|
||||||
|
import { useNavigate, useParams } from "@solidjs/router"
|
||||||
|
import { useSync } from "@/context/sync"
|
||||||
|
import { useSDK } from "@/context/sdk"
|
||||||
|
import { usePrompt } from "@/context/prompt"
|
||||||
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
|
import { List } from "@opencode-ai/ui/list"
|
||||||
|
import { extractPromptFromParts } from "@/utils/prompt"
|
||||||
|
import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client"
|
||||||
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
|
|
||||||
|
interface ForkableMessage {
|
||||||
|
id: string
|
||||||
|
text: string
|
||||||
|
time: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(date: Date): string {
|
||||||
|
return date.toLocaleTimeString(undefined, { timeStyle: "short" })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DialogFork: Component = () => {
|
||||||
|
const params = useParams()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const sync = useSync()
|
||||||
|
const sdk = useSDK()
|
||||||
|
const prompt = usePrompt()
|
||||||
|
const dialog = useDialog()
|
||||||
|
|
||||||
|
const messages = createMemo((): ForkableMessage[] => {
|
||||||
|
const sessionID = params.id
|
||||||
|
if (!sessionID) return []
|
||||||
|
|
||||||
|
const msgs = sync.data.message[sessionID] ?? []
|
||||||
|
const result: ForkableMessage[] = []
|
||||||
|
|
||||||
|
for (const message of msgs) {
|
||||||
|
if (message.role !== "user") continue
|
||||||
|
|
||||||
|
const parts = sync.data.part[message.id] ?? []
|
||||||
|
const textPart = parts.find((x): x is SDKTextPart => x.type === "text" && !x.synthetic && !x.ignored)
|
||||||
|
if (!textPart) continue
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
id: message.id,
|
||||||
|
text: textPart.text.replace(/\n/g, " ").slice(0, 200),
|
||||||
|
time: formatTime(new Date(message.time.created)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.reverse()
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSelect = (item: ForkableMessage | undefined) => {
|
||||||
|
if (!item) return
|
||||||
|
|
||||||
|
const sessionID = params.id
|
||||||
|
if (!sessionID) return
|
||||||
|
|
||||||
|
const parts = sync.data.part[item.id] ?? []
|
||||||
|
const restored = extractPromptFromParts(parts, { directory: sdk.directory })
|
||||||
|
|
||||||
|
dialog.close()
|
||||||
|
|
||||||
|
sdk.client.session.fork({ sessionID, messageID: item.id }).then((forked) => {
|
||||||
|
if (!forked.data) return
|
||||||
|
navigate(`/${base64Encode(sdk.directory)}/session/${forked.data.id}`)
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
prompt.set(restored)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog title="Fork from message">
|
||||||
|
<List
|
||||||
|
class="flex-1 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0"
|
||||||
|
search={{ placeholder: "Search", autofocus: true }}
|
||||||
|
emptyMessage="No messages to fork from"
|
||||||
|
key={(x) => x.id}
|
||||||
|
items={messages}
|
||||||
|
filterKeys={["text"]}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
>
|
||||||
|
{(item) => (
|
||||||
|
<div class="w-full flex items-center gap-2">
|
||||||
|
<span class="truncate flex-1 min-w-0 text-left" style={{ "font-weight": "400" }}>
|
||||||
|
{item.text}
|
||||||
|
</span>
|
||||||
|
<span class="text-text-weak shrink-0" style={{ "font-weight": "400" }}>
|
||||||
|
{item.time}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ export function DialogSelectFile() {
|
|||||||
const params = useParams()
|
const params = useParams()
|
||||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||||
const tabs = createMemo(() => layout.tabs(sessionKey()))
|
const tabs = createMemo(() => layout.tabs(sessionKey()))
|
||||||
|
const view = createMemo(() => layout.view(sessionKey()))
|
||||||
return (
|
return (
|
||||||
<Dialog title="Select file">
|
<Dialog title="Select file">
|
||||||
<List
|
<List
|
||||||
@@ -27,7 +28,7 @@ export function DialogSelectFile() {
|
|||||||
const value = file.tab(path)
|
const value = file.tab(path)
|
||||||
tabs().open(value)
|
tabs().open(value)
|
||||||
file.load(path)
|
file.load(path)
|
||||||
layout.review.open()
|
view().reviewPanel.open()
|
||||||
}
|
}
|
||||||
dialog.close()
|
dialog.close()
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export const ModelSelectorPopover: Component<{
|
|||||||
<Kobalte open={open()} onOpenChange={setOpen} placement="top-start" gutter={8}>
|
<Kobalte open={open()} onOpenChange={setOpen} placement="top-start" gutter={8}>
|
||||||
<Kobalte.Trigger as="div">{props.children}</Kobalte.Trigger>
|
<Kobalte.Trigger as="div">{props.children}</Kobalte.Trigger>
|
||||||
<Kobalte.Portal>
|
<Kobalte.Portal>
|
||||||
<Kobalte.Content class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none">
|
<Kobalte.Content class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden">
|
||||||
<Kobalte.Title class="sr-only">Select model</Kobalte.Title>
|
<Kobalte.Title class="sr-only">Select model</Kobalte.Title>
|
||||||
<ModelList provider={props.provider} onSelect={() => setOpen(false)} class="p-1" />
|
<ModelList provider={props.provider} onSelect={() => setOpen(false)} class="p-1" />
|
||||||
</Kobalte.Content>
|
</Kobalte.Content>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { createEffect, createMemo, onCleanup } from "solid-js"
|
import { createResource, createEffect, createMemo, onCleanup, Show } from "solid-js"
|
||||||
import { createStore, reconcile } from "solid-js/store"
|
import { createStore, reconcile } from "solid-js/store"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server"
|
import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||||
@@ -35,6 +36,8 @@ export function DialogSelectServer() {
|
|||||||
error: "",
|
error: "",
|
||||||
status: {} as Record<string, ServerStatus | undefined>,
|
status: {} as Record<string, ServerStatus | undefined>,
|
||||||
})
|
})
|
||||||
|
const [defaultUrl, defaultUrlActions] = createResource(() => platform.getDefaultServerUrl?.())
|
||||||
|
const isDesktop = platform.platform === "desktop"
|
||||||
|
|
||||||
const items = createMemo(() => {
|
const items = createMemo(() => {
|
||||||
const current = server.url
|
const current = server.url
|
||||||
@@ -114,6 +117,10 @@ export function DialogSelectServer() {
|
|||||||
select(value, true)
|
select(value, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleRemove(url: string) {
|
||||||
|
server.remove(url)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog title="Servers" description="Switch which OpenCode server this app connects to.">
|
<Dialog title="Servers" description="Switch which OpenCode server this app connects to.">
|
||||||
<div class="flex flex-col gap-4 pb-4">
|
<div class="flex flex-col gap-4 pb-4">
|
||||||
@@ -128,20 +135,33 @@ export function DialogSelectServer() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(i) => (
|
{(i) => (
|
||||||
<div
|
<div class="flex items-center gap-2 min-w-0 flex-1 group/item">
|
||||||
class="flex items-center gap-2 min-w-0 flex-1"
|
|
||||||
classList={{ "opacity-50": store.status[i]?.healthy === false }}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
classList={{
|
class="flex items-center gap-2 min-w-0 flex-1"
|
||||||
"size-1.5 rounded-full shrink-0": true,
|
classList={{ "opacity-50": store.status[i]?.healthy === false }}
|
||||||
"bg-icon-success-base": store.status[i]?.healthy === true,
|
>
|
||||||
"bg-icon-critical-base": store.status[i]?.healthy === false,
|
<div
|
||||||
"bg-border-weak-base": store.status[i] === undefined,
|
classList={{
|
||||||
}}
|
"size-1.5 rounded-full shrink-0": true,
|
||||||
/>
|
"bg-icon-success-base": store.status[i]?.healthy === true,
|
||||||
<span class="truncate">{serverDisplayName(i)}</span>
|
"bg-icon-critical-base": store.status[i]?.healthy === false,
|
||||||
<span class="text-text-weak">{store.status[i]?.version}</span>
|
"bg-border-weak-base": store.status[i] === undefined,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span class="truncate">{serverDisplayName(i)}</span>
|
||||||
|
<span class="text-text-weak">{store.status[i]?.version}</span>
|
||||||
|
</div>
|
||||||
|
<Show when={current() !== i && server.list.includes(i)}>
|
||||||
|
<IconButton
|
||||||
|
icon="circle-x"
|
||||||
|
variant="ghost"
|
||||||
|
class="bg-transparent transition-opacity shrink-0 hover:scale-110"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
handleRemove(i)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
@@ -173,6 +193,53 @@ export function DialogSelectServer() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Show when={isDesktop}>
|
||||||
|
<div class="mt-6 px-3 flex flex-col gap-1.5">
|
||||||
|
<div class="px-3">
|
||||||
|
<h3 class="text-14-regular text-text-weak">Default server</h3>
|
||||||
|
<p class="text-12-regular text-text-weak mt-1">
|
||||||
|
Connect to this server on app launch instead of starting a local server. Requires restart.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 px-3 py-2">
|
||||||
|
<Show
|
||||||
|
when={defaultUrl()}
|
||||||
|
fallback={
|
||||||
|
<Show
|
||||||
|
when={server.url}
|
||||||
|
fallback={<span class="text-14-regular text-text-weak">No server selected</span>}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
onClick={async () => {
|
||||||
|
await platform.setDefaultServerUrl?.(server.url)
|
||||||
|
defaultUrlActions.refetch(server.url)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Set current server as default
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2 flex-1 min-w-0">
|
||||||
|
<span class="truncate text-14-regular">{serverDisplayName(defaultUrl()!)}</span>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="small"
|
||||||
|
onClick={async () => {
|
||||||
|
await platform.setDefaultServerUrl?.(null)
|
||||||
|
defaultUrlActions.refetch()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,11 +33,14 @@ import { useSync } from "@/context/sync"
|
|||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
|
import type { IconName } from "@opencode-ai/ui/icons/provider"
|
||||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Select } from "@opencode-ai/ui/select"
|
import { Select } from "@opencode-ai/ui/select"
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
|
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
||||||
import { ModelSelectorPopover } from "@/components/dialog-select-model"
|
import { ModelSelectorPopover } from "@/components/dialog-select-model"
|
||||||
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
|
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
import { useProviders } from "@/hooks/use-providers"
|
||||||
@@ -361,6 +364,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
if (!isFocused()) setStore("popover", null)
|
if (!isFocused()) setStore("popover", null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Safety: reset composing state on focus change to prevent stuck state
|
||||||
|
// This handles edge cases where compositionend event may not fire
|
||||||
|
createEffect(() => {
|
||||||
|
if (!isFocused()) setComposing(false)
|
||||||
|
})
|
||||||
|
|
||||||
type AtOption = { type: "agent"; name: string; display: string } | { type: "file"; path: string; display: string }
|
type AtOption = { type: "agent"; name: string; display: string } | { type: "file"; path: string; display: string }
|
||||||
|
|
||||||
const agentList = createMemo(() =>
|
const agentList = createMemo(() =>
|
||||||
@@ -386,6 +395,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const {
|
const {
|
||||||
flat: atFlat,
|
flat: atFlat,
|
||||||
active: atActive,
|
active: atActive,
|
||||||
|
setActive: setAtActive,
|
||||||
onInput: atOnInput,
|
onInput: atOnInput,
|
||||||
onKeyDown: atOnKeyDown,
|
onKeyDown: atOnKeyDown,
|
||||||
} = useFilteredList<AtOption>({
|
} = useFilteredList<AtOption>({
|
||||||
@@ -452,6 +462,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const {
|
const {
|
||||||
flat: slashFlat,
|
flat: slashFlat,
|
||||||
active: slashActive,
|
active: slashActive,
|
||||||
|
setActive: setSlashActive,
|
||||||
onInput: slashOnInput,
|
onInput: slashOnInput,
|
||||||
onKeyDown: slashOnKeyDown,
|
onKeyDown: slashOnKeyDown,
|
||||||
refetch: slashRefetch,
|
refetch: slashRefetch,
|
||||||
@@ -876,6 +887,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Shift+Enter BEFORE IME check - Shift+Enter is never used for IME input
|
||||||
|
// and should always insert a newline regardless of composition state
|
||||||
|
if (event.key === "Enter" && event.shiftKey) {
|
||||||
|
addPart({ type: "text", content: "\n", start: 0, end: 0 })
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (event.key === "Enter" && isImeComposing(event)) {
|
if (event.key === "Enter" && isImeComposing(event)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -939,11 +958,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === "Enter" && event.shiftKey) {
|
// Note: Shift+Enter is handled earlier, before IME check
|
||||||
addPart({ type: "text", content: "\n", start: 0, end: 0 })
|
|
||||||
event.preventDefault()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.key === "Enter" && !event.shiftKey) {
|
if (event.key === "Enter" && !event.shiftKey) {
|
||||||
handleSubmit(event)
|
handleSubmit(event)
|
||||||
}
|
}
|
||||||
@@ -1299,6 +1314,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-80 min-h-10
|
class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-80 min-h-10
|
||||||
overflow-auto no-scrollbar flex flex-col p-2 rounded-md
|
overflow-auto no-scrollbar flex flex-col p-2 rounded-md
|
||||||
border border-border-base bg-surface-raised-stronger-non-alpha shadow-md"
|
border border-border-base bg-surface-raised-stronger-non-alpha shadow-md"
|
||||||
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={store.popover === "at"}>
|
<Match when={store.popover === "at"}>
|
||||||
@@ -1314,6 +1330,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
"bg-surface-raised-base-hover": atActive() === atKey(item),
|
"bg-surface-raised-base-hover": atActive() === atKey(item),
|
||||||
}}
|
}}
|
||||||
onClick={() => handleAtSelect(item)}
|
onClick={() => handleAtSelect(item)}
|
||||||
|
onMouseEnter={() => setAtActive(atKey(item))}
|
||||||
>
|
>
|
||||||
<Show
|
<Show
|
||||||
when={item.type === "agent"}
|
when={item.type === "agent"}
|
||||||
@@ -1360,6 +1377,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
"bg-surface-raised-base-hover": slashActive() === cmd.id,
|
"bg-surface-raised-base-hover": slashActive() === cmd.id,
|
||||||
}}
|
}}
|
||||||
onClick={() => handleSlashSelect(cmd)}
|
onClick={() => handleSlashSelect(cmd)}
|
||||||
|
onMouseEnter={() => setSlashActive(cmd.id)}
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
<div class="flex items-center gap-2 min-w-0">
|
||||||
<span class="text-14-regular text-text-strong whitespace-nowrap">/{cmd.trigger}</span>
|
<span class="text-14-regular text-text-strong whitespace-nowrap">/{cmd.trigger}</span>
|
||||||
@@ -1479,7 +1497,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
<img
|
<img
|
||||||
src={attachment.dataUrl}
|
src={attachment.dataUrl}
|
||||||
alt={attachment.filename}
|
alt={attachment.filename}
|
||||||
class="size-16 rounded-md object-cover border border-border-base"
|
class="size-16 rounded-md object-cover border border-border-base hover:border-border-strong-base transition-colors"
|
||||||
|
onClick={() =>
|
||||||
|
dialog.show(() => <ImagePreview src={attachment.dataUrl} alt={attachment.filename} />)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<button
|
<button
|
||||||
@@ -1551,6 +1572,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
fallback={
|
fallback={
|
||||||
<TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
|
<TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
|
||||||
<Button as="div" variant="ghost" onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}>
|
<Button as="div" variant="ghost" onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}>
|
||||||
|
<Show when={local.model.current()?.provider?.id}>
|
||||||
|
<ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
|
||||||
|
</Show>
|
||||||
{local.model.current()?.name ?? "Select model"}
|
{local.model.current()?.name ?? "Select model"}
|
||||||
<Icon name="chevron-down" size="small" />
|
<Icon name="chevron-down" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1560,6 +1584,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
<ModelSelectorPopover>
|
<ModelSelectorPopover>
|
||||||
<TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
|
<TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
|
||||||
<Button as="div" variant="ghost">
|
<Button as="div" variant="ghost">
|
||||||
|
<Show when={local.model.current()?.provider?.id}>
|
||||||
|
<ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
|
||||||
|
</Show>
|
||||||
{local.model.current()?.name ?? "Select model"}
|
{local.model.current()?.name ?? "Select model"}
|
||||||
<Icon name="chevron-down" size="small" />
|
<Icon name="chevron-down" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1574,10 +1601,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="text-text-base _hidden group-hover/prompt-input:inline-block"
|
class="text-text-base _hidden group-hover/prompt-input:inline-block capitalize text-12-regular"
|
||||||
onClick={() => local.model.variant.cycle()}
|
onClick={() => local.model.variant.cycle()}
|
||||||
>
|
>
|
||||||
<span class="capitalize text-12-regular">{local.model.variant.current() ?? "Default"}</span>
|
{local.model.variant.current() ?? "Default"}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
const variant = createMemo(() => props.variant ?? "button")
|
const variant = createMemo(() => props.variant ?? "button")
|
||||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||||
const tabs = createMemo(() => layout.tabs(sessionKey()))
|
const tabs = createMemo(() => layout.tabs(sessionKey()))
|
||||||
|
const view = createMemo(() => layout.view(sessionKey()))
|
||||||
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
|
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
|
||||||
|
|
||||||
const cost = createMemo(() => {
|
const cost = createMemo(() => {
|
||||||
@@ -48,7 +49,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
|
|
||||||
const openContext = () => {
|
const openContext = () => {
|
||||||
if (!params.id) return
|
if (!params.id) return
|
||||||
layout.review.open()
|
view().reviewPanel.open()
|
||||||
tabs().open("context")
|
tabs().open("context")
|
||||||
tabs().setActive("context")
|
tabs().setActive("context")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ export function SessionHeader() {
|
|||||||
})
|
})
|
||||||
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
|
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
|
||||||
const worktrees = createMemo(() => layout.projects.list().map((p) => p.worktree), [], { equals: same })
|
const worktrees = createMemo(() => layout.projects.list().map((p) => p.worktree), [], { equals: same })
|
||||||
|
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||||
|
const view = createMemo(() => layout.view(sessionKey()))
|
||||||
|
|
||||||
function navigateToProject(directory: string) {
|
function navigateToProject(directory: string) {
|
||||||
navigate(`/${base64Encode(directory)}`)
|
navigate(`/${base64Encode(directory)}`)
|
||||||
@@ -171,20 +173,24 @@ export function SessionHeader() {
|
|||||||
title="Toggle review"
|
title="Toggle review"
|
||||||
keybind={command.keybind("review.toggle")}
|
keybind={command.keybind("review.toggle")}
|
||||||
>
|
>
|
||||||
<Button variant="ghost" class="group/review-toggle size-6 p-0" onClick={layout.review.toggle}>
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
class="group/review-toggle size-6 p-0"
|
||||||
|
onClick={() => view().reviewPanel.toggle()}
|
||||||
|
>
|
||||||
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||||
<Icon
|
<Icon
|
||||||
name={layout.review.opened() ? "layout-right" : "layout-left"}
|
name={view().reviewPanel.opened() ? "layout-right" : "layout-left"}
|
||||||
size="small"
|
size="small"
|
||||||
class="group-hover/review-toggle:hidden"
|
class="group-hover/review-toggle:hidden"
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
name={layout.review.opened() ? "layout-right-partial" : "layout-left-partial"}
|
name={view().reviewPanel.opened() ? "layout-right-partial" : "layout-left-partial"}
|
||||||
size="small"
|
size="small"
|
||||||
class="hidden group-hover/review-toggle:inline-block"
|
class="hidden group-hover/review-toggle:inline-block"
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
name={layout.review.opened() ? "layout-right-full" : "layout-left-full"}
|
name={view().reviewPanel.opened() ? "layout-right-full" : "layout-left-full"}
|
||||||
size="small"
|
size="small"
|
||||||
class="hidden group-active/review-toggle:inline-block"
|
class="hidden group-active/review-toggle:inline-block"
|
||||||
/>
|
/>
|
||||||
@@ -197,11 +203,11 @@ export function SessionHeader() {
|
|||||||
title="Toggle terminal"
|
title="Toggle terminal"
|
||||||
keybind={command.keybind("terminal.toggle")}
|
keybind={command.keybind("terminal.toggle")}
|
||||||
>
|
>
|
||||||
<Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
|
<Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={() => view().terminal.toggle()}>
|
||||||
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||||
<Icon
|
<Icon
|
||||||
size="small"
|
size="small"
|
||||||
name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
|
name={view().terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
|
||||||
class="group-hover/terminal-toggle:hidden"
|
class="group-hover/terminal-toggle:hidden"
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
@@ -211,7 +217,7 @@ export function SessionHeader() {
|
|||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
size="small"
|
size="small"
|
||||||
name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
|
name={view().terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
|
||||||
class="hidden group-active/terminal-toggle:inline-block"
|
class="hidden group-active/terminal-toggle:inline-block"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
let serializeAddon: SerializeAddon
|
let serializeAddon: SerializeAddon
|
||||||
let fitAddon: FitAddon
|
let fitAddon: FitAddon
|
||||||
let handleResize: () => void
|
let handleResize: () => void
|
||||||
|
let handleTextareaFocus: () => void
|
||||||
|
let handleTextareaBlur: () => void
|
||||||
let reconnect: number | undefined
|
let reconnect: number | undefined
|
||||||
let disposed = false
|
let disposed = false
|
||||||
|
|
||||||
@@ -98,13 +100,17 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
const mod = await import("ghostty-web")
|
const mod = await import("ghostty-web")
|
||||||
ghostty = await mod.Ghostty.load()
|
ghostty = await mod.Ghostty.load()
|
||||||
|
|
||||||
const socket = new WebSocket(
|
const url = new URL(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
|
||||||
sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`,
|
if (window.__OPENCODE__?.serverPassword) {
|
||||||
)
|
url.username = "opencode"
|
||||||
|
url.password = window.__OPENCODE__?.serverPassword
|
||||||
|
}
|
||||||
|
const socket = new WebSocket(url)
|
||||||
ws = socket
|
ws = socket
|
||||||
|
|
||||||
const t = new mod.Terminal({
|
const t = new mod.Terminal({
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
|
cursorStyle: "bar",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: "IBM Plex Mono, monospace",
|
fontFamily: "IBM Plex Mono, monospace",
|
||||||
allowTransparency: true,
|
allowTransparency: true,
|
||||||
@@ -170,6 +176,17 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
|
|
||||||
t.open(container)
|
t.open(container)
|
||||||
container.addEventListener("pointerdown", handlePointerDown)
|
container.addEventListener("pointerdown", handlePointerDown)
|
||||||
|
|
||||||
|
handleTextareaFocus = () => {
|
||||||
|
t.options.cursorBlink = true
|
||||||
|
}
|
||||||
|
handleTextareaBlur = () => {
|
||||||
|
t.options.cursorBlink = false
|
||||||
|
}
|
||||||
|
|
||||||
|
t.textarea?.addEventListener("focus", handleTextareaFocus)
|
||||||
|
t.textarea?.addEventListener("blur", handleTextareaBlur)
|
||||||
|
|
||||||
focusTerminal()
|
focusTerminal()
|
||||||
|
|
||||||
if (local.pty.buffer) {
|
if (local.pty.buffer) {
|
||||||
@@ -242,6 +259,8 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
window.removeEventListener("resize", handleResize)
|
window.removeEventListener("resize", handleResize)
|
||||||
}
|
}
|
||||||
container.removeEventListener("pointerdown", handlePointerDown)
|
container.removeEventListener("pointerdown", handlePointerDown)
|
||||||
|
term?.textarea?.removeEventListener("focus", handleTextareaFocus)
|
||||||
|
term?.textarea?.removeEventListener("blur", handleTextareaBlur)
|
||||||
|
|
||||||
const t = term
|
const t = term
|
||||||
if (serializeAddon && props.onCleanup && t) {
|
if (serializeAddon && props.onCleanup && t) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createEffect, createMemo, onCleanup } from "solid-js"
|
import { createEffect, createMemo, createRoot, onCleanup } from "solid-js"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import type { FileContent } from "@opencode-ai/sdk/v2"
|
import type { FileContent } from "@opencode-ai/sdk/v2"
|
||||||
@@ -82,8 +82,106 @@ function normalizeSelectedLines(range: SelectedLineRange): SelectedLineRange {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WORKSPACE_KEY = "__workspace__"
|
||||||
|
const MAX_FILE_VIEW_SESSIONS = 20
|
||||||
|
const MAX_VIEW_FILES = 500
|
||||||
|
|
||||||
|
type ViewSession = ReturnType<typeof createViewSession>
|
||||||
|
|
||||||
|
type ViewCacheEntry = {
|
||||||
|
value: ViewSession
|
||||||
|
dispose: VoidFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
function createViewSession(dir: string, id: string | undefined) {
|
||||||
|
const legacyViewKey = `${dir}/file${id ? "/" + id : ""}.v1`
|
||||||
|
|
||||||
|
const [view, setView, _, ready] = persisted(
|
||||||
|
Persist.scoped(dir, id, "file-view", [legacyViewKey]),
|
||||||
|
createStore<{
|
||||||
|
file: Record<string, FileViewState>
|
||||||
|
}>({
|
||||||
|
file: {},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const meta = { pruned: false }
|
||||||
|
|
||||||
|
const pruneView = (keep?: string) => {
|
||||||
|
const keys = Object.keys(view.file)
|
||||||
|
if (keys.length <= MAX_VIEW_FILES) return
|
||||||
|
|
||||||
|
const drop = keys.filter((key) => key !== keep).slice(0, keys.length - MAX_VIEW_FILES)
|
||||||
|
if (drop.length === 0) return
|
||||||
|
|
||||||
|
setView(
|
||||||
|
produce((draft) => {
|
||||||
|
for (const key of drop) {
|
||||||
|
delete draft.file[key]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!ready()) return
|
||||||
|
if (meta.pruned) return
|
||||||
|
meta.pruned = true
|
||||||
|
pruneView()
|
||||||
|
})
|
||||||
|
|
||||||
|
const scrollTop = (path: string) => view.file[path]?.scrollTop
|
||||||
|
const scrollLeft = (path: string) => view.file[path]?.scrollLeft
|
||||||
|
const selectedLines = (path: string) => view.file[path]?.selectedLines
|
||||||
|
|
||||||
|
const setScrollTop = (path: string, top: number) => {
|
||||||
|
setView("file", path, (current) => {
|
||||||
|
if (current?.scrollTop === top) return current
|
||||||
|
return {
|
||||||
|
...(current ?? {}),
|
||||||
|
scrollTop: top,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
pruneView(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setScrollLeft = (path: string, left: number) => {
|
||||||
|
setView("file", path, (current) => {
|
||||||
|
if (current?.scrollLeft === left) return current
|
||||||
|
return {
|
||||||
|
...(current ?? {}),
|
||||||
|
scrollLeft: left,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
pruneView(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setSelectedLines = (path: string, range: SelectedLineRange | null) => {
|
||||||
|
const next = range ? normalizeSelectedLines(range) : null
|
||||||
|
setView("file", path, (current) => {
|
||||||
|
if (current?.selectedLines === next) return current
|
||||||
|
return {
|
||||||
|
...(current ?? {}),
|
||||||
|
selectedLines: next,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
pruneView(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ready,
|
||||||
|
scrollTop,
|
||||||
|
scrollLeft,
|
||||||
|
selectedLines,
|
||||||
|
setScrollTop,
|
||||||
|
setScrollLeft,
|
||||||
|
setSelectedLines,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const { use: useFile, provider: FileProvider } = createSimpleContext({
|
export const { use: useFile, provider: FileProvider } = createSimpleContext({
|
||||||
name: "File",
|
name: "File",
|
||||||
|
gate: false,
|
||||||
init: () => {
|
init: () => {
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
@@ -134,42 +232,45 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
|
|||||||
file: {},
|
file: {},
|
||||||
})
|
})
|
||||||
|
|
||||||
const legacyViewKey = createMemo(() => `${params.dir}/file${params.id ? "/" + params.id : ""}.v1`)
|
const viewCache = new Map<string, ViewCacheEntry>()
|
||||||
|
|
||||||
const [view, setView, _, ready] = persisted(
|
const disposeViews = () => {
|
||||||
Persist.scoped(params.dir!, params.id, "file-view", [legacyViewKey()]),
|
for (const entry of viewCache.values()) {
|
||||||
createStore<{
|
entry.dispose()
|
||||||
file: Record<string, FileViewState>
|
}
|
||||||
}>({
|
viewCache.clear()
|
||||||
file: {},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
const MAX_VIEW_FILES = 500
|
|
||||||
const viewMeta = { pruned: false }
|
|
||||||
|
|
||||||
const pruneView = (keep?: string) => {
|
|
||||||
const keys = Object.keys(view.file)
|
|
||||||
if (keys.length <= MAX_VIEW_FILES) return
|
|
||||||
|
|
||||||
const drop = keys.filter((key) => key !== keep).slice(0, keys.length - MAX_VIEW_FILES)
|
|
||||||
if (drop.length === 0) return
|
|
||||||
|
|
||||||
setView(
|
|
||||||
produce((draft) => {
|
|
||||||
for (const key of drop) {
|
|
||||||
delete draft.file[key]
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
const pruneViews = () => {
|
||||||
if (!ready()) return
|
while (viewCache.size > MAX_FILE_VIEW_SESSIONS) {
|
||||||
if (viewMeta.pruned) return
|
const first = viewCache.keys().next().value
|
||||||
viewMeta.pruned = true
|
if (!first) return
|
||||||
pruneView()
|
const entry = viewCache.get(first)
|
||||||
})
|
entry?.dispose()
|
||||||
|
viewCache.delete(first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadView = (dir: string, id: string | undefined) => {
|
||||||
|
const key = `${dir}:${id ?? WORKSPACE_KEY}`
|
||||||
|
const existing = viewCache.get(key)
|
||||||
|
if (existing) {
|
||||||
|
viewCache.delete(key)
|
||||||
|
viewCache.set(key, existing)
|
||||||
|
return existing.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = createRoot((dispose) => ({
|
||||||
|
value: createViewSession(dir, id),
|
||||||
|
dispose,
|
||||||
|
}))
|
||||||
|
|
||||||
|
viewCache.set(key, entry)
|
||||||
|
pruneViews()
|
||||||
|
return entry.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const view = createMemo(() => loadView(params.dir!, params.id))
|
||||||
|
|
||||||
function ensure(path: string) {
|
function ensure(path: string) {
|
||||||
if (!path) return
|
if (!path) return
|
||||||
@@ -246,51 +347,32 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
|
|||||||
|
|
||||||
const get = (input: string) => store.file[normalize(input)]
|
const get = (input: string) => store.file[normalize(input)]
|
||||||
|
|
||||||
const scrollTop = (input: string) => view.file[normalize(input)]?.scrollTop
|
const scrollTop = (input: string) => view().scrollTop(normalize(input))
|
||||||
const scrollLeft = (input: string) => view.file[normalize(input)]?.scrollLeft
|
const scrollLeft = (input: string) => view().scrollLeft(normalize(input))
|
||||||
const selectedLines = (input: string) => view.file[normalize(input)]?.selectedLines
|
const selectedLines = (input: string) => view().selectedLines(normalize(input))
|
||||||
|
|
||||||
const setScrollTop = (input: string, top: number) => {
|
const setScrollTop = (input: string, top: number) => {
|
||||||
const path = normalize(input)
|
const path = normalize(input)
|
||||||
setView("file", path, (current) => {
|
view().setScrollTop(path, top)
|
||||||
if (current?.scrollTop === top) return current
|
|
||||||
return {
|
|
||||||
...(current ?? {}),
|
|
||||||
scrollTop: top,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
pruneView(path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setScrollLeft = (input: string, left: number) => {
|
const setScrollLeft = (input: string, left: number) => {
|
||||||
const path = normalize(input)
|
const path = normalize(input)
|
||||||
setView("file", path, (current) => {
|
view().setScrollLeft(path, left)
|
||||||
if (current?.scrollLeft === left) return current
|
|
||||||
return {
|
|
||||||
...(current ?? {}),
|
|
||||||
scrollLeft: left,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
pruneView(path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setSelectedLines = (input: string, range: SelectedLineRange | null) => {
|
const setSelectedLines = (input: string, range: SelectedLineRange | null) => {
|
||||||
const path = normalize(input)
|
const path = normalize(input)
|
||||||
const next = range ? normalizeSelectedLines(range) : null
|
view().setSelectedLines(path, range)
|
||||||
setView("file", path, (current) => {
|
|
||||||
if (current?.selectedLines === next) return current
|
|
||||||
return {
|
|
||||||
...(current ?? {}),
|
|
||||||
selectedLines: next,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
pruneView(path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCleanup(() => stop())
|
onCleanup(() => {
|
||||||
|
stop()
|
||||||
|
disposeViews()
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ready,
|
ready: () => view().ready(),
|
||||||
normalize,
|
normalize,
|
||||||
tab,
|
tab,
|
||||||
pathFromTab,
|
pathFromTab,
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
name: "GlobalSDK",
|
name: "GlobalSDK",
|
||||||
init: () => {
|
init: () => {
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
|
const platform = usePlatform()
|
||||||
const abort = new AbortController()
|
const abort = new AbortController()
|
||||||
|
|
||||||
const eventSdk = createOpencodeClient({
|
const eventSdk = createOpencodeClient({
|
||||||
baseUrl: server.url,
|
baseUrl: server.url,
|
||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
|
fetch: platform.fetch,
|
||||||
})
|
})
|
||||||
const emitter = createGlobalEmitter<{
|
const emitter = createGlobalEmitter<{
|
||||||
[key: string]: Event
|
[key: string]: Event
|
||||||
@@ -93,7 +95,6 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
stop()
|
stop()
|
||||||
})
|
})
|
||||||
|
|
||||||
const platform = usePlatform()
|
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: server.url,
|
baseUrl: server.url,
|
||||||
fetch: platform.fetch,
|
fetch: platform.fetch,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
type LspStatus,
|
type LspStatus,
|
||||||
type VcsInfo,
|
type VcsInfo,
|
||||||
type PermissionRequest,
|
type PermissionRequest,
|
||||||
|
type QuestionRequest,
|
||||||
createOpencodeClient,
|
createOpencodeClient,
|
||||||
} from "@opencode-ai/sdk/v2/client"
|
} from "@opencode-ai/sdk/v2/client"
|
||||||
import { createStore, produce, reconcile } from "solid-js/store"
|
import { createStore, produce, reconcile } from "solid-js/store"
|
||||||
@@ -26,6 +27,7 @@ import { ErrorPage, type InitError } from "../pages/error"
|
|||||||
import { batch, createContext, useContext, onCleanup, onMount, type ParentProps, Switch, Match } from "solid-js"
|
import { batch, createContext, useContext, onCleanup, onMount, type ParentProps, Switch, Match } from "solid-js"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
|
import { usePlatform } from "./platform"
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
status: "loading" | "partial" | "complete"
|
status: "loading" | "partial" | "complete"
|
||||||
@@ -36,6 +38,7 @@ type State = {
|
|||||||
config: Config
|
config: Config
|
||||||
path: Path
|
path: Path
|
||||||
session: Session[]
|
session: Session[]
|
||||||
|
sessionTotal: number
|
||||||
session_status: {
|
session_status: {
|
||||||
[sessionID: string]: SessionStatus
|
[sessionID: string]: SessionStatus
|
||||||
}
|
}
|
||||||
@@ -48,6 +51,9 @@ type State = {
|
|||||||
permission: {
|
permission: {
|
||||||
[sessionID: string]: PermissionRequest[]
|
[sessionID: string]: PermissionRequest[]
|
||||||
}
|
}
|
||||||
|
question: {
|
||||||
|
[sessionID: string]: QuestionRequest[]
|
||||||
|
}
|
||||||
mcp: {
|
mcp: {
|
||||||
[name: string]: McpStatus
|
[name: string]: McpStatus
|
||||||
}
|
}
|
||||||
@@ -64,6 +70,7 @@ type State = {
|
|||||||
|
|
||||||
function createGlobalSync() {
|
function createGlobalSync() {
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
|
const platform = usePlatform()
|
||||||
const [globalStore, setGlobalStore] = createStore<{
|
const [globalStore, setGlobalStore] = createStore<{
|
||||||
ready: boolean
|
ready: boolean
|
||||||
error?: InitError
|
error?: InitError
|
||||||
@@ -92,10 +99,12 @@ function createGlobalSync() {
|
|||||||
agent: [],
|
agent: [],
|
||||||
command: [],
|
command: [],
|
||||||
session: [],
|
session: [],
|
||||||
|
sessionTotal: 0,
|
||||||
session_status: {},
|
session_status: {},
|
||||||
session_diff: {},
|
session_diff: {},
|
||||||
todo: {},
|
todo: {},
|
||||||
permission: {},
|
permission: {},
|
||||||
|
question: {},
|
||||||
mcp: {},
|
mcp: {},
|
||||||
lsp: [],
|
lsp: [],
|
||||||
vcs: undefined,
|
vcs: undefined,
|
||||||
@@ -110,8 +119,10 @@ function createGlobalSync() {
|
|||||||
|
|
||||||
async function loadSessions(directory: string) {
|
async function loadSessions(directory: string) {
|
||||||
const [store, setStore] = child(directory)
|
const [store, setStore] = child(directory)
|
||||||
globalSDK.client.session
|
const limit = store.limit
|
||||||
.list({ directory })
|
|
||||||
|
return globalSDK.client.session
|
||||||
|
.list({ directory, roots: true })
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
|
const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
|
||||||
const nonArchived = (x.data ?? [])
|
const nonArchived = (x.data ?? [])
|
||||||
@@ -121,10 +132,12 @@ function createGlobalSync() {
|
|||||||
.sort((a, b) => a.id.localeCompare(b.id))
|
.sort((a, b) => a.id.localeCompare(b.id))
|
||||||
// Include up to the limit, plus any updated in the last 4 hours
|
// Include up to the limit, plus any updated in the last 4 hours
|
||||||
const sessions = nonArchived.filter((s, i) => {
|
const sessions = nonArchived.filter((s, i) => {
|
||||||
if (i < store.limit) return true
|
if (i < limit) return true
|
||||||
const updated = new Date(s.time?.updated ?? s.time?.created).getTime()
|
const updated = new Date(s.time?.updated ?? s.time?.created).getTime()
|
||||||
return updated > fourHoursAgo
|
return updated > fourHoursAgo
|
||||||
})
|
})
|
||||||
|
// Store total session count (used for "load more" pagination)
|
||||||
|
setStore("sessionTotal", nonArchived.length)
|
||||||
setStore("session", reconcile(sessions, { key: "id" }))
|
setStore("session", reconcile(sessions, { key: "id" }))
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -139,6 +152,7 @@ function createGlobalSync() {
|
|||||||
const [store, setStore] = child(directory)
|
const [store, setStore] = child(directory)
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: globalSDK.url,
|
baseUrl: globalSDK.url,
|
||||||
|
fetch: platform.fetch,
|
||||||
directory,
|
directory,
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
})
|
})
|
||||||
@@ -205,6 +219,38 @@ function createGlobalSync() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
sdk.question.list().then((x) => {
|
||||||
|
const grouped: Record<string, QuestionRequest[]> = {}
|
||||||
|
for (const question of x.data ?? []) {
|
||||||
|
if (!question?.id || !question.sessionID) continue
|
||||||
|
const existing = grouped[question.sessionID]
|
||||||
|
if (existing) {
|
||||||
|
existing.push(question)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
grouped[question.sessionID] = [question]
|
||||||
|
}
|
||||||
|
|
||||||
|
batch(() => {
|
||||||
|
for (const sessionID of Object.keys(store.question)) {
|
||||||
|
if (grouped[sessionID]) continue
|
||||||
|
setStore("question", sessionID, [])
|
||||||
|
}
|
||||||
|
for (const [sessionID, questions] of Object.entries(grouped)) {
|
||||||
|
setStore(
|
||||||
|
"question",
|
||||||
|
sessionID,
|
||||||
|
reconcile(
|
||||||
|
questions
|
||||||
|
.filter((q) => !!q?.id)
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => a.id.localeCompare(b.id)),
|
||||||
|
{ key: "id" },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
setStore("status", "complete")
|
setStore("status", "complete")
|
||||||
})
|
})
|
||||||
@@ -393,9 +439,48 @@ function createGlobalSync() {
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case "question.asked": {
|
||||||
|
const sessionID = event.properties.sessionID
|
||||||
|
const questions = store.question[sessionID]
|
||||||
|
if (!questions) {
|
||||||
|
setStore("question", sessionID, [event.properties])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = Binary.search(questions, event.properties.id, (q) => q.id)
|
||||||
|
if (result.found) {
|
||||||
|
setStore("question", sessionID, result.index, reconcile(event.properties))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
setStore(
|
||||||
|
"question",
|
||||||
|
sessionID,
|
||||||
|
produce((draft) => {
|
||||||
|
draft.splice(result.index, 0, event.properties)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "question.replied":
|
||||||
|
case "question.rejected": {
|
||||||
|
const questions = store.question[event.properties.sessionID]
|
||||||
|
if (!questions) break
|
||||||
|
const result = Binary.search(questions, event.properties.requestID, (q) => q.id)
|
||||||
|
if (!result.found) break
|
||||||
|
setStore(
|
||||||
|
"question",
|
||||||
|
event.properties.sessionID,
|
||||||
|
produce((draft) => {
|
||||||
|
draft.splice(result.index, 1)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
case "lsp.updated": {
|
case "lsp.updated": {
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: globalSDK.url,
|
baseUrl: globalSDK.url,
|
||||||
|
fetch: platform.fetch,
|
||||||
directory,
|
directory,
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ type SessionTabs = {
|
|||||||
type SessionView = {
|
type SessionView = {
|
||||||
scroll: Record<string, SessionScroll>
|
scroll: Record<string, SessionScroll>
|
||||||
reviewOpen?: string[]
|
reviewOpen?: string[]
|
||||||
|
terminalOpened?: boolean
|
||||||
|
reviewPanelOpened?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LocalProject = Partial<Project> & { worktree: string; expanded: boolean }
|
export type LocalProject = Partial<Project> & { worktree: string; expanded: boolean }
|
||||||
@@ -53,11 +55,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
width: 280,
|
width: 280,
|
||||||
},
|
},
|
||||||
terminal: {
|
terminal: {
|
||||||
opened: false,
|
|
||||||
height: 280,
|
height: 280,
|
||||||
},
|
},
|
||||||
review: {
|
review: {
|
||||||
opened: true,
|
|
||||||
diffStyle: "split" as ReviewDiffStyle,
|
diffStyle: "split" as ReviewDiffStyle,
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
@@ -150,7 +150,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
const current = store.sessionView[sessionKey]
|
const current = store.sessionView[sessionKey]
|
||||||
const keep = meta.active ?? sessionKey
|
const keep = meta.active ?? sessionKey
|
||||||
if (!current) {
|
if (!current) {
|
||||||
setStore("sessionView", sessionKey, { scroll: next })
|
setStore("sessionView", sessionKey, { scroll: next, terminalOpened: false, reviewPanelOpened: true })
|
||||||
prune(keep)
|
prune(keep)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -306,40 +306,20 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
terminal: {
|
terminal: {
|
||||||
opened: createMemo(() => store.terminal.opened),
|
|
||||||
open() {
|
|
||||||
setStore("terminal", "opened", true)
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
setStore("terminal", "opened", false)
|
|
||||||
},
|
|
||||||
toggle() {
|
|
||||||
setStore("terminal", "opened", (x) => !x)
|
|
||||||
},
|
|
||||||
height: createMemo(() => store.terminal.height),
|
height: createMemo(() => store.terminal.height),
|
||||||
resize(height: number) {
|
resize(height: number) {
|
||||||
setStore("terminal", "height", height)
|
setStore("terminal", "height", height)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
review: {
|
review: {
|
||||||
opened: createMemo(() => store.review?.opened ?? true),
|
|
||||||
diffStyle: createMemo(() => store.review?.diffStyle ?? "split"),
|
diffStyle: createMemo(() => store.review?.diffStyle ?? "split"),
|
||||||
setDiffStyle(diffStyle: ReviewDiffStyle) {
|
setDiffStyle(diffStyle: ReviewDiffStyle) {
|
||||||
if (!store.review) {
|
if (!store.review) {
|
||||||
setStore("review", { opened: true, diffStyle })
|
setStore("review", { diffStyle })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setStore("review", "diffStyle", diffStyle)
|
setStore("review", "diffStyle", diffStyle)
|
||||||
},
|
},
|
||||||
open() {
|
|
||||||
setStore("review", "opened", true)
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
setStore("review", "opened", false)
|
|
||||||
},
|
|
||||||
toggle() {
|
|
||||||
setStore("review", "opened", (x) => !x)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
width: createMemo(() => store.session?.width ?? 600),
|
width: createMemo(() => store.session?.width ?? 600),
|
||||||
@@ -367,6 +347,33 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
touch(sessionKey)
|
touch(sessionKey)
|
||||||
scroll.seed(sessionKey)
|
scroll.seed(sessionKey)
|
||||||
const s = createMemo(() => store.sessionView[sessionKey] ?? { scroll: {} })
|
const s = createMemo(() => store.sessionView[sessionKey] ?? { scroll: {} })
|
||||||
|
const terminalOpened = createMemo(() => s().terminalOpened ?? false)
|
||||||
|
const reviewPanelOpened = createMemo(() => s().reviewPanelOpened ?? true)
|
||||||
|
|
||||||
|
function setTerminalOpened(next: boolean) {
|
||||||
|
const current = store.sessionView[sessionKey]
|
||||||
|
if (!current) {
|
||||||
|
setStore("sessionView", sessionKey, { scroll: {}, terminalOpened: next, reviewPanelOpened: true })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = current.terminalOpened ?? false
|
||||||
|
if (value === next) return
|
||||||
|
setStore("sessionView", sessionKey, "terminalOpened", next)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setReviewPanelOpened(next: boolean) {
|
||||||
|
const current = store.sessionView[sessionKey]
|
||||||
|
if (!current) {
|
||||||
|
setStore("sessionView", sessionKey, { scroll: {}, terminalOpened: false, reviewPanelOpened: next })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = current.reviewPanelOpened ?? true
|
||||||
|
if (value === next) return
|
||||||
|
setStore("sessionView", sessionKey, "reviewPanelOpened", next)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scroll(tab: string) {
|
scroll(tab: string) {
|
||||||
return scroll.scroll(sessionKey, tab)
|
return scroll.scroll(sessionKey, tab)
|
||||||
@@ -374,12 +381,41 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
setScroll(tab: string, pos: SessionScroll) {
|
setScroll(tab: string, pos: SessionScroll) {
|
||||||
scroll.setScroll(sessionKey, tab, pos)
|
scroll.setScroll(sessionKey, tab, pos)
|
||||||
},
|
},
|
||||||
|
terminal: {
|
||||||
|
opened: terminalOpened,
|
||||||
|
open() {
|
||||||
|
setTerminalOpened(true)
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
setTerminalOpened(false)
|
||||||
|
},
|
||||||
|
toggle() {
|
||||||
|
setTerminalOpened(!terminalOpened())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reviewPanel: {
|
||||||
|
opened: reviewPanelOpened,
|
||||||
|
open() {
|
||||||
|
setReviewPanelOpened(true)
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
setReviewPanelOpened(false)
|
||||||
|
},
|
||||||
|
toggle() {
|
||||||
|
setReviewPanelOpened(!reviewPanelOpened())
|
||||||
|
},
|
||||||
|
},
|
||||||
review: {
|
review: {
|
||||||
open: createMemo(() => s().reviewOpen),
|
open: createMemo(() => s().reviewOpen),
|
||||||
setOpen(open: string[]) {
|
setOpen(open: string[]) {
|
||||||
const current = store.sessionView[sessionKey]
|
const current = store.sessionView[sessionKey]
|
||||||
if (!current) {
|
if (!current) {
|
||||||
setStore("sessionView", sessionKey, { scroll: {}, reviewOpen: open })
|
setStore("sessionView", sessionKey, {
|
||||||
|
scroll: {},
|
||||||
|
terminalOpened: false,
|
||||||
|
reviewPanelOpened: true,
|
||||||
|
reviewOpen: open,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -276,7 +276,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
visible(model: ModelKey) {
|
visible(model: ModelKey) {
|
||||||
const key = `${model.providerID}:${model.modelID}`
|
const key = `${model.providerID}:${model.modelID}`
|
||||||
const visibility = userVisibilityMap().get(key)
|
const visibility = userVisibilityMap().get(key)
|
||||||
return visibility !== "hide" && (latestSet().has(key) || visibility === "show")
|
if (visibility === "hide") return false
|
||||||
|
if (visibility === "show") return true
|
||||||
|
if (latestSet().has(key)) return true
|
||||||
|
// For models without valid release_date (e.g. custom models), show by default
|
||||||
|
const m = find(model)
|
||||||
|
if (!m?.release_date || !DateTime.fromISO(m.release_date).isValid) return true
|
||||||
|
return false
|
||||||
},
|
},
|
||||||
setVisibility(model: ModelKey, visible: boolean) {
|
setVisibility(model: ModelKey, visible: boolean) {
|
||||||
updateVisibility(model, visible ? "show" : "hide")
|
updateVisibility(model, visible ? "show" : "hide")
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ export type Platform = {
|
|||||||
|
|
||||||
/** Fetch override */
|
/** Fetch override */
|
||||||
fetch?: typeof fetch
|
fetch?: typeof fetch
|
||||||
|
|
||||||
|
/** Get the configured default server URL (desktop only) */
|
||||||
|
getDefaultServerUrl?(): Promise<string | null>
|
||||||
|
|
||||||
|
/** Set the default server URL to use on app startup (desktop only) */
|
||||||
|
setDefaultServerUrl?(url: string | null): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({
|
export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { batch, createMemo } from "solid-js"
|
import { batch, createMemo, createRoot, onCleanup } from "solid-js"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
import type { FileSelection } from "@/context/file"
|
import type { FileSelection } from "@/context/file"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
@@ -99,74 +99,146 @@ function clonePrompt(prompt: Prompt): Prompt {
|
|||||||
return prompt.map(clonePart)
|
return prompt.map(clonePart)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WORKSPACE_KEY = "__workspace__"
|
||||||
|
const MAX_PROMPT_SESSIONS = 20
|
||||||
|
|
||||||
|
type PromptSession = ReturnType<typeof createPromptSession>
|
||||||
|
|
||||||
|
type PromptCacheEntry = {
|
||||||
|
value: PromptSession
|
||||||
|
dispose: VoidFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPromptSession(dir: string, id: string | undefined) {
|
||||||
|
const legacy = `${dir}/prompt${id ? "/" + id : ""}.v2`
|
||||||
|
|
||||||
|
const [store, setStore, _, ready] = persisted(
|
||||||
|
Persist.scoped(dir, id, "prompt", [legacy]),
|
||||||
|
createStore<{
|
||||||
|
prompt: Prompt
|
||||||
|
cursor?: number
|
||||||
|
context: {
|
||||||
|
activeTab: boolean
|
||||||
|
items: (ContextItem & { key: string })[]
|
||||||
|
}
|
||||||
|
}>({
|
||||||
|
prompt: clonePrompt(DEFAULT_PROMPT),
|
||||||
|
cursor: undefined,
|
||||||
|
context: {
|
||||||
|
activeTab: true,
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
function keyForItem(item: ContextItem) {
|
||||||
|
if (item.type !== "file") return item.type
|
||||||
|
const start = item.selection?.startLine
|
||||||
|
const end = item.selection?.endLine
|
||||||
|
return `${item.type}:${item.path}:${start}:${end}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ready,
|
||||||
|
current: createMemo(() => store.prompt),
|
||||||
|
cursor: createMemo(() => store.cursor),
|
||||||
|
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
|
||||||
|
context: {
|
||||||
|
activeTab: createMemo(() => store.context.activeTab),
|
||||||
|
items: createMemo(() => store.context.items),
|
||||||
|
addActive() {
|
||||||
|
setStore("context", "activeTab", true)
|
||||||
|
},
|
||||||
|
removeActive() {
|
||||||
|
setStore("context", "activeTab", false)
|
||||||
|
},
|
||||||
|
add(item: ContextItem) {
|
||||||
|
const key = keyForItem(item)
|
||||||
|
if (store.context.items.find((x) => x.key === key)) return
|
||||||
|
setStore("context", "items", (items) => [...items, { key, ...item }])
|
||||||
|
},
|
||||||
|
remove(key: string) {
|
||||||
|
setStore("context", "items", (items) => items.filter((x) => x.key !== key))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
set(prompt: Prompt, cursorPosition?: number) {
|
||||||
|
const next = clonePrompt(prompt)
|
||||||
|
batch(() => {
|
||||||
|
setStore("prompt", next)
|
||||||
|
if (cursorPosition !== undefined) setStore("cursor", cursorPosition)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
batch(() => {
|
||||||
|
setStore("prompt", clonePrompt(DEFAULT_PROMPT))
|
||||||
|
setStore("cursor", 0)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const { use: usePrompt, provider: PromptProvider } = createSimpleContext({
|
export const { use: usePrompt, provider: PromptProvider } = createSimpleContext({
|
||||||
name: "Prompt",
|
name: "Prompt",
|
||||||
|
gate: false,
|
||||||
init: () => {
|
init: () => {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const legacy = createMemo(() => `${params.dir}/prompt${params.id ? "/" + params.id : ""}.v2`)
|
const cache = new Map<string, PromptCacheEntry>()
|
||||||
|
|
||||||
const [store, setStore, _, ready] = persisted(
|
const disposeAll = () => {
|
||||||
Persist.scoped(params.dir!, params.id, "prompt", [legacy()]),
|
for (const entry of cache.values()) {
|
||||||
createStore<{
|
entry.dispose()
|
||||||
prompt: Prompt
|
}
|
||||||
cursor?: number
|
cache.clear()
|
||||||
context: {
|
|
||||||
activeTab: boolean
|
|
||||||
items: (ContextItem & { key: string })[]
|
|
||||||
}
|
|
||||||
}>({
|
|
||||||
prompt: clonePrompt(DEFAULT_PROMPT),
|
|
||||||
cursor: undefined,
|
|
||||||
context: {
|
|
||||||
activeTab: true,
|
|
||||||
items: [],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
function keyForItem(item: ContextItem) {
|
|
||||||
if (item.type !== "file") return item.type
|
|
||||||
const start = item.selection?.startLine
|
|
||||||
const end = item.selection?.endLine
|
|
||||||
return `${item.type}:${item.path}:${start}:${end}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCleanup(disposeAll)
|
||||||
|
|
||||||
|
const prune = () => {
|
||||||
|
while (cache.size > MAX_PROMPT_SESSIONS) {
|
||||||
|
const first = cache.keys().next().value
|
||||||
|
if (!first) return
|
||||||
|
const entry = cache.get(first)
|
||||||
|
entry?.dispose()
|
||||||
|
cache.delete(first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const load = (dir: string, id: string | undefined) => {
|
||||||
|
const key = `${dir}:${id ?? WORKSPACE_KEY}`
|
||||||
|
const existing = cache.get(key)
|
||||||
|
if (existing) {
|
||||||
|
cache.delete(key)
|
||||||
|
cache.set(key, existing)
|
||||||
|
return existing.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = createRoot((dispose) => ({
|
||||||
|
value: createPromptSession(dir, id),
|
||||||
|
dispose,
|
||||||
|
}))
|
||||||
|
|
||||||
|
cache.set(key, entry)
|
||||||
|
prune()
|
||||||
|
return entry.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = createMemo(() => load(params.dir!, params.id))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ready,
|
ready: () => session().ready(),
|
||||||
current: createMemo(() => store.prompt),
|
current: () => session().current(),
|
||||||
cursor: createMemo(() => store.cursor),
|
cursor: () => session().cursor(),
|
||||||
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
|
dirty: () => session().dirty(),
|
||||||
context: {
|
context: {
|
||||||
activeTab: createMemo(() => store.context.activeTab),
|
activeTab: () => session().context.activeTab(),
|
||||||
items: createMemo(() => store.context.items),
|
items: () => session().context.items(),
|
||||||
addActive() {
|
addActive: () => session().context.addActive(),
|
||||||
setStore("context", "activeTab", true)
|
removeActive: () => session().context.removeActive(),
|
||||||
},
|
add: (item: ContextItem) => session().context.add(item),
|
||||||
removeActive() {
|
remove: (key: string) => session().context.remove(key),
|
||||||
setStore("context", "activeTab", false)
|
|
||||||
},
|
|
||||||
add(item: ContextItem) {
|
|
||||||
const key = keyForItem(item)
|
|
||||||
if (store.context.items.find((x) => x.key === key)) return
|
|
||||||
setStore("context", "items", (items) => [...items, { key, ...item }])
|
|
||||||
},
|
|
||||||
remove(key: string) {
|
|
||||||
setStore("context", "items", (items) => items.filter((x) => x.key !== key))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
set(prompt: Prompt, cursorPosition?: number) {
|
|
||||||
const next = clonePrompt(prompt)
|
|
||||||
batch(() => {
|
|
||||||
setStore("prompt", next)
|
|
||||||
if (cursorPosition !== undefined) setStore("cursor", cursorPosition)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
reset() {
|
|
||||||
batch(() => {
|
|
||||||
setStore("prompt", clonePrompt(DEFAULT_PROMPT))
|
|
||||||
setStore("cursor", 0)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
set: (prompt: Prompt, cursorPosition?: number) => session().set(prompt, cursorPosition),
|
||||||
|
reset: () => session().reset(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,10 +16,7 @@ export function normalizeServerUrl(input: string) {
|
|||||||
|
|
||||||
export function serverDisplayName(url: string) {
|
export function serverDisplayName(url: string) {
|
||||||
if (!url) return ""
|
if (!url) return ""
|
||||||
return url
|
return url.replace(/^https?:\/\//, "").replace(/\/+$/, "")
|
||||||
.replace(/^https?:\/\//, "")
|
|
||||||
.replace(/\/+$/, "")
|
|
||||||
.split("/")[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function projectsKey(url: string) {
|
function projectsKey(url: string) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { batch, createMemo } from "solid-js"
|
import { batch, createMemo } from "solid-js"
|
||||||
import { produce, reconcile } from "solid-js/store"
|
import { createStore, produce, reconcile } from "solid-js/store"
|
||||||
import { Binary } from "@opencode-ai/util/binary"
|
import { Binary } from "@opencode-ai/util/binary"
|
||||||
import { retry } from "@opencode-ai/util/retry"
|
import { retry } from "@opencode-ai/util/retry"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
@@ -14,6 +14,76 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const [store, setStore] = globalSync.child(sdk.directory)
|
const [store, setStore] = globalSync.child(sdk.directory)
|
||||||
const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/")
|
const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/")
|
||||||
|
const chunk = 200
|
||||||
|
const inflight = new Map<string, Promise<void>>()
|
||||||
|
const inflightDiff = new Map<string, Promise<void>>()
|
||||||
|
const inflightTodo = new Map<string, Promise<void>>()
|
||||||
|
const [meta, setMeta] = createStore({
|
||||||
|
limit: {} as Record<string, number>,
|
||||||
|
complete: {} as Record<string, boolean>,
|
||||||
|
loading: {} as Record<string, boolean>,
|
||||||
|
})
|
||||||
|
|
||||||
|
const getSession = (sessionID: string) => {
|
||||||
|
const match = Binary.search(store.session, sessionID, (s) => s.id)
|
||||||
|
if (match.found) return store.session[match.index]
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const limitFor = (count: number) => {
|
||||||
|
if (count <= chunk) return chunk
|
||||||
|
return Math.ceil(count / chunk) * chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
const hydrateMessages = (sessionID: string) => {
|
||||||
|
if (meta.limit[sessionID] !== undefined) return
|
||||||
|
|
||||||
|
const messages = store.message[sessionID]
|
||||||
|
if (!messages) return
|
||||||
|
|
||||||
|
const limit = limitFor(messages.length)
|
||||||
|
setMeta("limit", sessionID, limit)
|
||||||
|
setMeta("complete", sessionID, messages.length < limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadMessages = async (sessionID: string, limit: number) => {
|
||||||
|
if (meta.loading[sessionID]) return
|
||||||
|
|
||||||
|
setMeta("loading", sessionID, true)
|
||||||
|
await retry(() => sdk.client.session.messages({ sessionID, limit }))
|
||||||
|
.then((messages) => {
|
||||||
|
const items = (messages.data ?? []).filter((x) => !!x?.info?.id)
|
||||||
|
const next = items
|
||||||
|
.map((x) => x.info)
|
||||||
|
.filter((m) => !!m?.id)
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => a.id.localeCompare(b.id))
|
||||||
|
|
||||||
|
batch(() => {
|
||||||
|
setStore("message", sessionID, reconcile(next, { key: "id" }))
|
||||||
|
|
||||||
|
for (const message of items) {
|
||||||
|
setStore(
|
||||||
|
"part",
|
||||||
|
message.info.id,
|
||||||
|
reconcile(
|
||||||
|
message.parts
|
||||||
|
.filter((p) => !!p?.id)
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => a.id.localeCompare(b.id)),
|
||||||
|
{ key: "id" },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setMeta("limit", sessionID, limit)
|
||||||
|
setMeta("complete", sessionID, next.length < limit)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setMeta("loading", sessionID, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: store,
|
data: store,
|
||||||
@@ -30,11 +100,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
return undefined
|
return undefined
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
get(sessionID: string) {
|
get: getSession,
|
||||||
const match = Binary.search(store.session, sessionID, (s) => s.id)
|
|
||||||
if (match.found) return store.session[match.index]
|
|
||||||
return undefined
|
|
||||||
},
|
|
||||||
addOptimisticMessage(input: {
|
addOptimisticMessage(input: {
|
||||||
sessionID: string
|
sessionID: string
|
||||||
messageID: string
|
messageID: string
|
||||||
@@ -66,58 +132,98 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
async sync(sessionID: string, _isRetry = false) {
|
async sync(sessionID: string) {
|
||||||
const [session, messages, todo, diff] = await Promise.all([
|
const hasSession = getSession(sessionID) !== undefined
|
||||||
retry(() => sdk.client.session.get({ sessionID })),
|
hydrateMessages(sessionID)
|
||||||
retry(() => sdk.client.session.messages({ sessionID, limit: 1000 })),
|
|
||||||
retry(() => sdk.client.session.todo({ sessionID })),
|
|
||||||
retry(() => sdk.client.session.diff({ sessionID })),
|
|
||||||
])
|
|
||||||
|
|
||||||
batch(() => {
|
const hasMessages = store.message[sessionID] !== undefined
|
||||||
setStore(
|
if (hasSession && hasMessages) return
|
||||||
"session",
|
|
||||||
produce((draft) => {
|
|
||||||
const match = Binary.search(draft, sessionID, (s) => s.id)
|
|
||||||
if (match.found) {
|
|
||||||
draft[match.index] = session.data!
|
|
||||||
return
|
|
||||||
}
|
|
||||||
draft.splice(match.index, 0, session.data!)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
setStore("todo", sessionID, reconcile(todo.data ?? [], { key: "id" }))
|
const pending = inflight.get(sessionID)
|
||||||
setStore(
|
if (pending) return pending
|
||||||
"message",
|
|
||||||
sessionID,
|
|
||||||
reconcile(
|
|
||||||
(messages.data ?? [])
|
|
||||||
.map((x) => x.info)
|
|
||||||
.filter((m) => !!m?.id)
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => a.id.localeCompare(b.id)),
|
|
||||||
{ key: "id" },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const message of messages.data ?? []) {
|
const limit = meta.limit[sessionID] ?? chunk
|
||||||
if (!message?.info?.id) continue
|
|
||||||
setStore(
|
|
||||||
"part",
|
|
||||||
message.info.id,
|
|
||||||
reconcile(
|
|
||||||
message.parts
|
|
||||||
.filter((p) => !!p?.id)
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => a.id.localeCompare(b.id)),
|
|
||||||
{ key: "id" },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setStore("session_diff", sessionID, reconcile(diff.data ?? [], { key: "file" }))
|
const sessionReq = hasSession
|
||||||
})
|
? Promise.resolve()
|
||||||
|
: retry(() => sdk.client.session.get({ sessionID })).then((session) => {
|
||||||
|
const data = session.data
|
||||||
|
if (!data) return
|
||||||
|
setStore(
|
||||||
|
"session",
|
||||||
|
produce((draft) => {
|
||||||
|
const match = Binary.search(draft, sessionID, (s) => s.id)
|
||||||
|
if (match.found) {
|
||||||
|
draft[match.index] = data
|
||||||
|
return
|
||||||
|
}
|
||||||
|
draft.splice(match.index, 0, data)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const messagesReq = hasMessages ? Promise.resolve() : loadMessages(sessionID, limit)
|
||||||
|
|
||||||
|
const promise = Promise.all([sessionReq, messagesReq])
|
||||||
|
.then(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
inflight.delete(sessionID)
|
||||||
|
})
|
||||||
|
|
||||||
|
inflight.set(sessionID, promise)
|
||||||
|
return promise
|
||||||
|
},
|
||||||
|
async diff(sessionID: string) {
|
||||||
|
if (store.session_diff[sessionID] !== undefined) return
|
||||||
|
|
||||||
|
const pending = inflightDiff.get(sessionID)
|
||||||
|
if (pending) return pending
|
||||||
|
|
||||||
|
const promise = retry(() => sdk.client.session.diff({ sessionID }))
|
||||||
|
.then((diff) => {
|
||||||
|
setStore("session_diff", sessionID, reconcile(diff.data ?? [], { key: "file" }))
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
inflightDiff.delete(sessionID)
|
||||||
|
})
|
||||||
|
|
||||||
|
inflightDiff.set(sessionID, promise)
|
||||||
|
return promise
|
||||||
|
},
|
||||||
|
async todo(sessionID: string) {
|
||||||
|
if (store.todo[sessionID] !== undefined) return
|
||||||
|
|
||||||
|
const pending = inflightTodo.get(sessionID)
|
||||||
|
if (pending) return pending
|
||||||
|
|
||||||
|
const promise = retry(() => sdk.client.session.todo({ sessionID }))
|
||||||
|
.then((todo) => {
|
||||||
|
setStore("todo", sessionID, reconcile(todo.data ?? [], { key: "id" }))
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
inflightTodo.delete(sessionID)
|
||||||
|
})
|
||||||
|
|
||||||
|
inflightTodo.set(sessionID, promise)
|
||||||
|
return promise
|
||||||
|
},
|
||||||
|
history: {
|
||||||
|
more(sessionID: string) {
|
||||||
|
if (store.message[sessionID] === undefined) return false
|
||||||
|
if (meta.limit[sessionID] === undefined) return false
|
||||||
|
if (meta.complete[sessionID]) return false
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
loading(sessionID: string) {
|
||||||
|
return meta.loading[sessionID] ?? false
|
||||||
|
},
|
||||||
|
async loadMore(sessionID: string, count = chunk) {
|
||||||
|
if (meta.loading[sessionID]) return
|
||||||
|
if (meta.complete[sessionID]) return
|
||||||
|
|
||||||
|
const current = meta.limit[sessionID] ?? chunk
|
||||||
|
await loadMessages(sessionID, current + count)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fetch: async (count = 10) => {
|
fetch: async (count = 10) => {
|
||||||
setStore("limit", (x) => x + count)
|
setStore("limit", (x) => x + count)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { batch, createMemo } from "solid-js"
|
import { batch, createMemo, createRoot, onCleanup } from "solid-js"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
import { useSDK } from "./sdk"
|
import { useSDK } from "./sdk"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
@@ -8,114 +8,195 @@ import { Persist, persisted } from "@/utils/persist"
|
|||||||
export type LocalPTY = {
|
export type LocalPTY = {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
|
titleNumber: number
|
||||||
rows?: number
|
rows?: number
|
||||||
cols?: number
|
cols?: number
|
||||||
buffer?: string
|
buffer?: string
|
||||||
scrollY?: number
|
scrollY?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WORKSPACE_KEY = "__workspace__"
|
||||||
|
const MAX_TERMINAL_SESSIONS = 20
|
||||||
|
|
||||||
|
type TerminalSession = ReturnType<typeof createTerminalSession>
|
||||||
|
|
||||||
|
type TerminalCacheEntry = {
|
||||||
|
value: TerminalSession
|
||||||
|
dispose: VoidFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, id: string | undefined) {
|
||||||
|
const legacy = `${dir}/terminal${id ? "/" + id : ""}.v1`
|
||||||
|
|
||||||
|
const [store, setStore, _, ready] = persisted(
|
||||||
|
Persist.scoped(dir, id, "terminal", [legacy]),
|
||||||
|
createStore<{
|
||||||
|
active?: string
|
||||||
|
all: LocalPTY[]
|
||||||
|
}>({
|
||||||
|
all: [],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
ready,
|
||||||
|
all: createMemo(() => Object.values(store.all)),
|
||||||
|
active: createMemo(() => store.active),
|
||||||
|
new() {
|
||||||
|
const existingTitleNumbers = new Set(
|
||||||
|
store.all.map((pty) => {
|
||||||
|
const match = pty.titleNumber
|
||||||
|
return match
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
let nextNumber = 1
|
||||||
|
while (existingTitleNumbers.has(nextNumber)) {
|
||||||
|
nextNumber++
|
||||||
|
}
|
||||||
|
|
||||||
|
sdk.client.pty
|
||||||
|
.create({ title: `Terminal ${nextNumber}` })
|
||||||
|
.then((pty) => {
|
||||||
|
const id = pty.data?.id
|
||||||
|
if (!id) return
|
||||||
|
setStore("all", [
|
||||||
|
...store.all,
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
title: pty.data?.title ?? "Terminal",
|
||||||
|
titleNumber: nextNumber,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
setStore("active", id)
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("Failed to create terminal", e)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
update(pty: Partial<LocalPTY> & { id: string }) {
|
||||||
|
setStore("all", (x) => x.map((x) => (x.id === pty.id ? { ...x, ...pty } : x)))
|
||||||
|
sdk.client.pty
|
||||||
|
.update({
|
||||||
|
ptyID: pty.id,
|
||||||
|
title: pty.title,
|
||||||
|
size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined,
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("Failed to update terminal", e)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async clone(id: string) {
|
||||||
|
const index = store.all.findIndex((x) => x.id === id)
|
||||||
|
const pty = store.all[index]
|
||||||
|
if (!pty) return
|
||||||
|
const clone = await sdk.client.pty
|
||||||
|
.create({
|
||||||
|
title: pty.title,
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("Failed to clone terminal", e)
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
if (!clone?.data) return
|
||||||
|
setStore("all", index, {
|
||||||
|
...pty,
|
||||||
|
...clone.data,
|
||||||
|
})
|
||||||
|
if (store.active === pty.id) {
|
||||||
|
setStore("active", clone.data.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
open(id: string) {
|
||||||
|
setStore("active", id)
|
||||||
|
},
|
||||||
|
async close(id: string) {
|
||||||
|
batch(() => {
|
||||||
|
setStore(
|
||||||
|
"all",
|
||||||
|
store.all.filter((x) => x.id !== id),
|
||||||
|
)
|
||||||
|
if (store.active === id) {
|
||||||
|
const index = store.all.findIndex((f) => f.id === id)
|
||||||
|
const previous = store.all[Math.max(0, index - 1)]
|
||||||
|
setStore("active", previous?.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await sdk.client.pty.remove({ ptyID: id }).catch((e) => {
|
||||||
|
console.error("Failed to close terminal", e)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
move(id: string, to: number) {
|
||||||
|
const index = store.all.findIndex((f) => f.id === id)
|
||||||
|
if (index === -1) return
|
||||||
|
setStore(
|
||||||
|
"all",
|
||||||
|
produce((all) => {
|
||||||
|
all.splice(to, 0, all.splice(index, 1)[0])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const { use: useTerminal, provider: TerminalProvider } = createSimpleContext({
|
export const { use: useTerminal, provider: TerminalProvider } = createSimpleContext({
|
||||||
name: "Terminal",
|
name: "Terminal",
|
||||||
|
gate: false,
|
||||||
init: () => {
|
init: () => {
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const legacy = createMemo(() => `${params.dir}/terminal${params.id ? "/" + params.id : ""}.v1`)
|
const cache = new Map<string, TerminalCacheEntry>()
|
||||||
|
|
||||||
const [store, setStore, _, ready] = persisted(
|
const disposeAll = () => {
|
||||||
Persist.scoped(params.dir!, params.id, "terminal", [legacy()]),
|
for (const entry of cache.values()) {
|
||||||
createStore<{
|
entry.dispose()
|
||||||
active?: string
|
}
|
||||||
all: LocalPTY[]
|
cache.clear()
|
||||||
}>({
|
}
|
||||||
all: [],
|
|
||||||
}),
|
onCleanup(disposeAll)
|
||||||
)
|
|
||||||
|
const prune = () => {
|
||||||
|
while (cache.size > MAX_TERMINAL_SESSIONS) {
|
||||||
|
const first = cache.keys().next().value
|
||||||
|
if (!first) return
|
||||||
|
const entry = cache.get(first)
|
||||||
|
entry?.dispose()
|
||||||
|
cache.delete(first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const load = (dir: string, id: string | undefined) => {
|
||||||
|
const key = `${dir}:${id ?? WORKSPACE_KEY}`
|
||||||
|
const existing = cache.get(key)
|
||||||
|
if (existing) {
|
||||||
|
cache.delete(key)
|
||||||
|
cache.set(key, existing)
|
||||||
|
return existing.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = createRoot((dispose) => ({
|
||||||
|
value: createTerminalSession(sdk, dir, id),
|
||||||
|
dispose,
|
||||||
|
}))
|
||||||
|
|
||||||
|
cache.set(key, entry)
|
||||||
|
prune()
|
||||||
|
return entry.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = createMemo(() => load(params.dir!, params.id))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ready,
|
ready: () => session().ready(),
|
||||||
all: createMemo(() => Object.values(store.all)),
|
all: () => session().all(),
|
||||||
active: createMemo(() => store.active),
|
active: () => session().active(),
|
||||||
new() {
|
new: () => session().new(),
|
||||||
sdk.client.pty
|
update: (pty: Partial<LocalPTY> & { id: string }) => session().update(pty),
|
||||||
.create({ title: `Terminal ${store.all.length + 1}` })
|
clone: (id: string) => session().clone(id),
|
||||||
.then((pty) => {
|
open: (id: string) => session().open(id),
|
||||||
const id = pty.data?.id
|
close: (id: string) => session().close(id),
|
||||||
if (!id) return
|
move: (id: string, to: number) => session().move(id, to),
|
||||||
setStore("all", [
|
|
||||||
...store.all,
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
title: pty.data?.title ?? "Terminal",
|
|
||||||
},
|
|
||||||
])
|
|
||||||
setStore("active", id)
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.error("Failed to create terminal", e)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
update(pty: Partial<LocalPTY> & { id: string }) {
|
|
||||||
setStore("all", (x) => x.map((x) => (x.id === pty.id ? { ...x, ...pty } : x)))
|
|
||||||
sdk.client.pty
|
|
||||||
.update({
|
|
||||||
ptyID: pty.id,
|
|
||||||
title: pty.title,
|
|
||||||
size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined,
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.error("Failed to update terminal", e)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async clone(id: string) {
|
|
||||||
const index = store.all.findIndex((x) => x.id === id)
|
|
||||||
const pty = store.all[index]
|
|
||||||
if (!pty) return
|
|
||||||
const clone = await sdk.client.pty
|
|
||||||
.create({
|
|
||||||
title: pty.title,
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.error("Failed to clone terminal", e)
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
if (!clone?.data) return
|
|
||||||
setStore("all", index, {
|
|
||||||
...pty,
|
|
||||||
...clone.data,
|
|
||||||
})
|
|
||||||
if (store.active === pty.id) {
|
|
||||||
setStore("active", clone.data.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
open(id: string) {
|
|
||||||
setStore("active", id)
|
|
||||||
},
|
|
||||||
async close(id: string) {
|
|
||||||
batch(() => {
|
|
||||||
setStore(
|
|
||||||
"all",
|
|
||||||
store.all.filter((x) => x.id !== id),
|
|
||||||
)
|
|
||||||
if (store.active === id) {
|
|
||||||
const index = store.all.findIndex((f) => f.id === id)
|
|
||||||
const previous = store.all[Math.max(0, index - 1)]
|
|
||||||
setStore("active", previous?.id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await sdk.client.pty.remove({ ptyID: id }).catch((e) => {
|
|
||||||
console.error("Failed to close terminal", e)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
move(id: string, to: number) {
|
|
||||||
const index = store.all.findIndex((f) => f.id === id)
|
|
||||||
if (index === -1) return
|
|
||||||
setStore(
|
|
||||||
"all",
|
|
||||||
produce((all) => {
|
|
||||||
all.splice(to, 0, all.splice(index, 1)[0])
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { LocalProvider } from "@/context/local"
|
|||||||
import { base64Decode } from "@opencode-ai/util/encode"
|
import { base64Decode } from "@opencode-ai/util/encode"
|
||||||
import { DataProvider } from "@opencode-ai/ui/context"
|
import { DataProvider } from "@opencode-ai/ui/context"
|
||||||
import { iife } from "@opencode-ai/util/iife"
|
import { iife } from "@opencode-ai/util/iife"
|
||||||
|
import type { QuestionAnswer } from "@opencode-ai/sdk/v2"
|
||||||
|
|
||||||
export default function Layout(props: ParentProps) {
|
export default function Layout(props: ParentProps) {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
@@ -27,6 +28,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
response: "once" | "always" | "reject"
|
response: "once" | "always" | "reject"
|
||||||
}) => sdk.client.permission.respond(input)
|
}) => sdk.client.permission.respond(input)
|
||||||
|
|
||||||
|
const replyToQuestion = (input: { requestID: string; answers: QuestionAnswer[] }) =>
|
||||||
|
sdk.client.question.reply(input)
|
||||||
|
|
||||||
|
const rejectQuestion = (input: { requestID: string }) => sdk.client.question.reject(input)
|
||||||
|
|
||||||
const navigateToSession = (sessionID: string) => {
|
const navigateToSession = (sessionID: string) => {
|
||||||
navigate(`/${params.dir}/session/${sessionID}`)
|
navigate(`/${params.dir}/session/${sessionID}`)
|
||||||
}
|
}
|
||||||
@@ -36,6 +42,8 @@ export default function Layout(props: ParentProps) {
|
|||||||
data={sync.data}
|
data={sync.data}
|
||||||
directory={directory()}
|
directory={directory()}
|
||||||
onPermissionRespond={respond}
|
onPermissionRespond={respond}
|
||||||
|
onQuestionReply={replyToQuestion}
|
||||||
|
onQuestionReject={rejectQuestion}
|
||||||
onNavigateToSession={navigateToSession}
|
onNavigateToSession={navigateToSession}
|
||||||
>
|
>
|
||||||
<LocalProvider>{props.children}</LocalProvider>
|
<LocalProvider>{props.children}</LocalProvider>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
batch,
|
||||||
createEffect,
|
createEffect,
|
||||||
createMemo,
|
createMemo,
|
||||||
createSignal,
|
createSignal,
|
||||||
@@ -31,7 +32,7 @@ import { getFilename } from "@opencode-ai/util/path"
|
|||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||||
import { Session } from "@opencode-ai/sdk/v2/client"
|
import { Session } from "@opencode-ai/sdk/v2/client"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce, reconcile } from "solid-js/store"
|
||||||
import {
|
import {
|
||||||
DragDropProvider,
|
DragDropProvider,
|
||||||
DragDropSensors,
|
DragDropSensors,
|
||||||
@@ -47,6 +48,7 @@ import { useGlobalSDK } from "@/context/global-sdk"
|
|||||||
import { useNotification } from "@/context/notification"
|
import { useNotification } from "@/context/notification"
|
||||||
import { usePermission } from "@/context/permission"
|
import { usePermission } from "@/context/permission"
|
||||||
import { Binary } from "@opencode-ai/util/binary"
|
import { Binary } from "@opencode-ai/util/binary"
|
||||||
|
import { retry } from "@opencode-ai/util/retry"
|
||||||
|
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
||||||
@@ -55,6 +57,7 @@ import { DialogEditProject } from "@/components/dialog-edit-project"
|
|||||||
import { DialogSelectServer } from "@/components/dialog-select-server"
|
import { DialogSelectServer } from "@/components/dialog-select-server"
|
||||||
import { useCommand, type CommandOption } from "@/context/command"
|
import { useCommand, type CommandOption } from "@/context/command"
|
||||||
import { ConstrainDragXAxis } from "@/utils/solid-dnd"
|
import { ConstrainDragXAxis } from "@/utils/solid-dnd"
|
||||||
|
import { navStart } from "@/utils/perf"
|
||||||
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
||||||
import { useServer } from "@/context/server"
|
import { useServer } from "@/context/server"
|
||||||
|
|
||||||
@@ -284,6 +287,146 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
const currentSessions = createMemo(() => projectSessions(currentProject()))
|
const currentSessions = createMemo(() => projectSessions(currentProject()))
|
||||||
|
|
||||||
|
type PrefetchQueue = {
|
||||||
|
inflight: Set<string>
|
||||||
|
pending: string[]
|
||||||
|
pendingSet: Set<string>
|
||||||
|
running: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefetchChunk = 200
|
||||||
|
const prefetchConcurrency = 1
|
||||||
|
const prefetchPendingLimit = 6
|
||||||
|
const prefetchToken = { value: 0 }
|
||||||
|
const prefetchQueues = new Map<string, PrefetchQueue>()
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
params.dir
|
||||||
|
globalSDK.url
|
||||||
|
|
||||||
|
prefetchToken.value += 1
|
||||||
|
for (const q of prefetchQueues.values()) {
|
||||||
|
q.pending.length = 0
|
||||||
|
q.pendingSet.clear()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const queueFor = (directory: string) => {
|
||||||
|
const existing = prefetchQueues.get(directory)
|
||||||
|
if (existing) return existing
|
||||||
|
|
||||||
|
const created: PrefetchQueue = {
|
||||||
|
inflight: new Set(),
|
||||||
|
pending: [],
|
||||||
|
pendingSet: new Set(),
|
||||||
|
running: 0,
|
||||||
|
}
|
||||||
|
prefetchQueues.set(directory, created)
|
||||||
|
return created
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefetchMessages = (directory: string, sessionID: string, token: number) => {
|
||||||
|
const [, setStore] = globalSync.child(directory)
|
||||||
|
|
||||||
|
return retry(() => globalSDK.client.session.messages({ directory, sessionID, limit: prefetchChunk }))
|
||||||
|
.then((messages) => {
|
||||||
|
if (prefetchToken.value !== token) return
|
||||||
|
|
||||||
|
const items = (messages.data ?? []).filter((x) => !!x?.info?.id)
|
||||||
|
const next = items
|
||||||
|
.map((x) => x.info)
|
||||||
|
.filter((m) => !!m?.id)
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => a.id.localeCompare(b.id))
|
||||||
|
|
||||||
|
batch(() => {
|
||||||
|
setStore("message", sessionID, reconcile(next, { key: "id" }))
|
||||||
|
|
||||||
|
for (const message of items) {
|
||||||
|
setStore(
|
||||||
|
"part",
|
||||||
|
message.info.id,
|
||||||
|
reconcile(
|
||||||
|
message.parts
|
||||||
|
.filter((p) => !!p?.id)
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => a.id.localeCompare(b.id)),
|
||||||
|
{ key: "id" },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pumpPrefetch = (directory: string) => {
|
||||||
|
const q = queueFor(directory)
|
||||||
|
if (q.running >= prefetchConcurrency) return
|
||||||
|
|
||||||
|
const sessionID = q.pending.shift()
|
||||||
|
if (!sessionID) return
|
||||||
|
|
||||||
|
q.pendingSet.delete(sessionID)
|
||||||
|
q.inflight.add(sessionID)
|
||||||
|
q.running += 1
|
||||||
|
|
||||||
|
const token = prefetchToken.value
|
||||||
|
|
||||||
|
void prefetchMessages(directory, sessionID, token).finally(() => {
|
||||||
|
q.running -= 1
|
||||||
|
q.inflight.delete(sessionID)
|
||||||
|
pumpPrefetch(directory)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefetchSession = (session: Session, priority: "high" | "low" = "low") => {
|
||||||
|
const directory = session.directory
|
||||||
|
if (!directory) return
|
||||||
|
|
||||||
|
const [store] = globalSync.child(directory)
|
||||||
|
if (store.message[session.id] !== undefined) return
|
||||||
|
|
||||||
|
const q = queueFor(directory)
|
||||||
|
if (q.inflight.has(session.id)) return
|
||||||
|
if (q.pendingSet.has(session.id)) return
|
||||||
|
|
||||||
|
if (priority === "high") q.pending.unshift(session.id)
|
||||||
|
if (priority !== "high") q.pending.push(session.id)
|
||||||
|
q.pendingSet.add(session.id)
|
||||||
|
|
||||||
|
while (q.pending.length > prefetchPendingLimit) {
|
||||||
|
const dropped = q.pending.pop()
|
||||||
|
if (!dropped) continue
|
||||||
|
q.pendingSet.delete(dropped)
|
||||||
|
}
|
||||||
|
|
||||||
|
pumpPrefetch(directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const sessions = currentSessions()
|
||||||
|
const id = params.id
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
const first = sessions[0]
|
||||||
|
if (first) prefetchSession(first)
|
||||||
|
|
||||||
|
const second = sessions[1]
|
||||||
|
if (second) prefetchSession(second)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = sessions.findIndex((s) => s.id === id)
|
||||||
|
if (index === -1) return
|
||||||
|
|
||||||
|
const next = sessions[index + 1]
|
||||||
|
if (next) prefetchSession(next)
|
||||||
|
|
||||||
|
const prev = sessions[index - 1]
|
||||||
|
if (prev) prefetchSession(prev)
|
||||||
|
})
|
||||||
|
|
||||||
function navigateSessionByOffset(offset: number) {
|
function navigateSessionByOffset(offset: number) {
|
||||||
const projects = layout.projects.list()
|
const projects = layout.projects.list()
|
||||||
if (projects.length === 0) return
|
if (projects.length === 0) return
|
||||||
@@ -309,6 +452,27 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
if (targetIndex >= 0 && targetIndex < sessions.length) {
|
if (targetIndex >= 0 && targetIndex < sessions.length) {
|
||||||
const session = sessions[targetIndex]
|
const session = sessions[targetIndex]
|
||||||
|
const next = sessions[targetIndex + 1]
|
||||||
|
const prev = sessions[targetIndex - 1]
|
||||||
|
|
||||||
|
if (offset > 0) {
|
||||||
|
if (next) prefetchSession(next, "high")
|
||||||
|
if (prev) prefetchSession(prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < 0) {
|
||||||
|
if (prev) prefetchSession(prev, "high")
|
||||||
|
if (next) prefetchSession(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
navStart({
|
||||||
|
dir: base64Encode(session.directory),
|
||||||
|
from: params.id,
|
||||||
|
to: session.id,
|
||||||
|
trigger: offset > 0 ? "alt+arrowdown" : "alt+arrowup",
|
||||||
|
})
|
||||||
|
}
|
||||||
navigateToSession(session)
|
navigateToSession(session)
|
||||||
queueMicrotask(() => scrollToSession(session.id))
|
queueMicrotask(() => scrollToSession(session.id))
|
||||||
return
|
return
|
||||||
@@ -324,7 +488,27 @@ export default function Layout(props: ParentProps) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetSession = offset > 0 ? nextProjectSessions[0] : nextProjectSessions[nextProjectSessions.length - 1]
|
const index = offset > 0 ? 0 : nextProjectSessions.length - 1
|
||||||
|
const targetSession = nextProjectSessions[index]
|
||||||
|
const nextSession = nextProjectSessions[index + 1]
|
||||||
|
const prevSession = nextProjectSessions[index - 1]
|
||||||
|
|
||||||
|
if (offset > 0) {
|
||||||
|
if (nextSession) prefetchSession(nextSession, "high")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < 0) {
|
||||||
|
if (prevSession) prefetchSession(prevSession, "high")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
navStart({
|
||||||
|
dir: base64Encode(targetSession.directory),
|
||||||
|
from: params.id,
|
||||||
|
to: targetSession.id,
|
||||||
|
trigger: offset > 0 ? "alt+arrowdown" : "alt+arrowup",
|
||||||
|
})
|
||||||
|
}
|
||||||
navigateToSession(targetSession)
|
navigateToSession(targetSession)
|
||||||
queueMicrotask(() => scrollToSession(targetSession.id))
|
queueMicrotask(() => scrollToSession(targetSession.id))
|
||||||
}
|
}
|
||||||
@@ -679,6 +863,8 @@ export default function Layout(props: ParentProps) {
|
|||||||
<A
|
<A
|
||||||
href={`${props.slug}/session/${props.session.id}`}
|
href={`${props.slug}/session/${props.session.id}`}
|
||||||
class="flex flex-col min-w-0 text-left w-full focus:outline-none pl-4 pr-2 py-1"
|
class="flex flex-col min-w-0 text-left w-full focus:outline-none pl-4 pr-2 py-1"
|
||||||
|
onMouseEnter={() => prefetchSession(props.session, "high")}
|
||||||
|
onFocus={() => prefetchSession(props.session, "high")}
|
||||||
>
|
>
|
||||||
<div class="flex items-center self-stretch gap-6 justify-between transition-[padding] group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7">
|
<div class="flex items-center self-stretch gap-6 justify-between transition-[padding] group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7">
|
||||||
<span
|
<span
|
||||||
@@ -758,7 +944,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
.toSorted(sortSessions),
|
.toSorted(sortSessions),
|
||||||
)
|
)
|
||||||
const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID))
|
const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID))
|
||||||
const hasMoreSessions = createMemo(() => store.session.length >= store.limit)
|
const hasMoreSessions = createMemo(() => store.sessionTotal > store.session.length)
|
||||||
const loadMoreSessions = async () => {
|
const loadMoreSessions = async () => {
|
||||||
setProjectStore("limit", (limit) => limit + 5)
|
setProjectStore("limit", (limit) => limit + 5)
|
||||||
await globalSync.project.loadSessions(props.project.worktree)
|
await globalSync.project.loadSessions(props.project.worktree)
|
||||||
@@ -875,7 +1061,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
</Collapsible>
|
</Collapsible>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<Tooltip placement="right" value={props.project.worktree}>
|
<Tooltip placement="right" value={getFilename(props.project.worktree)}>
|
||||||
<ProjectVisual project={props.project} />
|
<ProjectVisual project={props.project} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Match>
|
</Match>
|
||||||
|
|||||||
+533
-203
@@ -1,4 +1,4 @@
|
|||||||
import { For, onCleanup, Show, Match, Switch, createMemo, createEffect, on } from "solid-js"
|
import { For, onCleanup, onMount, Show, Match, Switch, createMemo, createEffect, on } from "solid-js"
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
||||||
import { Dynamic } from "solid-js/web"
|
import { Dynamic } from "solid-js/web"
|
||||||
@@ -8,6 +8,7 @@ import { createStore } from "solid-js/store"
|
|||||||
import { PromptInput } from "@/components/prompt-input"
|
import { PromptInput } from "@/components/prompt-input"
|
||||||
import { SessionContextUsage } from "@/components/session-context-usage"
|
import { SessionContextUsage } from "@/components/session-context-usage"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
||||||
@@ -30,6 +31,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|||||||
import { DialogSelectFile } from "@/components/dialog-select-file"
|
import { DialogSelectFile } from "@/components/dialog-select-file"
|
||||||
import { DialogSelectModel } from "@/components/dialog-select-model"
|
import { DialogSelectModel } from "@/components/dialog-select-model"
|
||||||
import { DialogSelectMcp } from "@/components/dialog-select-mcp"
|
import { DialogSelectMcp } from "@/components/dialog-select-mcp"
|
||||||
|
import { DialogFork } from "@/components/dialog-fork"
|
||||||
import { useCommand } from "@/context/command"
|
import { useCommand } from "@/context/command"
|
||||||
import { useNavigate, useParams } from "@solidjs/router"
|
import { useNavigate, useParams } from "@solidjs/router"
|
||||||
import { UserMessage } from "@opencode-ai/sdk/v2"
|
import { UserMessage } from "@opencode-ai/sdk/v2"
|
||||||
@@ -49,10 +51,17 @@ import {
|
|||||||
NewSessionView,
|
NewSessionView,
|
||||||
} from "@/components/session"
|
} from "@/components/session"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { navMark, navParams } from "@/utils/perf"
|
||||||
import { same } from "@/utils/same"
|
import { same } from "@/utils/same"
|
||||||
|
|
||||||
type DiffStyle = "unified" | "split"
|
type DiffStyle = "unified" | "split"
|
||||||
|
|
||||||
|
const handoff = {
|
||||||
|
prompt: "",
|
||||||
|
terminals: [] as string[],
|
||||||
|
files: {} as Record<string, SelectedLineRange | null>,
|
||||||
|
}
|
||||||
|
|
||||||
interface SessionReviewTabProps {
|
interface SessionReviewTabProps {
|
||||||
diffs: () => FileDiff[]
|
diffs: () => FileDiff[]
|
||||||
view: () => ReturnType<ReturnType<typeof useLayout>["view"]>
|
view: () => ReturnType<ReturnType<typeof useLayout>["view"]>
|
||||||
@@ -162,6 +171,46 @@ export default function Page() {
|
|||||||
const tabs = createMemo(() => layout.tabs(sessionKey()))
|
const tabs = createMemo(() => layout.tabs(sessionKey()))
|
||||||
const view = createMemo(() => layout.view(sessionKey()))
|
const view = createMemo(() => layout.view(sessionKey()))
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => [params.dir, params.id] as const,
|
||||||
|
([dir, id], prev) => {
|
||||||
|
if (!id) return
|
||||||
|
navParams({ dir, from: prev?.[1], to: id })
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return
|
||||||
|
if (!prompt.ready()) return
|
||||||
|
navMark({ dir: params.dir, to: id, name: "storage:prompt-ready" })
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return
|
||||||
|
if (!terminal.ready()) return
|
||||||
|
navMark({ dir: params.dir, to: id, name: "storage:terminal-ready" })
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return
|
||||||
|
if (!file.ready()) return
|
||||||
|
navMark({ dir: params.dir, to: id, name: "storage:file-view-ready" })
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return
|
||||||
|
if (sync.data.message[id] === undefined) return
|
||||||
|
navMark({ dir: params.dir, to: id, name: "session:data-ready" })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const isDesktop = createMediaQuery("(min-width: 768px)")
|
const isDesktop = createMediaQuery("(min-width: 768px)")
|
||||||
|
|
||||||
function normalizeTab(tab: string) {
|
function normalizeTab(tab: string) {
|
||||||
@@ -216,6 +265,8 @@ export default function Page() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
||||||
|
const reviewCount = createMemo(() => info()?.summary?.files ?? 0)
|
||||||
|
const hasReview = createMemo(() => reviewCount() > 0)
|
||||||
const revertMessageID = createMemo(() => info()?.revert?.messageID)
|
const revertMessageID = createMemo(() => info()?.revert?.messageID)
|
||||||
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
|
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
|
||||||
const messagesReady = createMemo(() => {
|
const messagesReady = createMemo(() => {
|
||||||
@@ -223,6 +274,16 @@ export default function Page() {
|
|||||||
if (!id) return true
|
if (!id) return true
|
||||||
return sync.data.message[id] !== undefined
|
return sync.data.message[id] !== undefined
|
||||||
})
|
})
|
||||||
|
const historyMore = createMemo(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return false
|
||||||
|
return sync.session.history.more(id)
|
||||||
|
})
|
||||||
|
const historyLoading = createMemo(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return false
|
||||||
|
return sync.session.history.loading(id)
|
||||||
|
})
|
||||||
const emptyUserMessages: UserMessage[] = []
|
const emptyUserMessages: UserMessage[] = []
|
||||||
const userMessages = createMemo(() => messages().filter((m) => m.role === "user") as UserMessage[], emptyUserMessages)
|
const userMessages = createMemo(() => messages().filter((m) => m.role === "user") as UserMessage[], emptyUserMessages)
|
||||||
const visibleUserMessages = createMemo(() => {
|
const visibleUserMessages = createMemo(() => {
|
||||||
@@ -249,11 +310,20 @@ export default function Page() {
|
|||||||
activeTerminalDraggable: undefined as string | undefined,
|
activeTerminalDraggable: undefined as string | undefined,
|
||||||
expanded: {} as Record<string, boolean>,
|
expanded: {} as Record<string, boolean>,
|
||||||
messageId: undefined as string | undefined,
|
messageId: undefined as string | undefined,
|
||||||
|
turnStart: 0,
|
||||||
mobileTab: "session" as "session" | "review",
|
mobileTab: "session" as "session" | "review",
|
||||||
newSessionWorktree: "main",
|
newSessionWorktree: "main",
|
||||||
promptHeight: 0,
|
promptHeight: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const renderedUserMessages = createMemo(() => {
|
||||||
|
const msgs = visibleUserMessages()
|
||||||
|
const start = store.turnStart
|
||||||
|
if (start <= 0) return msgs
|
||||||
|
if (start >= msgs.length) return emptyUserMessages
|
||||||
|
return msgs.slice(start)
|
||||||
|
}, emptyUserMessages)
|
||||||
|
|
||||||
const newSessionWorktree = createMemo(() => {
|
const newSessionWorktree = createMemo(() => {
|
||||||
if (store.newSessionWorktree === "create") return "create"
|
if (store.newSessionWorktree === "create") return "create"
|
||||||
const project = sync.project
|
const project = sync.project
|
||||||
@@ -290,6 +360,12 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : []))
|
const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : []))
|
||||||
|
const diffsReady = createMemo(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return true
|
||||||
|
if (!hasReview()) return true
|
||||||
|
return sync.data.session_diff[id] !== undefined
|
||||||
|
})
|
||||||
|
|
||||||
const idle = { type: "idle" as const }
|
const idle = { type: "idle" as const }
|
||||||
let inputRef!: HTMLDivElement
|
let inputRef!: HTMLDivElement
|
||||||
@@ -302,11 +378,10 @@ export default function Page() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (layout.terminal.opened()) {
|
if (!view().terminal.opened()) return
|
||||||
if (terminal.all().length === 0) {
|
if (!terminal.ready()) return
|
||||||
terminal.new()
|
if (terminal.all().length !== 0) return
|
||||||
}
|
terminal.new()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
@@ -366,7 +441,7 @@ export default function Page() {
|
|||||||
category: "View",
|
category: "View",
|
||||||
keybind: "ctrl+`",
|
keybind: "ctrl+`",
|
||||||
slash: "terminal",
|
slash: "terminal",
|
||||||
onSelect: () => layout.terminal.toggle(),
|
onSelect: () => view().terminal.toggle(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "review.toggle",
|
id: "review.toggle",
|
||||||
@@ -374,7 +449,7 @@ export default function Page() {
|
|||||||
description: "Show or hide the review panel",
|
description: "Show or hide the review panel",
|
||||||
category: "View",
|
category: "View",
|
||||||
keybind: "mod+shift+r",
|
keybind: "mod+shift+r",
|
||||||
onSelect: () => layout.review.toggle(),
|
onSelect: () => view().reviewPanel.toggle(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "terminal.new",
|
id: "terminal.new",
|
||||||
@@ -571,6 +646,15 @@ export default function Page() {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "session.fork",
|
||||||
|
title: "Fork from message",
|
||||||
|
description: "Create a new session from a previous message",
|
||||||
|
category: "Session",
|
||||||
|
slash: "fork",
|
||||||
|
disabled: !params.id || visibleUserMessages().length === 0,
|
||||||
|
onSelect: () => dialog.show(() => <DialogFork />),
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
@@ -643,11 +727,11 @@ export default function Page() {
|
|||||||
.filter((tab) => tab !== "context"),
|
.filter((tab) => tab !== "context"),
|
||||||
)
|
)
|
||||||
|
|
||||||
const reviewTab = createMemo(() => diffs().length > 0 || tabs().active() === "review")
|
const reviewTab = createMemo(() => hasReview() || tabs().active() === "review")
|
||||||
const mobileReview = createMemo(() => !isDesktop() && diffs().length > 0 && store.mobileTab === "review")
|
const mobileReview = createMemo(() => !isDesktop() && hasReview() && store.mobileTab === "review")
|
||||||
|
|
||||||
const showTabs = createMemo(
|
const showTabs = createMemo(
|
||||||
() => layout.review.opened() && (diffs().length > 0 || tabs().all().length > 0 || contextOpen()),
|
() => view().reviewPanel.opened() && (hasReview() || tabs().all().length > 0 || contextOpen()),
|
||||||
)
|
)
|
||||||
|
|
||||||
const activeTab = createMemo(() => {
|
const activeTab = createMemo(() => {
|
||||||
@@ -664,10 +748,22 @@ export default function Page() {
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!layout.ready()) return
|
if (!layout.ready()) return
|
||||||
if (tabs().active()) return
|
if (tabs().active()) return
|
||||||
if (diffs().length === 0 && openedTabs().length === 0 && !contextOpen()) return
|
if (!hasReview() && openedTabs().length === 0 && !contextOpen()) return
|
||||||
tabs().setActive(activeTab())
|
tabs().setActive(activeTab())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return
|
||||||
|
if (!hasReview()) return
|
||||||
|
|
||||||
|
const wants = isDesktop() ? view().reviewPanel.opened() && activeTab() === "review" : store.mobileTab === "review"
|
||||||
|
if (!wants) return
|
||||||
|
if (diffsReady()) return
|
||||||
|
|
||||||
|
sync.session.diff(id)
|
||||||
|
})
|
||||||
|
|
||||||
const isWorking = createMemo(() => status().type !== "idle")
|
const isWorking = createMemo(() => status().type !== "idle")
|
||||||
const autoScroll = createAutoScroll({
|
const autoScroll = createAutoScroll({
|
||||||
working: isWorking,
|
working: isWorking,
|
||||||
@@ -683,6 +779,88 @@ export default function Page() {
|
|||||||
autoScroll.scrollRef(el)
|
autoScroll.scrollRef(el)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const turnInit = 20
|
||||||
|
const turnBatch = 20
|
||||||
|
let turnHandle: number | undefined
|
||||||
|
let turnIdle = false
|
||||||
|
|
||||||
|
function cancelTurnBackfill() {
|
||||||
|
const handle = turnHandle
|
||||||
|
if (handle === undefined) return
|
||||||
|
turnHandle = undefined
|
||||||
|
|
||||||
|
if (turnIdle && window.cancelIdleCallback) {
|
||||||
|
window.cancelIdleCallback(handle)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleTurnBackfill() {
|
||||||
|
if (turnHandle !== undefined) return
|
||||||
|
if (store.turnStart <= 0) return
|
||||||
|
|
||||||
|
if (window.requestIdleCallback) {
|
||||||
|
turnIdle = true
|
||||||
|
turnHandle = window.requestIdleCallback(() => {
|
||||||
|
turnHandle = undefined
|
||||||
|
backfillTurns()
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
turnIdle = false
|
||||||
|
turnHandle = window.setTimeout(() => {
|
||||||
|
turnHandle = undefined
|
||||||
|
backfillTurns()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function backfillTurns() {
|
||||||
|
const start = store.turnStart
|
||||||
|
if (start <= 0) return
|
||||||
|
|
||||||
|
const next = start - turnBatch
|
||||||
|
const nextStart = next > 0 ? next : 0
|
||||||
|
|
||||||
|
const el = scroller
|
||||||
|
if (!el) {
|
||||||
|
setStore("turnStart", nextStart)
|
||||||
|
scheduleTurnBackfill()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeTop = el.scrollTop
|
||||||
|
const beforeHeight = el.scrollHeight
|
||||||
|
|
||||||
|
setStore("turnStart", nextStart)
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const delta = el.scrollHeight - beforeHeight
|
||||||
|
if (delta) el.scrollTop = beforeTop + delta
|
||||||
|
})
|
||||||
|
|
||||||
|
scheduleTurnBackfill()
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => [params.id, messagesReady()] as const,
|
||||||
|
([id, ready]) => {
|
||||||
|
cancelTurnBackfill()
|
||||||
|
setStore("turnStart", 0)
|
||||||
|
if (!id || !ready) return
|
||||||
|
|
||||||
|
const len = visibleUserMessages().length
|
||||||
|
const start = len > turnInit ? len - turnInit : 0
|
||||||
|
setStore("turnStart", start)
|
||||||
|
scheduleTurnBackfill()
|
||||||
|
},
|
||||||
|
{ defer: true },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
createResizeObserver(
|
createResizeObserver(
|
||||||
() => promptDock,
|
() => promptDock,
|
||||||
({ height }) => {
|
({ height }) => {
|
||||||
@@ -710,6 +888,21 @@ export default function Page() {
|
|||||||
const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
|
const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
|
||||||
setActiveMessage(message)
|
setActiveMessage(message)
|
||||||
|
|
||||||
|
const msgs = visibleUserMessages()
|
||||||
|
const index = msgs.findIndex((m) => m.id === message.id)
|
||||||
|
if (index !== -1 && index < store.turnStart) {
|
||||||
|
setStore("turnStart", index)
|
||||||
|
scheduleTurnBackfill()
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const el = document.getElementById(anchor(message.id))
|
||||||
|
if (el) el.scrollIntoView({ behavior, block: "start" })
|
||||||
|
})
|
||||||
|
|
||||||
|
updateHash(message.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const el = document.getElementById(anchor(message.id))
|
const el = document.getElementById(anchor(message.id))
|
||||||
if (el) el.scrollIntoView({ behavior, block: "start" })
|
if (el) el.scrollIntoView({ behavior, block: "start" })
|
||||||
updateHash(message.id)
|
updateHash(message.id)
|
||||||
@@ -755,12 +948,27 @@ export default function Page() {
|
|||||||
if (!sessionID || !ready) return
|
if (!sessionID || !ready) return
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const id = window.location.hash.slice(1)
|
const hash = window.location.hash.slice(1)
|
||||||
const hashTarget = id ? document.getElementById(id) : undefined
|
if (!hash) {
|
||||||
|
autoScroll.forceScrollToBottom()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashTarget = document.getElementById(hash)
|
||||||
if (hashTarget) {
|
if (hashTarget) {
|
||||||
hashTarget.scrollIntoView({ behavior: "auto", block: "start" })
|
hashTarget.scrollIntoView({ behavior: "auto", block: "start" })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const match = hash.match(/^message-(.+)$/)
|
||||||
|
if (match) {
|
||||||
|
const msg = visibleUserMessages().find((m) => m.id === match[1])
|
||||||
|
if (msg) {
|
||||||
|
scrollToMessage(msg, "auto")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
autoScroll.forceScrollToBottom()
|
autoScroll.forceScrollToBottom()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -769,7 +977,43 @@ export default function Page() {
|
|||||||
document.addEventListener("keydown", handleKeyDown)
|
document.addEventListener("keydown", handleKeyDown)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const previewPrompt = () =>
|
||||||
|
prompt
|
||||||
|
.current()
|
||||||
|
.map((part) => {
|
||||||
|
if (part.type === "file") return `[file:${part.path}]`
|
||||||
|
if (part.type === "agent") return `@${part.name}`
|
||||||
|
if (part.type === "image") return `[image:${part.filename}]`
|
||||||
|
return part.content
|
||||||
|
})
|
||||||
|
.join("")
|
||||||
|
.trim()
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!prompt.ready()) return
|
||||||
|
handoff.prompt = previewPrompt()
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!terminal.ready()) return
|
||||||
|
handoff.terminals = terminal.all().map((t) => t.title)
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!file.ready()) return
|
||||||
|
handoff.files = Object.fromEntries(
|
||||||
|
tabs()
|
||||||
|
.all()
|
||||||
|
.flatMap((tab) => {
|
||||||
|
const path = file.pathFromTab(tab)
|
||||||
|
if (!path) return []
|
||||||
|
return [[path, file.selectedLines(path) ?? null] as const]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
|
cancelTurnBackfill()
|
||||||
document.removeEventListener("keydown", handleKeyDown)
|
document.removeEventListener("keydown", handleKeyDown)
|
||||||
if (scrollSpyFrame !== undefined) cancelAnimationFrame(scrollSpyFrame)
|
if (scrollSpyFrame !== undefined) cancelAnimationFrame(scrollSpyFrame)
|
||||||
})
|
})
|
||||||
@@ -779,7 +1023,7 @@ export default function Page() {
|
|||||||
<SessionHeader />
|
<SessionHeader />
|
||||||
<div class="flex-1 min-h-0 flex flex-col md:flex-row">
|
<div class="flex-1 min-h-0 flex flex-col md:flex-row">
|
||||||
{/* Mobile tab bar - only shown on mobile when there are diffs */}
|
{/* Mobile tab bar - only shown on mobile when there are diffs */}
|
||||||
<Show when={!isDesktop() && diffs().length > 0}>
|
<Show when={!isDesktop() && hasReview()}>
|
||||||
<Tabs class="h-auto">
|
<Tabs class="h-auto">
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
@@ -796,7 +1040,7 @@ export default function Page() {
|
|||||||
classes={{ button: "w-full" }}
|
classes={{ button: "w-full" }}
|
||||||
onClick={() => setStore("mobileTab", "review")}
|
onClick={() => setStore("mobileTab", "review")}
|
||||||
>
|
>
|
||||||
{diffs().length} Files Changed
|
{reviewCount()} Files Changed
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -821,21 +1065,26 @@ export default function Page() {
|
|||||||
when={!mobileReview()}
|
when={!mobileReview()}
|
||||||
fallback={
|
fallback={
|
||||||
<div class="relative h-full overflow-hidden">
|
<div class="relative h-full overflow-hidden">
|
||||||
<SessionReviewTab
|
<Show
|
||||||
diffs={diffs}
|
when={diffsReady()}
|
||||||
view={view}
|
fallback={<div class="px-4 py-4 text-text-weak">Loading changes...</div>}
|
||||||
diffStyle="unified"
|
>
|
||||||
onViewFile={(path) => {
|
<SessionReviewTab
|
||||||
const value = file.tab(path)
|
diffs={diffs}
|
||||||
tabs().open(value)
|
view={view}
|
||||||
file.load(path)
|
diffStyle="unified"
|
||||||
}}
|
onViewFile={(path) => {
|
||||||
classes={{
|
const value = file.tab(path)
|
||||||
root: "pb-[calc(var(--prompt-height,8rem)+32px)]",
|
tabs().open(value)
|
||||||
header: "px-4",
|
file.load(path)
|
||||||
container: "px-4",
|
}}
|
||||||
}}
|
classes={{
|
||||||
/>
|
root: "pb-[calc(var(--prompt-height,8rem)+32px)]",
|
||||||
|
header: "px-4",
|
||||||
|
container: "px-4",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -868,42 +1117,82 @@ export default function Page() {
|
|||||||
"mt-0": showTabs(),
|
"mt-0": showTabs(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<For each={visibleUserMessages()}>
|
<Show when={store.turnStart > 0}>
|
||||||
{(message) => (
|
<div class="w-full flex justify-center">
|
||||||
<div
|
<Button
|
||||||
id={anchor(message.id)}
|
variant="ghost"
|
||||||
data-message-id={message.id}
|
size="large"
|
||||||
classList={{
|
class="text-12-medium opacity-50"
|
||||||
"min-w-0 w-full max-w-full": true,
|
onClick={() => setStore("turnStart", 0)}
|
||||||
"last:min-h-[calc(100vh-5.5rem-var(--prompt-height,8rem)-64px)] md:last:min-h-[calc(100vh-4.5rem-var(--prompt-height,10rem)-64px)]":
|
>
|
||||||
platform.platform !== "desktop",
|
Render earlier messages
|
||||||
"last:min-h-[calc(100vh-7rem-var(--prompt-height,8rem)-64px)] md:last:min-h-[calc(100vh-6rem-var(--prompt-height,10rem)-64px)]":
|
</Button>
|
||||||
platform.platform === "desktop",
|
</div>
|
||||||
|
</Show>
|
||||||
|
<Show when={historyMore()}>
|
||||||
|
<div class="w-full flex justify-center">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="large"
|
||||||
|
class="text-12-medium opacity-50"
|
||||||
|
disabled={historyLoading()}
|
||||||
|
onClick={() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return
|
||||||
|
setStore("turnStart", 0)
|
||||||
|
sync.session.history.loadMore(id)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SessionTurn
|
{historyLoading() ? "Loading earlier messages..." : "Load earlier messages"}
|
||||||
sessionID={params.id!}
|
</Button>
|
||||||
messageID={message.id}
|
</div>
|
||||||
lastUserMessageID={lastUserMessage()?.id}
|
</Show>
|
||||||
stepsExpanded={store.expanded[message.id] ?? false}
|
<For each={renderedUserMessages()}>
|
||||||
onStepsExpandedToggle={() =>
|
{(message) => {
|
||||||
setStore("expanded", message.id, (open: boolean | undefined) => !open)
|
if (import.meta.env.DEV) {
|
||||||
}
|
onMount(() => {
|
||||||
classes={{
|
const id = params.id
|
||||||
root: "min-w-0 w-full relative",
|
if (!id) return
|
||||||
content:
|
navMark({ dir: params.dir, to: id, name: "session:first-turn-mounted" })
|
||||||
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
|
})
|
||||||
container:
|
}
|
||||||
"px-4 md:px-6 " +
|
|
||||||
(!showTabs()
|
return (
|
||||||
? "md:max-w-200 md:mx-auto"
|
<div
|
||||||
: visibleUserMessages().length > 1
|
id={anchor(message.id)}
|
||||||
? "md:pr-6 md:pl-18"
|
data-message-id={message.id}
|
||||||
: ""),
|
classList={{
|
||||||
|
"min-w-0 w-full max-w-full": true,
|
||||||
|
"last:min-h-[calc(100vh-5.5rem-var(--prompt-height,8rem)-64px)] md:last:min-h-[calc(100vh-4.5rem-var(--prompt-height,10rem)-64px)]":
|
||||||
|
platform.platform !== "desktop",
|
||||||
|
"last:min-h-[calc(100vh-7rem-var(--prompt-height,8rem)-64px)] md:last:min-h-[calc(100vh-6rem-var(--prompt-height,10rem)-64px)]":
|
||||||
|
platform.platform === "desktop",
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
</div>
|
<SessionTurn
|
||||||
)}
|
sessionID={params.id!}
|
||||||
|
messageID={message.id}
|
||||||
|
lastUserMessageID={lastUserMessage()?.id}
|
||||||
|
stepsExpanded={store.expanded[message.id] ?? false}
|
||||||
|
onStepsExpandedToggle={() =>
|
||||||
|
setStore("expanded", message.id, (open: boolean | undefined) => !open)
|
||||||
|
}
|
||||||
|
classes={{
|
||||||
|
root: "min-w-0 w-full relative",
|
||||||
|
content:
|
||||||
|
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
|
||||||
|
container:
|
||||||
|
"px-4 md:px-6 " +
|
||||||
|
(!showTabs()
|
||||||
|
? "md:max-w-200 md:mx-auto"
|
||||||
|
: visibleUserMessages().length > 1
|
||||||
|
? "md:pr-6 md:pl-18"
|
||||||
|
: ""),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -944,13 +1233,22 @@ export default function Page() {
|
|||||||
"md:max-w-200": !showTabs(),
|
"md:max-w-200": !showTabs(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PromptInput
|
<Show
|
||||||
ref={(el) => {
|
when={prompt.ready()}
|
||||||
inputRef = el
|
fallback={
|
||||||
}}
|
<div class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none">
|
||||||
newSessionWorktree={newSessionWorktree()}
|
{handoff.prompt || "Loading prompt..."}
|
||||||
onNewSessionWorktreeReset={() => setStore("newSessionWorktree", "main")}
|
</div>
|
||||||
/>
|
}
|
||||||
|
>
|
||||||
|
<PromptInput
|
||||||
|
ref={(el) => {
|
||||||
|
inputRef = el
|
||||||
|
}}
|
||||||
|
newSessionWorktree={newSessionWorktree()}
|
||||||
|
onNewSessionWorktreeReset={() => setStore("newSessionWorktree", "main")}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1034,31 +1332,40 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
<Show when={reviewTab()}>
|
<Show when={reviewTab()}>
|
||||||
<Tabs.Content value="review" class="flex flex-col h-full overflow-hidden contain-strict">
|
<Tabs.Content value="review" class="flex flex-col h-full overflow-hidden contain-strict">
|
||||||
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
|
<Show when={activeTab() === "review"}>
|
||||||
<SessionReviewTab
|
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
|
||||||
diffs={diffs}
|
<Show
|
||||||
view={view}
|
when={diffsReady()}
|
||||||
diffStyle={layout.review.diffStyle()}
|
fallback={<div class="px-6 py-4 text-text-weak">Loading changes...</div>}
|
||||||
onDiffStyleChange={layout.review.setDiffStyle}
|
>
|
||||||
onViewFile={(path) => {
|
<SessionReviewTab
|
||||||
const value = file.tab(path)
|
diffs={diffs}
|
||||||
tabs().open(value)
|
view={view}
|
||||||
file.load(path)
|
diffStyle={layout.review.diffStyle()}
|
||||||
}}
|
onDiffStyleChange={layout.review.setDiffStyle}
|
||||||
/>
|
onViewFile={(path) => {
|
||||||
</div>
|
const value = file.tab(path)
|
||||||
|
tabs().open(value)
|
||||||
|
file.load(path)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={contextOpen()}>
|
<Show when={contextOpen()}>
|
||||||
<Tabs.Content value="context" class="flex flex-col h-full overflow-hidden contain-strict">
|
<Tabs.Content value="context" class="flex flex-col h-full overflow-hidden contain-strict">
|
||||||
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
|
<Show when={activeTab() === "context"}>
|
||||||
<SessionContextTab
|
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
|
||||||
messages={messages}
|
<SessionContextTab
|
||||||
visibleUserMessages={visibleUserMessages}
|
messages={messages}
|
||||||
view={view}
|
visibleUserMessages={visibleUserMessages}
|
||||||
info={info}
|
view={view}
|
||||||
/>
|
info={info}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
</Show>
|
</Show>
|
||||||
<For each={openedTabs()}>
|
<For each={openedTabs()}>
|
||||||
@@ -1107,7 +1414,8 @@ export default function Page() {
|
|||||||
const selectedLines = createMemo(() => {
|
const selectedLines = createMemo(() => {
|
||||||
const p = path()
|
const p = path()
|
||||||
if (!p) return null
|
if (!p) return null
|
||||||
return file.selectedLines(p) ?? null
|
if (file.ready()) return file.selectedLines(p) ?? null
|
||||||
|
return handoff.files[p] ?? null
|
||||||
})
|
})
|
||||||
const selection = createMemo(() => {
|
const selection = createMemo(() => {
|
||||||
const range = selectedLines()
|
const range = selectedLines()
|
||||||
@@ -1204,37 +1512,63 @@ export default function Page() {
|
|||||||
}}
|
}}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
<Show when={selection()}>
|
<Show when={activeTab() === tab}>
|
||||||
{(sel) => (
|
<Show when={selection()}>
|
||||||
<div class="hidden sticky top-0 z-10 px-6 py-2 _flex justify-end bg-background-base border-b border-border-weak-base">
|
{(sel) => (
|
||||||
<button
|
<div class="hidden sticky top-0 z-10 px-6 py-2 _flex justify-end bg-background-base border-b border-border-weak-base">
|
||||||
type="button"
|
<button
|
||||||
class="flex items-center gap-2 px-2 py-1 rounded-md bg-surface-base border border-border-base text-12-regular text-text-strong hover:bg-surface-raised-base-hover"
|
type="button"
|
||||||
onClick={() => {
|
class="flex items-center gap-2 px-2 py-1 rounded-md bg-surface-base border border-border-base text-12-regular text-text-strong hover:bg-surface-raised-base-hover"
|
||||||
const p = path()
|
onClick={() => {
|
||||||
if (!p) return
|
const p = path()
|
||||||
prompt.context.add({ type: "file", path: p, selection: sel() })
|
if (!p) return
|
||||||
}}
|
prompt.context.add({ type: "file", path: p, selection: sel() })
|
||||||
>
|
}}
|
||||||
<Icon name="plus-small" size="small" />
|
>
|
||||||
<span>Add {selectionLabel()} to context</span>
|
<Icon name="plus-small" size="small" />
|
||||||
</button>
|
<span>Add {selectionLabel()} to context</span>
|
||||||
</div>
|
</button>
|
||||||
)}
|
</div>
|
||||||
</Show>
|
)}
|
||||||
<Switch>
|
</Show>
|
||||||
<Match when={state()?.loaded && isImage()}>
|
<Switch>
|
||||||
<div class="px-6 py-4 pb-40">
|
<Match when={state()?.loaded && isImage()}>
|
||||||
<img src={imageDataUrl()} alt={path()} class="max-w-full" />
|
<div class="px-6 py-4 pb-40">
|
||||||
</div>
|
<img src={imageDataUrl()} alt={path()} class="max-w-full" />
|
||||||
</Match>
|
</div>
|
||||||
<Match when={state()?.loaded && isSvg()}>
|
</Match>
|
||||||
<div class="flex flex-col gap-4 px-6 py-4">
|
<Match when={state()?.loaded && isSvg()}>
|
||||||
|
<div class="flex flex-col gap-4 px-6 py-4">
|
||||||
|
<Dynamic
|
||||||
|
component={codeComponent}
|
||||||
|
file={{
|
||||||
|
name: path() ?? "",
|
||||||
|
contents: svgContent() ?? "",
|
||||||
|
cacheKey: cacheKey(),
|
||||||
|
}}
|
||||||
|
enableLineSelection
|
||||||
|
selectedLines={selectedLines()}
|
||||||
|
onLineSelected={(range: SelectedLineRange | null) => {
|
||||||
|
const p = path()
|
||||||
|
if (!p) return
|
||||||
|
file.setSelectedLines(p, range)
|
||||||
|
}}
|
||||||
|
overflow="scroll"
|
||||||
|
class="select-text"
|
||||||
|
/>
|
||||||
|
<Show when={svgPreviewUrl()}>
|
||||||
|
<div class="flex justify-center pb-40">
|
||||||
|
<img src={svgPreviewUrl()} alt={path()} class="max-w-full max-h-96" />
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</Match>
|
||||||
|
<Match when={state()?.loaded}>
|
||||||
<Dynamic
|
<Dynamic
|
||||||
component={codeComponent}
|
component={codeComponent}
|
||||||
file={{
|
file={{
|
||||||
name: path() ?? "",
|
name: path() ?? "",
|
||||||
contents: svgContent() ?? "",
|
contents: contents(),
|
||||||
cacheKey: cacheKey(),
|
cacheKey: cacheKey(),
|
||||||
}}
|
}}
|
||||||
enableLineSelection
|
enableLineSelection
|
||||||
@@ -1245,41 +1579,17 @@ export default function Page() {
|
|||||||
file.setSelectedLines(p, range)
|
file.setSelectedLines(p, range)
|
||||||
}}
|
}}
|
||||||
overflow="scroll"
|
overflow="scroll"
|
||||||
class="select-text"
|
class="select-text pb-40"
|
||||||
/>
|
/>
|
||||||
<Show when={svgPreviewUrl()}>
|
</Match>
|
||||||
<div class="flex justify-center pb-40">
|
<Match when={state()?.loading}>
|
||||||
<img src={svgPreviewUrl()} alt={path()} class="max-w-full max-h-96" />
|
<div class="px-6 py-4 text-text-weak">Loading...</div>
|
||||||
</div>
|
</Match>
|
||||||
</Show>
|
<Match when={state()?.error}>
|
||||||
</div>
|
{(err) => <div class="px-6 py-4 text-text-weak">{err()}</div>}
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={state()?.loaded}>
|
</Switch>
|
||||||
<Dynamic
|
</Show>
|
||||||
component={codeComponent}
|
|
||||||
file={{
|
|
||||||
name: path() ?? "",
|
|
||||||
contents: contents(),
|
|
||||||
cacheKey: cacheKey(),
|
|
||||||
}}
|
|
||||||
enableLineSelection
|
|
||||||
selectedLines={selectedLines()}
|
|
||||||
onLineSelected={(range: SelectedLineRange | null) => {
|
|
||||||
const p = path()
|
|
||||||
if (!p) return
|
|
||||||
file.setSelectedLines(p, range)
|
|
||||||
}}
|
|
||||||
overflow="scroll"
|
|
||||||
class="select-text pb-40"
|
|
||||||
/>
|
|
||||||
</Match>
|
|
||||||
<Match when={state()?.loading}>
|
|
||||||
<div class="px-6 py-4 text-text-weak">Loading...</div>
|
|
||||||
</Match>
|
|
||||||
<Match when={state()?.error}>
|
|
||||||
{(err) => <div class="px-6 py-4 text-text-weak">{err()}</div>}
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -1302,7 +1612,7 @@ export default function Page() {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={isDesktop() && layout.terminal.opened()}>
|
<Show when={isDesktop() && view().terminal.opened()}>
|
||||||
<div
|
<div
|
||||||
class="relative w-full flex-col shrink-0 border-t border-border-weak-base"
|
class="relative w-full flex-col shrink-0 border-t border-border-weak-base"
|
||||||
style={{ height: `${layout.terminal.height()}px` }}
|
style={{ height: `${layout.terminal.height()}px` }}
|
||||||
@@ -1314,56 +1624,76 @@ export default function Page() {
|
|||||||
max={window.innerHeight * 0.6}
|
max={window.innerHeight * 0.6}
|
||||||
collapseThreshold={50}
|
collapseThreshold={50}
|
||||||
onResize={layout.terminal.resize}
|
onResize={layout.terminal.resize}
|
||||||
onCollapse={layout.terminal.close}
|
onCollapse={view().terminal.close}
|
||||||
/>
|
/>
|
||||||
<DragDropProvider
|
<Show
|
||||||
onDragStart={handleTerminalDragStart}
|
when={terminal.ready()}
|
||||||
onDragEnd={handleTerminalDragEnd}
|
fallback={
|
||||||
onDragOver={handleTerminalDragOver}
|
<div class="flex flex-col h-full pointer-events-none">
|
||||||
collisionDetector={closestCenter}
|
<div class="h-10 flex items-center gap-2 px-2 border-b border-border-weak-base bg-background-stronger overflow-hidden">
|
||||||
>
|
<For each={handoff.terminals}>
|
||||||
<DragDropSensors />
|
{(title) => (
|
||||||
<ConstrainDragYAxis />
|
<div class="px-2 py-1 rounded-md bg-surface-base text-14-regular text-text-weak truncate max-w-40">
|
||||||
<Tabs variant="alt" value={terminal.active()} onChange={terminal.open}>
|
{title}
|
||||||
<Tabs.List class="h-10">
|
</div>
|
||||||
<SortableProvider ids={terminal.all().map((t: LocalPTY) => t.id)}>
|
)}
|
||||||
<For each={terminal.all()}>{(pty) => <SortableTerminalTab terminal={pty} />}</For>
|
</For>
|
||||||
</SortableProvider>
|
<div class="flex-1" />
|
||||||
<div class="h-full flex items-center justify-center">
|
<div class="text-text-weak pr-2">Loading...</div>
|
||||||
<TooltipKeybind
|
|
||||||
title="New terminal"
|
|
||||||
keybind={command.keybind("terminal.new")}
|
|
||||||
class="flex items-center"
|
|
||||||
>
|
|
||||||
<IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={terminal.new} />
|
|
||||||
</TooltipKeybind>
|
|
||||||
</div>
|
</div>
|
||||||
</Tabs.List>
|
<div class="flex-1 flex items-center justify-center text-text-weak">Loading terminal...</div>
|
||||||
<For each={terminal.all()}>
|
</div>
|
||||||
{(pty) => (
|
}
|
||||||
<Tabs.Content value={pty.id}>
|
>
|
||||||
<Terminal pty={pty} onCleanup={terminal.update} onConnectError={() => terminal.clone(pty.id)} />
|
<DragDropProvider
|
||||||
</Tabs.Content>
|
onDragStart={handleTerminalDragStart}
|
||||||
)}
|
onDragEnd={handleTerminalDragEnd}
|
||||||
</For>
|
onDragOver={handleTerminalDragOver}
|
||||||
</Tabs>
|
collisionDetector={closestCenter}
|
||||||
<DragOverlay>
|
>
|
||||||
<Show when={store.activeTerminalDraggable}>
|
<DragDropSensors />
|
||||||
{(draggedId) => {
|
<ConstrainDragYAxis />
|
||||||
const pty = createMemo(() => terminal.all().find((t: LocalPTY) => t.id === draggedId()))
|
<Tabs variant="alt" value={terminal.active()} onChange={terminal.open}>
|
||||||
return (
|
<Tabs.List class="h-10">
|
||||||
<Show when={pty()}>
|
<SortableProvider ids={terminal.all().map((t: LocalPTY) => t.id)}>
|
||||||
{(t) => (
|
<For each={terminal.all()}>{(pty) => <SortableTerminalTab terminal={pty} />}</For>
|
||||||
<div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular">
|
</SortableProvider>
|
||||||
{t().title}
|
<div class="h-full flex items-center justify-center">
|
||||||
</div>
|
<TooltipKeybind
|
||||||
)}
|
title="New terminal"
|
||||||
</Show>
|
keybind={command.keybind("terminal.new")}
|
||||||
)
|
class="flex items-center"
|
||||||
}}
|
>
|
||||||
</Show>
|
<IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={terminal.new} />
|
||||||
</DragOverlay>
|
</TooltipKeybind>
|
||||||
</DragDropProvider>
|
</div>
|
||||||
|
</Tabs.List>
|
||||||
|
<For each={terminal.all()}>
|
||||||
|
{(pty) => (
|
||||||
|
<Tabs.Content value={pty.id}>
|
||||||
|
<Terminal pty={pty} onCleanup={terminal.update} onConnectError={() => terminal.clone(pty.id)} />
|
||||||
|
</Tabs.Content>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Tabs>
|
||||||
|
<DragOverlay>
|
||||||
|
<Show when={store.activeTerminalDraggable}>
|
||||||
|
{(draggedId) => {
|
||||||
|
const pty = createMemo(() => terminal.all().find((t: LocalPTY) => t.id === draggedId()))
|
||||||
|
return (
|
||||||
|
<Show when={pty()}>
|
||||||
|
{(t) => (
|
||||||
|
<div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular">
|
||||||
|
{t().title}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Show>
|
||||||
|
</DragOverlay>
|
||||||
|
</DragDropProvider>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
type Nav = {
|
||||||
|
id: string
|
||||||
|
dir?: string
|
||||||
|
from?: string
|
||||||
|
to: string
|
||||||
|
trigger?: string
|
||||||
|
start: number
|
||||||
|
marks: Record<string, number>
|
||||||
|
logged: boolean
|
||||||
|
timer?: ReturnType<typeof setTimeout>
|
||||||
|
}
|
||||||
|
|
||||||
|
const dev = import.meta.env.DEV
|
||||||
|
|
||||||
|
const key = (dir: string | undefined, to: string) => `${dir ?? ""}:${to}`
|
||||||
|
|
||||||
|
const now = () => performance.now()
|
||||||
|
|
||||||
|
const uid = () => crypto.randomUUID?.() ?? Math.random().toString(16).slice(2)
|
||||||
|
|
||||||
|
const navs = new Map<string, Nav>()
|
||||||
|
const pending = new Map<string, string>()
|
||||||
|
const active = new Map<string, string>()
|
||||||
|
|
||||||
|
const required = [
|
||||||
|
"session:params",
|
||||||
|
"session:data-ready",
|
||||||
|
"session:first-turn-mounted",
|
||||||
|
"storage:prompt-ready",
|
||||||
|
"storage:terminal-ready",
|
||||||
|
"storage:file-view-ready",
|
||||||
|
]
|
||||||
|
|
||||||
|
function flush(id: string, reason: "complete" | "timeout") {
|
||||||
|
if (!dev) return
|
||||||
|
const nav = navs.get(id)
|
||||||
|
if (!nav) return
|
||||||
|
if (nav.logged) return
|
||||||
|
|
||||||
|
nav.logged = true
|
||||||
|
if (nav.timer) clearTimeout(nav.timer)
|
||||||
|
|
||||||
|
const baseName = nav.marks["navigate:start"] !== undefined ? "navigate:start" : "session:params"
|
||||||
|
const base = nav.marks[baseName] ?? nav.start
|
||||||
|
|
||||||
|
const ms = Object.fromEntries(
|
||||||
|
Object.entries(nav.marks)
|
||||||
|
.slice()
|
||||||
|
.sort(([a], [b]) => a.localeCompare(b))
|
||||||
|
.map(([name, t]) => [name, Math.round((t - base) * 100) / 100]),
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"perf.session-nav " +
|
||||||
|
JSON.stringify({
|
||||||
|
type: "perf.session-nav.v0",
|
||||||
|
id: nav.id,
|
||||||
|
dir: nav.dir,
|
||||||
|
from: nav.from,
|
||||||
|
to: nav.to,
|
||||||
|
trigger: nav.trigger,
|
||||||
|
base: baseName,
|
||||||
|
reason,
|
||||||
|
ms,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
navs.delete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeFlush(id: string) {
|
||||||
|
if (!dev) return
|
||||||
|
const nav = navs.get(id)
|
||||||
|
if (!nav) return
|
||||||
|
if (nav.logged) return
|
||||||
|
if (!required.every((name) => nav.marks[name] !== undefined)) return
|
||||||
|
flush(id, "complete")
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensure(id: string, data: Omit<Nav, "marks" | "logged" | "timer">) {
|
||||||
|
const existing = navs.get(id)
|
||||||
|
if (existing) return existing
|
||||||
|
|
||||||
|
const nav: Nav = {
|
||||||
|
...data,
|
||||||
|
marks: {},
|
||||||
|
logged: false,
|
||||||
|
}
|
||||||
|
nav.timer = setTimeout(() => flush(id, "timeout"), 5000)
|
||||||
|
navs.set(id, nav)
|
||||||
|
return nav
|
||||||
|
}
|
||||||
|
|
||||||
|
export function navStart(input: { dir?: string; from?: string; to: string; trigger?: string }) {
|
||||||
|
if (!dev) return
|
||||||
|
|
||||||
|
const id = uid()
|
||||||
|
const start = now()
|
||||||
|
const nav = ensure(id, { ...input, id, start })
|
||||||
|
nav.marks["navigate:start"] = start
|
||||||
|
|
||||||
|
pending.set(key(input.dir, input.to), id)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
export function navParams(input: { dir?: string; from?: string; to: string }) {
|
||||||
|
if (!dev) return
|
||||||
|
|
||||||
|
const k = key(input.dir, input.to)
|
||||||
|
const pendingId = pending.get(k)
|
||||||
|
if (pendingId) pending.delete(k)
|
||||||
|
const id = pendingId ?? uid()
|
||||||
|
|
||||||
|
const start = now()
|
||||||
|
const nav = ensure(id, { ...input, id, start, trigger: pendingId ? "key" : "route" })
|
||||||
|
nav.marks["session:params"] = start
|
||||||
|
|
||||||
|
active.set(k, id)
|
||||||
|
maybeFlush(id)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
export function navMark(input: { dir?: string; to: string; name: string }) {
|
||||||
|
if (!dev) return
|
||||||
|
|
||||||
|
const id = active.get(key(input.dir, input.to))
|
||||||
|
if (!id) return
|
||||||
|
|
||||||
|
const nav = navs.get(id)
|
||||||
|
if (!nav) return
|
||||||
|
if (nav.marks[input.name] !== undefined) return
|
||||||
|
|
||||||
|
nav.marks[input.name] = now()
|
||||||
|
maybeFlush(id)
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
"dev": "vite dev --host 0.0.0.0",
|
"dev": "vite dev --host 0.0.0.0",
|
||||||
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai VITE_STRIPE_PUBLISHABLE_KEY=pk_test_51RtuLNE7fOCwHSD4mewwzFejyytjdGoSDK7CAvhbffwaZnPbNb2rwJICw6LTOXCmWO320fSNXvb5NzI08RZVkAxd00syfqrW7t bun sst shell --stage=dev bun dev",
|
||||||
"build": "./script/generate-sitemap.ts && vite build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
"build": "./script/generate-sitemap.ts && vite build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
||||||
"start": "vite start"
|
"start": "vite start"
|
||||||
},
|
},
|
||||||
@@ -23,9 +23,12 @@
|
|||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
"@solidjs/router": "catalog:",
|
"@solidjs/router": "catalog:",
|
||||||
"@solidjs/start": "catalog:",
|
"@solidjs/start": "catalog:",
|
||||||
|
"@stripe/stripe-js": "8.6.1",
|
||||||
"chart.js": "4.5.1",
|
"chart.js": "4.5.1",
|
||||||
"nitro": "3.0.1-alpha.1",
|
"nitro": "3.0.1-alpha.1",
|
||||||
"solid-js": "catalog:",
|
"solid-js": "catalog:",
|
||||||
|
"solid-list": "0.3.0",
|
||||||
|
"solid-stripe": "0.8.1",
|
||||||
"vite": "catalog:",
|
"vite": "catalog:",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -9,8 +9,8 @@ export const config = {
|
|||||||
github: {
|
github: {
|
||||||
repoUrl: "https://github.com/anomalyco/opencode",
|
repoUrl: "https://github.com/anomalyco/opencode",
|
||||||
starsFormatted: {
|
starsFormatted: {
|
||||||
compact: "50K",
|
compact: "60K",
|
||||||
full: "50,000",
|
full: "60,000",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { useSession } from "@solidjs/start/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",
|
|
||||||
maxAge: 60 * 60 * 24 * 365,
|
|
||||||
cookie: {
|
|
||||||
secure: false,
|
|
||||||
httpOnly: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,13 +5,38 @@ import { redirect } from "@solidjs/router"
|
|||||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
|
|
||||||
import { createClient } from "@openauthjs/openauth/client"
|
import { createClient } from "@openauthjs/openauth/client"
|
||||||
import { useAuthSession } from "./auth.session"
|
|
||||||
|
|
||||||
export const AuthClient = createClient({
|
export const AuthClient = createClient({
|
||||||
clientID: "app",
|
clientID: "app",
|
||||||
issuer: import.meta.env.VITE_AUTH_URL,
|
issuer: import.meta.env.VITE_AUTH_URL,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
import { useSession } from "@solidjs/start/http"
|
||||||
|
import { Resource } from "@opencode-ai/console-resource"
|
||||||
|
|
||||||
|
export interface AuthSession {
|
||||||
|
account?: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
id: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
>
|
||||||
|
current?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuthSession() {
|
||||||
|
return useSession<AuthSession>({
|
||||||
|
password: Resource.ZEN_SESSION_SECRET.value,
|
||||||
|
name: "auth",
|
||||||
|
maxAge: 60 * 60 * 24 * 365,
|
||||||
|
cookie: {
|
||||||
|
secure: false,
|
||||||
|
httpOnly: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const getActor = async (workspace?: string): Promise<Actor.Info> => {
|
export const getActor = async (workspace?: string): Promise<Actor.Info> => {
|
||||||
"use server"
|
"use server"
|
||||||
const evt = getRequestEvent()
|
const evt = getRequestEvent()
|
||||||
|
|||||||
+3
-2
@@ -1,10 +1,11 @@
|
|||||||
import { redirect } from "@solidjs/router"
|
import { redirect } from "@solidjs/router"
|
||||||
import type { APIEvent } from "@solidjs/start/server"
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
import { AuthClient } from "~/context/auth"
|
import { AuthClient } from "~/context/auth"
|
||||||
import { useAuthSession } from "~/context/auth.session"
|
import { useAuthSession } from "~/context/auth"
|
||||||
|
|
||||||
export async function GET(input: APIEvent) {
|
export async function GET(input: APIEvent) {
|
||||||
const url = new URL(input.request.url)
|
const url = new URL(input.request.url)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const code = url.searchParams.get("code")
|
const code = url.searchParams.get("code")
|
||||||
if (!code) throw new Error("No code found")
|
if (!code) throw new Error("No code found")
|
||||||
@@ -27,7 +28,7 @@ export async function GET(input: APIEvent) {
|
|||||||
current: id,
|
current: id,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return redirect("/auth")
|
return redirect(url.pathname === "/auth/callback" ? "/auth" : url.pathname.replace("/auth/callback", ""))
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -2,6 +2,9 @@ import type { APIEvent } from "@solidjs/start/server"
|
|||||||
import { AuthClient } from "~/context/auth"
|
import { AuthClient } from "~/context/auth"
|
||||||
|
|
||||||
export async function GET(input: APIEvent) {
|
export async function GET(input: APIEvent) {
|
||||||
const result = await AuthClient.authorize(new URL("./callback", input.request.url).toString(), "code")
|
const url = new URL(input.request.url)
|
||||||
|
const cont = url.searchParams.get("continue") ?? ""
|
||||||
|
const callbackUrl = new URL(`./callback${cont}`, input.request.url)
|
||||||
|
const result = await AuthClient.authorize(callbackUrl.toString(), "code")
|
||||||
return Response.redirect(result.url, 302)
|
return Response.redirect(result.url, 302)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { redirect } from "@solidjs/router"
|
import { redirect } from "@solidjs/router"
|
||||||
import { APIEvent } from "@solidjs/start"
|
import { APIEvent } from "@solidjs/start"
|
||||||
import { useAuthSession } from "~/context/auth.session"
|
import { useAuthSession } from "~/context/auth"
|
||||||
|
|
||||||
export async function GET(event: APIEvent) {
|
export async function GET(event: APIEvent) {
|
||||||
const auth = await useAuthSession()
|
const auth = await useAuthSession()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { APIEvent } from "@solidjs/start"
|
import { APIEvent } from "@solidjs/start"
|
||||||
import { useAuthSession } from "~/context/auth.session"
|
import { useAuthSession } from "~/context/auth"
|
||||||
|
|
||||||
export async function GET(input: APIEvent) {
|
export async function GET(input: APIEvent) {
|
||||||
const session = await useAuthSession()
|
const session = await useAuthSession()
|
||||||
|
|||||||
@@ -0,0 +1,742 @@
|
|||||||
|
[data-page="black"] {
|
||||||
|
background: #000;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: stretch;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
[data-component="header-gradient"] {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 288px;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(0, 0, 0, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="header"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 40px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
/* [data-component="header-logo"] { */
|
||||||
|
/* } */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="content"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
[data-slot="hero"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 40px;
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 18px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
font-size: 15px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="hero-black"] {
|
||||||
|
margin-top: 40px;
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 540px;
|
||||||
|
height: auto;
|
||||||
|
filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="cta"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: -40px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
margin-top: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="heading"] {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%; /* 28.8px */
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[data-slot="subheading"] {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
font-size: 15px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 160%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[data-slot="button"] {
|
||||||
|
display: inline-flex;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 12px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
text-decoration: none;
|
||||||
|
color: #000;
|
||||||
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: normal;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[data-slot="back-soon"] {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%; /* 20.8px */
|
||||||
|
}
|
||||||
|
[data-slot="follow-us"] {
|
||||||
|
display: inline-flex;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 12px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.17);
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="pricing"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 540px;
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="pricing-card"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.17);
|
||||||
|
border-radius: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: border-color 0.15s ease;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="icon"] {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="price"] {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="amount"] {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="period"] {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="multiplier"] {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "·";
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="selected-plan"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 32px;
|
||||||
|
width: fit-content;
|
||||||
|
max-width: calc(100% - 40px);
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="selected-card"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.17);
|
||||||
|
border-radius: 4px;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
[data-slot="icon"] {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="price"] {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="amount"] {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="period"] {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="multiplier"] {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "·";
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="terms"] {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
li {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding-left: 16px;
|
||||||
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "▪";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="actions"] {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
|
button,
|
||||||
|
a {
|
||||||
|
flex: 1;
|
||||||
|
display: inline-flex;
|
||||||
|
height: 48px;
|
||||||
|
padding: 0 16px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="cancel"] {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.17);
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.35);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="continue"] {
|
||||||
|
background: rgba(255, 255, 255, 0.17);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.17);
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="fine-print"] {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%; /* 20.8px */
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subscribe page styles */
|
||||||
|
[data-slot="subscribe-form"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 32px;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: -18px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 540px;
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
margin-top: 40px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="form-card"] {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.17);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="plan-header"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="title"] {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="icon"] {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="price"] {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="amount"] {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="period"] {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="multiplier"] {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "·";
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="divider"] {
|
||||||
|
height: 1px;
|
||||||
|
background: rgba(255, 255, 255, 0.17);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="section-title"] {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="tax-id-section"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
[data-slot="label"] {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="input"] {
|
||||||
|
width: 100%;
|
||||||
|
height: 44px;
|
||||||
|
padding: 0 12px;
|
||||||
|
background: #1a1a1a;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.17);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s ease;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: rgba(255, 255, 255, 0.35);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="checkout-form"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="error"] {
|
||||||
|
color: #ff6b6b;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="submit-button"] {
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #000;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="charge-notice"] {
|
||||||
|
color: #d4a500;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="success"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
|
||||||
|
[data-slot="title"] {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="details"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="charge-notice"] {
|
||||||
|
color: #d4a500;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="loading"] {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 0;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="fine-print"] {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="workspace-picker"] {
|
||||||
|
[data-slot="workspace-list"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
align-self: stretch;
|
||||||
|
outline: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 240px;
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="workspace-item"] {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding: 8px 12px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
align-self: stretch;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
[data-slot="selected-icon"] {
|
||||||
|
visibility: hidden;
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%;
|
||||||
|
}
|
||||||
|
|
||||||
|
span:last-child {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&[data-active="true"] {
|
||||||
|
background: #161616;
|
||||||
|
|
||||||
|
[data-slot="selected-icon"] {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="footer"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="footer-content"] {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
gap: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span,
|
||||||
|
a {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="github-stars"] {
|
||||||
|
color: rgba(255, 255, 255, 0.25);
|
||||||
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="anomaly"] {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[data-slot="anomaly-alt"] {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,43 @@
|
|||||||
|
import { Match, Switch } from "solid-js"
|
||||||
|
|
||||||
|
export const plans = [
|
||||||
|
{ id: "20", multiplier: null },
|
||||||
|
{ id: "100", multiplier: "6x more usage than Black 20" },
|
||||||
|
{ id: "200", multiplier: "21x more usage than Black 20" },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export type PlanID = (typeof plans)[number]["id"]
|
||||||
|
export type Plan = (typeof plans)[number]
|
||||||
|
|
||||||
|
export function PlanIcon(props: { plan: string }) {
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
<Match when={props.plan === "20"}>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="4" y="4" width="16" height="16" rx="2" stroke="currentColor" stroke-width="1.5" />
|
||||||
|
</svg>
|
||||||
|
</Match>
|
||||||
|
<Match when={props.plan === "100"}>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="3" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.5" />
|
||||||
|
<rect x="14" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.5" />
|
||||||
|
<rect x="3" y="14" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.5" />
|
||||||
|
<rect x="14" y="14" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.5" />
|
||||||
|
</svg>
|
||||||
|
</Match>
|
||||||
|
<Match when={props.plan === "200"}>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="2" y="2" width="4" height="4" rx="0.5" stroke="currentColor" stroke-width="1" />
|
||||||
|
<rect x="10" y="2" width="4" height="4" rx="0.5" stroke="currentColor" stroke-width="1" />
|
||||||
|
<rect x="18" y="2" width="4" height="4" rx="0.5" stroke="currentColor" stroke-width="1" />
|
||||||
|
<rect x="2" y="10" width="4" height="4" rx="0.5" stroke="currentColor" stroke-width="1" />
|
||||||
|
<rect x="10" y="10" width="4" height="4" rx="0.5" stroke="currentColor" stroke-width="1" />
|
||||||
|
<rect x="18" y="10" width="4" height="4" rx="0.5" stroke="currentColor" stroke-width="1" />
|
||||||
|
<rect x="2" y="18" width="4" height="4" rx="0.5" stroke="currentColor" stroke-width="1" />
|
||||||
|
<rect x="10" y="18" width="4" height="4" rx="0.5" stroke="currentColor" stroke-width="1" />
|
||||||
|
<rect x="18" y="18" width="4" height="4" rx="0.5" stroke="currentColor" stroke-width="1" />
|
||||||
|
</svg>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { A, useSearchParams } from "@solidjs/router"
|
||||||
|
import { Title } from "@solidjs/meta"
|
||||||
|
import { createMemo, createSignal, For, Match, Show, Switch } from "solid-js"
|
||||||
|
import { PlanIcon, plans } from "./common"
|
||||||
|
|
||||||
|
export default function Black() {
|
||||||
|
const [params] = useSearchParams()
|
||||||
|
const [selected, setSelected] = createSignal<string | null>((params.plan as string) || null)
|
||||||
|
const selectedPlan = createMemo(() => plans.find((p) => p.id === selected()))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title>opencode</Title>
|
||||||
|
<section data-slot="cta">
|
||||||
|
<Switch>
|
||||||
|
<Match when={!selected()}>
|
||||||
|
<div data-slot="pricing">
|
||||||
|
<For each={plans}>
|
||||||
|
{(plan) => (
|
||||||
|
<button type="button" onClick={() => setSelected(plan.id)} data-slot="pricing-card">
|
||||||
|
<div data-slot="icon">
|
||||||
|
<PlanIcon plan={plan.id} />
|
||||||
|
</div>
|
||||||
|
<p data-slot="price">
|
||||||
|
<span data-slot="amount">${plan.id}</span> <span data-slot="period">per month</span>
|
||||||
|
<Show when={plan.multiplier}>
|
||||||
|
<span data-slot="multiplier">{plan.multiplier}</span>
|
||||||
|
</Show>
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
<p data-slot="fine-print">
|
||||||
|
Prices shown don't include applicable tax · <A href="/legal/terms-of-service">Terms of Service</A>
|
||||||
|
</p>
|
||||||
|
</Match>
|
||||||
|
<Match when={selectedPlan()}>
|
||||||
|
{(plan) => (
|
||||||
|
<div data-slot="selected-plan">
|
||||||
|
<div data-slot="selected-card">
|
||||||
|
<div data-slot="icon">
|
||||||
|
<PlanIcon plan={plan().id} />
|
||||||
|
</div>
|
||||||
|
<p data-slot="price">
|
||||||
|
<span data-slot="amount">${plan().id}</span>{" "}
|
||||||
|
<span data-slot="period">per person billed monthly</span>
|
||||||
|
<Show when={plan().multiplier}>
|
||||||
|
<span data-slot="multiplier">{plan().multiplier}</span>
|
||||||
|
</Show>
|
||||||
|
</p>
|
||||||
|
<ul data-slot="terms">
|
||||||
|
<li>Your subscription will not start immediately</li>
|
||||||
|
<li>You will be added to the waitlist and activated soon</li>
|
||||||
|
<li>Your card will be only charged when your subscription is activated</li>
|
||||||
|
<li>Usage limits apply, heavily automated use may reach limits sooner</li>
|
||||||
|
<li>Subscriptions for individuals, contact Enterprise for teams</li>
|
||||||
|
<li>Limits may be adjusted and plans may be discontinued in the future</li>
|
||||||
|
<li>Cancel your subscription at anytime</li>
|
||||||
|
</ul>
|
||||||
|
<div data-slot="actions">
|
||||||
|
<button type="button" onClick={() => setSelected(null)} data-slot="cancel">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<a href={`/black/subscribe/${plan().id}`} data-slot="continue">
|
||||||
|
Continue
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p data-slot="fine-print">
|
||||||
|
Prices shown don't include applicable tax · <A href="/legal/terms-of-service">Terms of Service</A>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,450 @@
|
|||||||
|
import { A, createAsync, query, redirect, useParams } from "@solidjs/router"
|
||||||
|
import { Title } from "@solidjs/meta"
|
||||||
|
import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js"
|
||||||
|
import { type Stripe, type PaymentMethod, loadStripe } from "@stripe/stripe-js"
|
||||||
|
import { Elements, PaymentElement, useStripe, useElements, AddressElement } from "solid-stripe"
|
||||||
|
import { PlanID, plans } from "../common"
|
||||||
|
import { getActor, useAuthSession } from "~/context/auth"
|
||||||
|
import { withActor } from "~/context/auth.withActor"
|
||||||
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
|
import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js"
|
||||||
|
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
|
||||||
|
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||||
|
import { createList } from "solid-list"
|
||||||
|
import { Modal } from "~/component/modal"
|
||||||
|
import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||||
|
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||||
|
|
||||||
|
const plansMap = Object.fromEntries(plans.map((p) => [p.id, p])) as Record<PlanID, (typeof plans)[number]>
|
||||||
|
const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY!)
|
||||||
|
|
||||||
|
const getWorkspaces = query(async () => {
|
||||||
|
"use server"
|
||||||
|
const actor = await getActor()
|
||||||
|
if (actor.type === "public") throw redirect("/auth/authorize?continue=/black/subscribe")
|
||||||
|
return withActor(async () => {
|
||||||
|
return Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
id: WorkspaceTable.id,
|
||||||
|
name: WorkspaceTable.name,
|
||||||
|
slug: WorkspaceTable.slug,
|
||||||
|
billing: {
|
||||||
|
customerID: BillingTable.customerID,
|
||||||
|
paymentMethodID: BillingTable.paymentMethodID,
|
||||||
|
paymentMethodType: BillingTable.paymentMethodType,
|
||||||
|
paymentMethodLast4: BillingTable.paymentMethodLast4,
|
||||||
|
subscriptionID: BillingTable.subscriptionID,
|
||||||
|
timeSubscriptionBooked: BillingTable.timeSubscriptionBooked,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.from(UserTable)
|
||||||
|
.innerJoin(WorkspaceTable, eq(UserTable.workspaceID, WorkspaceTable.id))
|
||||||
|
.innerJoin(BillingTable, eq(WorkspaceTable.id, BillingTable.workspaceID))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(UserTable.accountID, Actor.account()),
|
||||||
|
isNull(WorkspaceTable.timeDeleted),
|
||||||
|
isNull(UserTable.timeDeleted),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}, "black.subscribe.workspaces")
|
||||||
|
|
||||||
|
const createSetupIntent = async (input: { plan: string; workspaceID: string }) => {
|
||||||
|
"use server"
|
||||||
|
const { plan, workspaceID } = input
|
||||||
|
|
||||||
|
if (!plan || !["20", "100", "200"].includes(plan)) return { error: "Invalid plan" }
|
||||||
|
if (!workspaceID) return { error: "Workspace ID is required" }
|
||||||
|
|
||||||
|
return withActor(async () => {
|
||||||
|
const session = await useAuthSession()
|
||||||
|
const account = session.data.account?.[session.data.current ?? ""]
|
||||||
|
const email = account?.email
|
||||||
|
|
||||||
|
const customer = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
customerID: BillingTable.customerID,
|
||||||
|
subscriptionID: BillingTable.subscriptionID,
|
||||||
|
})
|
||||||
|
.from(BillingTable)
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
if (customer?.subscriptionID) {
|
||||||
|
return { error: "This workspace already has a subscription" }
|
||||||
|
}
|
||||||
|
|
||||||
|
let customerID = customer?.customerID
|
||||||
|
if (!customerID) {
|
||||||
|
const customer = await Billing.stripe().customers.create({
|
||||||
|
email,
|
||||||
|
metadata: {
|
||||||
|
workspaceID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
customerID = customer.id
|
||||||
|
await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({
|
||||||
|
customerID,
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const intent = await Billing.stripe().setupIntents.create({
|
||||||
|
customer: customerID,
|
||||||
|
payment_method_types: ["card"],
|
||||||
|
metadata: {
|
||||||
|
workspaceID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return { clientSecret: intent.client_secret ?? undefined }
|
||||||
|
}, workspaceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookSubscription = async (input: {
|
||||||
|
workspaceID: string
|
||||||
|
plan: PlanID
|
||||||
|
paymentMethodID: string
|
||||||
|
paymentMethodType: string
|
||||||
|
paymentMethodLast4?: string
|
||||||
|
}) => {
|
||||||
|
"use server"
|
||||||
|
return withActor(
|
||||||
|
() =>
|
||||||
|
Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({
|
||||||
|
paymentMethodID: input.paymentMethodID,
|
||||||
|
paymentMethodType: input.paymentMethodType,
|
||||||
|
paymentMethodLast4: input.paymentMethodLast4,
|
||||||
|
subscriptionPlan: input.plan,
|
||||||
|
timeSubscriptionBooked: new Date(),
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, input.workspaceID)),
|
||||||
|
),
|
||||||
|
input.workspaceID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SuccessData {
|
||||||
|
plan: string
|
||||||
|
paymentMethodType: string
|
||||||
|
paymentMethodLast4?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function Failure(props: { message: string }) {
|
||||||
|
return (
|
||||||
|
<div data-slot="failure">
|
||||||
|
<p data-slot="message">Uh oh! {props.message}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Success(props: SuccessData) {
|
||||||
|
return (
|
||||||
|
<div data-slot="success">
|
||||||
|
<p data-slot="title">You're on the OpenCode Black waitlist</p>
|
||||||
|
<dl data-slot="details">
|
||||||
|
<div>
|
||||||
|
<dt>Subscription plan</dt>
|
||||||
|
<dd>OpenCode Black {props.plan}</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>Amount</dt>
|
||||||
|
<dd>${props.plan} per month</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>Payment method</dt>
|
||||||
|
<dd>
|
||||||
|
<Show when={props.paymentMethodLast4} fallback={<span>{props.paymentMethodType}</span>}>
|
||||||
|
<span>
|
||||||
|
{props.paymentMethodType} - {props.paymentMethodLast4}
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>Date joined</dt>
|
||||||
|
<dd>{new Date().toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
<p data-slot="charge-notice">Your card will be charged when your subscription is activated</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IntentForm(props: { plan: PlanID; workspaceID: string; onSuccess: (data: SuccessData) => void }) {
|
||||||
|
const stripe = useStripe()
|
||||||
|
const elements = useElements()
|
||||||
|
const [error, setError] = createSignal<string | undefined>(undefined)
|
||||||
|
const [loading, setLoading] = createSignal(false)
|
||||||
|
|
||||||
|
const handleSubmit = async (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (!stripe() || !elements()) return
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
setError(undefined)
|
||||||
|
|
||||||
|
const result = await elements()!.submit()
|
||||||
|
if (result.error) {
|
||||||
|
setError(result.error.message ?? "An error occurred")
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error: confirmError, setupIntent } = await stripe()!.confirmSetup({
|
||||||
|
elements: elements()!,
|
||||||
|
confirmParams: {
|
||||||
|
expand: ["payment_method"],
|
||||||
|
payment_method_data: {
|
||||||
|
allow_redisplay: "always",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
redirect: "if_required",
|
||||||
|
})
|
||||||
|
|
||||||
|
if (confirmError) {
|
||||||
|
setError(confirmError.message ?? "An error occurred")
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
console.log(setupIntent)
|
||||||
|
if (setupIntent?.status === "succeeded") {
|
||||||
|
const pm = setupIntent.payment_method as PaymentMethod
|
||||||
|
|
||||||
|
await bookSubscription({
|
||||||
|
workspaceID: props.workspaceID,
|
||||||
|
plan: props.plan,
|
||||||
|
paymentMethodID: pm.id,
|
||||||
|
paymentMethodType: pm.type,
|
||||||
|
paymentMethodLast4: pm.card?.last4,
|
||||||
|
})
|
||||||
|
|
||||||
|
props.onSuccess({
|
||||||
|
plan: props.plan,
|
||||||
|
paymentMethodType: pm.type,
|
||||||
|
paymentMethodLast4: pm.card?.last4,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit} data-slot="checkout-form">
|
||||||
|
<PaymentElement />
|
||||||
|
<AddressElement options={{ mode: "billing" }} />
|
||||||
|
<Show when={error()}>
|
||||||
|
<p data-slot="error">{error()}</p>
|
||||||
|
</Show>
|
||||||
|
<button type="submit" disabled={loading() || !stripe() || !elements()} data-slot="submit-button">
|
||||||
|
{loading() ? "Processing..." : `Subscribe $${props.plan}`}
|
||||||
|
</button>
|
||||||
|
<p data-slot="charge-notice">You will only be charged when your subscription is activated</p>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BlackSubscribe() {
|
||||||
|
const workspaces = createAsync(() => getWorkspaces())
|
||||||
|
const [selectedWorkspace, setSelectedWorkspace] = createSignal<string | undefined>(undefined)
|
||||||
|
const [success, setSuccess] = createSignal<SuccessData | undefined>(undefined)
|
||||||
|
const [failure, setFailure] = createSignal<string | undefined>(undefined)
|
||||||
|
const [clientSecret, setClientSecret] = createSignal<string | undefined>(undefined)
|
||||||
|
const [stripe, setStripe] = createSignal<Stripe | undefined>(undefined)
|
||||||
|
const params = useParams()
|
||||||
|
const planData = plansMap[(params.plan as PlanID) ?? "20"] ?? plansMap["20"]
|
||||||
|
const plan = planData.id
|
||||||
|
|
||||||
|
// Resolve stripe promise once
|
||||||
|
createEffect(() => {
|
||||||
|
stripePromise.then((s) => {
|
||||||
|
if (s) setStripe(s)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Auto-select if only one workspace
|
||||||
|
createEffect(() => {
|
||||||
|
const ws = workspaces()
|
||||||
|
if (ws?.length === 1 && !selectedWorkspace()) {
|
||||||
|
setSelectedWorkspace(ws[0].id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Fetch setup intent when workspace is selected (unless workspace already has payment method)
|
||||||
|
createEffect(async () => {
|
||||||
|
const id = selectedWorkspace()
|
||||||
|
if (!id) return
|
||||||
|
|
||||||
|
const ws = workspaces()?.find((w) => w.id === id)
|
||||||
|
if (ws?.billing?.subscriptionID) {
|
||||||
|
setFailure("This workspace already has a subscription")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (ws?.billing?.paymentMethodID) {
|
||||||
|
if (!ws?.billing?.timeSubscriptionBooked) {
|
||||||
|
await bookSubscription({
|
||||||
|
workspaceID: id,
|
||||||
|
plan: planData.id,
|
||||||
|
paymentMethodID: ws.billing.paymentMethodID!,
|
||||||
|
paymentMethodType: ws.billing.paymentMethodType!,
|
||||||
|
paymentMethodLast4: ws.billing.paymentMethodLast4 ?? undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setSuccess({
|
||||||
|
plan: planData.id,
|
||||||
|
paymentMethodType: ws.billing.paymentMethodType!,
|
||||||
|
paymentMethodLast4: ws.billing.paymentMethodLast4 ?? undefined,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await createSetupIntent({ plan, workspaceID: id })
|
||||||
|
if (result.error) {
|
||||||
|
setFailure(result.error)
|
||||||
|
} else if ("clientSecret" in result) {
|
||||||
|
setClientSecret(result.clientSecret)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Keyboard navigation for workspace picker
|
||||||
|
const { active, setActive, onKeyDown } = createList({
|
||||||
|
items: () => workspaces()?.map((w) => w.id) ?? [],
|
||||||
|
initialActive: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSelectWorkspace = (id: string) => {
|
||||||
|
setSelectedWorkspace(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
let listRef: HTMLUListElement | undefined
|
||||||
|
|
||||||
|
// Show workspace picker if multiple workspaces and none selected
|
||||||
|
const showWorkspacePicker = () => {
|
||||||
|
const ws = workspaces()
|
||||||
|
return ws && ws.length > 1 && !selectedWorkspace()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title>Subscribe to OpenCode Black</Title>
|
||||||
|
<section data-slot="subscribe-form">
|
||||||
|
<div data-slot="form-card">
|
||||||
|
<Switch>
|
||||||
|
<Match when={success()}>{(data) => <Success {...data()} />}</Match>
|
||||||
|
<Match when={failure()}>{(data) => <Failure message={data()} />}</Match>
|
||||||
|
<Match when={true}>
|
||||||
|
<>
|
||||||
|
<div data-slot="plan-header">
|
||||||
|
<p data-slot="title">Subscribe to OpenCode Black</p>
|
||||||
|
<p data-slot="price">
|
||||||
|
<span data-slot="amount">${planData.id}</span> <span data-slot="period">per month</span>
|
||||||
|
<Show when={planData.multiplier}>
|
||||||
|
<span data-slot="multiplier">{planData.multiplier}</span>
|
||||||
|
</Show>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div data-slot="divider" />
|
||||||
|
<p data-slot="section-title">Payment method</p>
|
||||||
|
|
||||||
|
<Show
|
||||||
|
when={clientSecret() && selectedWorkspace() && stripe()}
|
||||||
|
fallback={
|
||||||
|
<div data-slot="loading">
|
||||||
|
<p>{selectedWorkspace() ? "Loading payment form..." : "Select a workspace to continue"}</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Elements
|
||||||
|
stripe={stripe()!}
|
||||||
|
options={{
|
||||||
|
clientSecret: clientSecret()!,
|
||||||
|
appearance: {
|
||||||
|
theme: "night",
|
||||||
|
variables: {
|
||||||
|
colorPrimary: "#ffffff",
|
||||||
|
colorBackground: "#1a1a1a",
|
||||||
|
colorText: "#ffffff",
|
||||||
|
colorTextSecondary: "#999999",
|
||||||
|
colorDanger: "#ff6b6b",
|
||||||
|
fontFamily: "JetBrains Mono, monospace",
|
||||||
|
borderRadius: "4px",
|
||||||
|
spacingUnit: "4px",
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
".Input": {
|
||||||
|
backgroundColor: "#1a1a1a",
|
||||||
|
border: "1px solid rgba(255, 255, 255, 0.17)",
|
||||||
|
color: "#ffffff",
|
||||||
|
},
|
||||||
|
".Input:focus": {
|
||||||
|
borderColor: "rgba(255, 255, 255, 0.35)",
|
||||||
|
boxShadow: "none",
|
||||||
|
},
|
||||||
|
".Label": {
|
||||||
|
color: "rgba(255, 255, 255, 0.59)",
|
||||||
|
fontSize: "14px",
|
||||||
|
marginBottom: "8px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IntentForm plan={plan} workspaceID={selectedWorkspace()!} onSuccess={setSuccess} />
|
||||||
|
</Elements>
|
||||||
|
</Show>
|
||||||
|
</>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Workspace picker modal */}
|
||||||
|
<Modal open={showWorkspacePicker() ?? false} onClose={() => {}} title="Select a workspace for this plan">
|
||||||
|
<div data-slot="workspace-picker">
|
||||||
|
<ul
|
||||||
|
ref={listRef}
|
||||||
|
data-slot="workspace-list"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" && active()) {
|
||||||
|
handleSelectWorkspace(active()!)
|
||||||
|
} else {
|
||||||
|
onKeyDown(e)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<For each={workspaces()}>
|
||||||
|
{(workspace) => (
|
||||||
|
<li
|
||||||
|
data-slot="workspace-item"
|
||||||
|
data-active={active() === workspace.id}
|
||||||
|
onMouseEnter={() => setActive(workspace.id)}
|
||||||
|
onClick={() => handleSelectWorkspace(workspace.id)}
|
||||||
|
>
|
||||||
|
<span data-slot="selected-icon">[*]</span>
|
||||||
|
<span>{workspace.name || workspace.slug}</span>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
<p data-slot="fine-print">
|
||||||
|
Prices shown don't include applicable tax · <A href="/legal/terms-of-service">Terms of Service</A>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
[data-page="black"] {
|
||||||
|
background: #000;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: stretch;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
[data-component="header-gradient"] {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 288px;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(0, 0, 0, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="header"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 40px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
/* [data-component="header-logo"] { */
|
||||||
|
/* } */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="content"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
[data-slot="hero-black"] {
|
||||||
|
margin-top: 110px;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
margin-top: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="select-workspace"] {
|
||||||
|
display: flex;
|
||||||
|
margin-top: -24px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 480px;
|
||||||
|
height: 305px;
|
||||||
|
padding: 32px 20px 0 20px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 24px;
|
||||||
|
|
||||||
|
border: 1px solid #303030;
|
||||||
|
background: #0a0a0a;
|
||||||
|
box-shadow:
|
||||||
|
0 100px 80px 0 rgba(0, 0, 0, 0.04),
|
||||||
|
0 41.778px 33.422px 0 rgba(0, 0, 0, 0.05),
|
||||||
|
0 22.336px 17.869px 0 rgba(0, 0, 0, 0.06),
|
||||||
|
0 12.522px 10.017px 0 rgba(0, 0, 0, 0.08),
|
||||||
|
0 6.65px 5.32px 0 rgba(0, 0, 0, 0.09),
|
||||||
|
0 2.767px 2.214px 0 rgba(0, 0, 0, 0.13);
|
||||||
|
|
||||||
|
[data-slot="select-workspace-title"] {
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: stretch;
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%; /* 25.6px */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="workspaces"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
align-self: stretch;
|
||||||
|
outline: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="workspace"] {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding: 8px 12px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
align-self: stretch;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
[data-slot="selected-icon"] {
|
||||||
|
visibility: hidden;
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-family: "IBM Plex Mono";
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%; /* 25.6px */
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%; /* 25.6px */
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="workspace"]:hover,
|
||||||
|
[data-slot="workspace"][data-active="true"] {
|
||||||
|
background: #161616;
|
||||||
|
|
||||||
|
[data-slot="selected-icon"] {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="footer"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="footer-content"] {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
gap: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span,
|
||||||
|
a {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="github-stars"] {
|
||||||
|
color: rgba(255, 255, 255, 0.25);
|
||||||
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="anomaly"] {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[data-slot="anomaly-alt"] {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -24,7 +24,7 @@ export async function GET({ params: { platform } }: APIEvent) {
|
|||||||
const resp = await fetch(`https://github.com/anomalyco/opencode/releases/latest/download/${assetName}`, {
|
const resp = await fetch(`https://github.com/anomalyco/opencode/releases/latest/download/${assetName}`, {
|
||||||
cf: {
|
cf: {
|
||||||
// in case gh releases has rate limits
|
// in case gh releases has rate limits
|
||||||
cacheTtl: 60 * 60 * 24,
|
cacheTtl: 60 * 5,
|
||||||
cacheEverything: true,
|
cacheEverything: true,
|
||||||
},
|
},
|
||||||
} as any)
|
} as any)
|
||||||
|
|||||||
@@ -129,9 +129,9 @@ export default function Download() {
|
|||||||
</code>
|
</code>
|
||||||
<CopyStatus />
|
<CopyStatus />
|
||||||
</button>
|
</button>
|
||||||
<button data-component="cli-row" onClick={handleCopyClick("brew install opencode")}>
|
<button data-component="cli-row" onClick={handleCopyClick("brew install anomalyco/tap/opencode")}>
|
||||||
<code>
|
<code>
|
||||||
brew install <strong>opencode</strong>
|
brew install <strong>anomalyco/tap/opencode</strong>
|
||||||
</code>
|
</code>
|
||||||
<CopyStatus />
|
<CopyStatus />
|
||||||
</button>
|
</button>
|
||||||
@@ -244,7 +244,8 @@ export default function Download() {
|
|||||||
Download
|
Download
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div data-component="download-row">
|
{/* Disabled temporarily as it doesn't work */}
|
||||||
|
{/*<div data-component="download-row">
|
||||||
<div data-component="download-info">
|
<div data-component="download-info">
|
||||||
<span data-slot="icon">
|
<span data-slot="icon">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -259,7 +260,7 @@ export default function Download() {
|
|||||||
<a href={getDownloadHref("linux-x64-appimage")} data-component="action-button">
|
<a href={getDownloadHref("linux-x64-appimage")} data-component="action-button">
|
||||||
Download
|
Download
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>*/}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -522,7 +522,7 @@ body {
|
|||||||
[data-slot="content"] {
|
[data-slot="content"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 1ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="text"] {
|
[data-slot="text"] {
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export default function Home() {
|
|||||||
<button data-copy data-slot="command" onClick={handleCopyClick}>
|
<button data-copy data-slot="command" onClick={handleCopyClick}>
|
||||||
<span>
|
<span>
|
||||||
<span data-slot="protocol">brew install </span>
|
<span data-slot="protocol">brew install </span>
|
||||||
<span data-slot="highlight">opencode</span>
|
<span data-slot="highlight">anomalyco/tap/opencode</span>
|
||||||
</span>
|
</span>
|
||||||
<CopyStatus />
|
<CopyStatus />
|
||||||
</button>
|
</button>
|
||||||
@@ -195,6 +195,12 @@ export default function Home() {
|
|||||||
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
|
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<span>[*]</span>
|
||||||
|
<div>
|
||||||
|
<strong>ChatGPT Plus/Pro</strong> Log in with OpenAI to use your ChatGPT Plus or Pro account
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span>[*]</span>
|
<span>[*]</span>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||||
import type { APIEvent } from "@solidjs/start/server"
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
import { and, Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js"
|
import { and, Database, eq, isNull, sql } from "@opencode-ai/console-core/drizzle/index.js"
|
||||||
import { BillingTable, PaymentTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
import { BillingTable, PaymentTable, SubscriptionTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||||
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
|
||||||
import { Identifier } from "@opencode-ai/console-core/identifier.js"
|
import { Identifier } from "@opencode-ai/console-core/identifier.js"
|
||||||
import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
|
import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
|
||||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
import { Resource } from "@opencode-ai/console-resource"
|
import { Resource } from "@opencode-ai/console-resource"
|
||||||
|
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||||
|
import { AuthTable } from "@opencode-ai/console-core/schema/auth.sql.js"
|
||||||
|
|
||||||
export async function POST(input: APIEvent) {
|
export async function POST(input: APIEvent) {
|
||||||
const body = await Billing.stripe().webhooks.constructEventAsync(
|
const body = await Billing.stripe().webhooks.constructEventAsync(
|
||||||
@@ -40,7 +41,7 @@ export async function POST(input: APIEvent) {
|
|||||||
.where(eq(BillingTable.customerID, customerID))
|
.where(eq(BillingTable.customerID, customerID))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (body.type === "checkout.session.completed") {
|
if (body.type === "checkout.session.completed" && body.data.object.mode === "payment") {
|
||||||
const workspaceID = body.data.object.metadata?.workspaceID
|
const workspaceID = body.data.object.metadata?.workspaceID
|
||||||
const amountInCents = body.data.object.metadata?.amount && parseInt(body.data.object.metadata?.amount)
|
const amountInCents = body.data.object.metadata?.amount && parseInt(body.data.object.metadata?.amount)
|
||||||
const customerID = body.data.object.customer as string
|
const customerID = body.data.object.customer as string
|
||||||
@@ -103,85 +104,112 @@ export async function POST(input: APIEvent) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (body.type === "charge.refunded") {
|
if (body.type === "checkout.session.completed" && body.data.object.mode === "subscription") {
|
||||||
|
const workspaceID = body.data.object.custom_fields.find((f) => f.key === "workspaceid")?.text?.value
|
||||||
|
const amountInCents = body.data.object.amount_total as number
|
||||||
const customerID = body.data.object.customer as string
|
const customerID = body.data.object.customer as string
|
||||||
const paymentIntentID = body.data.object.payment_intent as string
|
const customerEmail = body.data.object.customer_details?.email as string
|
||||||
if (!customerID) throw new Error("Customer ID not found")
|
const invoiceID = body.data.object.invoice as string
|
||||||
if (!paymentIntentID) throw new Error("Payment ID not found")
|
const subscriptionID = body.data.object.subscription as string
|
||||||
|
const promoCode = body.data.object.discounts?.[0]?.promotion_code as string
|
||||||
|
|
||||||
const workspaceID = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select({
|
|
||||||
workspaceID: BillingTable.workspaceID,
|
|
||||||
})
|
|
||||||
.from(BillingTable)
|
|
||||||
.where(eq(BillingTable.customerID, customerID))
|
|
||||||
.then((rows) => rows[0]?.workspaceID),
|
|
||||||
)
|
|
||||||
if (!workspaceID) throw new Error("Workspace ID not found")
|
if (!workspaceID) throw new Error("Workspace ID not found")
|
||||||
|
|
||||||
const amount = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select({
|
|
||||||
amount: PaymentTable.amount,
|
|
||||||
})
|
|
||||||
.from(PaymentTable)
|
|
||||||
.where(and(eq(PaymentTable.paymentID, paymentIntentID), eq(PaymentTable.workspaceID, workspaceID)))
|
|
||||||
.then((rows) => rows[0]?.amount),
|
|
||||||
)
|
|
||||||
if (!amount) throw new Error("Payment not found")
|
|
||||||
|
|
||||||
await Database.transaction(async (tx) => {
|
|
||||||
await tx
|
|
||||||
.update(PaymentTable)
|
|
||||||
.set({
|
|
||||||
timeRefunded: new Date(body.created * 1000),
|
|
||||||
})
|
|
||||||
.where(and(eq(PaymentTable.paymentID, paymentIntentID), eq(PaymentTable.workspaceID, workspaceID)))
|
|
||||||
|
|
||||||
await tx
|
|
||||||
.update(BillingTable)
|
|
||||||
.set({
|
|
||||||
balance: sql`${BillingTable.balance} - ${amount}`,
|
|
||||||
})
|
|
||||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (body.type === "invoice.payment_succeeded" && body.data.object.billing_reason === "subscription_cycle") {
|
|
||||||
const invoiceID = body.data.object.id as string
|
|
||||||
const amountInCents = body.data.object.amount_paid
|
|
||||||
const customerID = body.data.object.customer as string
|
|
||||||
const subscriptionID = body.data.object.parent?.subscription_details?.subscription as string
|
|
||||||
|
|
||||||
if (!customerID) throw new Error("Customer ID not found")
|
if (!customerID) throw new Error("Customer ID not found")
|
||||||
|
if (!amountInCents) throw new Error("Amount not found")
|
||||||
if (!invoiceID) throw new Error("Invoice ID not found")
|
if (!invoiceID) throw new Error("Invoice ID not found")
|
||||||
if (!subscriptionID) throw new Error("Subscription ID not found")
|
if (!subscriptionID) throw new Error("Subscription ID not found")
|
||||||
|
|
||||||
|
// get payment id from invoice
|
||||||
const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
|
const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
|
||||||
expand: ["payments"],
|
expand: ["payments"],
|
||||||
})
|
})
|
||||||
const paymentID = invoice.payments?.data[0].payment.payment_intent as string
|
const paymentID = invoice.payments?.data[0].payment.payment_intent as string
|
||||||
if (!paymentID) throw new Error("Payment ID not found")
|
if (!paymentID) throw new Error("Payment ID not found")
|
||||||
|
|
||||||
const workspaceID = await Database.use((tx) =>
|
// get payment method for the payment intent
|
||||||
tx
|
const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
|
||||||
.select({ workspaceID: BillingTable.workspaceID })
|
expand: ["payment_method"],
|
||||||
.from(BillingTable)
|
})
|
||||||
.where(eq(BillingTable.customerID, customerID))
|
const paymentMethod = paymentIntent.payment_method
|
||||||
.then((rows) => rows[0]?.workspaceID),
|
if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
|
||||||
)
|
|
||||||
if (!workspaceID) throw new Error("Workspace ID not found for customer")
|
|
||||||
|
|
||||||
await Database.use((tx) =>
|
// get coupon id from promotion code
|
||||||
tx.insert(PaymentTable).values({
|
const couponID = await (async () => {
|
||||||
workspaceID,
|
if (!promoCode) return
|
||||||
id: Identifier.create("payment"),
|
const coupon = await Billing.stripe().promotionCodes.retrieve(promoCode)
|
||||||
amount: centsToMicroCents(amountInCents),
|
const couponID = coupon.coupon.id
|
||||||
paymentID,
|
if (!couponID) throw new Error("Coupon not found for promotion code")
|
||||||
invoiceID,
|
return couponID
|
||||||
customerID,
|
})()
|
||||||
}),
|
|
||||||
)
|
// get user
|
||||||
|
|
||||||
|
await Actor.provide("system", { workspaceID }, async () => {
|
||||||
|
// look up current billing
|
||||||
|
const billing = await Billing.get()
|
||||||
|
if (!billing) throw new Error(`Workspace with ID ${workspaceID} not found`)
|
||||||
|
|
||||||
|
// Temporarily skip this check because during Black drop, user can checkout
|
||||||
|
// as a new customer
|
||||||
|
//if (billing.customerID !== customerID) throw new Error("Customer ID mismatch")
|
||||||
|
|
||||||
|
// Temporarily check the user to apply to. After Black drop, we will allow
|
||||||
|
// look up the user to apply to
|
||||||
|
const users = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({ id: UserTable.id, email: AuthTable.subject })
|
||||||
|
.from(UserTable)
|
||||||
|
.innerJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email")))
|
||||||
|
.where(and(eq(UserTable.workspaceID, workspaceID), isNull(UserTable.timeDeleted))),
|
||||||
|
)
|
||||||
|
const user = users.find((u) => u.email === customerEmail) ?? users[0]
|
||||||
|
if (!user) {
|
||||||
|
console.error(`Error: User with email ${customerEmail} not found in workspace ${workspaceID}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set customer metadata
|
||||||
|
if (!billing?.customerID) {
|
||||||
|
await Billing.stripe().customers.update(customerID, {
|
||||||
|
metadata: {
|
||||||
|
workspaceID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await Database.transaction(async (tx) => {
|
||||||
|
await tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({
|
||||||
|
customerID,
|
||||||
|
subscriptionID,
|
||||||
|
subscriptionCouponID: couponID,
|
||||||
|
paymentMethodID: paymentMethod.id,
|
||||||
|
paymentMethodLast4: paymentMethod.card?.last4 ?? null,
|
||||||
|
paymentMethodType: paymentMethod.type,
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
|
|
||||||
|
await tx.insert(SubscriptionTable).values({
|
||||||
|
workspaceID,
|
||||||
|
id: Identifier.create("subscription"),
|
||||||
|
userID: user.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
await tx.insert(PaymentTable).values({
|
||||||
|
workspaceID,
|
||||||
|
id: Identifier.create("payment"),
|
||||||
|
amount: centsToMicroCents(amountInCents),
|
||||||
|
paymentID,
|
||||||
|
invoiceID,
|
||||||
|
customerID,
|
||||||
|
enrichment: {
|
||||||
|
type: "subscription",
|
||||||
|
couponID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (body.type === "customer.subscription.created") {
|
if (body.type === "customer.subscription.created") {
|
||||||
const data = {
|
const data = {
|
||||||
@@ -378,9 +406,111 @@ export async function POST(input: APIEvent) {
|
|||||||
if (!workspaceID) throw new Error("Workspace ID not found for subscription")
|
if (!workspaceID) throw new Error("Workspace ID not found for subscription")
|
||||||
|
|
||||||
await Database.transaction(async (tx) => {
|
await Database.transaction(async (tx) => {
|
||||||
await tx.update(BillingTable).set({ subscriptionID: null }).where(eq(BillingTable.workspaceID, workspaceID))
|
await tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({ subscriptionID: null, subscriptionCouponID: null })
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
|
|
||||||
await tx.update(UserTable).set({ timeSubscribed: null }).where(eq(UserTable.workspaceID, workspaceID))
|
await tx.delete(SubscriptionTable).where(eq(SubscriptionTable.workspaceID, workspaceID))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (body.type === "invoice.payment_succeeded") {
|
||||||
|
if (body.data.object.billing_reason === "subscription_cycle") {
|
||||||
|
const invoiceID = body.data.object.id as string
|
||||||
|
const amountInCents = body.data.object.amount_paid
|
||||||
|
const customerID = body.data.object.customer as string
|
||||||
|
const subscriptionID = body.data.object.parent?.subscription_details?.subscription as string
|
||||||
|
|
||||||
|
if (!customerID) throw new Error("Customer ID not found")
|
||||||
|
if (!invoiceID) throw new Error("Invoice ID not found")
|
||||||
|
if (!subscriptionID) throw new Error("Subscription ID not found")
|
||||||
|
|
||||||
|
// get coupon id from subscription
|
||||||
|
const subscriptionData = await Billing.stripe().subscriptions.retrieve(subscriptionID, {
|
||||||
|
expand: ["discounts"],
|
||||||
|
})
|
||||||
|
const couponID =
|
||||||
|
typeof subscriptionData.discounts[0] === "string"
|
||||||
|
? subscriptionData.discounts[0]
|
||||||
|
: subscriptionData.discounts[0]?.coupon?.id
|
||||||
|
|
||||||
|
// get payment id from invoice
|
||||||
|
const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
|
||||||
|
expand: ["payments"],
|
||||||
|
})
|
||||||
|
const paymentID = invoice.payments?.data[0].payment.payment_intent as string
|
||||||
|
if (!paymentID) {
|
||||||
|
// payment id can be undefined when using coupon
|
||||||
|
if (!couponID) throw new Error("Payment ID not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceID = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({ workspaceID: BillingTable.workspaceID })
|
||||||
|
.from(BillingTable)
|
||||||
|
.where(eq(BillingTable.customerID, customerID))
|
||||||
|
.then((rows) => rows[0]?.workspaceID),
|
||||||
|
)
|
||||||
|
if (!workspaceID) throw new Error("Workspace ID not found for customer")
|
||||||
|
|
||||||
|
await Database.use((tx) =>
|
||||||
|
tx.insert(PaymentTable).values({
|
||||||
|
workspaceID,
|
||||||
|
id: Identifier.create("payment"),
|
||||||
|
amount: centsToMicroCents(amountInCents),
|
||||||
|
paymentID,
|
||||||
|
invoiceID,
|
||||||
|
customerID,
|
||||||
|
enrichment: {
|
||||||
|
type: "subscription",
|
||||||
|
couponID,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (body.type === "charge.refunded") {
|
||||||
|
const customerID = body.data.object.customer as string
|
||||||
|
const paymentIntentID = body.data.object.payment_intent as string
|
||||||
|
if (!customerID) throw new Error("Customer ID not found")
|
||||||
|
if (!paymentIntentID) throw new Error("Payment ID not found")
|
||||||
|
|
||||||
|
const workspaceID = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
workspaceID: BillingTable.workspaceID,
|
||||||
|
})
|
||||||
|
.from(BillingTable)
|
||||||
|
.where(eq(BillingTable.customerID, customerID))
|
||||||
|
.then((rows) => rows[0]?.workspaceID),
|
||||||
|
)
|
||||||
|
if (!workspaceID) throw new Error("Workspace ID not found")
|
||||||
|
|
||||||
|
const amount = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
amount: PaymentTable.amount,
|
||||||
|
})
|
||||||
|
.from(PaymentTable)
|
||||||
|
.where(and(eq(PaymentTable.paymentID, paymentIntentID), eq(PaymentTable.workspaceID, workspaceID)))
|
||||||
|
.then((rows) => rows[0]?.amount),
|
||||||
|
)
|
||||||
|
if (!amount) throw new Error("Payment not found")
|
||||||
|
|
||||||
|
await Database.transaction(async (tx) => {
|
||||||
|
await tx
|
||||||
|
.update(PaymentTable)
|
||||||
|
.set({
|
||||||
|
timeRefunded: new Date(body.created * 1000),
|
||||||
|
})
|
||||||
|
.where(and(eq(PaymentTable.paymentID, paymentIntentID), eq(PaymentTable.workspaceID, workspaceID)))
|
||||||
|
|
||||||
|
await tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({
|
||||||
|
balance: sql`${BillingTable.balance} - ${amount}`,
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ export default function Home() {
|
|||||||
<li>
|
<li>
|
||||||
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
|
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>ChatGPT Plus/Pro</strong> Log in with OpenAI to use your ChatGPT Plus or Pro account
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Use any model</strong> Supports 75+ LLM providers through{" "}
|
<strong>Use any model</strong> Supports 75+ LLM providers through{" "}
|
||||||
<a href="https://models.dev">Models.dev</a>, including local models
|
<a href="https://models.dev">Models.dev</a>, including local models
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { action } from "@solidjs/router"
|
import { action } from "@solidjs/router"
|
||||||
import { getRequestEvent } from "solid-js/web"
|
import { getRequestEvent } from "solid-js/web"
|
||||||
import { useAuthSession } from "~/context/auth.session"
|
import { useAuthSession } from "~/context/auth"
|
||||||
import { Dropdown } from "~/component/dropdown"
|
import { Dropdown } from "~/component/dropdown"
|
||||||
import "./user-menu.css"
|
import "./user-menu.css"
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export default function () {
|
|||||||
<div data-page="workspace-[id]">
|
<div data-page="workspace-[id]">
|
||||||
<div data-slot="sections">
|
<div data-slot="sections">
|
||||||
<Show when={sessionInfo()?.isAdmin}>
|
<Show when={sessionInfo()?.isAdmin}>
|
||||||
<Show when={sessionInfo()?.isBeta && billingInfo()?.subscriptionID}>
|
<Show when={billingInfo()?.subscriptionID}>
|
||||||
<BlackSection />
|
<BlackSection />
|
||||||
</Show>
|
</Show>
|
||||||
<BillingSection />
|
<BillingSection />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||||
import { query, action, useParams, createAsync, useAction } from "@solidjs/router"
|
import { query, action, useParams, createAsync, useAction } from "@solidjs/router"
|
||||||
import { For, Show } from "solid-js"
|
import { For, Match, Show, Switch } from "solid-js"
|
||||||
import { withActor } from "~/context/auth.withActor"
|
import { withActor } from "~/context/auth.withActor"
|
||||||
import { formatDateUTC, formatDateForTable } from "../../common"
|
import { formatDateUTC, formatDateForTable } from "../../common"
|
||||||
import styles from "./payment-section.module.css"
|
import styles from "./payment-section.module.css"
|
||||||
@@ -77,7 +77,8 @@ export function PaymentSection() {
|
|||||||
<For each={payments()!}>
|
<For each={payments()!}>
|
||||||
{(payment) => {
|
{(payment) => {
|
||||||
const date = new Date(payment.timeCreated)
|
const date = new Date(payment.timeCreated)
|
||||||
const isCredit = !payment.paymentID
|
const amount =
|
||||||
|
payment.enrichment?.type === "subscription" && payment.enrichment.couponID ? 0 : payment.amount
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td data-slot="payment-date" title={formatDateUTC(date)}>
|
<td data-slot="payment-date" title={formatDateUTC(date)}>
|
||||||
@@ -85,13 +86,14 @@ export function PaymentSection() {
|
|||||||
</td>
|
</td>
|
||||||
<td data-slot="payment-id">{payment.id}</td>
|
<td data-slot="payment-id">{payment.id}</td>
|
||||||
<td data-slot="payment-amount" data-refunded={!!payment.timeRefunded}>
|
<td data-slot="payment-amount" data-refunded={!!payment.timeRefunded}>
|
||||||
${((payment.amount ?? 0) / 100000000).toFixed(2)}
|
${((amount ?? 0) / 100000000).toFixed(2)}
|
||||||
{isCredit ? " (credit)" : ""}
|
<Switch>
|
||||||
|
<Match when={payment.enrichment?.type === "credit"}> (credit)</Match>
|
||||||
|
<Match when={payment.enrichment?.type === "subscription"}> (subscription)</Match>
|
||||||
|
</Switch>
|
||||||
</td>
|
</td>
|
||||||
<td data-slot="payment-receipt">
|
<td data-slot="payment-receipt">
|
||||||
{isCredit ? (
|
{payment.paymentID ? (
|
||||||
<span>-</span>
|
|
||||||
) : (
|
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const receiptUrl = await downloadReceiptAction(params.id!, payment.paymentID!)
|
const receiptUrl = await downloadReceiptAction(params.id!, payment.paymentID!)
|
||||||
@@ -103,6 +105,8 @@ export function PaymentSection() {
|
|||||||
>
|
>
|
||||||
View
|
View
|
||||||
</button>
|
</button>
|
||||||
|
) : (
|
||||||
|
<span>-</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ async function getCosts(workspaceID: string, year: number, month: number) {
|
|||||||
eq(UsageTable.workspaceID, workspaceID),
|
eq(UsageTable.workspaceID, workspaceID),
|
||||||
gte(UsageTable.timeCreated, startDate),
|
gte(UsageTable.timeCreated, startDate),
|
||||||
lte(UsageTable.timeCreated, endDate),
|
lte(UsageTable.timeCreated, endDate),
|
||||||
|
or(isNull(UsageTable.enrichment), sql`JSON_EXTRACT(${UsageTable.enrichment}, '$.plan') != 'sub'`),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.groupBy(sql`DATE(${UsageTable.timeCreated})`, UsageTable.model, UsageTable.keyID)
|
.groupBy(sql`DATE(${UsageTable.timeCreated})`, UsageTable.model, UsageTable.keyID)
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server"
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
import { and, Database, eq, isNull, lt, or, sql } from "@opencode-ai/console-core/drizzle/index.js"
|
import { and, Database, eq, isNull, lt, or, sql } from "@opencode-ai/console-core/drizzle/index.js"
|
||||||
import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
|
import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
|
||||||
import { BillingTable, UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
import { BillingTable, SubscriptionTable, UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||||
import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
|
import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
|
||||||
|
import { getWeekBounds } from "@opencode-ai/console-core/util/date.js"
|
||||||
import { Identifier } from "@opencode-ai/console-core/identifier.js"
|
import { Identifier } from "@opencode-ai/console-core/identifier.js"
|
||||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
@@ -415,11 +416,11 @@ export async function handler(
|
|||||||
timeMonthlyUsageUpdated: UserTable.timeMonthlyUsageUpdated,
|
timeMonthlyUsageUpdated: UserTable.timeMonthlyUsageUpdated,
|
||||||
},
|
},
|
||||||
subscription: {
|
subscription: {
|
||||||
timeSubscribed: UserTable.timeSubscribed,
|
id: SubscriptionTable.id,
|
||||||
subIntervalUsage: UserTable.subIntervalUsage,
|
rollingUsage: SubscriptionTable.rollingUsage,
|
||||||
subMonthlyUsage: UserTable.subMonthlyUsage,
|
fixedUsage: SubscriptionTable.fixedUsage,
|
||||||
timeSubIntervalUsageUpdated: UserTable.timeSubIntervalUsageUpdated,
|
timeRollingUpdated: SubscriptionTable.timeRollingUpdated,
|
||||||
timeSubMonthlyUsageUpdated: UserTable.timeSubMonthlyUsageUpdated,
|
timeFixedUpdated: SubscriptionTable.timeFixedUpdated,
|
||||||
},
|
},
|
||||||
provider: {
|
provider: {
|
||||||
credentials: ProviderTable.credentials,
|
credentials: ProviderTable.credentials,
|
||||||
@@ -440,6 +441,14 @@ export async function handler(
|
|||||||
)
|
)
|
||||||
: sql`false`,
|
: sql`false`,
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
SubscriptionTable,
|
||||||
|
and(
|
||||||
|
eq(SubscriptionTable.workspaceID, KeyTable.workspaceID),
|
||||||
|
eq(SubscriptionTable.userID, KeyTable.userID),
|
||||||
|
isNull(SubscriptionTable.timeDeleted),
|
||||||
|
),
|
||||||
|
)
|
||||||
.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
|
.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
|
||||||
.then((rows) => rows[0]),
|
.then((rows) => rows[0]),
|
||||||
)
|
)
|
||||||
@@ -448,7 +457,7 @@ export async function handler(
|
|||||||
logger.metric({
|
logger.metric({
|
||||||
api_key: data.apiKey,
|
api_key: data.apiKey,
|
||||||
workspace: data.workspaceID,
|
workspace: data.workspaceID,
|
||||||
isSubscription: data.subscription.timeSubscribed ? true : false,
|
isSubscription: data.subscription ? true : false,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -456,7 +465,7 @@ export async function handler(
|
|||||||
workspaceID: data.workspaceID,
|
workspaceID: data.workspaceID,
|
||||||
billing: data.billing,
|
billing: data.billing,
|
||||||
user: data.user,
|
user: data.user,
|
||||||
subscription: data.subscription.timeSubscribed ? data.subscription : undefined,
|
subscription: data.subscription,
|
||||||
provider: data.provider,
|
provider: data.provider,
|
||||||
isFree: FREE_WORKSPACES.includes(data.workspaceID),
|
isFree: FREE_WORKSPACES.includes(data.workspaceID),
|
||||||
isDisabled: !!data.timeDisabled,
|
isDisabled: !!data.timeDisabled,
|
||||||
@@ -484,23 +493,11 @@ export async function handler(
|
|||||||
return `${minutes}min`
|
return `${minutes}min`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check monthly limit (based on subscription billing cycle)
|
// Check weekly limit
|
||||||
if (
|
if (sub.fixedUsage && sub.timeFixedUpdated) {
|
||||||
sub.subMonthlyUsage &&
|
const week = getWeekBounds(now)
|
||||||
sub.timeSubMonthlyUsageUpdated &&
|
if (sub.timeFixedUpdated >= week.start && sub.fixedUsage >= centsToMicroCents(black.fixedLimit * 100)) {
|
||||||
sub.subMonthlyUsage >= centsToMicroCents(black.monthlyLimit * 100)
|
const retryAfter = Math.ceil((week.end.getTime() - now.getTime()) / 1000)
|
||||||
) {
|
|
||||||
const subscribeDay = sub.timeSubscribed!.getUTCDate()
|
|
||||||
const cycleStart = new Date(
|
|
||||||
Date.UTC(
|
|
||||||
now.getUTCFullYear(),
|
|
||||||
now.getUTCDate() >= subscribeDay ? now.getUTCMonth() : now.getUTCMonth() - 1,
|
|
||||||
subscribeDay,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
const cycleEnd = new Date(Date.UTC(cycleStart.getUTCFullYear(), cycleStart.getUTCMonth() + 1, subscribeDay))
|
|
||||||
if (sub.timeSubMonthlyUsageUpdated >= cycleStart && sub.timeSubMonthlyUsageUpdated < cycleEnd) {
|
|
||||||
const retryAfter = Math.ceil((cycleEnd.getTime() - now.getTime()) / 1000)
|
|
||||||
throw new SubscriptionError(
|
throw new SubscriptionError(
|
||||||
`Subscription quota exceeded. Retry in ${formatRetryTime(retryAfter)}.`,
|
`Subscription quota exceeded. Retry in ${formatRetryTime(retryAfter)}.`,
|
||||||
retryAfter,
|
retryAfter,
|
||||||
@@ -508,14 +505,12 @@ export async function handler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check interval limit
|
// Check rolling limit
|
||||||
const intervalMs = black.intervalLength * 3600 * 1000
|
if (sub.rollingUsage && sub.timeRollingUpdated) {
|
||||||
if (sub.subIntervalUsage && sub.timeSubIntervalUsageUpdated) {
|
const rollingWindowMs = black.rollingWindow * 3600 * 1000
|
||||||
const currentInterval = Math.floor(now.getTime() / intervalMs)
|
const windowStart = new Date(now.getTime() - rollingWindowMs)
|
||||||
const usageInterval = Math.floor(sub.timeSubIntervalUsageUpdated.getTime() / intervalMs)
|
if (sub.timeRollingUpdated >= windowStart && sub.rollingUsage >= centsToMicroCents(black.rollingLimit * 100)) {
|
||||||
if (currentInterval === usageInterval && sub.subIntervalUsage >= centsToMicroCents(black.intervalLimit * 100)) {
|
const retryAfter = Math.ceil((sub.timeRollingUpdated.getTime() + rollingWindowMs - now.getTime()) / 1000)
|
||||||
const nextInterval = (currentInterval + 1) * intervalMs
|
|
||||||
const retryAfter = Math.ceil((nextInterval - now.getTime()) / 1000)
|
|
||||||
throw new SubscriptionError(
|
throw new SubscriptionError(
|
||||||
`Subscription quota exceeded. Retry in ${formatRetryTime(retryAfter)}.`,
|
`Subscription quota exceeded. Retry in ${formatRetryTime(retryAfter)}.`,
|
||||||
retryAfter,
|
retryAfter,
|
||||||
@@ -661,38 +656,39 @@ export async function handler(
|
|||||||
.where(and(eq(KeyTable.workspaceID, authInfo.workspaceID), eq(KeyTable.id, authInfo.apiKeyId))),
|
.where(and(eq(KeyTable.workspaceID, authInfo.workspaceID), eq(KeyTable.id, authInfo.apiKeyId))),
|
||||||
...(authInfo.subscription
|
...(authInfo.subscription
|
||||||
? (() => {
|
? (() => {
|
||||||
const now = new Date()
|
const black = BlackData.get()
|
||||||
const subscribeDay = authInfo.subscription.timeSubscribed!.getUTCDate()
|
const week = getWeekBounds(new Date())
|
||||||
const cycleStart = new Date(
|
const rollingWindowSeconds = black.rollingWindow * 3600
|
||||||
Date.UTC(
|
|
||||||
now.getUTCFullYear(),
|
|
||||||
now.getUTCDate() >= subscribeDay ? now.getUTCMonth() : now.getUTCMonth() - 1,
|
|
||||||
subscribeDay,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
const cycleEnd = new Date(
|
|
||||||
Date.UTC(cycleStart.getUTCFullYear(), cycleStart.getUTCMonth() + 1, subscribeDay),
|
|
||||||
)
|
|
||||||
return [
|
return [
|
||||||
db
|
db
|
||||||
.update(UserTable)
|
.update(SubscriptionTable)
|
||||||
.set({
|
.set({
|
||||||
subMonthlyUsage: sql`
|
fixedUsage: sql`
|
||||||
CASE
|
CASE
|
||||||
WHEN ${UserTable.timeSubMonthlyUsageUpdated} >= ${cycleStart} AND ${UserTable.timeSubMonthlyUsageUpdated} < ${cycleEnd} THEN ${UserTable.subMonthlyUsage} + ${cost}
|
WHEN ${SubscriptionTable.timeFixedUpdated} >= ${week.start} THEN ${SubscriptionTable.fixedUsage} + ${cost}
|
||||||
ELSE ${cost}
|
ELSE ${cost}
|
||||||
END
|
END
|
||||||
`,
|
`,
|
||||||
timeSubMonthlyUsageUpdated: sql`now()`,
|
timeFixedUpdated: sql`now()`,
|
||||||
subIntervalUsage: sql`
|
rollingUsage: sql`
|
||||||
CASE
|
CASE
|
||||||
WHEN FLOOR(UNIX_TIMESTAMP(${UserTable.timeSubIntervalUsageUpdated}) / (${BlackData.get().intervalLength} * 3600)) = FLOOR(UNIX_TIMESTAMP(now()) / (${BlackData.get().intervalLength} * 3600)) THEN ${UserTable.subIntervalUsage} + ${cost}
|
WHEN UNIX_TIMESTAMP(${SubscriptionTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${SubscriptionTable.rollingUsage} + ${cost}
|
||||||
ELSE ${cost}
|
ELSE ${cost}
|
||||||
END
|
END
|
||||||
`,
|
`,
|
||||||
timeSubIntervalUsageUpdated: sql`now()`,
|
timeRollingUpdated: sql`
|
||||||
|
CASE
|
||||||
|
WHEN UNIX_TIMESTAMP(${SubscriptionTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${SubscriptionTable.timeRollingUpdated}
|
||||||
|
ELSE now()
|
||||||
|
END
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
.where(and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id))),
|
.where(
|
||||||
|
and(
|
||||||
|
eq(SubscriptionTable.workspaceID, authInfo.workspaceID),
|
||||||
|
eq(SubscriptionTable.userID, authInfo.user.id),
|
||||||
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
})()
|
})()
|
||||||
: [
|
: [
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE `subscription` (
|
||||||
|
`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` varchar(30) NOT NULL,
|
||||||
|
`rolling_usage` bigint,
|
||||||
|
`fixed_usage` bigint,
|
||||||
|
`time_rolling_updated` timestamp(3),
|
||||||
|
`time_fixed_updated` timestamp(3),
|
||||||
|
CONSTRAINT `subscription_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`)
|
||||||
|
);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE INDEX `workspace_user_id` ON `subscription` (`workspace_id`,`user_id`);--> statement-breakpoint
|
||||||
|
ALTER TABLE `user` DROP COLUMN `time_subscribed`;--> statement-breakpoint
|
||||||
|
ALTER TABLE `user` DROP COLUMN `sub_interval_usage`;--> statement-breakpoint
|
||||||
|
ALTER TABLE `user` DROP COLUMN `sub_monthly_usage`;--> statement-breakpoint
|
||||||
|
ALTER TABLE `user` DROP COLUMN `sub_time_interval_usage_updated`;--> statement-breakpoint
|
||||||
|
ALTER TABLE `user` DROP COLUMN `sub_time_monthly_usage_updated`;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
DROP INDEX `workspace_user_id` ON `subscription`;--> statement-breakpoint
|
||||||
|
ALTER TABLE `subscription` ADD CONSTRAINT `workspace_user_id` UNIQUE(`workspace_id`,`user_id`);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `billing` ADD `subscription_coupon_id` varchar(28);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `payment` ADD `enrichment` json;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `billing` ADD `time_subscription_booked` timestamp(3);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `billing` ADD `subscription_plan` enum('20','100','200');
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -323,6 +323,55 @@
|
|||||||
"when": 1767765497502,
|
"when": 1767765497502,
|
||||||
"tag": "0045_cuddly_diamondback",
|
"tag": "0045_cuddly_diamondback",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 46,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1767912262458,
|
||||||
|
"tag": "0046_charming_black_bolt",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 47,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1767916965243,
|
||||||
|
"tag": "0047_huge_omega_red",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 48,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1767917785224,
|
||||||
|
"tag": "0048_mean_frank_castle",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 49,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1767922954153,
|
||||||
|
"tag": "0049_noisy_domino",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 50,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1767931290031,
|
||||||
|
"tag": "0050_bumpy_mephistopheles",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 51,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1768341152722,
|
||||||
|
"tag": "0051_jazzy_green_goblin",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 52,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1768343920467,
|
||||||
|
"tag": "0052_aromatic_agent_zero",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.1.6",
|
"version": "1.1.20",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { Database, eq, sql, inArray } from "../src/drizzle/index.js"
|
import { Database, and, eq, sql } from "../src/drizzle/index.js"
|
||||||
import { AuthTable } from "../src/schema/auth.sql.js"
|
import { AuthTable } from "../src/schema/auth.sql.js"
|
||||||
import { UserTable } from "../src/schema/user.sql.js"
|
import { UserTable } from "../src/schema/user.sql.js"
|
||||||
import { BillingTable, PaymentTable, UsageTable } from "../src/schema/billing.sql.js"
|
import { BillingTable, PaymentTable, SubscriptionTable, UsageTable } from "../src/schema/billing.sql.js"
|
||||||
import { WorkspaceTable } from "../src/schema/workspace.sql.js"
|
import { WorkspaceTable } from "../src/schema/workspace.sql.js"
|
||||||
|
import { BlackData } from "../src/black.js"
|
||||||
|
import { centsToMicroCents } from "../src/util/price.js"
|
||||||
|
import { getWeekBounds } from "../src/util/date.js"
|
||||||
|
|
||||||
// get input from command line
|
// get input from command line
|
||||||
const identifier = process.argv[2]
|
const identifier = process.argv[2]
|
||||||
@@ -35,10 +38,21 @@ if (identifier.startsWith("wrk_")) {
|
|||||||
workspaceID: UserTable.workspaceID,
|
workspaceID: UserTable.workspaceID,
|
||||||
workspaceName: WorkspaceTable.name,
|
workspaceName: WorkspaceTable.name,
|
||||||
role: UserTable.role,
|
role: UserTable.role,
|
||||||
|
subscribed: SubscriptionTable.timeCreated,
|
||||||
})
|
})
|
||||||
.from(UserTable)
|
.from(UserTable)
|
||||||
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
|
.rightJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
|
||||||
.where(eq(UserTable.accountID, accountID)),
|
.leftJoin(SubscriptionTable, eq(SubscriptionTable.userID, UserTable.id))
|
||||||
|
.where(eq(UserTable.accountID, accountID))
|
||||||
|
.then((rows) =>
|
||||||
|
rows.map((row) => ({
|
||||||
|
userID: row.userID,
|
||||||
|
workspaceID: row.workspaceID,
|
||||||
|
workspaceName: row.workspaceName,
|
||||||
|
role: row.role,
|
||||||
|
subscribed: formatDate(row.subscribed),
|
||||||
|
})),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get all payments for these workspaces
|
// Get all payments for these workspaces
|
||||||
@@ -56,11 +70,51 @@ async function printWorkspace(workspaceID: string) {
|
|||||||
|
|
||||||
printHeader(`Workspace "${workspace.name}" (${workspace.id})`)
|
printHeader(`Workspace "${workspace.name}" (${workspace.id})`)
|
||||||
|
|
||||||
|
await printTable("Users", (tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
authEmail: AuthTable.subject,
|
||||||
|
inviteEmail: UserTable.email,
|
||||||
|
role: UserTable.role,
|
||||||
|
timeSeen: UserTable.timeSeen,
|
||||||
|
monthlyLimit: UserTable.monthlyLimit,
|
||||||
|
monthlyUsage: UserTable.monthlyUsage,
|
||||||
|
timeDeleted: UserTable.timeDeleted,
|
||||||
|
fixedUsage: SubscriptionTable.fixedUsage,
|
||||||
|
rollingUsage: SubscriptionTable.rollingUsage,
|
||||||
|
timeFixedUpdated: SubscriptionTable.timeFixedUpdated,
|
||||||
|
timeRollingUpdated: SubscriptionTable.timeRollingUpdated,
|
||||||
|
timeSubscriptionCreated: SubscriptionTable.timeCreated,
|
||||||
|
})
|
||||||
|
.from(UserTable)
|
||||||
|
.leftJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")))
|
||||||
|
.leftJoin(SubscriptionTable, eq(SubscriptionTable.userID, UserTable.id))
|
||||||
|
.where(eq(UserTable.workspaceID, workspace.id))
|
||||||
|
.then((rows) =>
|
||||||
|
rows.map((row) => {
|
||||||
|
const subStatus = getSubscriptionStatus(row)
|
||||||
|
return {
|
||||||
|
email: (row.timeDeleted ? "❌ " : "") + (row.authEmail ?? row.inviteEmail),
|
||||||
|
role: row.role,
|
||||||
|
timeSeen: formatDate(row.timeSeen),
|
||||||
|
monthly: formatMonthlyUsage(row.monthlyUsage, row.monthlyLimit),
|
||||||
|
subscribed: formatDate(row.timeSubscriptionCreated),
|
||||||
|
subWeekly: subStatus.weekly,
|
||||||
|
subRolling: subStatus.rolling,
|
||||||
|
rateLimited: subStatus.rateLimited,
|
||||||
|
retryIn: subStatus.retryIn,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
await printTable("Billing", (tx) =>
|
await printTable("Billing", (tx) =>
|
||||||
tx
|
tx
|
||||||
.select({
|
.select({
|
||||||
balance: BillingTable.balance,
|
balance: BillingTable.balance,
|
||||||
customerID: BillingTable.customerID,
|
customerID: BillingTable.customerID,
|
||||||
|
subscriptionID: BillingTable.subscriptionID,
|
||||||
|
subscriptionCouponID: BillingTable.subscriptionCouponID,
|
||||||
})
|
})
|
||||||
.from(BillingTable)
|
.from(BillingTable)
|
||||||
.where(eq(BillingTable.workspaceID, workspace.id))
|
.where(eq(BillingTable.workspaceID, workspace.id))
|
||||||
@@ -97,6 +151,7 @@ async function printWorkspace(workspaceID: string) {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
await printTable("Usage", (tx) =>
|
await printTable("Usage", (tx) =>
|
||||||
tx
|
tx
|
||||||
.select({
|
.select({
|
||||||
@@ -122,6 +177,81 @@ async function printWorkspace(workspaceID: string) {
|
|||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMicroCents(value: number | null | undefined) {
|
||||||
|
if (value === null || value === undefined) return null
|
||||||
|
return `$${(value / 100000000).toFixed(2)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(value: Date | null | undefined) {
|
||||||
|
if (!value) return null
|
||||||
|
return value.toISOString().split("T")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMonthlyUsage(usage: number | null | undefined, limit: number | null | undefined) {
|
||||||
|
const usageText = formatMicroCents(usage) ?? "$0.00"
|
||||||
|
if (limit === null || limit === undefined) return `${usageText} / no limit`
|
||||||
|
return `${usageText} / $${limit.toFixed(2)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatRetryTime(seconds: number) {
|
||||||
|
const days = Math.floor(seconds / 86400)
|
||||||
|
if (days >= 1) return `${days} day${days > 1 ? "s" : ""}`
|
||||||
|
const hours = Math.floor(seconds / 3600)
|
||||||
|
const minutes = Math.ceil((seconds % 3600) / 60)
|
||||||
|
if (hours >= 1) return `${hours}hr ${minutes}min`
|
||||||
|
return `${minutes}min`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSubscriptionStatus(row: {
|
||||||
|
timeSubscriptionCreated: Date | null
|
||||||
|
fixedUsage: number | null
|
||||||
|
rollingUsage: number | null
|
||||||
|
timeFixedUpdated: Date | null
|
||||||
|
timeRollingUpdated: Date | null
|
||||||
|
}) {
|
||||||
|
if (!row.timeSubscriptionCreated) {
|
||||||
|
return { weekly: null, rolling: null, rateLimited: null, retryIn: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
const black = BlackData.get()
|
||||||
|
const now = new Date()
|
||||||
|
const week = getWeekBounds(now)
|
||||||
|
|
||||||
|
const fixedLimit = black.fixedLimit ? centsToMicroCents(black.fixedLimit * 100) : null
|
||||||
|
const rollingLimit = black.rollingLimit ? centsToMicroCents(black.rollingLimit * 100) : null
|
||||||
|
const rollingWindowMs = (black.rollingWindow ?? 5) * 3600 * 1000
|
||||||
|
|
||||||
|
// Calculate current weekly usage (reset if outside current week)
|
||||||
|
const currentWeekly =
|
||||||
|
row.fixedUsage && row.timeFixedUpdated && row.timeFixedUpdated >= week.start ? row.fixedUsage : 0
|
||||||
|
|
||||||
|
// Calculate current rolling usage
|
||||||
|
const windowStart = new Date(now.getTime() - rollingWindowMs)
|
||||||
|
const currentRolling =
|
||||||
|
row.rollingUsage && row.timeRollingUpdated && row.timeRollingUpdated >= windowStart ? row.rollingUsage : 0
|
||||||
|
|
||||||
|
// Check rate limiting
|
||||||
|
const isWeeklyLimited = fixedLimit !== null && currentWeekly >= fixedLimit
|
||||||
|
const isRollingLimited = rollingLimit !== null && currentRolling >= rollingLimit
|
||||||
|
|
||||||
|
let retryIn: string | null = null
|
||||||
|
if (isWeeklyLimited) {
|
||||||
|
const retryAfter = Math.ceil((week.end.getTime() - now.getTime()) / 1000)
|
||||||
|
retryIn = formatRetryTime(retryAfter)
|
||||||
|
} else if (isRollingLimited && row.timeRollingUpdated) {
|
||||||
|
const retryAfter = Math.ceil((row.timeRollingUpdated.getTime() + rollingWindowMs - now.getTime()) / 1000)
|
||||||
|
retryIn = formatRetryTime(retryAfter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
weekly: fixedLimit !== null ? `${formatMicroCents(currentWeekly)} / $${black.fixedLimit}` : null,
|
||||||
|
rolling: rollingLimit !== null ? `${formatMicroCents(currentRolling)} / $${black.rollingLimit}` : null,
|
||||||
|
rateLimited: isWeeklyLimited || isRollingLimited ? "yes" : "no",
|
||||||
|
retryIn,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function printHeader(title: string) {
|
function printHeader(title: string) {
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
import { Billing } from "../src/billing.js"
|
import { Billing } from "../src/billing.js"
|
||||||
import { Database, eq, and, sql } from "../src/drizzle/index.js"
|
import { and, Database, eq, isNull, sql } from "../src/drizzle/index.js"
|
||||||
import { AuthTable } from "../src/schema/auth.sql.js"
|
|
||||||
import { UserTable } from "../src/schema/user.sql.js"
|
import { UserTable } from "../src/schema/user.sql.js"
|
||||||
import { BillingTable, PaymentTable } from "../src/schema/billing.sql.js"
|
import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js"
|
||||||
import { Identifier } from "../src/identifier.js"
|
import { Identifier } from "../src/identifier.js"
|
||||||
import { centsToMicroCents } from "../src/util/price.js"
|
import { centsToMicroCents } from "../src/util/price.js"
|
||||||
|
import { AuthTable } from "../src/schema/auth.sql.js"
|
||||||
|
|
||||||
const workspaceID = process.argv[2]
|
const workspaceID = process.argv[2]
|
||||||
const email = process.argv[3]
|
const email = process.argv[3]
|
||||||
|
|
||||||
|
console.log(`Onboarding workspace ${workspaceID} for email ${email}`)
|
||||||
|
|
||||||
if (!workspaceID || !email) {
|
if (!workspaceID || !email) {
|
||||||
console.error("Usage: bun onboard-zen-black.ts <workspaceID> <email>")
|
console.error("Usage: bun onboard-zen-black.ts <workspaceID> <email>")
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the Stripe customer by email
|
// Look up the Stripe customer by email
|
||||||
const customers = await Billing.stripe().customers.list({ email, limit: 1 })
|
const customers = await Billing.stripe().customers.list({ email, limit: 10, expand: ["data.subscriptions"] })
|
||||||
const customer = customers.data[0]
|
if (!customers.data) {
|
||||||
if (!customer) {
|
|
||||||
console.error(`Error: No Stripe customer found for email ${email}`)
|
console.error(`Error: No Stripe customer found for email ${email}`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
const customerID = customer.id
|
const customer = customers.data.find((c) => c.subscriptions?.data[0]?.items.data[0]?.price.unit_amount === 20000)
|
||||||
|
if (!customer) {
|
||||||
// Get the subscription id
|
console.error(`Error: No Stripe customer found for email ${email} with $200 subscription`)
|
||||||
const subscriptions = await Billing.stripe().subscriptions.list({ customer: customerID, limit: 1 })
|
|
||||||
const subscription = subscriptions.data[0]
|
|
||||||
if (!subscription) {
|
|
||||||
console.error(`Error: Customer ${customerID} does not have a subscription`)
|
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const customerID = customer.id
|
||||||
|
const subscription = customer.subscriptions!.data[0]
|
||||||
const subscriptionID = subscription.id
|
const subscriptionID = subscription.id
|
||||||
|
|
||||||
// Validate the subscription is $200
|
// Validate the subscription is $200
|
||||||
@@ -39,6 +39,12 @@ if (amountInCents !== 20000) {
|
|||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subscriptionData = await Billing.stripe().subscriptions.retrieve(subscription.id, { expand: ["discounts"] })
|
||||||
|
const couponID =
|
||||||
|
typeof subscriptionData.discounts[0] === "string"
|
||||||
|
? subscriptionData.discounts[0]
|
||||||
|
: subscriptionData.discounts[0]?.coupon?.id
|
||||||
|
|
||||||
// Check if subscription is already tied to another workspace
|
// Check if subscription is already tied to another workspace
|
||||||
const existingSubscription = await Database.use((tx) =>
|
const existingSubscription = await Database.use((tx) =>
|
||||||
tx
|
tx
|
||||||
@@ -90,29 +96,21 @@ const paymentMethod = paymentMethodID ? await Billing.stripe().paymentMethods.re
|
|||||||
const paymentMethodLast4 = paymentMethod?.card?.last4 ?? null
|
const paymentMethodLast4 = paymentMethod?.card?.last4 ?? null
|
||||||
const paymentMethodType = paymentMethod?.type ?? null
|
const paymentMethodType = paymentMethod?.type ?? null
|
||||||
|
|
||||||
// Look up the user by email via AuthTable
|
// Look up the user in the workspace
|
||||||
const auth = await Database.use((tx) =>
|
const users = await Database.use((tx) =>
|
||||||
tx
|
tx
|
||||||
.select({ accountID: AuthTable.accountID })
|
.select({ id: UserTable.id, email: AuthTable.subject })
|
||||||
.from(AuthTable)
|
.from(UserTable)
|
||||||
.where(and(eq(AuthTable.provider, "email"), eq(AuthTable.subject, email)))
|
.innerJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email")))
|
||||||
.then((rows) => rows[0]),
|
.where(and(eq(UserTable.workspaceID, workspaceID), isNull(UserTable.timeDeleted))),
|
||||||
)
|
)
|
||||||
if (!auth) {
|
if (users.length === 0) {
|
||||||
console.error(`Error: No user found with email ${email}`)
|
console.error(`Error: No users found in workspace ${workspaceID}`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
const user = users.length === 1 ? users[0] : users.find((u) => u.email === email)
|
||||||
// Look up the user in the workspace
|
|
||||||
const user = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select({ id: UserTable.id })
|
|
||||||
.from(UserTable)
|
|
||||||
.where(and(eq(UserTable.workspaceID, workspaceID), eq(UserTable.accountID, auth.accountID)))
|
|
||||||
.then((rows) => rows[0]),
|
|
||||||
)
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.error(`Error: User with email ${email} is not a member of workspace ${workspaceID}`)
|
console.error(`Error: User with email ${email} not found in workspace ${workspaceID}`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,19 +128,19 @@ await Database.transaction(async (tx) => {
|
|||||||
.set({
|
.set({
|
||||||
customerID,
|
customerID,
|
||||||
subscriptionID,
|
subscriptionID,
|
||||||
|
subscriptionCouponID: couponID,
|
||||||
paymentMethodID,
|
paymentMethodID,
|
||||||
paymentMethodLast4,
|
paymentMethodLast4,
|
||||||
paymentMethodType,
|
paymentMethodType,
|
||||||
})
|
})
|
||||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
|
|
||||||
// Set current time as timeSubscribed on user
|
// Create a row in subscription table
|
||||||
await tx
|
await tx.insert(SubscriptionTable).values({
|
||||||
.update(UserTable)
|
workspaceID,
|
||||||
.set({
|
id: Identifier.create("subscription"),
|
||||||
timeSubscribed: sql`now()`,
|
userID: user.id,
|
||||||
})
|
})
|
||||||
.where(eq(UserTable.id, user.id))
|
|
||||||
|
|
||||||
// Create a row in payments table
|
// Create a row in payments table
|
||||||
await tx.insert(PaymentTable).values({
|
await tx.insert(PaymentTable).values({
|
||||||
@@ -152,6 +150,10 @@ await Database.transaction(async (tx) => {
|
|||||||
customerID,
|
customerID,
|
||||||
invoiceID,
|
invoiceID,
|
||||||
paymentID,
|
paymentID,
|
||||||
|
enrichment: {
|
||||||
|
type: "subscription",
|
||||||
|
couponID,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { Billing } from "../src/billing.js"
|
||||||
|
import { and, Database, eq } from "../src/drizzle/index.js"
|
||||||
|
import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js"
|
||||||
|
|
||||||
|
const workspaceID = process.argv[2]
|
||||||
|
|
||||||
|
if (!workspaceID) {
|
||||||
|
console.error("Usage: bun remove-black.ts <workspaceID>")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Removing subscription from workspace ${workspaceID}`)
|
||||||
|
|
||||||
|
// Look up the workspace billing
|
||||||
|
const billing = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
customerID: BillingTable.customerID,
|
||||||
|
subscriptionID: BillingTable.subscriptionID,
|
||||||
|
})
|
||||||
|
.from(BillingTable)
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!billing) {
|
||||||
|
console.error(`Error: No billing record found for workspace ${workspaceID}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!billing.subscriptionID) {
|
||||||
|
console.error(`Error: Workspace ${workspaceID} does not have a subscription`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` Customer ID: ${billing.customerID}`)
|
||||||
|
console.log(` Subscription ID: ${billing.subscriptionID}`)
|
||||||
|
|
||||||
|
// Clear workspaceID from Stripe customer metadata
|
||||||
|
if (billing.customerID) {
|
||||||
|
//await Billing.stripe().customers.update(billing.customerID, {
|
||||||
|
// metadata: {
|
||||||
|
// workspaceID: "",
|
||||||
|
// },
|
||||||
|
//})
|
||||||
|
//console.log(`Cleared workspaceID from Stripe customer metadata`)
|
||||||
|
}
|
||||||
|
|
||||||
|
await Database.transaction(async (tx) => {
|
||||||
|
// Clear subscription-related fields from billing table
|
||||||
|
await tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({
|
||||||
|
// customerID: null,
|
||||||
|
subscriptionID: null,
|
||||||
|
subscriptionCouponID: null,
|
||||||
|
// paymentMethodID: null,
|
||||||
|
// paymentMethodLast4: null,
|
||||||
|
// paymentMethodType: null,
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
|
|
||||||
|
// Delete from subscription table
|
||||||
|
await tx.delete(SubscriptionTable).where(eq(SubscriptionTable.workspaceID, workspaceID))
|
||||||
|
|
||||||
|
// Delete from payments table
|
||||||
|
await tx
|
||||||
|
.delete(PaymentTable)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(PaymentTable.workspaceID, workspaceID),
|
||||||
|
eq(PaymentTable.enrichment, { type: "subscription" }),
|
||||||
|
eq(PaymentTable.amount, 20000000000),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Successfully removed subscription from workspace ${workspaceID}`)
|
||||||
@@ -171,6 +171,9 @@ export namespace Billing {
|
|||||||
workspaceID,
|
workspaceID,
|
||||||
id: Identifier.create("payment"),
|
id: Identifier.create("payment"),
|
||||||
amount: amountInMicroCents,
|
amount: amountInMicroCents,
|
||||||
|
enrichment: {
|
||||||
|
type: "credit",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return amountInMicroCents
|
return amountInMicroCents
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { Resource } from "@opencode-ai/console-resource"
|
|||||||
|
|
||||||
export namespace BlackData {
|
export namespace BlackData {
|
||||||
const Schema = z.object({
|
const Schema = z.object({
|
||||||
monthlyLimit: z.number().int(),
|
fixedLimit: z.number().int(),
|
||||||
intervalLimit: z.number().int(),
|
rollingLimit: z.number().int(),
|
||||||
intervalLength: z.number().int(),
|
rollingWindow: z.number().int(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const validate = fn(Schema, (input) => {
|
export const validate = fn(Schema, (input) => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export namespace Identifier {
|
|||||||
model: "mod",
|
model: "mod",
|
||||||
payment: "pay",
|
payment: "pay",
|
||||||
provider: "prv",
|
provider: "prv",
|
||||||
|
subscription: "sub",
|
||||||
usage: "usg",
|
usage: "usg",
|
||||||
user: "usr",
|
user: "usr",
|
||||||
workspace: "wrk",
|
workspace: "wrk",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user