mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-03 00:36:20 -04:00
Compare commits
259 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f22580e943 | |||
| 6d98db57c7 | |||
| 59f127a250 | |||
| 3068e7dcf7 | |||
| f83d62191a | |||
| 3b72857124 | |||
| 68cd105d9d | |||
| e09af2cb4b | |||
| 14bd3b1d30 | |||
| 3a9c2152f7 | |||
| 7283bfa480 | |||
| 37d5099728 | |||
| d45fc030b2 | |||
| c7042c807f | |||
| 202f6f1be9 | |||
| 759635eefa | |||
| a9981441ae | |||
| 71302de4f1 | |||
| 333b8e907b | |||
| 13f319b64f | |||
| b573eadd9e | |||
| 50bfff89c0 | |||
| fc5fc2c570 | |||
| 4069999b78 | |||
| 5ba9b47b3c | |||
| 7c0cc94023 | |||
| 3ed1bd2e8e | |||
| ce6436280a | |||
| e49204bd33 | |||
| 856c87d05c | |||
| de35c3fb84 | |||
| 4359719f9a | |||
| 5e13527416 | |||
| aba94c658f | |||
| 6e318ba567 | |||
| ddddecf88a | |||
| 16cb77c094 | |||
| a5564f730e | |||
| a15c97bbfe | |||
| a398eed8b8 | |||
| a10fd8ca5c | |||
| ff7513238b | |||
| af1cd60d3e | |||
| c66def2049 | |||
| 008ccb4729 | |||
| bc232045a1 | |||
| 16cab556df | |||
| 66148df74b | |||
| 4611e08f09 | |||
| bf6204f577 | |||
| 17cde9feb7 | |||
| 7eccbdc4ac | |||
| ab072290fc | |||
| ad9d83748c | |||
| 55b57e1aae | |||
| 21b7877beb | |||
| de50234a1a | |||
| d60102ba52 | |||
| 066a876f3d | |||
| c07a241ca8 | |||
| 0a2fffa9b5 | |||
| bdfa213ccf | |||
| 7f0b2ce1ac | |||
| 0a2d7af179 | |||
| 37652f48fb | |||
| 8b19c6c7e4 | |||
| a5365ce294 | |||
| f4a4514a9f | |||
| 154006469c | |||
| a1214fff2e | |||
| 9fd43ec616 | |||
| 5731c268b6 | |||
| f4d892d4e1 | |||
| 10b3702938 | |||
| e96442310c | |||
| 5c722bf8c4 | |||
| 58cc5cdf2a | |||
| 3c6dcad2af | |||
| 2535f9febf | |||
| 25678fa504 | |||
| d7f4f3ec1f | |||
| 16ccb39459 | |||
| f8630fb188 | |||
| 72e604744d | |||
| 832be6e7eb | |||
| 8ba48ed71d | |||
| cf266f6162 | |||
| 1e6589526d | |||
| f6b3ffaf64 | |||
| 5d765d63d4 | |||
| 0e12dd62a3 | |||
| 2b957b5d1c | |||
| 31c7a0157c | |||
| e728b94bca | |||
| 49040c0130 | |||
| 0d05238ee6 | |||
| 9b8a7da1e6 | |||
| 61fd21182c | |||
| 487c2b5e76 | |||
| 0e4703b227 | |||
| 84e0232bd5 | |||
| 35fbb011b2 | |||
| 6527a123f0 | |||
| 0377cfd37c | |||
| edc933d816 | |||
| 0d608f6014 | |||
| 69a45ef7d7 | |||
| 1056b36eae | |||
| 35c737ac68 | |||
| 725a2c2e95 | |||
| c724d2392f | |||
| f5230d1f02 | |||
| 078111bd96 | |||
| 736f8882f5 | |||
| 37cf365927 | |||
| b939470302 | |||
| ef4b2baedc | |||
| 64d28ea457 | |||
| 2520780846 | |||
| 986c60353e | |||
| 5fc26c958a | |||
| c1cf9cda6a | |||
| 10d376eab2 | |||
| 53fc8a861b | |||
| 1d8330331c | |||
| 7a03c7fe38 | |||
| 09bd32169c | |||
| 7ec32f834e | |||
| 205492c7e8 | |||
| 4c2e888709 | |||
| c78fd097d1 | |||
| 340966195b | |||
| 92604b391b | |||
| 0c51feb9c2 | |||
| d0b4169a6b | |||
| 1fc6c6fb2a | |||
| 14f9b95557 | |||
| d3bf1fa1fa | |||
| a8836c5615 | |||
| 779a27693a | |||
| 829d86840a | |||
| e225294dd4 | |||
| a673e3650d | |||
| ff462dfd7a | |||
| 73443585e5 | |||
| 609ab069a9 | |||
| ec3579d7cb | |||
| f80a3fea31 | |||
| 43a8d1b1ae | |||
| 09fa84ccfc | |||
| b981f0a205 | |||
| 767038afc3 | |||
| a7774115c5 | |||
| 288bc88e40 | |||
| 6d36dbf9de | |||
| 4ab4baf3a4 | |||
| 90f05eb9c2 | |||
| b63b6d04c6 | |||
| 8addaa7e08 | |||
| a96bf8e62d | |||
| c8bda598f5 | |||
| c857cff585 | |||
| fd9d2db755 | |||
| b19fd14f80 | |||
| a0f469095c | |||
| 0ccb26df94 | |||
| 71fd5966ad | |||
| c02230de4f | |||
| aa2e2c76c0 | |||
| 7c2d4ee79a | |||
| e3a2728fa3 | |||
| 18260b037b | |||
| ad83dd3ad9 | |||
| 6f37315cd1 | |||
| d81dce6a82 | |||
| 0bd11e970b | |||
| 7e29e1dd23 | |||
| 491a2adf8d | |||
| c07d6487a8 | |||
| 9990e84d37 | |||
| 0b86adbe99 | |||
| 834a2c09d5 | |||
| f13c17e654 | |||
| a0611d92e4 | |||
| 0b001c3e80 | |||
| 53b7cb62c4 | |||
| c5e096c76a | |||
| e1fc4a756b | |||
| e5bc4cbbcf | |||
| 459d5ec19b | |||
| 8baa222621 | |||
| ce1397cc34 | |||
| dc7c5ced4c | |||
| b8e8fe7e31 | |||
| 890085758f | |||
| 85f15893bc | |||
| 98be75b17c | |||
| b5cc27b8ea | |||
| 05937b52cc | |||
| 62b82570e1 | |||
| 4bf75c0b44 | |||
| a8a06c4983 | |||
| b0b7fd143b | |||
| 140498eb4f | |||
| ca5126e24d | |||
| fb2b3e567c | |||
| c672a1963b | |||
| 54bff6b120 | |||
| ab3f198fab | |||
| 0057ef6336 | |||
| 4f604b3839 | |||
| a20489584e | |||
| a6b066bd47 | |||
| 37fdcac05a | |||
| 299bf1dca8 | |||
| d685aa38ef | |||
| 995b23787c | |||
| ed8e663e13 | |||
| 38cee3b848 | |||
| 6d116d4b54 | |||
| 7c4f111b34 | |||
| f2fac29270 | |||
| 12892f0e12 | |||
| 9714a3558e | |||
| e49a1d1f39 | |||
| 528565510d | |||
| 36cfda933d | |||
| ecf5040966 | |||
| 7d56603c26 | |||
| 02b7cc8313 | |||
| c9a52c9a85 | |||
| dea668b0ea | |||
| 1bc3e92376 | |||
| 3f5acc3dff | |||
| 0588011476 | |||
| bba72c82ae | |||
| e95181a551 | |||
| 74e8c2e50f | |||
| cdabafa264 | |||
| 0a92af60a0 | |||
| c7808a4b01 | |||
| 7f978e07ff | |||
| a4ae1bb9eb | |||
| 96a39803cc | |||
| 16f8f20b31 | |||
| 06b1684ddb | |||
| c6e830c954 | |||
| fc78c28df6 | |||
| 930a1bf358 | |||
| 6cf7f18cc9 | |||
| 3f59570ee6 | |||
| 304e956b5d | |||
| 538eaa42aa | |||
| 67c41fd389 | |||
| 83ea19770a | |||
| 3ace8543b2 | |||
| 7088bfabd7 | |||
| dbdbfb8543 | |||
| 521803aaa3 |
@@ -13,9 +13,9 @@ runs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lockb', 'bun.lock') }}
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('package.json') }}-${{ hashFiles('bun.lockb', 'bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
${{ runner.os }}-bun-${{ hashFiles('package.json') }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#
|
||||
# This file is intentionally in the wrong dir, will move and add later....
|
||||
#
|
||||
|
||||
name: Guidelines Check
|
||||
|
||||
on:
|
||||
@@ -27,12 +27,12 @@ jobs:
|
||||
{
|
||||
"bash": {
|
||||
"gh issue*": "allow",
|
||||
"*": "deny"
|
||||
},
|
||||
"*": "deny"
|
||||
},
|
||||
"webfetch": "deny"
|
||||
}
|
||||
run: |
|
||||
opencode run -m anthropic/claude-sonnet-4-20250514 "A new issue has been created:'
|
||||
opencode run -m opencode/claude-haiku-4-5 "A new issue has been created:'
|
||||
|
||||
Issue number:
|
||||
${{ github.event.issue.number }}
|
||||
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- opentui
|
||||
- fix-snapshot-2
|
||||
- v0
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
@@ -28,3 +28,9 @@ jobs:
|
||||
bun turbo test
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Check SDK is up to date
|
||||
run: |
|
||||
bun ./packages/sdk/js/script/build.ts
|
||||
git diff --exit-code packages/sdk/js/src/gen packages/sdk/js/dist
|
||||
continue-on-error: false
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
name: Update Nix Hashes
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "bun.lock"
|
||||
- "package.json"
|
||||
- "packages/*/package.json"
|
||||
pull_request:
|
||||
paths:
|
||||
- "bun.lock"
|
||||
- "package.json"
|
||||
- "packages/*/package.json"
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SYSTEM: x86_64-linux
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- 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: Update node_modules hash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
nix/scripts/update-hashes.sh
|
||||
|
||||
- name: Commit hash changes
|
||||
env:
|
||||
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
summarize() {
|
||||
local status="$1"
|
||||
{
|
||||
echo "### Nix Hash Update"
|
||||
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=(flake.nix nix/node-modules.nix nix/hashes.json)
|
||||
STATUS="$(git status --short -- "${FILES[@]}" || true)"
|
||||
if [ -z "$STATUS" ]; then
|
||||
summarize "no changes"
|
||||
echo "No changes to tracked Nix files. Hashes are already up to date."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git add "${FILES[@]}"
|
||||
git commit -m "Update Nix hashes"
|
||||
|
||||
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
||||
git push origin HEAD:"$BRANCH"
|
||||
|
||||
summarize "committed $(git rev-parse --short HEAD)"
|
||||
@@ -13,3 +13,7 @@ dist
|
||||
.turbo
|
||||
**/.serena
|
||||
.serena/
|
||||
/result
|
||||
refs
|
||||
Session.vim
|
||||
opencode.json
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
description: Git commit and push
|
||||
subtask: true
|
||||
---
|
||||
|
||||
commit and push
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
description: "Find issue(s) on github"
|
||||
model: opencode/claude-haiku-4-5
|
||||
---
|
||||
|
||||
Search through existing issues in sst/opencode using the gh cli to find issues matching this query:
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
Consider:
|
||||
|
||||
1. Similar titles or descriptions
|
||||
2. Same error messages or symptoms
|
||||
3. Related functionality or components
|
||||
4. Similar feature requests
|
||||
|
||||
Please list any matching issues with:
|
||||
|
||||
- Issue number and title
|
||||
- Brief explanation of why it matches the query
|
||||
- Link to the issue
|
||||
|
||||
If no clear matches are found, say so.
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugin": ["opencode-openai-codex-auth"],
|
||||
"mcp": {
|
||||
"weather": {
|
||||
"type": "local",
|
||||
"command": ["bun", "x", "@h1deya/mcp-server-weather"]
|
||||
},
|
||||
"context7": {
|
||||
"type": "remote",
|
||||
"url": "https://mcp.context7.com/mcp",
|
||||
"headers": {
|
||||
"CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugin": ["opencode-openai-codex-auth"],
|
||||
"provider": {
|
||||
"opencode": {
|
||||
"options": {
|
||||
// "baseURL": "http://localhost:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/theme.json",
|
||||
"defs": {
|
||||
"nord0": "#2E3440",
|
||||
"nord1": "#3B4252",
|
||||
"nord2": "#434C5E",
|
||||
"nord3": "#4C566A",
|
||||
"nord4": "#D8DEE9",
|
||||
"nord5": "#E5E9F0",
|
||||
"nord6": "#ECEFF4",
|
||||
"nord7": "#8FBCBB",
|
||||
"nord8": "#88C0D0",
|
||||
"nord9": "#81A1C1",
|
||||
"nord10": "#5E81AC",
|
||||
"nord11": "#BF616A",
|
||||
"nord12": "#D08770",
|
||||
"nord13": "#EBCB8B",
|
||||
"nord14": "#A3BE8C",
|
||||
"nord15": "#B48EAD"
|
||||
},
|
||||
"theme": {
|
||||
"primary": {
|
||||
"dark": "nord8",
|
||||
"light": "nord10"
|
||||
},
|
||||
"secondary": {
|
||||
"dark": "nord9",
|
||||
"light": "nord9"
|
||||
},
|
||||
"accent": {
|
||||
"dark": "nord7",
|
||||
"light": "nord7"
|
||||
},
|
||||
"error": {
|
||||
"dark": "nord11",
|
||||
"light": "nord11"
|
||||
},
|
||||
"warning": {
|
||||
"dark": "nord12",
|
||||
"light": "nord12"
|
||||
},
|
||||
"success": {
|
||||
"dark": "nord14",
|
||||
"light": "nord14"
|
||||
},
|
||||
"info": {
|
||||
"dark": "nord8",
|
||||
"light": "nord10"
|
||||
},
|
||||
"text": {
|
||||
"dark": "nord4",
|
||||
"light": "nord0"
|
||||
},
|
||||
"textMuted": {
|
||||
"dark": "nord3",
|
||||
"light": "nord1"
|
||||
},
|
||||
"background": {
|
||||
"dark": "nord0",
|
||||
"light": "nord6"
|
||||
},
|
||||
"backgroundPanel": {
|
||||
"dark": "nord1",
|
||||
"light": "nord5"
|
||||
},
|
||||
"backgroundElement": {
|
||||
"dark": "nord1",
|
||||
"light": "nord4"
|
||||
},
|
||||
"border": {
|
||||
"dark": "nord2",
|
||||
"light": "nord3"
|
||||
},
|
||||
"borderActive": {
|
||||
"dark": "nord3",
|
||||
"light": "nord2"
|
||||
},
|
||||
"borderSubtle": {
|
||||
"dark": "nord2",
|
||||
"light": "nord3"
|
||||
},
|
||||
"diffAdded": {
|
||||
"dark": "nord14",
|
||||
"light": "nord14"
|
||||
},
|
||||
"diffRemoved": {
|
||||
"dark": "nord11",
|
||||
"light": "nord11"
|
||||
},
|
||||
"diffContext": {
|
||||
"dark": "nord3",
|
||||
"light": "nord3"
|
||||
},
|
||||
"diffHunkHeader": {
|
||||
"dark": "nord3",
|
||||
"light": "nord3"
|
||||
},
|
||||
"diffHighlightAdded": {
|
||||
"dark": "nord14",
|
||||
"light": "nord14"
|
||||
},
|
||||
"diffHighlightRemoved": {
|
||||
"dark": "nord11",
|
||||
"light": "nord11"
|
||||
},
|
||||
"diffAddedBg": {
|
||||
"dark": "#3B4252",
|
||||
"light": "#E5E9F0"
|
||||
},
|
||||
"diffRemovedBg": {
|
||||
"dark": "#3B4252",
|
||||
"light": "#E5E9F0"
|
||||
},
|
||||
"diffContextBg": {
|
||||
"dark": "nord1",
|
||||
"light": "nord5"
|
||||
},
|
||||
"diffLineNumber": {
|
||||
"dark": "nord2",
|
||||
"light": "nord4"
|
||||
},
|
||||
"diffAddedLineNumberBg": {
|
||||
"dark": "#3B4252",
|
||||
"light": "#E5E9F0"
|
||||
},
|
||||
"diffRemovedLineNumberBg": {
|
||||
"dark": "#3B4252",
|
||||
"light": "#E5E9F0"
|
||||
},
|
||||
"markdownText": {
|
||||
"dark": "nord4",
|
||||
"light": "nord0"
|
||||
},
|
||||
"markdownHeading": {
|
||||
"dark": "nord8",
|
||||
"light": "nord10"
|
||||
},
|
||||
"markdownLink": {
|
||||
"dark": "nord9",
|
||||
"light": "nord9"
|
||||
},
|
||||
"markdownLinkText": {
|
||||
"dark": "nord7",
|
||||
"light": "nord7"
|
||||
},
|
||||
"markdownCode": {
|
||||
"dark": "nord14",
|
||||
"light": "nord14"
|
||||
},
|
||||
"markdownBlockQuote": {
|
||||
"dark": "nord3",
|
||||
"light": "nord3"
|
||||
},
|
||||
"markdownEmph": {
|
||||
"dark": "nord12",
|
||||
"light": "nord12"
|
||||
},
|
||||
"markdownStrong": {
|
||||
"dark": "nord13",
|
||||
"light": "nord13"
|
||||
},
|
||||
"markdownHorizontalRule": {
|
||||
"dark": "nord3",
|
||||
"light": "nord3"
|
||||
},
|
||||
"markdownListItem": {
|
||||
"dark": "nord8",
|
||||
"light": "nord10"
|
||||
},
|
||||
"markdownListEnumeration": {
|
||||
"dark": "nord7",
|
||||
"light": "nord7"
|
||||
},
|
||||
"markdownImage": {
|
||||
"dark": "nord9",
|
||||
"light": "nord9"
|
||||
},
|
||||
"markdownImageText": {
|
||||
"dark": "nord7",
|
||||
"light": "nord7"
|
||||
},
|
||||
"markdownCodeBlock": {
|
||||
"dark": "nord4",
|
||||
"light": "nord0"
|
||||
},
|
||||
"syntaxComment": {
|
||||
"dark": "nord3",
|
||||
"light": "nord3"
|
||||
},
|
||||
"syntaxKeyword": {
|
||||
"dark": "nord9",
|
||||
"light": "nord9"
|
||||
},
|
||||
"syntaxFunction": {
|
||||
"dark": "nord8",
|
||||
"light": "nord8"
|
||||
},
|
||||
"syntaxVariable": {
|
||||
"dark": "nord7",
|
||||
"light": "nord7"
|
||||
},
|
||||
"syntaxString": {
|
||||
"dark": "nord14",
|
||||
"light": "nord14"
|
||||
},
|
||||
"syntaxNumber": {
|
||||
"dark": "nord15",
|
||||
"light": "nord15"
|
||||
},
|
||||
"syntaxType": {
|
||||
"dark": "nord7",
|
||||
"light": "nord7"
|
||||
},
|
||||
"syntaxOperator": {
|
||||
"dark": "nord9",
|
||||
"light": "nord9"
|
||||
},
|
||||
"syntaxPunctuation": {
|
||||
"dark": "nord4",
|
||||
"light": "nord0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "bun",
|
||||
"request": "attach",
|
||||
"name": "opencode (attach)",
|
||||
"url": "ws://localhost:6499/"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"oven.bun-vscode"
|
||||
]
|
||||
}
|
||||
@@ -42,6 +42,38 @@ Want to take on an issue? Leave a comment and a maintainer may assign it to you
|
||||
> [!NOTE]
|
||||
> After touching `packages/opencode/src/server/server.ts`, run "./packages/sdk/js/script/build.ts" to regenerate the JS sdk.
|
||||
|
||||
### Setting up a Debugger
|
||||
|
||||
Bun debugging is currently rough around the edges. We hope this guide helps you get set up and avoid some pain points.
|
||||
|
||||
The most reliable way to debug OpenCode is to run it manually in a terminal via `bun run --inspect=<url> dev ...` and attach
|
||||
your debugger via that URL. Other methods can result in breakpoints being mapped incorrectly, at least in VSCode (YMMV).
|
||||
|
||||
Caveats:
|
||||
|
||||
- `*.tsx` files won't have their breakpoints correctly mapped. This seems due to Bun currently not supporting source maps on code transformed
|
||||
via `BunPlugin`s (currently necessary due to our dependency on `@opentui/solid`). Currently, the best you can do in terms of debugging `*.tsx`
|
||||
files is writing a `debugger;` statement. Debugging facilities like stepping won't work, but at least you will be informed if a specific code
|
||||
is triggered.
|
||||
- If you want to run the OpenCode TUI and have breakpoints triggered in the server code, you might need to run `bun dev spawn` instead of
|
||||
the usual `bun dev`. This is because `bun dev` runs the server in a worker thread and breakpoints might not work there.
|
||||
|
||||
Other tips and tricks:
|
||||
|
||||
- You might want to use `--inspect-wait` or `--inspect-brk` instead of `--inspect`, depending on your workflow
|
||||
- Specifying `--inspect=ws://localhost:6499/` on every invocation can be tiresome, you may want to `export BUN_OPTIONS=--inspect=ws://localhost:6499/` instead
|
||||
|
||||
#### VSCode Setup
|
||||
|
||||
If you use VSCode, you can use our example configurations [.vscode/settings.example.json](.vscode/settings.example.json) and [.vscode/launch.example.json](.vscode/launch.example.json).
|
||||
|
||||
Some debug methods that can be problematic:
|
||||
|
||||
- Debug configurations with `"request": "launch"` can have breakpoints incorrectly mapped and thus unusable
|
||||
- The same problem arises when running OpenCode in the VSCode `JavaScript Debug Terminal`
|
||||
|
||||
With that said, you may want to try these methods, as they might work for you.
|
||||
|
||||
## Pull Request Expectations
|
||||
|
||||
- Try to keep pull requests small and focused.
|
||||
|
||||
@@ -28,8 +28,10 @@ curl -fsSL https://opencode.ai/install | bash
|
||||
npm i -g opencode-ai@latest # or bun/pnpm/yarn
|
||||
scoop bucket add extras; scoop install extras/opencode # Windows
|
||||
choco install opencode # Windows
|
||||
brew install opencode # macOS and Linux
|
||||
brew install opencode # macOS and Linux
|
||||
paru -S opencode-bin # Arch Linux
|
||||
mise use --pin -g ubi:sst/opencode # Any OS
|
||||
nix run nixpkgs#opencode # or github:sst/opencode for latest dev branch
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
@@ -50,6 +52,22 @@ OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bas
|
||||
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
|
||||
```
|
||||
|
||||
### Agents
|
||||
|
||||
OpenCode includes two built-in agents you can switch between,
|
||||
you can switch between these using the `Tab` key.
|
||||
|
||||
- **build** - Default, full access agent for development work
|
||||
- **plan** - Read-only agent for analysis and code exploration
|
||||
- Denies file edits by default
|
||||
- Asks permission before running bash commands
|
||||
- Ideal for exploring unfamiliar codebases or planning changes
|
||||
|
||||
Also, included is a **general** subagent for complex searches and multi-step tasks.
|
||||
This is used internally and can be invoked using `@general` in messages.
|
||||
|
||||
Learn more about [agents](https://opencode.ai/docs/agents).
|
||||
|
||||
### Documentation
|
||||
|
||||
For more info on how to configure OpenCode [**head over to our docs**](https://opencode.ai/docs).
|
||||
@@ -58,6 +76,10 @@ For more info on how to configure OpenCode [**head over to our docs**](https://o
|
||||
|
||||
If you're interested in contributing to OpenCode, please read our [contributing docs](./CONTRIBUTING.md) before submitting a pull request.
|
||||
|
||||
### Building on OpenCode
|
||||
|
||||
If you are working on a project that's related to OpenCode and is using "opencode" as a part of its name; for example, "opencode-dashboard" or "opencode-mobile", please add a note to your README to clarify that it is not built by the OpenCode team and is not affiliated with us in anyway.
|
||||
|
||||
### FAQ
|
||||
|
||||
#### How is this different than Claude Code?
|
||||
|
||||
@@ -135,3 +135,12 @@
|
||||
| 2025-11-07 | 696,646 (+10,394) | 642,146 (+11,261) | 1,338,792 (+21,655) |
|
||||
| 2025-11-08 | 706,035 (+9,389) | 653,489 (+11,343) | 1,359,524 (+20,732) |
|
||||
| 2025-11-09 | 713,462 (+7,427) | 660,459 (+6,970) | 1,373,921 (+14,397) |
|
||||
| 2025-11-10 | 722,288 (+8,826) | 668,225 (+7,766) | 1,390,513 (+16,592) |
|
||||
| 2025-11-11 | 729,769 (+7,481) | 677,501 (+9,276) | 1,407,270 (+16,757) |
|
||||
| 2025-11-12 | 740,180 (+10,411) | 686,454 (+8,953) | 1,426,634 (+19,364) |
|
||||
| 2025-11-13 | 749,905 (+9,725) | 696,157 (+9,703) | 1,446,062 (+19,428) |
|
||||
| 2025-11-14 | 759,928 (+10,023) | 705,237 (+9,080) | 1,465,165 (+19,103) |
|
||||
| 2025-11-15 | 765,955 (+6,027) | 712,870 (+7,633) | 1,478,825 (+13,660) |
|
||||
| 2025-11-16 | 771,069 (+5,114) | 716,596 (+3,726) | 1,487,665 (+8,840) |
|
||||
| 2025-11-17 | 780,161 (+9,092) | 723,339 (+6,743) | 1,503,500 (+15,835) |
|
||||
| 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) |
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "opencode",
|
||||
@@ -28,6 +29,7 @@
|
||||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.15.0",
|
||||
"@solidjs/start": "^1.1.0",
|
||||
"chart.js": "4.5.1",
|
||||
"solid-js": "catalog:",
|
||||
"vinxi": "^0.5.7",
|
||||
"zod": "catalog:",
|
||||
@@ -39,7 +41,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -66,7 +68,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -90,7 +92,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -114,7 +116,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -154,7 +156,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
@@ -170,7 +172,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -188,8 +190,8 @@
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opentui/core": "0.0.0-20251108-0c7899b1",
|
||||
"@opentui/solid": "0.0.0-20251108-0c7899b1",
|
||||
"@opentui/core": "0.1.46",
|
||||
"@opentui/solid": "0.1.46",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -248,7 +250,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -268,7 +270,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.81.0",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -279,7 +281,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -292,7 +294,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -322,7 +324,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -437,7 +439,7 @@
|
||||
|
||||
"@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.1", "", { "dependencies": { "@astrojs/internal-helpers": "0.6.1", "@astrojs/prism": "3.2.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", "remark-smartypants": "^3.0.2", "shiki": "^3.0.0", "smol-toml": "^1.3.1", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-c5F5gGrkczUaTVgmMW9g1YMJGzOtRvjjhw6IfGuxarM6ct09MpwysP10US729dy07gg8y+ofVifezvP3BNsWZg=="],
|
||||
|
||||
"@astrojs/mdx": ["@astrojs/mdx@4.3.9", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.8", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.15.0", "es-module-lexer": "^1.7.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "picocolors": "^1.1.1", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-80LHiM4z3FxAjATHNgFpa8nlTNSprAWB4UUKnr/QG56Pwk7uRnJWrXlok4wSCi/3fg8kTZ98A408Q91M+iqJdw=="],
|
||||
"@astrojs/mdx": ["@astrojs/mdx@4.3.10", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.8", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.15.0", "es-module-lexer": "^1.7.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "picocolors": "^1.1.1", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-2T5+XIr7PMqMeXhRofXY5NlY4lA0Km+wkfsqmr9lq5KXUHpGlKPQ9dlDZJP9E/CtljJyEBNS17zq66LrIJ1tiQ=="],
|
||||
|
||||
"@astrojs/prism": ["@astrojs/prism@3.2.0", "", { "dependencies": { "prismjs": "^1.29.0" } }, "sha512-GilTHKGCW6HMq7y3BUv9Ac7GMe/MO9gi9GW62GzKtth0SwukCu/qp2wLiGpEujhY+VVhaG9v7kv/5vFzvf4NYw=="],
|
||||
|
||||
@@ -577,15 +579,15 @@
|
||||
|
||||
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.9", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "^1.20250927.0" }, "optionalPeers": ["workerd"] }, "sha512-Drm7qlTKnvncEv+DANiQNEonq0H0LyIsoFZYJ6tJ8OhAoy5udIE8yp6BsVDYcIjcYLIybp4M7c/P7ly/56SoHg=="],
|
||||
|
||||
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20251011.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0DirVP+Z82RtZLlK2B+VhLOkk+ShBqDYO/jhcRw4oVlp0TOvk3cOVZChrt3+y3NV8Y/PYgTEywzLKFSziK4wCg=="],
|
||||
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20251105.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-nztUP35wTtUKM+681dBWtUNSySNWELTV+LY43oWy7ZhK19/iBJPQoFY7xpvF7zy4qOOShtise259B65DS4/71Q=="],
|
||||
|
||||
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20251011.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-1WuFBGwZd15p4xssGN/48OE2oqokIuc51YvHvyNivyV8IYnAs3G9bJNGWth1X7iMDPe4g44pZrKhRnISS2+5dA=="],
|
||||
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20251105.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-WS/dvPYTW/+gs8s0UvDqDY7wcuIAg/hUpjrMNGepr+Mo38vMU39FYhJQOly99oJCXxMluQqAnRKg09b/9Gr+Rg=="],
|
||||
|
||||
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20251011.0", "", { "os": "linux", "cpu": "x64" }, "sha512-BccMiBzFlWZyFghIw2szanmYJrJGBGHomw2y/GV6pYXChFzMGZkeCEMfmCyJj29xczZXxcZmUVJxNy4eJxO8QA=="],
|
||||
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20251105.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RdHRHo/hpjR6sNw529FkmslVSz/K3Pb1+i3fIoqUrHCrZOUYzFyz3nLeZh4EYaAhcztLWiSTwBv54bcl4sG3wA=="],
|
||||
|
||||
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20251011.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-79o/216lsbAbKEVDZYXR24ivEIE2ysDL9jvo0rDTkViLWju9dAp3CpyetglpJatbSi3uWBPKZBEOqN68zIjVsQ=="],
|
||||
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20251105.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-5zkxQCqLjwrqZVVJh92J2Drv6xifkP8kN2ltjHdwZQlVzfDW48d7tAtCm1ZooUv204ixvZFarusCfL+IRjExZg=="],
|
||||
|
||||
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20251011.0", "", { "os": "win32", "cpu": "x64" }, "sha512-RIXUQRchFdqEvaUqn1cXZXSKjpqMaSaVAkI5jNZ8XzAw/bw2bcdOVUtakrflgxDprltjFb0PTNtuss1FKtH9Jg=="],
|
||||
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20251105.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6BpkfjBIbGR+4FBOcZGcWDLM0XQuoI6R9Dublj/BKf4pv0/xJ4zHdnaYUb5NIlC75L55Ouqw0CEJasoKlMjgnw=="],
|
||||
|
||||
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251008.0", "", {}, "sha512-dZLkO4PbCL0qcCSKzuW7KE4GYe49lI12LCfQ5y9XeSwgYBoAUbwH4gmJ6A0qUIURiTJTkGkRkhVPqpq2XNgYRA=="],
|
||||
|
||||
@@ -869,6 +871,8 @@
|
||||
|
||||
"@kobalte/utils": ["@kobalte/utils@0.9.1", "", { "dependencies": { "@solid-primitives/event-listener": "^2.2.14", "@solid-primitives/keyed": "^1.2.0", "@solid-primitives/map": "^0.4.7", "@solid-primitives/media": "^2.2.4", "@solid-primitives/props": "^3.1.8", "@solid-primitives/refs": "^1.0.5", "@solid-primitives/utils": "^6.2.1" }, "peerDependencies": { "solid-js": "^1.8.8" } }, "sha512-eeU60A3kprIiBDAfv9gUJX1tXGLuZiKMajUfSQURAF2pk4ZoMYiqIzmrMBvzcxP39xnYttgTyQEVLwiTZnrV4w=="],
|
||||
|
||||
"@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="],
|
||||
|
||||
"@mapbox/node-pre-gyp": ["@mapbox/node-pre-gyp@2.0.0", "", { "dependencies": { "consola": "^3.2.3", "detect-libc": "^2.0.0", "https-proxy-agent": "^7.0.5", "node-fetch": "^2.6.7", "nopt": "^8.0.0", "semver": "^7.5.3", "tar": "^7.4.0" }, "bin": { "node-pre-gyp": "bin/node-pre-gyp" } }, "sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg=="],
|
||||
|
||||
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
|
||||
@@ -965,21 +969,21 @@
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.0.0-20251108-0c7899b1", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.0.0-20251108-0c7899b1", "@opentui/core-darwin-x64": "0.0.0-20251108-0c7899b1", "@opentui/core-linux-arm64": "0.0.0-20251108-0c7899b1", "@opentui/core-linux-x64": "0.0.0-20251108-0c7899b1", "@opentui/core-win32-arm64": "0.0.0-20251108-0c7899b1", "@opentui/core-win32-x64": "0.0.0-20251108-0c7899b1", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-uJ7wbVw2v5NnL6g3v72SjPLUwMl2wqOejUEo8t4NeBA8nsboSxggqkrqOYf6OOmCADoAqyFDY7akZMsz6HMZtg=="],
|
||||
"@opentui/core": ["@opentui/core@0.1.46", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.46", "@opentui/core-darwin-x64": "0.1.46", "@opentui/core-linux-arm64": "0.1.46", "@opentui/core-linux-x64": "0.1.46", "@opentui/core-win32-arm64": "0.1.46", "@opentui/core-win32-x64": "0.1.46", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-9682jrr65vYP0hPHfrZRK3xymlCSLVBMrRKNtclFasDi6bRvACUrtziFOIIyMIvPHRJCFWPbtz0MppARmN4zvQ=="],
|
||||
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.0.0-20251108-0c7899b1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DS9CmFmZZjwe6PIhz6zhZAsDx11DtyMFDxn8V3On2b8G892aBG6rHYtBBnsM28/1GGEJBTeDQ/jUXPVd6FNJ/g=="],
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.46", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sp/uFS8J/1yVfhgkTJ43OZfy64hv1/9sdT+oC5yb8XTNPI3QGtg6ixjr3nRoD/Lkxuj2i5SJ30RZufqH6rkCpA=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.0.0-20251108-0c7899b1", "", { "os": "darwin", "cpu": "x64" }, "sha512-K4XwdmT6FTShn7EG8AKliPzO5H59R0XUlZi9+kfRVW59IIJtna5wxbu69SkA28dFoWj5i4yDumwoBI+tI7T6vg=="],
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.46", "", { "os": "darwin", "cpu": "x64" }, "sha512-JtxEruRyLQRK8ByPmBm1nYYSvnX6mXNC+mngvd5RDiCzLzkM6qVBQBd/m3Hxp2/s6MO5Z2+iVBzZ8XFH5T4IZw=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.0.0-20251108-0c7899b1", "", { "os": "linux", "cpu": "arm64" }, "sha512-3JUmxZeSvxV5yU7NEXSecy5Z1/LcVUMy1oWyusZgp96X0CTYAXMrolZt9IJDGO5raeO7JId1UaJmWW0r4DR8TA=="],
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.46", "", { "os": "linux", "cpu": "arm64" }, "sha512-pN8nR4CwBlkZ5uh5KvoytiXXav2GhkP9cB2d3gPe49c7MBz2XrjGexgb47xjaq0hAVbytv9XUifqdPTcFQdPaQ=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.0.0-20251108-0c7899b1", "", { "os": "linux", "cpu": "x64" }, "sha512-i/AQWGyanpPRpk9NK7Ze1tn+d5bqzM9wZFKNB3rd9d2Vbt/ROgBJItG6igz8vzKPKgnlHK4Gw9b5iG5sbjpd+Q=="],
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.46", "", { "os": "linux", "cpu": "x64" }, "sha512-oH4/FEYZYce9qMQVqGl4+Btw+Mfsf6ybpWIIJUJjXMWWZlAgsTMAWM8m195Oe6WstfFLF+nRH7NUcm/YOsCHnw=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.0.0-20251108-0c7899b1", "", { "os": "win32", "cpu": "arm64" }, "sha512-C7JLWuNN3w2txiVx3demwNwogVi4DQB5ZNHy2b09++kd2m449/RwGPyLcKpuoTzU4s/usYOeY4TxKIAd8cKedQ=="],
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.46", "", { "os": "win32", "cpu": "arm64" }, "sha512-C/rTBJ9bzBcZJRCLIxi9Ka/DANe2SaHtryotseWPk9RDydw7LTHGoi3VtRW0RFijQGqmvFg+31MeNhvY1YZ65Q=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.0.0-20251108-0c7899b1", "", { "os": "win32", "cpu": "x64" }, "sha512-mpOryp37YaHlTsN70LhiSn9hJJBktbyhlH/eB3N2K7H1ANYQVrekgBJ3rDxlH1GDVtRz6vLS3IDlyK75qNX4pg=="],
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.46", "", { "os": "win32", "cpu": "x64" }, "sha512-d2DXSlA93LbSriX+pDDZ5sMwkcW1+eVoeykxeW4UParSb4/3ceBCD4aSARaZ6yoq0rR1IWOdgKdiihZH4mwdJQ=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.0.0-20251108-0c7899b1", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.0.0-20251108-0c7899b1", "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-tcsYnFGH/KBlQNG0IyZE2bisnm5NwN/w7theuWga3L1zoXqZqA5dQHutAVg4zkq5l/YKULeDI4jBlvz0lzH88A=="],
|
||||
"@opentui/solid": ["@opentui/solid@0.1.46", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.46", "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-t+LDXS+FBT+fjQHOxuL44Bx1jGuXuLouyB25BZuypQKKT8sOhi8rJ5+Q4UwH5lrI4OoRBXmzrgsWj7DD58sHDw=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
@@ -1129,49 +1133,49 @@
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.5", "", { "os": "android", "cpu": "arm" }, "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ=="],
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.2", "", { "os": "android", "cpu": "arm" }, "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.5", "", { "os": "android", "cpu": "arm64" }, "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA=="],
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.2", "", { "os": "android", "cpu": "arm64" }, "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA=="],
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA=="],
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA=="],
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ=="],
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.5", "", { "os": "linux", "cpu": "arm" }, "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ=="],
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.5", "", { "os": "linux", "cpu": "arm" }, "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ=="],
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg=="],
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q=="],
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA=="],
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw=="],
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw=="],
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg=="],
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ=="],
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.5", "", { "os": "linux", "cpu": "x64" }, "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q=="],
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.5", "", { "os": "linux", "cpu": "x64" }, "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg=="],
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.5", "", { "os": "none", "cpu": "arm64" }, "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw=="],
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.2", "", { "os": "none", "cpu": "arm64" }, "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w=="],
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg=="],
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ=="],
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg=="],
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA=="],
|
||||
|
||||
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
|
||||
|
||||
@@ -1207,57 +1211,57 @@
|
||||
|
||||
"@slack/web-api": ["@slack/web-api@6.13.0", "", { "dependencies": { "@slack/logger": "^3.0.0", "@slack/types": "^2.11.0", "@types/is-stream": "^1.1.0", "@types/node": ">=12.0.0", "axios": "^1.7.4", "eventemitter3": "^3.1.0", "form-data": "^2.5.0", "is-electron": "2.2.2", "is-stream": "^1.1.0", "p-queue": "^6.6.1", "p-retry": "^4.0.0" } }, "sha512-dv65crIgdh9ZYHrevLU6XFHTQwTyDmNqEqzuIrV+Vqe/vgiG6w37oex5ePDU1RGm2IJ90H8iOvHFvzdEO/vB+g=="],
|
||||
|
||||
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ=="],
|
||||
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="],
|
||||
|
||||
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-4Jys0ni2tB2VZzgslbEgszZyMdTkPOFGA8g+So/NjR8oy6Qwaq4eSwsrRI+NMtb0Dq4kqCzGUu/nGUx7OM/xfw=="],
|
||||
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw=="],
|
||||
|
||||
"@smithy/core": ["@smithy/core@3.17.2", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-stream": "^4.5.5", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ=="],
|
||||
"@smithy/core": ["@smithy/core@3.18.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-vGSDXOJFZgOPTatSI1ly7Gwyy/d/R9zh2TO3y0JZ0uut5qQ88p9IaWaZYIWSSqtdekNM4CGok/JppxbAff4KcQ=="],
|
||||
|
||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/property-provider": "^4.2.4", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-YVNMjhdz2pVto5bRdux7GMs0x1m0Afz3OcQy/4Yf9DH4fWOtroGH7uLvs7ZmDyoBJzLdegtIPpXrpJOZWvUXdw=="],
|
||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="],
|
||||
|
||||
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-aV8blR9RBDKrOlZVgjOdmOibTC2sBXNiT7WA558b4MPdsLTV6sbyc1WIE9QiIuYMJjYtnPLciefoqSW8Gi+MZQ=="],
|
||||
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="],
|
||||
|
||||
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ=="],
|
||||
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg=="],
|
||||
|
||||
"@smithy/hash-node": ["@smithy/hash-node@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kKU0gVhx/ppVMntvUOZE7WRMFW86HuaxLwvqileBEjL7PoILI8/djoILw3gPQloGVE6O0oOzqafxeNi2KbnUJw=="],
|
||||
"@smithy/hash-node": ["@smithy/hash-node@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA=="],
|
||||
|
||||
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-z6aDLGiHzsMhbS2MjetlIWopWz//K+mCoPXjW6aLr0mypF+Y7qdEh5TyJ20Onf9FbWHiWl4eC+rITdizpnXqOw=="],
|
||||
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A=="],
|
||||
|
||||
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="],
|
||||
|
||||
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-hJRZuFS9UsElX4DJSJfoX4M1qXRH+VFiLMUnhsWvtOOUWRNvvOfDaUSdlNbjwv1IkpVjj/Rd/O59Jl3nhAcxow=="],
|
||||
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A=="],
|
||||
|
||||
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.6", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-serde": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w=="],
|
||||
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.7", "", { "dependencies": { "@smithy/core": "^3.18.0", "@smithy/middleware-serde": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-i8Mi8OuY6Yi82Foe3iu7/yhBj1HBRoOQwBSsUNYglJTNSFaWYTNM2NauBBs/7pq2sqkLRqeUXA3Ogi2utzpUlQ=="],
|
||||
|
||||
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/protocol-http": "^5.3.4", "@smithy/service-error-classification": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "@smithy/util-middleware": "^4.2.4", "@smithy/util-retry": "^4.2.4", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-OhLx131znrEDxZPAvH/OYufR9d1nB2CQADyYFN4C3V/NQS7Mg4V6uvxHC/Dr96ZQW8IlHJTJ+vAhKt6oxWRndA=="],
|
||||
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.7", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/service-error-classification": "^4.2.5", "@smithy/smithy-client": "^4.9.3", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-E7Vc6WHCHlzDRTx1W0jZ6J1L6ziEV0PIWcUdmfL4y+c8r7WYr6I+LkQudaD8Nfb7C5c4P3SQ972OmXHtv6m/OA=="],
|
||||
|
||||
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="],
|
||||
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-La1ldWTJTZ5NqQyPqnCNeH9B+zjFhrNoQIL1jTh4zuqXRlmXhxYHhMtI1/92OlnoAtp6JoN7kzuwhWoXrBwPqg=="],
|
||||
|
||||
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA=="],
|
||||
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ=="],
|
||||
|
||||
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.4", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw=="],
|
||||
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg=="],
|
||||
|
||||
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="],
|
||||
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw=="],
|
||||
|
||||
"@smithy/property-provider": ["@smithy/property-provider@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w=="],
|
||||
"@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="],
|
||||
|
||||
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="],
|
||||
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ=="],
|
||||
|
||||
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="],
|
||||
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="],
|
||||
|
||||
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-aHb5cqXZocdzEkZ/CvhVjdw5l4r1aU/9iMEyoKzH4eXMowT6M0YjBpp7W/+XjkBnY8Xh0kVd55GKjnPKlCwinQ=="],
|
||||
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ=="],
|
||||
|
||||
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1" } }, "sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng=="],
|
||||
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0" } }, "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ=="],
|
||||
|
||||
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-y5ozxeQ9omVjbnJo9dtTsdXj9BEvGx2X8xvRgKnV+/7wLBuYJQL6dOa/qMY6omyHi7yjt1OA97jZLoVRYi8lxA=="],
|
||||
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA=="],
|
||||
|
||||
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="],
|
||||
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.5", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w=="],
|
||||
|
||||
"@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="],
|
||||
"@smithy/smithy-client": ["@smithy/smithy-client@4.9.3", "", { "dependencies": { "@smithy/core": "^3.18.0", "@smithy/middleware-endpoint": "^4.3.7", "@smithy/middleware-stack": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-8tlueuTgV5n7inQCkhyptrB3jo2AO80uGrps/XTYZivv5MFQKKBj3CIWIGMI2fRY5LEduIiazOhAWdFknY1O9w=="],
|
||||
|
||||
"@smithy/types": ["@smithy/types@4.8.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA=="],
|
||||
"@smithy/types": ["@smithy/types@4.9.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA=="],
|
||||
|
||||
"@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="],
|
||||
"@smithy/url-parser": ["@smithy/url-parser@4.2.5", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ=="],
|
||||
|
||||
"@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="],
|
||||
|
||||
@@ -1269,19 +1273,19 @@
|
||||
|
||||
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
|
||||
|
||||
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-GwaGjv/QLuL/QHQaqhf/maM7+MnRFQQs7Bsl6FlaeK6lm6U7mV5AAnVabw68cIoMl5FQFyKK62u7RWRzWL25OQ=="],
|
||||
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.6", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.3", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-kbpuXbEf2YQ9zEE6eeVnUCQWO0e1BjMnKrXL8rfXgiWA0m8/E0leU4oSNzxP04WfCmW8vjEqaDeXWxwE4tpOjQ=="],
|
||||
|
||||
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.8", "", { "dependencies": { "@smithy/config-resolver": "^4.4.2", "@smithy/credential-provider-imds": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-gIoTf9V/nFSIZ0TtgDNLd+Ws59AJvijmMDYrOozoMHPJaG9cMRdqNO50jZTlbM6ydzQYY8L/mQ4tKSw/TB+s6g=="],
|
||||
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.9", "", { "dependencies": { "@smithy/config-resolver": "^4.4.3", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.3", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-dgyribrVWN5qE5usYJ0m5M93mVM3L3TyBPZWe1Xl6uZlH2gzfQx3dz+ZCdW93lWqdedJRkOecnvbnoEEXRZ5VQ=="],
|
||||
|
||||
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg=="],
|
||||
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A=="],
|
||||
|
||||
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="],
|
||||
|
||||
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="],
|
||||
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA=="],
|
||||
|
||||
"@smithy/util-retry": ["@smithy/util-retry@4.2.4", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA=="],
|
||||
"@smithy/util-retry": ["@smithy/util-retry@4.2.5", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg=="],
|
||||
|
||||
"@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="],
|
||||
"@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="],
|
||||
|
||||
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
|
||||
|
||||
@@ -1321,7 +1325,7 @@
|
||||
|
||||
"@solidjs/meta": ["@solidjs/meta@0.29.4", "", { "peerDependencies": { "solid-js": ">=1.8.4" } }, "sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g=="],
|
||||
|
||||
"@solidjs/router": ["@solidjs/router@0.15.3", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw=="],
|
||||
"@solidjs/router": ["@solidjs/router@0.15.4", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ=="],
|
||||
|
||||
"@solidjs/start": ["@solidjs/start@1.2.0", "", { "dependencies": { "@tanstack/server-functions-plugin": "1.121.21", "@vinxi/plugin-directives": "^0.5.0", "@vinxi/server-components": "^0.5.0", "cookie-es": "^2.0.0", "defu": "^6.1.2", "error-stack-parser": "^2.1.4", "html-to-image": "^1.11.11", "radix3": "^1.1.0", "seroval": "^1.0.2", "seroval-plugins": "^1.0.2", "shiki": "^1.26.1", "source-map-js": "^1.0.2", "terracotta": "^1.0.4", "tinyglobby": "^0.2.2", "vite-plugin-solid": "^2.11.1" }, "peerDependencies": { "vinxi": "^0.5.7" } }, "sha512-SRv1g3R+4sxZnxCBPK1IedtLKsPhPJ7W/Yv4xEHjM4jJGPWi3ed35/yd0D5zhRK0C7zJIkZKbhnR/S3g8JUD5w=="],
|
||||
|
||||
@@ -1455,7 +1459,7 @@
|
||||
|
||||
"@types/scheduler": ["@types/scheduler@0.26.0", "", {}, "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA=="],
|
||||
|
||||
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
|
||||
"@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
|
||||
|
||||
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
|
||||
|
||||
@@ -1587,7 +1591,7 @@
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
|
||||
"autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="],
|
||||
"autoprefixer": ["autoprefixer@10.4.22", "", { "dependencies": { "browserslist": "^4.27.0", "caniuse-lite": "^1.0.30001754", "fraction.js": "^5.3.4", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg=="],
|
||||
|
||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||
|
||||
@@ -1617,9 +1621,9 @@
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"bare-events": ["bare-events@2.8.1", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-oxSAxTS1hRfnyit2CL5QpAOS5ixfBjj6ex3yTNvXyY/kE719jQ/IjuESJBK2w5v4wwQRAHGseVJXx9QBYOtFGQ=="],
|
||||
"bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="],
|
||||
|
||||
"bare-fs": ["bare-fs@4.5.0", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-GljgCjeupKZJNetTqxKaQArLK10vpmK28or0+RwWjEl5Rk+/xG3wkpmkv+WrcBm3q1BwHKlnhXzR8O37kcvkXQ=="],
|
||||
"bare-fs": ["bare-fs@4.5.1", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-zGUCsm3yv/ePt2PHNbVxjjn0nNB1MkIaR4wOCxJ2ig5pCf5cCVAYJXVhQg/3OhhJV6DB1ts7Hv0oUaElc2TPQg=="],
|
||||
|
||||
"bare-os": ["bare-os@3.6.2", "", {}, "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A=="],
|
||||
|
||||
@@ -1669,7 +1673,7 @@
|
||||
|
||||
"brotli": ["brotli@1.3.3", "", { "dependencies": { "base64-js": "^1.1.2" } }, "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg=="],
|
||||
|
||||
"browserslist": ["browserslist@4.27.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", "electron-to-chromium": "^1.5.238", "node-releases": "^2.0.26", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw=="],
|
||||
"browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="],
|
||||
|
||||
"buffer": ["buffer@4.9.2", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="],
|
||||
|
||||
@@ -1679,19 +1683,19 @@
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"bun-ffi-structs": ["bun-ffi-structs@0.1.0", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-NoRfJ81pgLIHCzw624/2GS2FuxcU0G4SRJww/4PXvheNVUPSIUjkOC6v1/8rk66tJVCb9oR0D6rDNKK0qT5O2Q=="],
|
||||
"bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="],
|
||||
|
||||
"bun-webgpu": ["bun-webgpu@0.1.3", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.3", "bun-webgpu-darwin-x64": "^0.1.3", "bun-webgpu-linux-x64": "^0.1.3", "bun-webgpu-win32-x64": "^0.1.3" } }, "sha512-IXFxaIi4rgsEEpl9n/QVDm5RajCK/0FcOXZeMb52YRjoiAR1YVYK5hLrXT8cm+KDi6LVahA9GJFqOR4yiloVCw=="],
|
||||
"bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="],
|
||||
|
||||
"bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-KkNQ9gT7dxGDndQaHTTHss9miukqpczML3pO2nZJoT/nITwe9lw3ZGFJMujkW41BUQ1mDYKFgo5nBGf9xYHPAg=="],
|
||||
"bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eDgLN9teKTfmvrCqgwwmWNsNszxYs7IZdCqk0S1DCarvMhr4wcajoSBlA/nQA0/owwLduPTS8xxCnQp4/N/gDg=="],
|
||||
|
||||
"bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-TODWnMUbCoqD/wqzlB3oGOBIUWIFly0lqMeBFz/MBV+ndjbnkNrP9huaZJCTkCVEPKGtd1FCM3ExZUtBbnGziA=="],
|
||||
"bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-X+PjwJUWenUmdQBP8EtdItMyieQ6Nlpn+BH518oaouDiSnWj5+b0Y7DNDZJq7Ezom4EaxmqL/uGYZK3aCQ7CXg=="],
|
||||
|
||||
"bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-lVHORoVu1G61XVM8CRRqUsqr6w8kMlpuSpbPGpKUpmvrsoay6ymXAhT5lRPKyrGNamHUQTknmWdI59aRDCfLtQ=="],
|
||||
"bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-zMLs2YIGB+/jxrYFXaFhVKX/GBt05UTF45lc9srcHc9JXGjEj+12CIo1CHLTAWatXMTqt0Jsu6ukWEoWVT/ayA=="],
|
||||
|
||||
"bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-vlspsFffctJlBnFfs2lW3QgDD6LyFu8VT18ryID7Qka5poTj0clGVRxz7DFRi7yva3GovEGw/82z/WVc5US8Pw=="],
|
||||
"bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.4", "", { "os": "win32", "cpu": "x64" }, "sha512-Z5yAK28xrcm8Wb5k7TZ8FJKpOI/r+aVCRdlHYAqI2SDJFN3nD4mJs900X6kNVmG/xFzb5yOuKVYWGg+6ZXWbyA=="],
|
||||
|
||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||
|
||||
@@ -1711,7 +1715,7 @@
|
||||
|
||||
"camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001753", "", {}, "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw=="],
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001754", "", {}, "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg=="],
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
|
||||
@@ -1725,6 +1729,8 @@
|
||||
|
||||
"character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
|
||||
|
||||
"chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="],
|
||||
|
||||
"cheerio": ["cheerio@1.0.0-rc.12", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "htmlparser2": "^8.0.1", "parse5": "^7.0.0", "parse5-htmlparser2-tree-adapter": "^7.0.0" } }, "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q=="],
|
||||
|
||||
"cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
|
||||
@@ -1931,7 +1937,7 @@
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.245", "", {}, "sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.250", "", {}, "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
@@ -2029,7 +2035,7 @@
|
||||
|
||||
"expressive-code": ["expressive-code@0.41.3", "", { "dependencies": { "@expressive-code/core": "^0.41.3", "@expressive-code/plugin-frames": "^0.41.3", "@expressive-code/plugin-shiki": "^0.41.3", "@expressive-code/plugin-text-markers": "^0.41.3" } }, "sha512-YLnD62jfgBZYrXIPQcJ0a51Afv9h8VlWqEGK9uU2T5nL/5rb8SnA86+7+mgCZe5D34Tff5RNEA5hjNVJYHzrFg=="],
|
||||
|
||||
"exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="],
|
||||
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
|
||||
@@ -2085,7 +2091,7 @@
|
||||
|
||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||
|
||||
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
|
||||
"fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
|
||||
|
||||
"framer-motion": ["framer-motion@8.5.5", "", { "dependencies": { "@motionone/dom": "^10.15.3", "hey-listen": "^1.0.8", "tslib": "^2.4.0" }, "optionalDependencies": { "@emotion/is-prop-valid": "^0.8.2" }, "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" } }, "sha512-5IDx5bxkjWHWUF3CVJoSyUVOtrbAxtzYBBowRE2uYI/6VYhkEBD+rbTHEGuUmbGHRj6YqqSfoG7Aa1cLyWCrBA=="],
|
||||
|
||||
@@ -2683,7 +2689,7 @@
|
||||
|
||||
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
|
||||
|
||||
"miniflare": ["miniflare@4.20251011.2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251011.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-5oAaz6lqZus4QFwzEJiNtgpjZR2TBVwBeIhOW33V4gu+l23EukpKja831tFIX2o6sOD/hqZmKZHplOrWl3YGtQ=="],
|
||||
"miniflare": ["miniflare@4.20251105.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251105.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-n+lCQbGLPjHFm5EKMohxCl+hLIki9rIlJSU9FkYKdJ62cGacetmTH5IgWUZhUFFM+NqhqZLOuWXTAsoZTm0hog=="],
|
||||
|
||||
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
||||
|
||||
@@ -2855,7 +2861,7 @@
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"path-scurry": ["path-scurry@2.0.0", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg=="],
|
||||
"path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
|
||||
|
||||
@@ -3073,7 +3079,7 @@
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"rollup": ["rollup@4.52.5", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.5", "@rollup/rollup-android-arm64": "4.52.5", "@rollup/rollup-darwin-arm64": "4.52.5", "@rollup/rollup-darwin-x64": "4.52.5", "@rollup/rollup-freebsd-arm64": "4.52.5", "@rollup/rollup-freebsd-x64": "4.52.5", "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", "@rollup/rollup-linux-arm-musleabihf": "4.52.5", "@rollup/rollup-linux-arm64-gnu": "4.52.5", "@rollup/rollup-linux-arm64-musl": "4.52.5", "@rollup/rollup-linux-loong64-gnu": "4.52.5", "@rollup/rollup-linux-ppc64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-musl": "4.52.5", "@rollup/rollup-linux-s390x-gnu": "4.52.5", "@rollup/rollup-linux-x64-gnu": "4.52.5", "@rollup/rollup-linux-x64-musl": "4.52.5", "@rollup/rollup-openharmony-arm64": "4.52.5", "@rollup/rollup-win32-arm64-msvc": "4.52.5", "@rollup/rollup-win32-ia32-msvc": "4.52.5", "@rollup/rollup-win32-x64-gnu": "4.52.5", "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw=="],
|
||||
"rollup": ["rollup@4.53.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.2", "@rollup/rollup-android-arm64": "4.53.2", "@rollup/rollup-darwin-arm64": "4.53.2", "@rollup/rollup-darwin-x64": "4.53.2", "@rollup/rollup-freebsd-arm64": "4.53.2", "@rollup/rollup-freebsd-x64": "4.53.2", "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", "@rollup/rollup-linux-arm-musleabihf": "4.53.2", "@rollup/rollup-linux-arm64-gnu": "4.53.2", "@rollup/rollup-linux-arm64-musl": "4.53.2", "@rollup/rollup-linux-loong64-gnu": "4.53.2", "@rollup/rollup-linux-ppc64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-musl": "4.53.2", "@rollup/rollup-linux-s390x-gnu": "4.53.2", "@rollup/rollup-linux-x64-gnu": "4.53.2", "@rollup/rollup-linux-x64-musl": "4.53.2", "@rollup/rollup-openharmony-arm64": "4.53.2", "@rollup/rollup-win32-arm64-msvc": "4.53.2", "@rollup/rollup-win32-ia32-msvc": "4.53.2", "@rollup/rollup-win32-x64-gnu": "4.53.2", "@rollup/rollup-win32-x64-msvc": "4.53.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g=="],
|
||||
|
||||
"rollup-plugin-visualizer": ["rollup-plugin-visualizer@6.0.5", "", { "dependencies": { "open": "^8.0.0", "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^17.5.1" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg=="],
|
||||
|
||||
@@ -3525,9 +3531,9 @@
|
||||
|
||||
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
|
||||
|
||||
"workerd": ["workerd@1.20251011.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251011.0", "@cloudflare/workerd-darwin-arm64": "1.20251011.0", "@cloudflare/workerd-linux-64": "1.20251011.0", "@cloudflare/workerd-linux-arm64": "1.20251011.0", "@cloudflare/workerd-windows-64": "1.20251011.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Dq35TLPEJAw7BuYQMkN3p9rge34zWMU2Gnd4DSJFeVqld4+DAO2aPG7+We2dNIAyM97S8Y9BmHulbQ00E0HC7Q=="],
|
||||
"workerd": ["workerd@1.20251105.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251105.0", "@cloudflare/workerd-darwin-arm64": "1.20251105.0", "@cloudflare/workerd-linux-64": "1.20251105.0", "@cloudflare/workerd-linux-arm64": "1.20251105.0", "@cloudflare/workerd-windows-64": "1.20251105.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-8D1UmsxrRr3Go7enbYCsYoiWeGn66u1WFNojPSgtjp7z8pV2cXskjr05vQ1OOzl7+rg1hDDofnCJqVwChMym8g=="],
|
||||
|
||||
"wrangler": ["wrangler@4.45.4", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.9", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251011.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20251011.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251011.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-niXT7B463wQi7WXIHjYK8txgWhuKQLrGmhjoR58SnPhlkq4wGjd3rFrkVyRc/O58clGTfs672BSGOph4XMoQKw=="],
|
||||
"wrangler": ["wrangler@4.46.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.9", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251105.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20251105.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251014.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-WRROO7CL+MW/E44RMT4X7w32qPjufiPpGdey5D6H7iKzzVqfUkTRULxYBfWANiU1yGnsiCXQtu3Ap0G2TmohtA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
@@ -3557,7 +3563,7 @@
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@1.2.1", "", {}, "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="],
|
||||
"yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="],
|
||||
|
||||
"yocto-spinner": ["yocto-spinner@0.2.3", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ=="],
|
||||
|
||||
@@ -3727,6 +3733,8 @@
|
||||
|
||||
"@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
|
||||
|
||||
"@opencode-ai/desktop/@solidjs/router": ["@solidjs/router@0.15.3", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw=="],
|
||||
|
||||
"@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.4.2", "", { "dependencies": { "@shikijs/core": "3.4.2", "@shikijs/types": "3.4.2" } }, "sha512-I5baLVi/ynLEOZoWSAMlACHNnG+yw5HDmse0oe+GW6U1u+ULdEB3UHiVWaHoJSSONV7tlcVxuaMy74sREDkSvg=="],
|
||||
|
||||
"@opencode-ai/web/@types/luxon": ["@types/luxon@3.6.2", "", {}, "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw=="],
|
||||
@@ -3799,8 +3807,6 @@
|
||||
|
||||
"@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
|
||||
|
||||
"@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
|
||||
|
||||
"@vercel/nft/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@vercel/nft/glob": ["glob@10.4.5", "", { "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-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
|
||||
@@ -3909,6 +3915,8 @@
|
||||
|
||||
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
"gel/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"giget/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"giget/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
|
||||
@@ -3961,7 +3969,7 @@
|
||||
|
||||
"named-placeholders/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
|
||||
|
||||
"nitropack/c12": ["c12@3.3.1", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-LcWQ01LT9tkoUINHgpIOv3mMs+Abv7oVCrtpMRi1PaapVEpWoMga5WuT7/DqFTu7URP9ftbOmimNw1KNIGh9DQ=="],
|
||||
"nitropack/c12": ["c12@3.3.2", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A=="],
|
||||
|
||||
"nitropack/globby": ["globby@15.0.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "fast-glob": "^3.3.3", "ignore": "^7.0.5", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" } }, "sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw=="],
|
||||
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
[install]
|
||||
exact = true
|
||||
exact = true
|
||||
|
||||
Generated
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1762156382,
|
||||
"narHash": "sha256-Yg7Ag7ov5+36jEFC1DaZh/12SEXo6OO3/8rqADRxiqs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7241bcbb4f099a66aafca120d37c65e8dda32717",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
{
|
||||
description = "OpenCode development flake";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
nixpkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
systems = [
|
||||
"aarch64-linux"
|
||||
"x86_64-linux"
|
||||
"aarch64-darwin"
|
||||
"x86_64-darwin"
|
||||
];
|
||||
lib = nixpkgs.lib;
|
||||
forEachSystem = lib.genAttrs systems;
|
||||
pkgsFor = system: nixpkgs.legacyPackages.${system};
|
||||
packageJson = builtins.fromJSON (builtins.readFile ./packages/opencode/package.json);
|
||||
bunTarget = {
|
||||
"aarch64-linux" = "bun-linux-arm64";
|
||||
"x86_64-linux" = "bun-linux-x64";
|
||||
"aarch64-darwin" = "bun-darwin-arm64";
|
||||
"x86_64-darwin" = "bun-darwin-x64";
|
||||
};
|
||||
defaultNodeModules = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
hashesFile = "${./nix}/hashes.json";
|
||||
hashesData =
|
||||
if builtins.pathExists hashesFile then builtins.fromJSON (builtins.readFile hashesFile) else { };
|
||||
nodeModulesHash = hashesData.nodeModules or defaultNodeModules;
|
||||
modelsDev = forEachSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = pkgsFor system;
|
||||
in
|
||||
pkgs."models-dev"
|
||||
);
|
||||
in
|
||||
{
|
||||
devShells = forEachSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = pkgsFor system;
|
||||
in
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
bun
|
||||
nodejs_20
|
||||
pkg-config
|
||||
openssl
|
||||
git
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
packages = forEachSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = pkgsFor system;
|
||||
mkNodeModules = pkgs.callPackage ./nix/node-modules.nix {
|
||||
hash = nodeModulesHash;
|
||||
};
|
||||
mkPackage = pkgs.callPackage ./nix/opencode.nix { };
|
||||
in
|
||||
{
|
||||
default = mkPackage {
|
||||
version = packageJson.version;
|
||||
src = ./.;
|
||||
scripts = ./nix/scripts;
|
||||
target = bunTarget.${system};
|
||||
modelsDev = "${modelsDev.${system}}/dist/_api.json";
|
||||
mkNodeModules = mkNodeModules;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
apps = forEachSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = pkgsFor system;
|
||||
in
|
||||
{
|
||||
opencode-dev = {
|
||||
type = "app";
|
||||
meta = {
|
||||
description = "Nix devshell shell for OpenCode";
|
||||
runtimeInputs = [ pkgs.bun ];
|
||||
};
|
||||
program = "${
|
||||
pkgs.writeShellApplication {
|
||||
name = "opencode-dev";
|
||||
text = ''
|
||||
exec bun run dev "$@"
|
||||
'';
|
||||
}
|
||||
}/bin/opencode-dev";
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"nodeModules": "sha256-lAU5G92UJ998pKOd0EAt9gUa/HUXXvu6xAUdH2STmDo="
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{ hash, lib, stdenvNoCC, bun, cacert, curl }:
|
||||
args:
|
||||
stdenvNoCC.mkDerivation {
|
||||
pname = "opencode-node_modules";
|
||||
version = args.version;
|
||||
src = args.src;
|
||||
|
||||
impureEnvVars =
|
||||
lib.fetchers.proxyImpureEnvVars
|
||||
++ [
|
||||
"GIT_PROXY_COMMAND"
|
||||
"SOCKS_SERVER"
|
||||
];
|
||||
|
||||
nativeBuildInputs = [ bun cacert curl ];
|
||||
|
||||
dontConfigure = true;
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
export HOME=$(mktemp -d)
|
||||
export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
|
||||
bun install \
|
||||
--cpu="*" \
|
||||
--os="*" \
|
||||
--frozen-lockfile \
|
||||
--ignore-scripts \
|
||||
--no-progress \
|
||||
--linker=isolated
|
||||
bun --bun ${args.canonicalizeScript}
|
||||
bun --bun ${args.normalizeBinsScript}
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out
|
||||
while IFS= read -r dir; do
|
||||
rel="''${dir#./}"
|
||||
dest="$out/$rel"
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
cp -R "$dir" "$dest"
|
||||
done < <(find . -type d -name node_modules -prune | sort)
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
dontFixup = true;
|
||||
|
||||
outputHashAlgo = "sha256";
|
||||
outputHashMode = "recursive";
|
||||
outputHash = hash;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
{ lib, stdenv, stdenvNoCC, bun, fzf, ripgrep, makeBinaryWrapper }:
|
||||
args:
|
||||
let
|
||||
scripts = args.scripts;
|
||||
mkModules =
|
||||
attrs:
|
||||
args.mkNodeModules (
|
||||
attrs
|
||||
// {
|
||||
canonicalizeScript = scripts + "/canonicalize-node-modules.ts";
|
||||
normalizeBinsScript = scripts + "/normalize-bun-binaries.ts";
|
||||
}
|
||||
);
|
||||
in
|
||||
stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
pname = "opencode";
|
||||
version = args.version;
|
||||
|
||||
src = args.src;
|
||||
|
||||
node_modules = mkModules {
|
||||
version = finalAttrs.version;
|
||||
src = finalAttrs.src;
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
bun
|
||||
makeBinaryWrapper
|
||||
];
|
||||
|
||||
configurePhase = ''
|
||||
runHook preConfigure
|
||||
cp -R ${finalAttrs.node_modules}/. .
|
||||
runHook postConfigure
|
||||
'';
|
||||
|
||||
env.MODELS_DEV_API_JSON = args.modelsDev;
|
||||
env.OPENCODE_VERSION = args.version;
|
||||
env.OPENCODE_CHANNEL = "stable";
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
cp ${scripts + "/bun-build.ts"} bun-build.ts
|
||||
|
||||
substituteInPlace bun-build.ts \
|
||||
--replace '@VERSION@' "${finalAttrs.version}"
|
||||
|
||||
export BUN_COMPILE_TARGET=${args.target}
|
||||
bun --bun bun-build.ts
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
dontStrip = true;
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
cd packages/opencode
|
||||
if [ ! -f opencode ]; then
|
||||
echo "ERROR: opencode binary not found in $(pwd)"
|
||||
ls -la
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f opencode-worker.js ]; then
|
||||
echo "ERROR: opencode worker bundle not found in $(pwd)"
|
||||
ls -la
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install -Dm755 opencode $out/bin/opencode
|
||||
install -Dm644 opencode-worker.js $out/bin/opencode-worker.js
|
||||
if [ -f opencode-assets.manifest ]; then
|
||||
while IFS= read -r asset; do
|
||||
[ -z "$asset" ] && continue
|
||||
if [ ! -f "$asset" ]; then
|
||||
echo "ERROR: referenced asset \"$asset\" missing"
|
||||
exit 1
|
||||
fi
|
||||
install -Dm644 "$asset" "$out/bin/$(basename "$asset")"
|
||||
done < opencode-assets.manifest
|
||||
fi
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
postFixup = ''
|
||||
wrapProgram "$out/bin/opencode" --prefix PATH : ${lib.makeBinPath [ fzf ripgrep ]}
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "AI coding agent built for the terminal";
|
||||
longDescription = ''
|
||||
OpenCode is a terminal-based agent that can build anything.
|
||||
It combines a TypeScript/JavaScript core with a Go-based TUI
|
||||
to provide an interactive AI coding experience.
|
||||
'';
|
||||
homepage = "https://github.com/sst/opencode";
|
||||
license = lib.licenses.mit;
|
||||
platforms = [
|
||||
"aarch64-linux"
|
||||
"x86_64-linux"
|
||||
"aarch64-darwin"
|
||||
"x86_64-darwin"
|
||||
];
|
||||
mainProgram = "opencode";
|
||||
};
|
||||
})
|
||||
@@ -0,0 +1,115 @@
|
||||
import solidPlugin from "./packages/opencode/node_modules/@opentui/solid/scripts/solid-plugin"
|
||||
import path from "path"
|
||||
import fs from "fs"
|
||||
|
||||
const version = "@VERSION@"
|
||||
const pkg = path.join(process.cwd(), "packages/opencode")
|
||||
const parser = fs.realpathSync(path.join(pkg, "./node_modules/@opentui/core/parser.worker.js"))
|
||||
const worker = "./src/cli/cmd/tui/worker.ts"
|
||||
const target = process.env["BUN_COMPILE_TARGET"]
|
||||
|
||||
if (!target) {
|
||||
throw new Error("BUN_COMPILE_TARGET not set")
|
||||
}
|
||||
|
||||
process.chdir(pkg)
|
||||
|
||||
const manifestName = "opencode-assets.manifest"
|
||||
const manifestPath = path.join(pkg, manifestName)
|
||||
|
||||
const readTrackedAssets = () => {
|
||||
if (!fs.existsSync(manifestPath)) return []
|
||||
return fs
|
||||
.readFileSync(manifestPath, "utf8")
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0)
|
||||
}
|
||||
|
||||
const removeTrackedAssets = () => {
|
||||
for (const file of readTrackedAssets()) {
|
||||
const filePath = path.join(pkg, file)
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.rmSync(filePath, { force: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const assets = new Set<string>()
|
||||
|
||||
const addAsset = async (p: string) => {
|
||||
const file = path.basename(p)
|
||||
const dest = path.join(pkg, file)
|
||||
await Bun.write(dest, Bun.file(p))
|
||||
assets.add(file)
|
||||
}
|
||||
|
||||
removeTrackedAssets()
|
||||
|
||||
const result = await Bun.build({
|
||||
conditions: ["browser"],
|
||||
tsconfig: "./tsconfig.json",
|
||||
plugins: [solidPlugin],
|
||||
sourcemap: "external",
|
||||
entrypoints: ["./src/index.ts", parser, worker],
|
||||
define: {
|
||||
OPENCODE_VERSION: `'@VERSION@'`,
|
||||
OTUI_TREE_SITTER_WORKER_PATH: "/$bunfs/root/" + path.relative(pkg, parser).replace(/\\/g, "/"),
|
||||
OPENCODE_CHANNEL: "'latest'",
|
||||
},
|
||||
compile: {
|
||||
target,
|
||||
outfile: "opencode",
|
||||
execArgv: ["--user-agent=opencode/" + version, '--env-file=""', "--"],
|
||||
windows: {},
|
||||
},
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
console.error("Build failed!")
|
||||
for (const log of result.logs) {
|
||||
console.error(log)
|
||||
}
|
||||
throw new Error("Compilation failed")
|
||||
}
|
||||
|
||||
const assetOutputs = result.outputs?.filter((x) => x.kind === "asset") ?? []
|
||||
for (const x of assetOutputs) {
|
||||
await addAsset(x.path)
|
||||
}
|
||||
|
||||
const bundle = await Bun.build({
|
||||
entrypoints: [worker],
|
||||
tsconfig: "./tsconfig.json",
|
||||
plugins: [solidPlugin],
|
||||
target: "bun",
|
||||
outdir: "./.opencode-worker",
|
||||
sourcemap: "none",
|
||||
})
|
||||
|
||||
if (!bundle.success) {
|
||||
console.error("Worker build failed!")
|
||||
for (const log of bundle.logs) {
|
||||
console.error(log)
|
||||
}
|
||||
throw new Error("Worker compilation failed")
|
||||
}
|
||||
|
||||
const workerAssets = bundle.outputs?.filter((x) => x.kind === "asset") ?? []
|
||||
for (const x of workerAssets) {
|
||||
await addAsset(x.path)
|
||||
}
|
||||
|
||||
const output = bundle.outputs.find((x) => x.kind === "entry-point")
|
||||
if (!output) {
|
||||
throw new Error("Worker build produced no entry-point output")
|
||||
}
|
||||
|
||||
const dest = path.join(pkg, "opencode-worker.js")
|
||||
await Bun.write(dest, Bun.file(output.path))
|
||||
fs.rmSync(path.dirname(output.path), { recursive: true, force: true })
|
||||
|
||||
const list = Array.from(assets)
|
||||
await Bun.write(manifestPath, list.length > 0 ? list.join("\n") + "\n" : "")
|
||||
|
||||
console.log("Build successful!")
|
||||
@@ -0,0 +1,96 @@
|
||||
import { lstat, mkdir, readdir, rm, symlink } from "fs/promises"
|
||||
import { join, relative } from "path"
|
||||
|
||||
type SemverLike = {
|
||||
valid: (value: string) => string | null
|
||||
rcompare: (left: string, right: string) => number
|
||||
}
|
||||
|
||||
type Entry = {
|
||||
dir: string
|
||||
version: string
|
||||
label: string
|
||||
}
|
||||
|
||||
const root = process.cwd()
|
||||
const bunRoot = join(root, "node_modules/.bun")
|
||||
const linkRoot = join(bunRoot, "node_modules")
|
||||
const directories = (await readdir(bunRoot)).sort()
|
||||
const versions = new Map<string, Entry[]>()
|
||||
|
||||
for (const entry of directories) {
|
||||
const full = join(bunRoot, entry)
|
||||
const info = await lstat(full)
|
||||
if (!info.isDirectory()) {
|
||||
continue
|
||||
}
|
||||
const marker = entry.lastIndexOf("@")
|
||||
if (marker <= 0) {
|
||||
continue
|
||||
}
|
||||
const slug = entry.slice(0, marker).replace(/\+/g, "/")
|
||||
const version = entry.slice(marker + 1)
|
||||
const list = versions.get(slug) ?? []
|
||||
list.push({ dir: full, version, label: entry })
|
||||
versions.set(slug, list)
|
||||
}
|
||||
|
||||
const semverModule = (await import(join(bunRoot, "node_modules/semver"))) as
|
||||
| SemverLike
|
||||
| {
|
||||
default: SemverLike
|
||||
}
|
||||
const semver = "default" in semverModule ? semverModule.default : semverModule
|
||||
const selections = new Map<string, Entry>()
|
||||
|
||||
for (const [slug, list] of versions) {
|
||||
list.sort((a, b) => {
|
||||
const left = semver.valid(a.version)
|
||||
const right = semver.valid(b.version)
|
||||
if (left && right) {
|
||||
const delta = semver.rcompare(left, right)
|
||||
if (delta !== 0) {
|
||||
return delta
|
||||
}
|
||||
}
|
||||
if (left && !right) {
|
||||
return -1
|
||||
}
|
||||
if (!left && right) {
|
||||
return 1
|
||||
}
|
||||
return b.version.localeCompare(a.version)
|
||||
})
|
||||
selections.set(slug, list[0])
|
||||
}
|
||||
|
||||
await rm(linkRoot, { recursive: true, force: true })
|
||||
await mkdir(linkRoot, { recursive: true })
|
||||
|
||||
const rewrites: string[] = []
|
||||
|
||||
for (const [slug, entry] of Array.from(selections.entries()).sort((a, b) => a[0].localeCompare(b[0]))) {
|
||||
const parts = slug.split("/")
|
||||
const leaf = parts.pop()
|
||||
if (!leaf) {
|
||||
continue
|
||||
}
|
||||
const parent = join(linkRoot, ...parts)
|
||||
await mkdir(parent, { recursive: true })
|
||||
const linkPath = join(parent, leaf)
|
||||
const desired = join(entry.dir, "node_modules", slug)
|
||||
const relativeTarget = relative(parent, desired)
|
||||
const resolved = relativeTarget.length === 0 ? "." : relativeTarget
|
||||
await rm(linkPath, { recursive: true, force: true })
|
||||
await symlink(resolved, linkPath)
|
||||
rewrites.push(slug + " -> " + resolved)
|
||||
}
|
||||
|
||||
rewrites.sort()
|
||||
console.log("[canonicalize-node-modules] rebuilt", rewrites.length, "links")
|
||||
for (const line of rewrites.slice(0, 20)) {
|
||||
console.log(" ", line)
|
||||
}
|
||||
if (rewrites.length > 20) {
|
||||
console.log(" ...")
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { lstat, mkdir, readdir, rm, symlink } from "fs/promises"
|
||||
import { join, relative } from "path"
|
||||
|
||||
type PackageManifest = {
|
||||
name?: string
|
||||
bin?: string | Record<string, string>
|
||||
}
|
||||
|
||||
const root = process.cwd()
|
||||
const bunRoot = join(root, "node_modules/.bun")
|
||||
const bunEntries = (await safeReadDir(bunRoot)).sort()
|
||||
let rewritten = 0
|
||||
|
||||
for (const entry of bunEntries) {
|
||||
const modulesRoot = join(bunRoot, entry, "node_modules")
|
||||
if (!(await exists(modulesRoot))) {
|
||||
continue
|
||||
}
|
||||
const binRoot = join(modulesRoot, ".bin")
|
||||
await rm(binRoot, { recursive: true, force: true })
|
||||
await mkdir(binRoot, { recursive: true })
|
||||
|
||||
const packageDirs = await collectPackages(modulesRoot)
|
||||
for (const packageDir of packageDirs) {
|
||||
const manifest = await readManifest(packageDir)
|
||||
if (!manifest) {
|
||||
continue
|
||||
}
|
||||
const binField = manifest.bin
|
||||
if (!binField) {
|
||||
continue
|
||||
}
|
||||
const seen = new Set<string>()
|
||||
if (typeof binField === "string") {
|
||||
const fallback = manifest.name ?? packageDir.split("/").pop()
|
||||
if (fallback) {
|
||||
await linkBinary(binRoot, fallback, packageDir, binField, seen)
|
||||
}
|
||||
} else {
|
||||
const entries = Object.entries(binField).sort((a, b) => a[0].localeCompare(b[0]))
|
||||
for (const [name, target] of entries) {
|
||||
await linkBinary(binRoot, name, packageDir, target, seen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[normalize-bun-binaries] rewrote ${rewritten} links`)
|
||||
|
||||
async function collectPackages(modulesRoot: string) {
|
||||
const found: string[] = []
|
||||
const topLevel = (await safeReadDir(modulesRoot)).sort()
|
||||
for (const name of topLevel) {
|
||||
if (name === ".bin" || name === ".bun") {
|
||||
continue
|
||||
}
|
||||
const full = join(modulesRoot, name)
|
||||
if (!(await isDirectory(full))) {
|
||||
continue
|
||||
}
|
||||
if (name.startsWith("@")) {
|
||||
const scoped = (await safeReadDir(full)).sort()
|
||||
for (const child of scoped) {
|
||||
const scopedDir = join(full, child)
|
||||
if (await isDirectory(scopedDir)) {
|
||||
found.push(scopedDir)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
found.push(full)
|
||||
}
|
||||
return found.sort()
|
||||
}
|
||||
|
||||
async function readManifest(dir: string) {
|
||||
const file = Bun.file(join(dir, "package.json"))
|
||||
if (!(await file.exists())) {
|
||||
return null
|
||||
}
|
||||
const data = (await file.json()) as PackageManifest
|
||||
return data
|
||||
}
|
||||
|
||||
async function linkBinary(binRoot: string, name: string, packageDir: string, target: string, seen: Set<string>) {
|
||||
if (!name || !target) {
|
||||
return
|
||||
}
|
||||
const normalizedName = normalizeBinName(name)
|
||||
if (seen.has(normalizedName)) {
|
||||
return
|
||||
}
|
||||
const resolved = join(packageDir, target)
|
||||
const script = Bun.file(resolved)
|
||||
if (!(await script.exists())) {
|
||||
return
|
||||
}
|
||||
seen.add(normalizedName)
|
||||
const destination = join(binRoot, normalizedName)
|
||||
const relativeTarget = relative(binRoot, resolved) || "."
|
||||
await rm(destination, { force: true })
|
||||
await symlink(relativeTarget, destination)
|
||||
rewritten++
|
||||
}
|
||||
|
||||
async function exists(path: string) {
|
||||
try {
|
||||
await lstat(path)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function isDirectory(path: string) {
|
||||
try {
|
||||
const info = await lstat(path)
|
||||
return info.isDirectory()
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function safeReadDir(path: string) {
|
||||
try {
|
||||
return await readdir(path)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeBinName(name: string) {
|
||||
const slash = name.lastIndexOf("/")
|
||||
if (slash >= 0) {
|
||||
return name.slice(slash + 1)
|
||||
}
|
||||
return name
|
||||
}
|
||||
Executable
+112
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DUMMY="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||
SYSTEM=${SYSTEM:-x86_64-linux}
|
||||
DEFAULT_HASH_FILE=${MODULES_HASH_FILE:-nix/hashes.json}
|
||||
HASH_FILE=${HASH_FILE:-$DEFAULT_HASH_FILE}
|
||||
|
||||
if [ ! -f "$HASH_FILE" ]; then
|
||||
cat >"$HASH_FILE" <<EOF
|
||||
{
|
||||
"nodeModules": "$DUMMY"
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
if ! git ls-files --error-unmatch "$HASH_FILE" >/dev/null 2>&1; then
|
||||
git add -N "$HASH_FILE" >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
|
||||
export DUMMY
|
||||
export NIX_KEEP_OUTPUTS=1
|
||||
export NIX_KEEP_DERIVATIONS=1
|
||||
|
||||
cleanup() {
|
||||
rm -f "${JSON_OUTPUT:-}" "${BUILD_LOG:-}" "${TMP_EXPR:-}"
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
write_node_modules_hash() {
|
||||
local value="$1"
|
||||
local temp
|
||||
temp=$(mktemp)
|
||||
jq --arg value "$value" '.nodeModules = $value' "$HASH_FILE" >"$temp"
|
||||
mv "$temp" "$HASH_FILE"
|
||||
}
|
||||
|
||||
TARGET="packages.${SYSTEM}.default"
|
||||
MODULES_ATTR=".#packages.${SYSTEM}.default.node_modules"
|
||||
CORRECT_HASH=""
|
||||
|
||||
DRV_PATH="$(nix eval --raw "${MODULES_ATTR}.drvPath")"
|
||||
|
||||
echo "Setting dummy node_modules outputHash for ${SYSTEM}..."
|
||||
write_node_modules_hash "$DUMMY"
|
||||
|
||||
BUILD_LOG=$(mktemp)
|
||||
JSON_OUTPUT=$(mktemp)
|
||||
|
||||
echo "Building node_modules for ${SYSTEM} to discover correct outputHash..."
|
||||
echo "Attempting to realize derivation: ${DRV_PATH}"
|
||||
REALISE_OUT=$(nix-store --realise "$DRV_PATH" --keep-failed 2>&1 | tee "$BUILD_LOG" || true)
|
||||
|
||||
BUILD_PATH=$(echo "$REALISE_OUT" | grep "^/nix/store/" | head -n1 || true)
|
||||
if [ -n "$BUILD_PATH" ] && [ -d "$BUILD_PATH" ]; then
|
||||
echo "Realized node_modules output: $BUILD_PATH"
|
||||
CORRECT_HASH=$(nix hash path --sri "$BUILD_PATH" 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
if [ -z "$CORRECT_HASH" ]; then
|
||||
CORRECT_HASH="$(grep -E 'got:\s+sha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | awk '{print $2}' | head -n1 || true)"
|
||||
|
||||
if [ -z "$CORRECT_HASH" ]; then
|
||||
CORRECT_HASH="$(grep -A2 'hash mismatch' "$BUILD_LOG" | grep 'got:' | awk '{print $2}' | sed 's/sha256:/sha256-/' || true)"
|
||||
fi
|
||||
|
||||
if [ -z "$CORRECT_HASH" ]; then
|
||||
echo "Searching for kept failed build directory..."
|
||||
KEPT_DIR=$(grep -oE "build directory.*'[^']+'" "$BUILD_LOG" | grep -oE "'/[^']+'" | tr -d "'" | head -n1)
|
||||
|
||||
if [ -z "$KEPT_DIR" ]; then
|
||||
KEPT_DIR=$(grep -oE '/nix/var/nix/builds/[^ ]+' "$BUILD_LOG" | head -n1)
|
||||
fi
|
||||
|
||||
if [ -n "$KEPT_DIR" ] && [ -d "$KEPT_DIR" ]; then
|
||||
echo "Found kept build directory: $KEPT_DIR"
|
||||
if [ -d "$KEPT_DIR/build" ]; then
|
||||
HASH_PATH="$KEPT_DIR/build"
|
||||
else
|
||||
HASH_PATH="$KEPT_DIR"
|
||||
fi
|
||||
|
||||
echo "Attempting to hash: $HASH_PATH"
|
||||
ls -la "$HASH_PATH" || true
|
||||
|
||||
if [ -d "$HASH_PATH/node_modules" ]; then
|
||||
CORRECT_HASH=$(nix hash path --sri "$HASH_PATH" 2>/dev/null || true)
|
||||
echo "Computed hash from kept build: $CORRECT_HASH"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$CORRECT_HASH" ]; then
|
||||
echo "Failed to determine correct node_modules hash for ${SYSTEM}."
|
||||
echo "Build log:"
|
||||
cat "$BUILD_LOG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
write_node_modules_hash "$CORRECT_HASH"
|
||||
|
||||
jq -e --arg hash "$CORRECT_HASH" '.nodeModules == $hash' "$HASH_FILE" >/dev/null
|
||||
|
||||
echo "node_modules hash updated for ${SYSTEM}: $CORRECT_HASH"
|
||||
|
||||
rm -f "$BUILD_LOG"
|
||||
unset BUILD_LOG
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"description": "AI-powered development tool",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.3.1",
|
||||
"packageManager": "bun@1.3.2",
|
||||
"scripts": {
|
||||
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
||||
"typecheck": "bun turbo typecheck",
|
||||
|
||||
@@ -7,19 +7,20 @@
|
||||
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
||||
"build": "./script/generate-sitemap.ts && vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
||||
"start": "vinxi start",
|
||||
"version": "1.0.53"
|
||||
"version": "1.0.77"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
"@kobalte/core": "catalog:",
|
||||
"@openauthjs/openauth": "catalog:",
|
||||
"@opencode-ai/console-core": "workspace:*",
|
||||
"@opencode-ai/console-mail": "workspace:*",
|
||||
"@openauthjs/openauth": "catalog:",
|
||||
"@kobalte/core": "catalog:",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
"@opencode-ai/console-resource": "workspace:*",
|
||||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.15.0",
|
||||
"@solidjs/start": "^1.1.0",
|
||||
"chart.js": "4.5.1",
|
||||
"solid-js": "catalog:",
|
||||
"vinxi": "^0.5.7",
|
||||
"zod": "catalog:"
|
||||
|
||||
@@ -212,3 +212,19 @@ export function IconStealth(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconChevronLeft(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox="0 0 20 20" fill="none">
|
||||
<path d="M12 15L7 10L12 5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconChevronRight(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox="0 0 20 20" fill="none">
|
||||
<path d="M8 5L13 10L8 15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ const getUserEmail = query(async (workspaceID: string) => {
|
||||
|
||||
export default function WorkspaceLayout(props: RouteSectionProps) {
|
||||
const params = useParams()
|
||||
const userEmail = createAsync(() => getUserEmail(params.id))
|
||||
const userEmail = createAsync(() => getUserEmail(params.id!))
|
||||
return (
|
||||
<main data-page="workspace">
|
||||
<Link rel="icon" type="image/svg+xml" href="/favicon-zen.svg" />
|
||||
|
||||
@@ -5,7 +5,7 @@ import "./[id].css"
|
||||
|
||||
export default function WorkspaceLayout(props: RouteSectionProps) {
|
||||
const params = useParams()
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id))
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id!))
|
||||
|
||||
return (
|
||||
<main data-page="workspace">
|
||||
|
||||
@@ -27,7 +27,7 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
|
||||
export function BillingSection() {
|
||||
const params = useParams()
|
||||
// ORIGINAL CODE - COMMENTED OUT FOR TESTING
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
|
||||
const checkoutAction = useAction(createCheckoutUrl)
|
||||
const checkoutSubmission = useSubmission(createCheckoutUrl)
|
||||
const sessionAction = useAction(createSessionUrl)
|
||||
@@ -51,7 +51,7 @@ export function BillingSection() {
|
||||
const amount = parseInt(store.addBalanceAmount)
|
||||
const baseUrl = window.location.href
|
||||
|
||||
const checkout = await checkoutAction(params.id, amount, baseUrl, baseUrl)
|
||||
const checkout = await checkoutAction(params.id!, amount, baseUrl, baseUrl)
|
||||
if (checkout && checkout.data) {
|
||||
setStore("checkoutRedirecting", true)
|
||||
window.location.href = checkout.data
|
||||
@@ -60,7 +60,7 @@ export function BillingSection() {
|
||||
|
||||
async function onClickSession() {
|
||||
const baseUrl = window.location.href
|
||||
const sessionUrl = await sessionAction(params.id, baseUrl)
|
||||
const sessionUrl = await sessionAction(params.id!, baseUrl)
|
||||
if (sessionUrl && sessionUrl.data) {
|
||||
setStore("sessionRedirecting", true)
|
||||
window.location.href = sessionUrl.data
|
||||
|
||||
@@ -8,8 +8,8 @@ import { queryBillingInfo, querySessionInfo } from "../../common"
|
||||
|
||||
export default function () {
|
||||
const params = useParams()
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id))
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id!))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
|
||||
|
||||
return (
|
||||
<div data-page="workspace-[id]">
|
||||
|
||||
@@ -30,7 +30,7 @@ export function MonthlyLimitSection() {
|
||||
const params = useParams()
|
||||
const submission = useSubmission(setMonthlyLimit)
|
||||
const [store, setStore] = createStore({ show: false })
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
|
||||
|
||||
let input: HTMLInputElement
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ const downloadReceipt = action(async (workspaceID: string, paymentID: string) =>
|
||||
|
||||
export function PaymentSection() {
|
||||
const params = useParams()
|
||||
const payments = createAsync(() => getPaymentsInfo(params.id))
|
||||
const payments = createAsync(() => getPaymentsInfo(params.id!))
|
||||
const downloadReceiptAction = useAction(downloadReceipt)
|
||||
|
||||
// DUMMY DATA FOR TESTING
|
||||
@@ -89,7 +89,7 @@ export function PaymentSection() {
|
||||
<td data-slot="payment-receipt">
|
||||
<button
|
||||
onClick={async () => {
|
||||
const receiptUrl = await downloadReceiptAction(params.id, payment.paymentID!)
|
||||
const receiptUrl = await downloadReceiptAction(params.id!, payment.paymentID!)
|
||||
if (receiptUrl) {
|
||||
window.open(receiptUrl, "_blank")
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ const setReload = action(async (form: FormData) => {
|
||||
|
||||
export function ReloadSection() {
|
||||
const params = useParams()
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
|
||||
const setReloadSubmission = useSubmission(setReload)
|
||||
const reloadSubmission = useSubmission(reload)
|
||||
const [store, setStore] = createStore({
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
.root {
|
||||
[data-component="empty-state"] {
|
||||
padding: var(--space-20) var(--space-6);
|
||||
text-align: center;
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
height: 400px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="filter-container"] {
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
|
||||
[data-component="dropdown"] {
|
||||
[data-slot="trigger"] {
|
||||
border: 1px solid var(--color-border);
|
||||
background-color: var(--color-bg);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--border-radius-sm);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.5;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 3px var(--color-accent-alpha);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="chevron"] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
[data-slot="dropdown"] {
|
||||
min-width: 200px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="month-picker"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
[data-slot="month-button"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none !important;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--border-radius-xs);
|
||||
transition: background-color 0.2s;
|
||||
line-height: 1;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-hover);
|
||||
}
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
stroke-width: 2;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="month-label"] {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
line-height: 1.5;
|
||||
min-width: 140px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-slot="model-item"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text);
|
||||
border: none !important;
|
||||
background: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-bg-hover);
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="chart-container"] {
|
||||
padding: var(--space-6);
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
[data-slot="chart-container"] {
|
||||
height: 300px;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
[data-component="empty-state"] {
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
import { and, Database, eq, gte, inArray, isNull, lte, or, sql } from "@opencode-ai/console-core/drizzle/index.js"
|
||||
import { UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||
import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
|
||||
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||
import { AuthTable } from "@opencode-ai/console-core/schema/auth.sql.js"
|
||||
import { createAsync, query, useParams } from "@solidjs/router"
|
||||
import { createEffect, createMemo, onCleanup, Show, For } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { Dropdown } from "~/component/dropdown"
|
||||
import { IconChevronLeft, IconChevronRight } from "~/component/icon"
|
||||
import styles from "./graph-section.module.css"
|
||||
import {
|
||||
Chart,
|
||||
BarController,
|
||||
BarElement,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
Tooltip,
|
||||
Legend,
|
||||
type ChartConfiguration,
|
||||
} from "chart.js"
|
||||
|
||||
Chart.register(BarController, BarElement, CategoryScale, LinearScale, Tooltip, Legend)
|
||||
|
||||
async function getCosts(workspaceID: string, year: number, month: number) {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
const startDate = new Date(year, month, 1)
|
||||
const endDate = new Date(year, month + 1, 0)
|
||||
|
||||
// First query: get usage data without joining keys
|
||||
const usageData = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
date: sql<string>`DATE(${UsageTable.timeCreated})`,
|
||||
model: UsageTable.model,
|
||||
totalCost: sql<number>`SUM(${UsageTable.cost})`,
|
||||
keyId: UsageTable.keyID,
|
||||
})
|
||||
.from(UsageTable)
|
||||
.where(
|
||||
and(
|
||||
eq(UsageTable.workspaceID, workspaceID),
|
||||
gte(UsageTable.timeCreated, startDate),
|
||||
lte(UsageTable.timeCreated, endDate),
|
||||
),
|
||||
)
|
||||
.groupBy(sql`DATE(${UsageTable.timeCreated})`, UsageTable.model, UsageTable.keyID),
|
||||
)
|
||||
|
||||
// Get unique key IDs from usage
|
||||
const usageKeyIds = new Set(usageData.map((r) => r.keyId).filter((id) => id !== null))
|
||||
|
||||
// Second query: get all existing keys plus any keys from usage
|
||||
const keysData = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
keyId: KeyTable.id,
|
||||
keyName: KeyTable.name,
|
||||
userEmail: AuthTable.subject,
|
||||
timeDeleted: KeyTable.timeDeleted,
|
||||
})
|
||||
.from(KeyTable)
|
||||
.innerJoin(UserTable, and(eq(KeyTable.userID, UserTable.id), eq(KeyTable.workspaceID, UserTable.workspaceID)))
|
||||
.innerJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")))
|
||||
.where(
|
||||
and(
|
||||
eq(KeyTable.workspaceID, workspaceID),
|
||||
usageKeyIds.size > 0
|
||||
? or(inArray(KeyTable.id, Array.from(usageKeyIds)), isNull(KeyTable.timeDeleted))
|
||||
: isNull(KeyTable.timeDeleted),
|
||||
),
|
||||
)
|
||||
.orderBy(AuthTable.subject, KeyTable.name),
|
||||
)
|
||||
|
||||
return {
|
||||
usage: usageData,
|
||||
keys: keysData.map((key) => ({
|
||||
id: key.keyId,
|
||||
displayName:
|
||||
key.timeDeleted !== null
|
||||
? `${key.userEmail} - ${key.keyName} (deleted)`
|
||||
: `${key.userEmail} - ${key.keyName}`,
|
||||
})),
|
||||
}
|
||||
}, workspaceID)
|
||||
}
|
||||
|
||||
const queryCosts = query(getCosts, "costs.get")
|
||||
|
||||
const MODEL_COLORS: Record<string, string> = {
|
||||
"claude-sonnet-4-5": "#D4745C",
|
||||
"claude-sonnet-4": "#E8B4A4",
|
||||
"claude-opus-4": "#C8A098",
|
||||
"claude-haiku-4-5": "#F0D8D0",
|
||||
"claude-3-5-haiku": "#F8E8E0",
|
||||
"gpt-5.1": "#4A90E2",
|
||||
"gpt-5.1-codex": "#6BA8F0",
|
||||
"gpt-5": "#7DB8F8",
|
||||
"gpt-5-codex": "#9FCAFF",
|
||||
"gpt-5-nano": "#B8D8FF",
|
||||
"grok-code": "#8B5CF6",
|
||||
"big-pickle": "#10B981",
|
||||
"kimi-k2": "#F59E0B",
|
||||
"qwen3-coder": "#EC4899",
|
||||
"glm-4.6": "#14B8A6",
|
||||
}
|
||||
|
||||
function getModelColor(model: string): string {
|
||||
if (MODEL_COLORS[model]) return MODEL_COLORS[model]
|
||||
|
||||
const hash = model.split("").reduce((acc, char) => char.charCodeAt(0) + ((acc << 5) - acc), 0)
|
||||
const hue = Math.abs(hash) % 360
|
||||
return `hsl(${hue}, 50%, 65%)`
|
||||
}
|
||||
|
||||
function formatDateLabel(dateStr: string): string {
|
||||
const date = new Date()
|
||||
const [y, m, d] = dateStr.split("-").map(Number)
|
||||
date.setFullYear(y)
|
||||
date.setMonth(m - 1)
|
||||
date.setDate(d)
|
||||
date.setHours(0, 0, 0, 0)
|
||||
const month = date.toLocaleDateString("en-US", { month: "short" })
|
||||
const day = date.getUTCDate().toString().padStart(2, "0")
|
||||
return `${month} ${day}`
|
||||
}
|
||||
|
||||
function addOpacityToColor(color: string, opacity: number): string {
|
||||
if (color.startsWith("#")) {
|
||||
const r = parseInt(color.slice(1, 3), 16)
|
||||
const g = parseInt(color.slice(3, 5), 16)
|
||||
const b = parseInt(color.slice(5, 7), 16)
|
||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`
|
||||
}
|
||||
if (color.startsWith("hsl")) return color.replace(")", `, ${opacity})`).replace("hsl", "hsla")
|
||||
return color
|
||||
}
|
||||
|
||||
export function GraphSection() {
|
||||
let canvasRef: HTMLCanvasElement | undefined
|
||||
let chartInstance: Chart | undefined
|
||||
const params = useParams()
|
||||
const now = new Date()
|
||||
const [store, setStore] = createStore({
|
||||
data: null as Awaited<ReturnType<typeof getCosts>> | null,
|
||||
year: now.getFullYear(),
|
||||
month: now.getMonth(),
|
||||
key: null as string | null,
|
||||
model: null as string | null,
|
||||
modelDropdownOpen: false,
|
||||
keyDropdownOpen: false,
|
||||
})
|
||||
const initialData = createAsync(() => queryCosts(params.id!, store.year, store.month))
|
||||
|
||||
const onPreviousMonth = async () => {
|
||||
const month = store.month === 0 ? 11 : store.month - 1
|
||||
const year = store.month === 0 ? store.year - 1 : store.year
|
||||
const data = await getCosts(params.id!, year, month)
|
||||
setStore({ month, year, data })
|
||||
}
|
||||
|
||||
const onNextMonth = async () => {
|
||||
const month = store.month === 11 ? 0 : store.month + 1
|
||||
const year = store.month === 11 ? store.year + 1 : store.year
|
||||
setStore({ month, year, data: await getCosts(params.id!, year, month) })
|
||||
}
|
||||
|
||||
const onSelectModel = (model: string | null) => setStore({ model, modelDropdownOpen: false })
|
||||
|
||||
const onSelectKey = (keyID: string | null) => setStore({ key: keyID, keyDropdownOpen: false })
|
||||
|
||||
const getData = createMemo(() => store.data ?? initialData())
|
||||
|
||||
const getModels = createMemo(() => {
|
||||
const data = getData()
|
||||
if (!data?.usage) return []
|
||||
return Array.from(new Set(data.usage.map((row) => row.model))).sort()
|
||||
})
|
||||
|
||||
const getDates = createMemo(() => {
|
||||
const daysInMonth = new Date(store.year, store.month + 1, 0).getDate()
|
||||
return Array.from({ length: daysInMonth }, (_, i) => {
|
||||
const date = new Date(store.year, store.month, i + 1)
|
||||
return date.toISOString().split("T")[0]
|
||||
})
|
||||
})
|
||||
|
||||
const getKeyName = (keyID: string | null): string => {
|
||||
if (!keyID || !store.data?.keys) return "All Keys"
|
||||
const found = store.data.keys.find((k) => k.id === keyID)
|
||||
return found?.displayName ?? "All Keys"
|
||||
}
|
||||
|
||||
const formatMonthYear = () =>
|
||||
new Date(store.year, store.month, 1).toLocaleDateString("en-US", { month: "long", year: "numeric" })
|
||||
|
||||
const isCurrentMonth = () => store.year === now.getFullYear() && store.month === now.getMonth()
|
||||
|
||||
const chartConfig = createMemo((): ChartConfiguration | null => {
|
||||
const data = getData()
|
||||
const dates = getDates()
|
||||
if (!data?.usage?.length) return null
|
||||
|
||||
const filteredUsageResults = store.key ? data.usage.filter((row) => row.keyId === store.key) : data.usage
|
||||
|
||||
const dailyData = new Map<string, Map<string, number>>()
|
||||
for (const dateKey of dates) dailyData.set(dateKey, new Map())
|
||||
|
||||
for (const row of filteredUsageResults) {
|
||||
const dayMap = dailyData.get(row.date)
|
||||
if (dayMap) {
|
||||
const existing = dayMap.get(row.model) || 0
|
||||
dayMap.set(row.model, existing + row.totalCost)
|
||||
}
|
||||
}
|
||||
|
||||
const filteredModels = store.model === null ? getModels() : [store.model]
|
||||
|
||||
const datasets = filteredModels.map((model) => {
|
||||
const color = getModelColor(model)
|
||||
return {
|
||||
label: model,
|
||||
data: dates.map((date) => (dailyData.get(date)?.get(model) || 0) / 100000000),
|
||||
backgroundColor: color,
|
||||
hoverBackgroundColor: color,
|
||||
borderWidth: 0,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: dates.map(formatDateLabel),
|
||||
datasets,
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
ticks: {
|
||||
maxRotation: 0,
|
||||
autoSkipPadding: 20,
|
||||
color: "rgba(255, 255, 255, 0.5)",
|
||||
font: {
|
||||
family: "monospace",
|
||||
size: 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: "rgba(255, 255, 255, 0.1)",
|
||||
},
|
||||
ticks: {
|
||||
color: "rgba(255, 255, 255, 0.5)",
|
||||
font: {
|
||||
family: "monospace",
|
||||
size: 11,
|
||||
},
|
||||
callback: (value) => {
|
||||
const num = Number(value)
|
||||
return num >= 1000 ? `$${(num / 1000).toFixed(1)}k` : `$${num.toFixed(0)}`
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "index",
|
||||
intersect: false,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.9)",
|
||||
titleColor: "rgba(255, 255, 255, 0.9)",
|
||||
bodyColor: "rgba(255, 255, 255, 0.8)",
|
||||
borderColor: "rgba(255, 255, 255, 0.1)",
|
||||
borderWidth: 1,
|
||||
padding: 12,
|
||||
displayColors: true,
|
||||
callbacks: {
|
||||
label: (context) => {
|
||||
const value = context.parsed.y
|
||||
if (!value || value === 0) return
|
||||
return `${context.dataset.label}: $${value.toFixed(2)}`
|
||||
},
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: "bottom",
|
||||
labels: {
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
font: {
|
||||
size: 12,
|
||||
},
|
||||
padding: 16,
|
||||
boxWidth: 16,
|
||||
boxHeight: 16,
|
||||
usePointStyle: false,
|
||||
},
|
||||
onHover: (event, legendItem, legend) => {
|
||||
const chart = legend.chart
|
||||
chart.data.datasets?.forEach((dataset, i) => {
|
||||
const meta = chart.getDatasetMeta(i)
|
||||
const baseColor = getModelColor(dataset.label || "")
|
||||
const color = i === legendItem.datasetIndex ? baseColor : addOpacityToColor(baseColor, 0.3)
|
||||
meta.data.forEach((bar: any) => {
|
||||
bar.options.backgroundColor = color
|
||||
})
|
||||
})
|
||||
chart.update("none")
|
||||
},
|
||||
onLeave: (event, legendItem, legend) => {
|
||||
const chart = legend.chart
|
||||
chart.data.datasets?.forEach((dataset, i) => {
|
||||
const meta = chart.getDatasetMeta(i)
|
||||
const baseColor = getModelColor(dataset.label || "")
|
||||
meta.data.forEach((bar: any) => {
|
||||
bar.options.backgroundColor = baseColor
|
||||
})
|
||||
})
|
||||
chart.update("none")
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const config = chartConfig()
|
||||
if (!config || !canvasRef) return
|
||||
|
||||
if (chartInstance) chartInstance.destroy()
|
||||
chartInstance = new Chart(canvasRef, config)
|
||||
})
|
||||
|
||||
onCleanup(() => chartInstance?.destroy())
|
||||
|
||||
return (
|
||||
<section class={styles.root}>
|
||||
<div data-slot="section-title">
|
||||
<h2>Cost</h2>
|
||||
<p>Usage costs broken down by model.</p>
|
||||
</div>
|
||||
|
||||
<Show when={getData()}>
|
||||
<div data-slot="filter-container">
|
||||
<div data-slot="month-picker">
|
||||
<button data-slot="month-button" onClick={onPreviousMonth}>
|
||||
<IconChevronLeft />
|
||||
</button>
|
||||
<span data-slot="month-label">{formatMonthYear()}</span>
|
||||
<button data-slot="month-button" onClick={onNextMonth} disabled={isCurrentMonth()}>
|
||||
<IconChevronRight />
|
||||
</button>
|
||||
</div>
|
||||
<Dropdown
|
||||
trigger={store.model === null ? "All Models" : store.model}
|
||||
open={store.modelDropdownOpen}
|
||||
onOpenChange={(open) => setStore({ modelDropdownOpen: open })}
|
||||
>
|
||||
<>
|
||||
<button data-slot="model-item" onClick={() => onSelectModel(null)}>
|
||||
<span>All Models</span>
|
||||
</button>
|
||||
<For each={getModels()}>
|
||||
{(model) => (
|
||||
<button data-slot="model-item" onClick={() => onSelectModel(model)}>
|
||||
<span>{model}</span>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</>
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
trigger={getKeyName(store.key)}
|
||||
open={store.keyDropdownOpen}
|
||||
onOpenChange={(open) => setStore({ keyDropdownOpen: open })}
|
||||
>
|
||||
<>
|
||||
<button data-slot="model-item" onClick={() => onSelectKey(null)}>
|
||||
<span>All Keys</span>
|
||||
</button>
|
||||
<For each={getData()?.keys || []}>
|
||||
{(key) => (
|
||||
<button data-slot="model-item" onClick={() => onSelectKey(key.id)}>
|
||||
<span>{key.displayName}</span>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show
|
||||
when={chartConfig()}
|
||||
fallback={
|
||||
<div data-component="empty-state">
|
||||
<p>No usage data available for the selected period.</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div data-slot="chart-container">
|
||||
<canvas ref={canvasRef} />
|
||||
</div>
|
||||
</Show>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -5,13 +5,14 @@ import { NewUserSection } from "./new-user-section"
|
||||
import { UsageSection } from "./usage-section"
|
||||
import { ModelSection } from "./model-section"
|
||||
import { ProviderSection } from "./provider-section"
|
||||
import { GraphSection } from "./graph-section"
|
||||
import { IconLogo } from "~/component/icon"
|
||||
import { querySessionInfo, queryBillingInfo, createCheckoutUrl, formatBalance } from "../common"
|
||||
|
||||
export default function () {
|
||||
const params = useParams()
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id))
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id!))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
|
||||
const checkoutAction = useAction(createCheckoutUrl)
|
||||
const checkoutSubmission = useSubmission(createCheckoutUrl)
|
||||
const [store, setStore] = createStore({
|
||||
@@ -21,7 +22,7 @@ export default function () {
|
||||
|
||||
async function onClickCheckout() {
|
||||
const baseUrl = window.location.href
|
||||
const checkout = await checkoutAction(params.id, billingInfo()!.reloadAmount, baseUrl, baseUrl)
|
||||
const checkout = await checkoutAction(params.id!, billingInfo()!.reloadAmount, baseUrl, baseUrl)
|
||||
if (checkout && checkout.data) {
|
||||
setStore("checkoutRedirecting", true)
|
||||
window.location.href = checkout.data
|
||||
@@ -66,6 +67,9 @@ export default function () {
|
||||
|
||||
<div data-slot="sections">
|
||||
<NewUserSection />
|
||||
<Show when={userInfo()?.isAdmin}>
|
||||
<GraphSection />
|
||||
</Show>
|
||||
<ModelSection />
|
||||
<Show when={userInfo()?.isAdmin}>
|
||||
<ProviderSection />
|
||||
|
||||
@@ -45,7 +45,7 @@ const listKeys = query(async (workspaceID: string) => {
|
||||
|
||||
export function KeySection() {
|
||||
const params = useParams()
|
||||
const keys = createAsync(() => listKeys(params.id))
|
||||
const keys = createAsync(() => listKeys(params.id!))
|
||||
const submission = useSubmission(createKey)
|
||||
const [store, setStore] = createStore({ show: false })
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ const roleOptions = [
|
||||
|
||||
export function MemberSection() {
|
||||
const params = useParams()
|
||||
const data = createAsync(() => listMembers(params.id))
|
||||
const data = createAsync(() => listMembers(params.id!))
|
||||
const submission = useSubmission(inviteMember)
|
||||
const [store, setStore] = createStore({
|
||||
show: false,
|
||||
@@ -328,7 +328,7 @@ export function MemberSection() {
|
||||
{(member) => (
|
||||
<MemberRow
|
||||
member={member}
|
||||
workspaceID={params.id}
|
||||
workspaceID={params.id!}
|
||||
actorID={data()!.actorID}
|
||||
actorRole={data()!.actorRole}
|
||||
/>
|
||||
|
||||
@@ -22,8 +22,8 @@ const getModelsInfo = query(async (workspaceID: string) => {
|
||||
return withActor(async () => {
|
||||
return {
|
||||
all: Object.entries(ZenData.list().models)
|
||||
.filter(([id, _model]) => !["claude-3-5-haiku", "minimax-m2"].includes(id))
|
||||
.filter(([id, _model]) => !id.startsWith("an-"))
|
||||
.filter(([id, _model]) => !["claude-3-5-haiku"].includes(id))
|
||||
.filter(([id, _model]) => !id.startsWith("alpha-"))
|
||||
.sort(([_idA, modelA], [_idB, modelB]) => modelA.name.localeCompare(modelB.name))
|
||||
.map(([id, model]) => ({ id, name: model.name })),
|
||||
disabled: await Model.listDisabled(),
|
||||
@@ -52,8 +52,8 @@ const updateModel = action(async (form: FormData) => {
|
||||
|
||||
export function ModelSection() {
|
||||
const params = useParams()
|
||||
const modelsInfo = createAsync(() => getModelsInfo(params.id))
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id))
|
||||
const modelsInfo = createAsync(() => getModelsInfo(params.id!))
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id!))
|
||||
|
||||
const modelsWithLab = createMemo(() => {
|
||||
const info = modelsInfo()
|
||||
|
||||
@@ -21,8 +21,8 @@ const listKeys = query(async (workspaceID: string) => {
|
||||
export function NewUserSection() {
|
||||
const params = useParams()
|
||||
const [copiedKey, setCopiedKey] = createSignal(false)
|
||||
const keys = createAsync(() => listKeys(params.id))
|
||||
const usage = createAsync(() => getUsageInfo(params.id))
|
||||
const keys = createAsync(() => listKeys(params.id!))
|
||||
const usage = createAsync(() => getUsageInfo(params.id!))
|
||||
const isNew = createMemo(() => {
|
||||
const keysList = keys()
|
||||
const usageList = usage()
|
||||
|
||||
@@ -54,7 +54,7 @@ const listProviders = query(async (workspaceID: string) => {
|
||||
|
||||
function ProviderRow(props: { provider: Provider }) {
|
||||
const params = useParams()
|
||||
const providers = createAsync(() => listProviders(params.id))
|
||||
const providers = createAsync(() => listProviders(params.id!))
|
||||
const saveSubmission = useSubmission(saveProvider, ([fd]) => fd.get("provider")?.toString() === props.provider.key)
|
||||
const removeSubmission = useSubmission(
|
||||
removeProvider,
|
||||
|
||||
@@ -46,7 +46,7 @@ const updateWorkspace = action(async (form: FormData) => {
|
||||
|
||||
export function SettingsSection() {
|
||||
const params = useParams()
|
||||
const workspaceInfo = createAsync(() => getWorkspaceInfo(params.id))
|
||||
const workspaceInfo = createAsync(() => getWorkspaceInfo(params.id!))
|
||||
const submission = useSubmission(updateWorkspace)
|
||||
const [store, setStore] = createStore({ show: false })
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
.root {
|
||||
/* Empty state */
|
||||
[data-component="empty-state"] {
|
||||
padding: var(--space-20) var(--space-6);
|
||||
text-align: center;
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* Table container */
|
||||
[data-slot="usage-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* Table element */
|
||||
[data-slot="usage-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
@@ -48,7 +47,6 @@
|
||||
|
||||
&[data-slot="usage-model"] {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 400;
|
||||
color: var(--color-text-secondary);
|
||||
max-width: 200px;
|
||||
word-break: break-word;
|
||||
@@ -56,32 +54,65 @@
|
||||
|
||||
&[data-slot="usage-cost"] {
|
||||
color: var(--color-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Pagination */
|
||||
[data-slot="pagination"] {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-4) 0;
|
||||
border-top: 1px solid var(--color-border-muted);
|
||||
margin-top: var(--space-2);
|
||||
|
||||
button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--color-bg-tertiary);
|
||||
border-color: var(--color-border-hover);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
/* Mobile responsive */
|
||||
@media (max-width: 40rem) {
|
||||
[data-slot="usage-table-element"] {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(2) /* Model */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(2) /* Model */ {
|
||||
display: none;
|
||||
}
|
||||
/* Hide Model column on mobile */
|
||||
th:nth-child(2),
|
||||
td:nth-child(2) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,50 @@
|
||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||
import { query, useParams, createAsync } from "@solidjs/router"
|
||||
import { createMemo, For, Show } from "solid-js"
|
||||
import { createAsync, query, useParams } from "@solidjs/router"
|
||||
import { createMemo, For, Show, createEffect } from "solid-js"
|
||||
import { formatDateUTC, formatDateForTable } from "../common"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { IconChevronLeft, IconChevronRight } from "~/component/icon"
|
||||
import styles from "./usage-section.module.css"
|
||||
import { createStore } from "solid-js/store"
|
||||
|
||||
const getUsageInfo = query(async (workspaceID: string) => {
|
||||
const PAGE_SIZE = 50
|
||||
|
||||
async function getUsageInfo(workspaceID: string, page: number) {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
return await Billing.usages()
|
||||
return await Billing.usages(page, PAGE_SIZE)
|
||||
}, workspaceID)
|
||||
}, "usage.list")
|
||||
}
|
||||
|
||||
const queryUsageInfo = query(getUsageInfo, "usage.list")
|
||||
|
||||
export function UsageSection() {
|
||||
const params = useParams()
|
||||
// ORIGINAL CODE - COMMENTED OUT FOR TESTING
|
||||
const usage = createAsync(() => getUsageInfo(params.id))
|
||||
const usage = createAsync(() => queryUsageInfo(params.id!, 0))
|
||||
const [store, setStore] = createStore({ page: 0, usage: [] as Awaited<ReturnType<typeof getUsageInfo>> })
|
||||
|
||||
// DUMMY DATA FOR TESTING
|
||||
// const usage = () => [
|
||||
// {
|
||||
// timeCreated: new Date(Date.now() - 86400000 * 0).toISOString(), // Today
|
||||
// model: "claude-3-5-sonnet-20241022",
|
||||
// inputTokens: 1247,
|
||||
// outputTokens: 423,
|
||||
// cost: 125400000, // $1.254
|
||||
// },
|
||||
// {
|
||||
// timeCreated: new Date(Date.now() - 86400000 * 0.5).toISOString(), // 12 hours ago
|
||||
// model: "claude-3-haiku-20240307",
|
||||
// inputTokens: 892,
|
||||
// outputTokens: 156,
|
||||
// cost: 23500000, // $0.235
|
||||
// },
|
||||
// {
|
||||
// timeCreated: new Date(Date.now() - 86400000 * 1).toISOString(), // Yesterday
|
||||
// model: "claude-3-5-sonnet-20241022",
|
||||
// inputTokens: 2134,
|
||||
// outputTokens: 687,
|
||||
// cost: 234700000, // $2.347
|
||||
// },
|
||||
// {
|
||||
// timeCreated: new Date(Date.now() - 86400000 * 1.3).toISOString(), // 1.3 days ago
|
||||
// model: "gpt-4o-mini",
|
||||
// inputTokens: 567,
|
||||
// outputTokens: 234,
|
||||
// cost: 8900000, // $0.089
|
||||
// },
|
||||
// {
|
||||
// timeCreated: new Date(Date.now() - 86400000 * 2).toISOString(), // 2 days ago
|
||||
// model: "claude-3-opus-20240229",
|
||||
// inputTokens: 1893,
|
||||
// outputTokens: 945,
|
||||
// cost: 445600000, // $4.456
|
||||
// },
|
||||
// {
|
||||
// timeCreated: new Date(Date.now() - 86400000 * 2.7).toISOString(), // 2.7 days ago
|
||||
// model: "gpt-4o",
|
||||
// inputTokens: 1456,
|
||||
// outputTokens: 532,
|
||||
// cost: 156800000, // $1.568
|
||||
// },
|
||||
// {
|
||||
// timeCreated: new Date(Date.now() - 86400000 * 3).toISOString(), // 3 days ago
|
||||
// model: "claude-3-haiku-20240307",
|
||||
// inputTokens: 634,
|
||||
// outputTokens: 89,
|
||||
// cost: 12300000, // $0.123
|
||||
// },
|
||||
// {
|
||||
// timeCreated: new Date(Date.now() - 86400000 * 4).toISOString(), // 4 days ago
|
||||
// model: "claude-3-5-sonnet-20241022",
|
||||
// inputTokens: 3245,
|
||||
// outputTokens: 1123,
|
||||
// cost: 387200000, // $3.872
|
||||
// },
|
||||
// ]
|
||||
createEffect(() => {
|
||||
setStore({ usage: usage() })
|
||||
}, [usage])
|
||||
|
||||
const hasResults = createMemo(() => store.usage && store.usage.length > 0)
|
||||
const canGoPrev = createMemo(() => store.page > 0)
|
||||
const canGoNext = createMemo(() => store.usage && store.usage.length === PAGE_SIZE)
|
||||
|
||||
const goPrev = async () => {
|
||||
const usage = await getUsageInfo(params.id!, store.page - 1)
|
||||
setStore({
|
||||
page: store.page - 1,
|
||||
usage,
|
||||
})
|
||||
}
|
||||
const goNext = async () => {
|
||||
const usage = await getUsageInfo(params.id!, store.page + 1)
|
||||
setStore({
|
||||
page: store.page + 1,
|
||||
usage,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<section class={styles.root}>
|
||||
@@ -85,7 +54,7 @@ export function UsageSection() {
|
||||
</div>
|
||||
<div data-slot="usage-table">
|
||||
<Show
|
||||
when={usage() && usage()!.length > 0}
|
||||
when={hasResults()}
|
||||
fallback={
|
||||
<div data-component="empty-state">
|
||||
<p>Make your first API call to get started.</p>
|
||||
@@ -103,7 +72,7 @@ export function UsageSection() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={usage()!}>
|
||||
<For each={store.usage}>
|
||||
{(usage) => {
|
||||
const date = createMemo(() => new Date(usage.timeCreated))
|
||||
return (
|
||||
@@ -121,6 +90,16 @@ export function UsageSection() {
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
<Show when={canGoPrev() || canGoNext()}>
|
||||
<div data-slot="pagination">
|
||||
<button disabled={!canGoPrev()} onClick={goPrev}>
|
||||
<IconChevronLeft />
|
||||
</button>
|
||||
<button disabled={!canGoNext()} onClick={goNext}>
|
||||
<IconChevronRight />
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -15,72 +15,99 @@ import { logger } from "./logger"
|
||||
import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError, RateLimitError } from "./error"
|
||||
import { createBodyConverter, createStreamPartConverter, createResponseConverter } from "./provider/provider"
|
||||
import { anthropicHelper } from "./provider/anthropic"
|
||||
import { googleHelper } from "./provider/google"
|
||||
import { openaiHelper } from "./provider/openai"
|
||||
import { oaCompatHelper } from "./provider/openai-compatible"
|
||||
import { createRateLimiter } from "./rateLimiter"
|
||||
|
||||
type ZenData = Awaited<ReturnType<typeof ZenData.list>>
|
||||
type RetryOptions = {
|
||||
excludeProviders: string[]
|
||||
retryCount: number
|
||||
}
|
||||
|
||||
export async function handler(
|
||||
input: APIEvent,
|
||||
opts: {
|
||||
format: ZenData.Format
|
||||
parseApiKey: (headers: Headers) => string | undefined
|
||||
parseModel: (url: string, body: any) => string
|
||||
parseIsStream: (url: string, body: any) => boolean
|
||||
},
|
||||
) {
|
||||
type AuthInfo = Awaited<ReturnType<typeof authenticate>>
|
||||
type ModelInfo = Awaited<ReturnType<typeof validateModel>>
|
||||
type ProviderInfo = Awaited<ReturnType<typeof selectProvider>>
|
||||
|
||||
const MAX_RETRIES = 3
|
||||
const FREE_WORKSPACES = [
|
||||
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
|
||||
"wrk_01K6W1A3VE0KMNVSCQT43BG2SX", // opencode bench
|
||||
]
|
||||
|
||||
try {
|
||||
const url = input.request.url
|
||||
const body = await input.request.json()
|
||||
const ip = input.request.headers.get("x-real-ip") ?? ""
|
||||
const model = opts.parseModel(url, body)
|
||||
const isStream = opts.parseIsStream(url, body)
|
||||
logger.metric({
|
||||
is_tream: !!body.stream,
|
||||
is_tream: isStream,
|
||||
session: input.request.headers.get("x-opencode-session"),
|
||||
request: input.request.headers.get("x-opencode-request"),
|
||||
})
|
||||
const zenData = ZenData.list()
|
||||
const modelInfo = validateModel(zenData, body.model)
|
||||
const providerInfo = selectProvider(zenData, modelInfo, ip)
|
||||
const authInfo = await authenticate(modelInfo, providerInfo)
|
||||
const modelInfo = validateModel(zenData, model)
|
||||
const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip)
|
||||
await rateLimiter?.check()
|
||||
validateBilling(authInfo, modelInfo)
|
||||
validateModelSettings(authInfo)
|
||||
updateProviderKey(authInfo, providerInfo)
|
||||
logger.metric({ provider: providerInfo.id })
|
||||
|
||||
// Request to model provider
|
||||
const startTimestamp = Date.now()
|
||||
const reqUrl = providerInfo.modifyUrl(providerInfo.api)
|
||||
const reqBody = JSON.stringify(
|
||||
providerInfo.modifyBody({
|
||||
...createBodyConverter(opts.format, providerInfo.format)(body),
|
||||
model: providerInfo.model,
|
||||
}),
|
||||
)
|
||||
logger.debug("REQUEST URL: " + reqUrl)
|
||||
logger.debug("REQUEST: " + reqBody.substring(0, 300) + "...")
|
||||
const res = await fetch(reqUrl, {
|
||||
method: "POST",
|
||||
headers: (() => {
|
||||
const headers = input.request.headers
|
||||
headers.delete("host")
|
||||
headers.delete("content-length")
|
||||
providerInfo.modifyHeaders(headers, body, providerInfo.apiKey)
|
||||
Object.entries(providerInfo.headerMappings ?? {}).forEach(([k, v]) => {
|
||||
headers.set(k, headers.get(v)!)
|
||||
const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
|
||||
const providerInfo = selectProvider(zenData, modelInfo, ip, retry)
|
||||
const authInfo = await authenticate(modelInfo, providerInfo)
|
||||
validateBilling(authInfo, modelInfo)
|
||||
validateModelSettings(authInfo)
|
||||
updateProviderKey(authInfo, providerInfo)
|
||||
logger.metric({ provider: providerInfo.id })
|
||||
|
||||
const startTimestamp = Date.now()
|
||||
const reqUrl = providerInfo.modifyUrl(providerInfo.api, providerInfo.model, isStream)
|
||||
const reqBody = JSON.stringify(
|
||||
providerInfo.modifyBody({
|
||||
...createBodyConverter(opts.format, providerInfo.format)(body),
|
||||
model: providerInfo.model,
|
||||
}),
|
||||
)
|
||||
logger.debug("REQUEST URL: " + reqUrl)
|
||||
logger.debug("REQUEST: " + reqBody.substring(0, 300) + "...")
|
||||
const res = await fetch(reqUrl, {
|
||||
method: "POST",
|
||||
headers: (() => {
|
||||
const headers = new Headers(input.request.headers)
|
||||
providerInfo.modifyHeaders(headers, body, providerInfo.apiKey)
|
||||
Object.entries(providerInfo.headerMappings ?? {}).forEach(([k, v]) => {
|
||||
headers.set(k, headers.get(v)!)
|
||||
})
|
||||
headers.delete("host")
|
||||
headers.delete("content-length")
|
||||
headers.delete("x-opencode-request")
|
||||
headers.delete("x-opencode-session")
|
||||
return headers
|
||||
})(),
|
||||
body: reqBody,
|
||||
})
|
||||
|
||||
// Try another provider => stop retrying if using fallback provider
|
||||
if (res.status !== 200 && modelInfo.fallbackProvider && providerInfo.id !== modelInfo.fallbackProvider) {
|
||||
return retriableRequest({
|
||||
excludeProviders: [...retry.excludeProviders, providerInfo.id],
|
||||
retryCount: retry.retryCount + 1,
|
||||
})
|
||||
return headers
|
||||
})(),
|
||||
body: reqBody,
|
||||
})
|
||||
}
|
||||
|
||||
return { providerInfo, authInfo, res, startTimestamp }
|
||||
}
|
||||
|
||||
const { providerInfo, authInfo, res, startTimestamp } = await retriableRequest()
|
||||
|
||||
// Scrub response headers
|
||||
const resHeaders = new Headers()
|
||||
@@ -93,7 +120,7 @@ export async function handler(
|
||||
logger.debug("STATUS: " + res.status + " " + res.statusText)
|
||||
|
||||
// Handle non-streaming response
|
||||
if (!body.stream) {
|
||||
if (!isStream) {
|
||||
const responseConverter = createResponseConverter(providerInfo.format, opts.format)
|
||||
const json = await res.json()
|
||||
const body = JSON.stringify(responseConverter(json))
|
||||
@@ -148,7 +175,7 @@ export async function handler(
|
||||
responseLength += value.length
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
|
||||
const parts = buffer.split("\n\n")
|
||||
const parts = buffer.split(providerInfo.streamSeparator)
|
||||
buffer = parts.pop() ?? ""
|
||||
|
||||
for (let part of parts) {
|
||||
@@ -236,19 +263,25 @@ export async function handler(
|
||||
return { id: modelId, ...modelData }
|
||||
}
|
||||
|
||||
function selectProvider(zenData: ZenData, modelInfo: ModelInfo, ip: string) {
|
||||
const providers = modelInfo.providers
|
||||
.filter((provider) => !provider.disabled)
|
||||
.flatMap((provider) => Array<typeof provider>(provider.weight ?? 1).fill(provider))
|
||||
function selectProvider(zenData: ZenData, modelInfo: ModelInfo, ip: string, retry: RetryOptions) {
|
||||
const provider = (() => {
|
||||
if (retry.retryCount === MAX_RETRIES) {
|
||||
return modelInfo.providers.find((provider) => provider.id === modelInfo.fallbackProvider)
|
||||
}
|
||||
|
||||
// Use the last 2 characters of IP address to select a provider
|
||||
const lastChars = ip.slice(-2)
|
||||
const index = parseInt(lastChars, 16) % providers.length
|
||||
const provider = providers[index || 0]
|
||||
const providers = modelInfo.providers
|
||||
.filter((provider) => !provider.disabled)
|
||||
.filter((provider) => !retry.excludeProviders.includes(provider.id))
|
||||
.flatMap((provider) => Array<typeof provider>(provider.weight ?? 1).fill(provider))
|
||||
|
||||
if (!(provider.id in zenData.providers)) {
|
||||
throw new ModelError(`Provider ${provider.id} not supported`)
|
||||
}
|
||||
// Use the last 2 characters of IP address to select a provider
|
||||
const lastChars = ip.slice(-2)
|
||||
const index = parseInt(lastChars, 16) % providers.length
|
||||
return providers[index || 0]
|
||||
})()
|
||||
|
||||
if (!provider) throw new ModelError("No provider available")
|
||||
if (!(provider.id in zenData.providers)) throw new ModelError(`Provider ${provider.id} not supported`)
|
||||
|
||||
return {
|
||||
...provider,
|
||||
@@ -256,6 +289,7 @@ export async function handler(
|
||||
...(() => {
|
||||
const format = zenData.providers[provider.id].format
|
||||
if (format === "anthropic") return anthropicHelper
|
||||
if (format === "google") return googleHelper
|
||||
if (format === "openai") return openaiHelper
|
||||
return oaCompatHelper
|
||||
})(),
|
||||
@@ -264,7 +298,7 @@ export async function handler(
|
||||
|
||||
async function authenticate(modelInfo: ModelInfo, providerInfo: ProviderInfo) {
|
||||
const apiKey = opts.parseApiKey(input.request.headers)
|
||||
if (!apiKey) {
|
||||
if (!apiKey || apiKey === "public") {
|
||||
if (modelInfo.allowAnonymous) return
|
||||
throw new AuthError("Missing API key.")
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export const anthropicHelper = {
|
||||
service_tier: "standard_only",
|
||||
}
|
||||
},
|
||||
streamSeparator: "\n\n",
|
||||
createUsageParser: () => {
|
||||
let usage: Usage
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { ProviderHelper } from "./provider"
|
||||
|
||||
/*
|
||||
{
|
||||
promptTokenCount: 11453,
|
||||
candidatesTokenCount: 71,
|
||||
totalTokenCount: 11625,
|
||||
cachedContentTokenCount: 8100,
|
||||
promptTokensDetails: [
|
||||
{modality: "TEXT",tokenCount: 11453}
|
||||
],
|
||||
cacheTokensDetails: [
|
||||
{modality: "TEXT",tokenCount: 8100}
|
||||
],
|
||||
thoughtsTokenCount: 101
|
||||
}
|
||||
*/
|
||||
|
||||
type Usage = {
|
||||
promptTokenCount?: number
|
||||
candidatesTokenCount?: number
|
||||
totalTokenCount?: number
|
||||
cachedContentTokenCount?: number
|
||||
promptTokensDetails?: { modality: string; tokenCount: number }[]
|
||||
cacheTokensDetails?: { modality: string; tokenCount: number }[]
|
||||
thoughtsTokenCount?: number
|
||||
}
|
||||
|
||||
export const googleHelper = {
|
||||
format: "google",
|
||||
modifyUrl: (providerApi: string, model?: string, isStream?: boolean) =>
|
||||
`${providerApi}/models/${model}:${isStream ? "streamGenerateContent?alt=sse" : "generateContent"}`,
|
||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
||||
headers.set("x-goog-api-key", apiKey)
|
||||
},
|
||||
modifyBody: (body: Record<string, any>) => {
|
||||
return body
|
||||
},
|
||||
streamSeparator: "\r\n\r\n",
|
||||
createUsageParser: () => {
|
||||
let usage: Usage
|
||||
|
||||
return {
|
||||
parse: (chunk: string) => {
|
||||
if (!chunk.startsWith("data: ")) return
|
||||
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(chunk.slice(6)) as { usageMetadata?: Usage }
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!json.usageMetadata) return
|
||||
usage = json.usageMetadata
|
||||
},
|
||||
retrieve: () => usage,
|
||||
}
|
||||
},
|
||||
normalizeUsage: (usage: Usage) => {
|
||||
const inputTokens = usage.promptTokenCount ?? 0
|
||||
const outputTokens = usage.candidatesTokenCount ?? 0
|
||||
const reasoningTokens = usage.thoughtsTokenCount ?? 0
|
||||
const cacheReadTokens = usage.cachedContentTokenCount ?? 0
|
||||
return {
|
||||
inputTokens: inputTokens - cacheReadTokens,
|
||||
outputTokens,
|
||||
reasoningTokens,
|
||||
cacheReadTokens,
|
||||
cacheWrite5mTokens: undefined,
|
||||
cacheWrite1hTokens: undefined,
|
||||
}
|
||||
},
|
||||
} satisfies ProviderHelper
|
||||
@@ -33,6 +33,7 @@ export const oaCompatHelper = {
|
||||
...(body.stream ? { stream_options: { include_usage: true } } : {}),
|
||||
}
|
||||
},
|
||||
streamSeparator: "\n\n",
|
||||
createUsageParser: () => {
|
||||
let usage: Usage
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ export const openaiHelper = {
|
||||
modifyBody: (body: Record<string, any>) => {
|
||||
return body
|
||||
},
|
||||
streamSeparator: "\n\n",
|
||||
createUsageParser: () => {
|
||||
let usage: Usage
|
||||
|
||||
|
||||
@@ -26,9 +26,10 @@ import {
|
||||
|
||||
export type ProviderHelper = {
|
||||
format: ZenData.Format
|
||||
modifyUrl: (providerApi: string) => string
|
||||
modifyUrl: (providerApi: string, model?: string, isStream?: boolean) => string
|
||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => void
|
||||
modifyBody: (body: Record<string, any>) => Record<string, any>
|
||||
streamSeparator: string
|
||||
createUsageParser: () => {
|
||||
parse: (chunk: string) => void
|
||||
retrieve: () => any
|
||||
|
||||
@@ -5,5 +5,7 @@ export function POST(input: APIEvent) {
|
||||
return handler(input, {
|
||||
format: "oa-compat",
|
||||
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
||||
parseModel: (url: string, body: any) => body.model,
|
||||
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,5 +5,7 @@ export function POST(input: APIEvent) {
|
||||
return handler(input, {
|
||||
format: "anthropic",
|
||||
parseApiKey: (headers: Headers) => headers.get("x-api-key") ?? undefined,
|
||||
parseModel: (url: string, body: any) => body.model,
|
||||
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { handler } from "~/routes/zen/util/handler"
|
||||
|
||||
export function POST(input: APIEvent) {
|
||||
return handler(input, {
|
||||
format: "google",
|
||||
parseApiKey: (headers: Headers) => headers.get("x-goog-api-key") ?? undefined,
|
||||
parseModel: (url: string, body: any) => url.split("/").pop()?.split(":")?.[0] ?? "",
|
||||
parseIsStream: (url: string, body: any) =>
|
||||
// ie. url: https://opencode.ai/zen/v1/models/gemini-3-pro:streamGenerateContent?alt=sse'
|
||||
url.split("/").pop()?.split(":")?.[1]?.startsWith("streamGenerateContent") ?? false,
|
||||
})
|
||||
}
|
||||
@@ -5,5 +5,7 @@ export function POST(input: APIEvent) {
|
||||
return handler(input, {
|
||||
format: "openai",
|
||||
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
||||
parseModel: (url: string, body: any) => body.model,
|
||||
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -57,14 +57,15 @@ export namespace Billing {
|
||||
)
|
||||
}
|
||||
|
||||
export const usages = async () => {
|
||||
export const usages = async (page = 0, pageSize = 50) => {
|
||||
return await Database.use((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(UsageTable)
|
||||
.where(eq(UsageTable.workspaceID, Actor.workspace()))
|
||||
.orderBy(sql`${UsageTable.timeCreated} DESC`)
|
||||
.limit(100),
|
||||
.limit(pageSize)
|
||||
.offset(page * pageSize),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Actor } from "./actor"
|
||||
import { Resource } from "@opencode-ai/console-resource"
|
||||
|
||||
export namespace ZenData {
|
||||
const FormatSchema = z.enum(["anthropic", "openai", "oa-compat"])
|
||||
const FormatSchema = z.enum(["anthropic", "google", "openai", "oa-compat"])
|
||||
export type Format = z.infer<typeof FormatSchema>
|
||||
|
||||
const ModelCostSchema = z.object({
|
||||
@@ -25,6 +25,7 @@ export namespace ZenData {
|
||||
cost200K: ModelCostSchema.optional(),
|
||||
allowAnonymous: z.boolean().optional(),
|
||||
rateLimit: z.number().optional(),
|
||||
fallbackProvider: z.string().optional(),
|
||||
providers: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -8,14 +8,12 @@
|
||||
<title>OpenCode</title>
|
||||
</head>
|
||||
<body class="antialiased overscroll-none select-none text-12-regular">
|
||||
<!-- <script> -->
|
||||
<!-- ;(function () { -->
|
||||
<!-- const savedTheme = localStorage.getItem("theme") || "opencode" -->
|
||||
<!-- const savedDarkMode = localStorage.getItem("darkMode") !== "false" -->
|
||||
<!-- document.documentElement.setAttribute("data-theme", savedTheme) -->
|
||||
<!-- document.documentElement.setAttribute("data-dark", savedDarkMode.toString()) -->
|
||||
<!-- })() -->
|
||||
<!-- </script> -->
|
||||
<script>
|
||||
;(function () {
|
||||
const savedTheme = localStorage.getItem("theme") || "oc-1"
|
||||
document.documentElement.setAttribute("data-theme", savedTheme)
|
||||
})()
|
||||
</script>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script src="/src/index.tsx" type="module"></script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -64,7 +64,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const handleFileSelect = (path: string | undefined) => {
|
||||
if (!path) return
|
||||
addPart({ type: "file", path, content: "@" + getFilename(path), start: 0, end: 0 })
|
||||
setStore("popoverIsOpen", false)
|
||||
}
|
||||
|
||||
const { flat, active, onInput, onKeyDown, refetch } = useFilteredList<string>({
|
||||
@@ -114,16 +113,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
),
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => session.prompt.cursor(),
|
||||
(cursor) => {
|
||||
if (cursor === undefined) return
|
||||
queueMicrotask(() => setCursorPosition(editorRef, cursor))
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
const parseFromDOM = (): Prompt => {
|
||||
const newParts: Prompt = []
|
||||
let position = 0
|
||||
@@ -173,118 +162,70 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
|
||||
const addPart = (part: ContentPart) => {
|
||||
const selection = window.getSelection()
|
||||
if (!selection || selection.rangeCount === 0) return
|
||||
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
const prompt = session.prompt.current()
|
||||
const rawText = prompt.map((p) => p.content).join("")
|
||||
const textBeforeCursor = rawText.substring(0, cursorPosition)
|
||||
const atMatch = textBeforeCursor.match(/@(\S*)$/)
|
||||
|
||||
const startIndex = atMatch ? atMatch.index! : cursorPosition
|
||||
const endIndex = atMatch ? cursorPosition : cursorPosition
|
||||
if (part.type === "file") {
|
||||
const pill = document.createElement("span")
|
||||
pill.textContent = part.content
|
||||
pill.setAttribute("data-type", "file")
|
||||
pill.setAttribute("data-path", part.path)
|
||||
pill.setAttribute("contenteditable", "false")
|
||||
pill.style.userSelect = "text"
|
||||
pill.style.cursor = "default"
|
||||
|
||||
const pushText = (acc: { parts: ContentPart[]; runningIndex: number }, value: string) => {
|
||||
if (!value) return
|
||||
const last = acc.parts[acc.parts.length - 1]
|
||||
if (last && last.type === "text") {
|
||||
acc.parts[acc.parts.length - 1] = {
|
||||
type: "text",
|
||||
content: last.content + value,
|
||||
start: last.start,
|
||||
end: last.end + value.length,
|
||||
const gap = document.createTextNode(" ")
|
||||
const range = selection.getRangeAt(0)
|
||||
|
||||
if (atMatch) {
|
||||
let node: Node | null = range.startContainer
|
||||
let offset = range.startOffset
|
||||
let runningLength = 0
|
||||
|
||||
const walker = document.createTreeWalker(editorRef, NodeFilter.SHOW_TEXT, null)
|
||||
let currentNode = walker.nextNode()
|
||||
while (currentNode) {
|
||||
const textContent = currentNode.textContent || ""
|
||||
if (runningLength + textContent.length >= atMatch.index!) {
|
||||
const localStart = atMatch.index! - runningLength
|
||||
const localEnd = cursorPosition - runningLength
|
||||
if (currentNode === range.startContainer || runningLength + textContent.length >= cursorPosition) {
|
||||
range.setStart(currentNode, localStart)
|
||||
range.setEnd(currentNode, Math.min(localEnd, textContent.length))
|
||||
break
|
||||
}
|
||||
}
|
||||
runningLength += textContent.length
|
||||
currentNode = walker.nextNode()
|
||||
}
|
||||
return
|
||||
}
|
||||
acc.parts.push({ type: "text", content: value, start: acc.runningIndex, end: acc.runningIndex + value.length })
|
||||
|
||||
range.deleteContents()
|
||||
range.insertNode(gap)
|
||||
range.insertNode(pill)
|
||||
range.setStartAfter(gap)
|
||||
range.collapse(true)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
} else if (part.type === "text") {
|
||||
const textNode = document.createTextNode(part.content)
|
||||
const range = selection.getRangeAt(0)
|
||||
range.deleteContents()
|
||||
range.insertNode(textNode)
|
||||
range.setStartAfter(textNode)
|
||||
range.collapse(true)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
|
||||
const {
|
||||
parts: nextParts,
|
||||
inserted,
|
||||
cursorPositionAfter,
|
||||
} = prompt.reduce(
|
||||
(acc, item) => {
|
||||
if (acc.inserted) {
|
||||
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
|
||||
acc.runningIndex += item.content.length
|
||||
return acc
|
||||
}
|
||||
|
||||
const nextIndex = acc.runningIndex + item.content.length
|
||||
if (nextIndex <= startIndex) {
|
||||
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
|
||||
acc.runningIndex = nextIndex
|
||||
return acc
|
||||
}
|
||||
|
||||
if (item.type !== "text") {
|
||||
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
|
||||
acc.runningIndex = nextIndex
|
||||
return acc
|
||||
}
|
||||
|
||||
const headLength = Math.max(0, startIndex - acc.runningIndex)
|
||||
const tailLength = Math.max(0, endIndex - acc.runningIndex)
|
||||
const head = item.content.slice(0, headLength)
|
||||
const tail = item.content.slice(tailLength)
|
||||
|
||||
pushText(acc, head)
|
||||
acc.runningIndex += head.length
|
||||
|
||||
if (part.type === "text") {
|
||||
pushText(acc, part.content)
|
||||
acc.runningIndex += part.content.length
|
||||
}
|
||||
if (part.type !== "text") {
|
||||
acc.parts.push({ ...part, start: acc.runningIndex, end: acc.runningIndex + part.content.length })
|
||||
acc.runningIndex += part.content.length
|
||||
}
|
||||
|
||||
const needsGap = Boolean(atMatch)
|
||||
const rest = needsGap ? (tail ? (/^\s/.test(tail) ? tail : ` ${tail}`) : " ") : tail
|
||||
pushText(acc, rest)
|
||||
acc.runningIndex += rest.length
|
||||
|
||||
const baseCursor = startIndex + part.content.length
|
||||
const cursorAddition = needsGap && rest.length > 0 ? 1 : 0
|
||||
acc.cursorPositionAfter = baseCursor + cursorAddition
|
||||
|
||||
acc.inserted = true
|
||||
return acc
|
||||
},
|
||||
{
|
||||
parts: [] as ContentPart[],
|
||||
runningIndex: 0,
|
||||
inserted: false,
|
||||
cursorPositionAfter: cursorPosition + part.content.length,
|
||||
},
|
||||
)
|
||||
|
||||
if (!inserted) {
|
||||
const baseParts = prompt.filter((item) => !(item.type === "text" && item.content === ""))
|
||||
const runningIndex = baseParts.reduce((sum, p) => sum + p.content.length, 0)
|
||||
const appendedAcc = { parts: [...baseParts] as ContentPart[], runningIndex }
|
||||
if (part.type === "text") {
|
||||
pushText(appendedAcc, part.content)
|
||||
}
|
||||
if (part.type !== "text") {
|
||||
appendedAcc.parts.push({
|
||||
...part,
|
||||
start: appendedAcc.runningIndex,
|
||||
end: appendedAcc.runningIndex + part.content.length,
|
||||
})
|
||||
}
|
||||
const next = appendedAcc.parts.length > 0 ? appendedAcc.parts : DEFAULT_PROMPT
|
||||
const nextCursor = rawText.length + part.content.length
|
||||
session.prompt.set(next, nextCursor)
|
||||
setStore("popoverIsOpen", false)
|
||||
queueMicrotask(() => setCursorPosition(editorRef, nextCursor))
|
||||
return
|
||||
}
|
||||
|
||||
session.prompt.set(nextParts, cursorPositionAfter)
|
||||
handleInput()
|
||||
setStore("popoverIsOpen", false)
|
||||
|
||||
queueMicrotask(() => setCursorPosition(editorRef, cursorPositionAfter))
|
||||
}
|
||||
|
||||
const abort = () =>
|
||||
@@ -325,7 +266,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
if (!existing) {
|
||||
const created = await sdk.client.session.create()
|
||||
existing = created.data ?? undefined
|
||||
if (existing) navigate(`/session/${existing.id}`)
|
||||
if (existing) navigate(existing.id)
|
||||
}
|
||||
if (!existing) return
|
||||
|
||||
@@ -378,7 +319,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
session.layout.setActiveTab(undefined)
|
||||
session.messages.setActive(undefined)
|
||||
session.prompt.set(DEFAULT_PROMPT, 0)
|
||||
// Clear the editor DOM directly to ensure it's empty
|
||||
editorRef.innerHTML = ""
|
||||
session.prompt.set([{ type: "text", content: "", start: 0, end: 0 }], 0)
|
||||
|
||||
sdk.client.session.prompt({
|
||||
path: { id: existing.id },
|
||||
@@ -402,7 +345,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
return (
|
||||
<div class="relative size-full _max-h-[320px] flex flex-col gap-3">
|
||||
<Show when={store.popoverIsOpen}>
|
||||
<div class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-[252px] min-h-10 overflow-y-auto flex flex-col p-2 pb-0 rounded-2xl border border-border-base bg-surface-raised-stronger-non-alpha shadow-md">
|
||||
<div
|
||||
class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-[252px] min-h-10
|
||||
overflow-auto no-scrollbar flex flex-col p-2 pb-0 rounded-md
|
||||
border border-border-base bg-surface-raised-stronger-non-alpha shadow-md"
|
||||
>
|
||||
<Show when={flat().length > 0} fallback={<div class="text-text-weak px-2">No matching files</div>}>
|
||||
<For each={flat()}>
|
||||
{(i) => (
|
||||
@@ -419,7 +366,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
|
||||
{getDirectory(i)}
|
||||
</span>
|
||||
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
|
||||
<Show when={!i.endsWith("/")}>
|
||||
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-1 text-text-muted/40 shrink-0"></div>
|
||||
@@ -433,7 +382,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
onSubmit={handleSubmit}
|
||||
classList={{
|
||||
"bg-surface-raised-stronger-non-alpha border border-border-strong-base": true,
|
||||
"rounded-2xl overflow-clip focus-within:border-transparent focus-within:shadow-xs-border-select": true,
|
||||
"rounded-md overflow-clip focus-within:border-transparent focus-within:shadow-xs-border-select": true,
|
||||
[props.class ?? ""]: !!props.class,
|
||||
}}
|
||||
>
|
||||
@@ -447,17 +396,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
onInput={handleInput}
|
||||
onKeyDown={handleKeyDown}
|
||||
classList={{
|
||||
"w-full p-3 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
|
||||
"w-full px-5 py-3 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
|
||||
"[&>[data-type=file]]:text-icon-info-active": true,
|
||||
}}
|
||||
/>
|
||||
<Show when={!session.prompt.dirty()}>
|
||||
<div class="absolute top-0 left-0 p-3 text-14-regular text-text-weak pointer-events-none">
|
||||
<div class="absolute top-0 left-0 px-5 py-3 text-14-regular text-text-weak pointer-events-none">
|
||||
Plan and build anything
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="p-3 flex items-center justify-between">
|
||||
<div class="relative p-3 flex items-center justify-between">
|
||||
<div class="flex items-center justify-start gap-1">
|
||||
<Select
|
||||
options={local.agent.list().map((agent) => agent.name)}
|
||||
@@ -540,7 +489,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
disabled={!session.prompt.dirty() && !session.working()}
|
||||
icon={session.working() ? "stop" : "arrow-up"}
|
||||
variant="primary"
|
||||
class="rounded-full"
|
||||
class="h-10 w-8 absolute right-2 bottom-2"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { useSession } from "@/context/session"
|
||||
import { FileIcon } from "@/ui"
|
||||
import { getDirectory, getFilename } from "@/utils"
|
||||
import { Accordion, Button, Diff, DiffChanges, Icon, IconButton, Tooltip } from "@opencode-ai/ui"
|
||||
import { For, Match, Show, Switch } from "solid-js"
|
||||
import { StickyAccordionHeader } from "./sticky-accordion-header"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useLayout } from "@/context/layout"
|
||||
|
||||
export const SessionReview = (props: { split?: boolean; class?: string; hideExpand?: boolean }) => {
|
||||
const layout = useLayout()
|
||||
const session = useSession()
|
||||
const [store, setStore] = createStore({
|
||||
open: session.diffs().map((d) => d.file),
|
||||
})
|
||||
|
||||
const handleChange = (open: string[]) => {
|
||||
setStore("open", open)
|
||||
}
|
||||
|
||||
const handleExpandOrCollapseAll = () => {
|
||||
if (store.open.length > 0) {
|
||||
setStore("open", [])
|
||||
} else {
|
||||
setStore(
|
||||
"open",
|
||||
session.diffs().map((d) => d.file),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
classList={{
|
||||
"flex flex-col gap-3 h-full overflow-y-auto no-scrollbar": true,
|
||||
[props.class ?? ""]: !!props.class,
|
||||
}}
|
||||
>
|
||||
<div class="sticky top-0 z-20 bg-background-stronger h-8 shrink-0 flex justify-between items-center self-stretch">
|
||||
<div class="text-14-medium text-text-strong">Session changes</div>
|
||||
<div class="flex items-center gap-x-4 pr-px">
|
||||
<Button size="normal" icon="chevron-grabber-vertical" onClick={handleExpandOrCollapseAll}>
|
||||
<Switch>
|
||||
<Match when={store.open.length > 0}>Collapse all</Match>
|
||||
<Match when={true}>Expand all</Match>
|
||||
</Switch>
|
||||
</Button>
|
||||
<Show when={!props.hideExpand}>
|
||||
<Tooltip value="Open in tab">
|
||||
<IconButton
|
||||
icon="expand"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
layout.review.tab()
|
||||
session.layout.setActiveTab("review")
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Accordion multiple value={store.open} onChange={handleChange}>
|
||||
<For each={session.diffs()}>
|
||||
{(diff) => (
|
||||
<Accordion.Item value={diff.file}>
|
||||
<StickyAccordionHeader class="top-11 data-expanded:before:-top-11">
|
||||
<Accordion.Trigger class="bg-background-stronger">
|
||||
<div class="flex items-center justify-between w-full gap-5">
|
||||
<div class="grow flex items-center gap-5 min-w-0">
|
||||
<FileIcon node={{ path: diff.file, type: "file" }} class="shrink-0 size-4" />
|
||||
<div class="flex grow min-w-0">
|
||||
<Show when={diff.file.includes("/")}>
|
||||
<span class="text-text-base truncate-start">{getDirectory(diff.file)}‎</span>
|
||||
</Show>
|
||||
<span class="text-text-strong shrink-0">{getFilename(diff.file)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shrink-0 flex gap-4 items-center justify-end">
|
||||
<DiffChanges changes={diff} />
|
||||
<Icon name="chevron-grabber-vertical" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Trigger>
|
||||
</StickyAccordionHeader>
|
||||
<Accordion.Content>
|
||||
<Diff
|
||||
diffStyle={props.split ? "split" : "unified"}
|
||||
before={{
|
||||
name: diff.file!,
|
||||
contents: diff.before!,
|
||||
}}
|
||||
after={{
|
||||
name: diff.file!,
|
||||
contents: diff.after!,
|
||||
}}
|
||||
/>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
)}
|
||||
</For>
|
||||
</Accordion>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Accordion } from "@opencode-ai/ui"
|
||||
import { ParentProps } from "solid-js"
|
||||
|
||||
export function StickyAccordionHeader(props: ParentProps<{ class?: string }>) {
|
||||
return (
|
||||
<Accordion.Header
|
||||
classList={{
|
||||
"sticky top-0 data-expanded:z-10": true,
|
||||
"data-expanded:before:content-[''] data-expanded:before:z-[-10]": true,
|
||||
"data-expanded:before:absolute data-expanded:before:inset-0 data-expanded:before:bg-background-stronger": true,
|
||||
[props.class ?? ""]: !!props.class,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</Accordion.Header>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { createOpencodeClient, type Event } from "@opencode-ai/sdk/client"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
||||
import { onCleanup } from "solid-js"
|
||||
|
||||
export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleContext({
|
||||
name: "GlobalSDK",
|
||||
init: (props: { url: string }) => {
|
||||
const abort = new AbortController()
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: props.url,
|
||||
signal: abort.signal,
|
||||
})
|
||||
|
||||
const emitter = createGlobalEmitter<{
|
||||
[key: string]: Event
|
||||
}>()
|
||||
|
||||
sdk.global.event().then(async (events) => {
|
||||
for await (const event of events.stream) {
|
||||
// console.log("event", event)
|
||||
emitter.emit(event.directory, event.payload)
|
||||
}
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
abort.abort()
|
||||
})
|
||||
|
||||
return { url: props.url, client: sdk, event: emitter }
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,183 @@
|
||||
import type {
|
||||
Message,
|
||||
Agent,
|
||||
Provider,
|
||||
Session,
|
||||
Part,
|
||||
Config,
|
||||
Path,
|
||||
File,
|
||||
FileNode,
|
||||
Project,
|
||||
FileDiff,
|
||||
Todo,
|
||||
SessionStatus,
|
||||
} from "@opencode-ai/sdk"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { Binary } from "@/utils/binary"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { useGlobalSDK } from "./global-sdk"
|
||||
|
||||
type State = {
|
||||
ready: boolean
|
||||
provider: Provider[]
|
||||
agent: Agent[]
|
||||
project: Project
|
||||
config: Config
|
||||
path: Path
|
||||
session: Session[]
|
||||
session_status: {
|
||||
[sessionID: string]: SessionStatus
|
||||
}
|
||||
session_diff: {
|
||||
[sessionID: string]: FileDiff[]
|
||||
}
|
||||
todo: {
|
||||
[sessionID: string]: Todo[]
|
||||
}
|
||||
limit: number
|
||||
message: {
|
||||
[sessionID: string]: Message[]
|
||||
}
|
||||
part: {
|
||||
[messageID: string]: Part[]
|
||||
}
|
||||
node: FileNode[]
|
||||
changes: File[]
|
||||
}
|
||||
|
||||
export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimpleContext({
|
||||
name: "GlobalSync",
|
||||
init: () => {
|
||||
const [globalStore, setGlobalStore] = createStore<{
|
||||
ready: boolean
|
||||
defaultProject?: Project // TODO: remove this when we can select projects
|
||||
projects: Project[]
|
||||
children: Record<string, State>
|
||||
}>({
|
||||
ready: false,
|
||||
projects: [],
|
||||
children: {},
|
||||
})
|
||||
|
||||
const children: Record<string, ReturnType<typeof createStore<State>>> = {}
|
||||
|
||||
function child(directory: string) {
|
||||
if (!children[directory]) {
|
||||
setGlobalStore("children", directory, {
|
||||
project: { id: "", worktree: "", time: { created: 0, initialized: 0 } },
|
||||
config: {},
|
||||
path: { state: "", config: "", worktree: "", directory: "" },
|
||||
ready: false,
|
||||
agent: [],
|
||||
provider: [],
|
||||
session: [],
|
||||
session_status: {},
|
||||
session_diff: {},
|
||||
todo: {},
|
||||
limit: 10,
|
||||
message: {},
|
||||
part: {},
|
||||
node: [],
|
||||
changes: [],
|
||||
})
|
||||
children[directory] = createStore(globalStore.children[directory])
|
||||
}
|
||||
return children[directory]
|
||||
}
|
||||
|
||||
const sdk = useGlobalSDK()
|
||||
sdk.event.listen((e) => {
|
||||
const directory = e.name
|
||||
const [store, setStore] = child(directory)
|
||||
|
||||
const event = e.details
|
||||
switch (event.type) {
|
||||
case "session.updated": {
|
||||
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
|
||||
if (result.found) {
|
||||
setStore("session", result.index, reconcile(event.properties.info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"session",
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.info)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "session.diff":
|
||||
setStore("session_diff", event.properties.sessionID, event.properties.diff)
|
||||
break
|
||||
case "todo.updated":
|
||||
setStore("todo", event.properties.sessionID, event.properties.todos)
|
||||
break
|
||||
case "session.status": {
|
||||
setStore("session_status", event.properties.sessionID, event.properties.status)
|
||||
break
|
||||
}
|
||||
case "message.updated": {
|
||||
const messages = store.message[event.properties.info.sessionID]
|
||||
if (!messages) {
|
||||
setStore("message", event.properties.info.sessionID, [event.properties.info])
|
||||
break
|
||||
}
|
||||
const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
|
||||
if (result.found) {
|
||||
setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"message",
|
||||
event.properties.info.sessionID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.info)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "message.part.updated": {
|
||||
const part = event.properties.part
|
||||
const parts = store.part[part.messageID]
|
||||
if (!parts) {
|
||||
setStore("part", part.messageID, [part])
|
||||
break
|
||||
}
|
||||
const result = Binary.search(parts, part.id, (p) => p.id)
|
||||
if (result.found) {
|
||||
setStore("part", part.messageID, result.index, reconcile(part))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"part",
|
||||
part.messageID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, part)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Promise.all([
|
||||
sdk.client.project.list().then((x) =>
|
||||
setGlobalStore(
|
||||
"projects",
|
||||
x.data!.filter((x) => !x.worktree.includes("opencode-test")),
|
||||
),
|
||||
),
|
||||
// TODO: remove this when we can select projects
|
||||
sdk.client.project.current().then((x) => setGlobalStore("defaultProject", x.data)),
|
||||
]).then(() => setGlobalStore("ready", true))
|
||||
|
||||
return {
|
||||
data: globalStore,
|
||||
get ready() {
|
||||
return globalStore.ready
|
||||
},
|
||||
child,
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,75 @@
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createMemo } from "solid-js"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { makePersisted } from "@solid-primitives/storage"
|
||||
import { useGlobalSync } from "./global-sync"
|
||||
|
||||
export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
|
||||
name: "Layout",
|
||||
init: () => {
|
||||
const globalSync = useGlobalSync()
|
||||
const [store, setStore] = makePersisted(
|
||||
createStore({
|
||||
projects: [] as { directory: string; expanded: boolean }[],
|
||||
sidebar: {
|
||||
opened: true,
|
||||
width: 280,
|
||||
},
|
||||
review: {
|
||||
state: "pane" as "pane" | "tab",
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "___default-layout",
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
projects: {
|
||||
list: createMemo(() =>
|
||||
globalSync.data.defaultProject
|
||||
? [{ directory: globalSync.data.defaultProject!.worktree, expanded: true }, ...store.projects]
|
||||
: store.projects,
|
||||
),
|
||||
open(directory: string) {
|
||||
if (store.projects.find((x) => x.directory === directory)) return
|
||||
setStore("projects", (x) => [...x, { directory, expanded: true }])
|
||||
},
|
||||
close(directory: string) {
|
||||
setStore("projects", (x) => x.filter((x) => x.directory !== directory))
|
||||
},
|
||||
expand(directory: string) {
|
||||
setStore("projects", (x) => x.map((x) => (x.directory === directory ? { ...x, expanded: true } : x)))
|
||||
},
|
||||
collapse(directory: string) {
|
||||
setStore("projects", (x) => x.map((x) => (x.directory === directory ? { ...x, expanded: false } : x)))
|
||||
},
|
||||
},
|
||||
sidebar: {
|
||||
opened: createMemo(() => store.sidebar.opened),
|
||||
open() {
|
||||
setStore("sidebar", "opened", true)
|
||||
},
|
||||
close() {
|
||||
setStore("sidebar", "opened", false)
|
||||
},
|
||||
toggle() {
|
||||
setStore("sidebar", "opened", (x) => !x)
|
||||
},
|
||||
width: createMemo(() => store.sidebar.width),
|
||||
resize(width: number) {
|
||||
setStore("sidebar", "width", width)
|
||||
},
|
||||
},
|
||||
review: {
|
||||
state: createMemo(() => store.review?.state ?? "closed"),
|
||||
pane() {
|
||||
setStore("review", "state", "pane")
|
||||
},
|
||||
tab() {
|
||||
setStore("review", "state", "tab")
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -5,7 +5,7 @@ import type { FileContent, FileNode, Model, Provider, File as FileStatus } from
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { useSDK } from "./sdk"
|
||||
import { useSync } from "./sync"
|
||||
import { makePersisted } from "@solid-primitives/storage"
|
||||
import { base64Encode } from "@/utils"
|
||||
|
||||
export type LocalFile = FileNode &
|
||||
Partial<{
|
||||
@@ -457,60 +457,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
}
|
||||
})()
|
||||
|
||||
const layout = (() => {
|
||||
const [store, setStore] = makePersisted(
|
||||
createStore({
|
||||
sidebar: {
|
||||
opened: true,
|
||||
width: 240,
|
||||
},
|
||||
review: {
|
||||
state: "closed" as "open" | "closed" | "tab",
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "default-layout",
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
sidebar: {
|
||||
opened: createMemo(() => store.sidebar.opened),
|
||||
open() {
|
||||
setStore("sidebar", "opened", true)
|
||||
},
|
||||
close() {
|
||||
setStore("sidebar", "opened", false)
|
||||
},
|
||||
toggle() {
|
||||
setStore("sidebar", "opened", (x) => !x)
|
||||
},
|
||||
width: createMemo(() => store.sidebar.width),
|
||||
resize(width: number) {
|
||||
setStore("sidebar", "width", width)
|
||||
},
|
||||
},
|
||||
review: {
|
||||
state: createMemo(() => store.review?.state ?? "closed"),
|
||||
open() {
|
||||
setStore("review", "state", "open")
|
||||
},
|
||||
close() {
|
||||
setStore("review", "state", "closed")
|
||||
},
|
||||
tab() {
|
||||
setStore("review", "state", "tab")
|
||||
},
|
||||
},
|
||||
}
|
||||
})()
|
||||
|
||||
const result = {
|
||||
slug: createMemo(() => base64Encode(sdk.directory)),
|
||||
model,
|
||||
agent,
|
||||
file,
|
||||
context,
|
||||
layout,
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
@@ -2,36 +2,31 @@ import { createOpencodeClient, type Event } from "@opencode-ai/sdk/client"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
||||
import { onCleanup } from "solid-js"
|
||||
import { useGlobalSDK } from "./global-sdk"
|
||||
|
||||
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
||||
name: "SDK",
|
||||
init: (props: { url: string }) => {
|
||||
init: (props: { directory: string }) => {
|
||||
const globalSDK = useGlobalSDK()
|
||||
const abort = new AbortController()
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: props.url,
|
||||
baseUrl: globalSDK.url,
|
||||
signal: abort.signal,
|
||||
fetch: (req) => {
|
||||
// @ts-ignore
|
||||
req.timeout = false
|
||||
return fetch(req)
|
||||
},
|
||||
directory: props.directory,
|
||||
})
|
||||
|
||||
const emitter = createGlobalEmitter<{
|
||||
[key in Event["type"]]: Extract<Event, { type: key }>
|
||||
}>()
|
||||
|
||||
sdk.event.subscribe().then(async (events) => {
|
||||
for await (const event of events.stream) {
|
||||
console.log("event", event.type)
|
||||
emitter.emit(event.type, event)
|
||||
}
|
||||
globalSDK.event.on(props.directory, async (event) => {
|
||||
emitter.emit(event.type, event)
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
abort.abort()
|
||||
})
|
||||
|
||||
return { client: sdk, event: emitter }
|
||||
return { directory: props.directory, client: sdk, event: emitter }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,43 +3,49 @@ import { createSimpleContext } from "./helper"
|
||||
import { batch, createEffect, createMemo } from "solid-js"
|
||||
import { useSync } from "./sync"
|
||||
import { makePersisted } from "@solid-primitives/storage"
|
||||
import { TextSelection, useLocal } from "./local"
|
||||
import { TextSelection } from "./local"
|
||||
import { pipe, sumBy } from "remeda"
|
||||
import { AssistantMessage } from "@opencode-ai/sdk"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { base64Encode } from "@/utils"
|
||||
|
||||
export const { use: useSession, provider: SessionProvider } = createSimpleContext({
|
||||
name: "Session",
|
||||
init: (props: { sessionId?: string }) => {
|
||||
init: () => {
|
||||
const params = useParams()
|
||||
const sync = useSync()
|
||||
const local = useLocal()
|
||||
const name = createMemo(
|
||||
() => `___${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`,
|
||||
)
|
||||
|
||||
const [store, setStore] = makePersisted(
|
||||
createStore<{
|
||||
prompt: Prompt
|
||||
cursorPosition?: number
|
||||
messageId?: string
|
||||
tabs: {
|
||||
active?: string
|
||||
opened: string[]
|
||||
}
|
||||
prompt: Prompt
|
||||
cursor?: number
|
||||
}>({
|
||||
prompt: [{ type: "text", content: "", start: 0, end: 0 }],
|
||||
tabs: {
|
||||
opened: [],
|
||||
},
|
||||
prompt: clonePrompt(DEFAULT_PROMPT),
|
||||
cursor: undefined,
|
||||
}),
|
||||
{
|
||||
name: props.sessionId ?? "new-session",
|
||||
name: name(),
|
||||
},
|
||||
)
|
||||
|
||||
createEffect(() => {
|
||||
if (!props.sessionId) return
|
||||
sync.session.sync(props.sessionId)
|
||||
if (!params.id) return
|
||||
sync.session.sync(params.id)
|
||||
})
|
||||
|
||||
const info = createMemo(() => (props.sessionId ? sync.session.get(props.sessionId) : undefined))
|
||||
const messages = createMemo(() => (props.sessionId ? (sync.data.message[props.sessionId] ?? []) : []))
|
||||
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
||||
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
|
||||
const userMessages = createMemo(() =>
|
||||
messages()
|
||||
.filter((m) => m.role === "user")
|
||||
@@ -52,16 +58,13 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
||||
if (!store.messageId) return lastUserMessage()
|
||||
return userMessages()?.find((m) => m.id === store.messageId)
|
||||
})
|
||||
const working = createMemo(() => {
|
||||
if (!props.sessionId) return false
|
||||
const last = lastUserMessage()
|
||||
if (!last) return false
|
||||
const assistantMessages = sync.data.message[props.sessionId]?.filter(
|
||||
(m) => m.role === "assistant" && m.parentID == last?.id,
|
||||
) as AssistantMessage[]
|
||||
const error = assistantMessages?.find((m) => m?.error)?.error
|
||||
return !last?.summary?.body && !error
|
||||
})
|
||||
const status = createMemo(
|
||||
() =>
|
||||
sync.data.session_status[params.id] ?? {
|
||||
type: "idle",
|
||||
},
|
||||
)
|
||||
const working = createMemo(() => status()?.type !== "idle")
|
||||
|
||||
const cost = createMemo(() => {
|
||||
const total = pipe(
|
||||
@@ -80,7 +83,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
||||
const model = createMemo(() =>
|
||||
last() ? sync.data.provider.find((x) => x.id === last().providerID)?.models[last().modelID] : undefined,
|
||||
)
|
||||
const diffs = createMemo(() => (props.sessionId ? (sync.data.session_diff[props.sessionId] ?? []) : []))
|
||||
const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : []))
|
||||
|
||||
const tokens = createMemo(() => {
|
||||
if (!last()) return
|
||||
@@ -96,18 +99,22 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
||||
})
|
||||
|
||||
return {
|
||||
id: props.sessionId,
|
||||
get id() {
|
||||
return params.id
|
||||
},
|
||||
info,
|
||||
status,
|
||||
working,
|
||||
diffs,
|
||||
prompt: {
|
||||
current: createMemo(() => store.prompt),
|
||||
cursor: createMemo(() => store.cursorPosition),
|
||||
cursor: createMemo(() => store.cursor),
|
||||
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
|
||||
set(prompt: Prompt, cursorPosition?: number) {
|
||||
const next = clonePrompt(prompt)
|
||||
batch(() => {
|
||||
setStore("prompt", prompt)
|
||||
if (cursorPosition !== undefined) setStore("cursorPosition", cursorPosition)
|
||||
setStore("prompt", next)
|
||||
if (cursorPosition !== undefined) setStore("cursor", cursorPosition)
|
||||
})
|
||||
},
|
||||
},
|
||||
@@ -138,9 +145,6 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
||||
setStore("tabs", "active", undefined)
|
||||
return
|
||||
}
|
||||
if (tab.startsWith("file://")) {
|
||||
await local.file.open(tab.replace("file://", ""))
|
||||
}
|
||||
if (tab !== "review") {
|
||||
if (!store.tabs.opened.includes(tab)) {
|
||||
setStore("tabs", "opened", [...store.tabs.opened, tab])
|
||||
@@ -172,7 +176,6 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
||||
opened.splice(to, 0, opened.splice(index, 1)[0])
|
||||
}),
|
||||
)
|
||||
// setStore("node", path, "pinned", true)
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -215,3 +218,20 @@ export function isPromptEqual(promptA: Prompt, promptB: Prompt): boolean {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function cloneSelection(selection?: TextSelection) {
|
||||
if (!selection) return undefined
|
||||
return { ...selection }
|
||||
}
|
||||
|
||||
function clonePart(part: ContentPart): ContentPart {
|
||||
if (part.type === "text") return { ...part }
|
||||
return {
|
||||
...part,
|
||||
selection: cloneSelection(part.selection),
|
||||
}
|
||||
}
|
||||
|
||||
function clonePrompt(prompt: Prompt): Prompt {
|
||||
return prompt.map(clonePart)
|
||||
}
|
||||
|
||||
@@ -1,133 +1,17 @@
|
||||
import type {
|
||||
Message,
|
||||
Agent,
|
||||
Provider,
|
||||
Session,
|
||||
Part,
|
||||
Config,
|
||||
Path,
|
||||
File,
|
||||
FileNode,
|
||||
Project,
|
||||
FileDiff,
|
||||
Todo,
|
||||
} from "@opencode-ai/sdk"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import type { Part } from "@opencode-ai/sdk"
|
||||
import { produce } from "solid-js/store"
|
||||
import { createMemo } from "solid-js"
|
||||
import { Binary } from "@/utils/binary"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { useGlobalSync } from "./global-sync"
|
||||
import { useSDK } from "./sdk"
|
||||
|
||||
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
name: "Sync",
|
||||
init: () => {
|
||||
const [store, setStore] = createStore<{
|
||||
ready: boolean
|
||||
provider: Provider[]
|
||||
agent: Agent[]
|
||||
project: Project
|
||||
config: Config
|
||||
path: Path
|
||||
session: Session[]
|
||||
session_diff: {
|
||||
[sessionID: string]: FileDiff[]
|
||||
}
|
||||
todo: {
|
||||
[sessionID: string]: Todo[]
|
||||
}
|
||||
limit: number
|
||||
message: {
|
||||
[sessionID: string]: Message[]
|
||||
}
|
||||
part: {
|
||||
[messageID: string]: Part[]
|
||||
}
|
||||
node: FileNode[]
|
||||
changes: File[]
|
||||
}>({
|
||||
project: { id: "", worktree: "", time: { created: 0, initialized: 0 } },
|
||||
config: {},
|
||||
path: { state: "", config: "", worktree: "", directory: "" },
|
||||
ready: false,
|
||||
agent: [],
|
||||
provider: [],
|
||||
session: [],
|
||||
session_diff: {},
|
||||
todo: {},
|
||||
limit: 10,
|
||||
message: {},
|
||||
part: {},
|
||||
node: [],
|
||||
changes: [],
|
||||
})
|
||||
|
||||
const globalSync = useGlobalSync()
|
||||
const sdk = useSDK()
|
||||
sdk.event.listen((e) => {
|
||||
const event = e.details
|
||||
switch (event.type) {
|
||||
case "session.updated": {
|
||||
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
|
||||
if (result.found) {
|
||||
setStore("session", result.index, reconcile(event.properties.info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"session",
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.info)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "session.diff":
|
||||
setStore("session_diff", event.properties.sessionID, event.properties.diff)
|
||||
break
|
||||
case "todo.updated":
|
||||
setStore("todo", event.properties.sessionID, event.properties.todos)
|
||||
break
|
||||
case "message.updated": {
|
||||
const messages = store.message[event.properties.info.sessionID]
|
||||
if (!messages) {
|
||||
setStore("message", event.properties.info.sessionID, [event.properties.info])
|
||||
break
|
||||
}
|
||||
const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
|
||||
if (result.found) {
|
||||
setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"message",
|
||||
event.properties.info.sessionID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.info)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "message.part.updated": {
|
||||
const part = sanitizePart(event.properties.part)
|
||||
const parts = store.part[part.messageID]
|
||||
if (!parts) {
|
||||
setStore("part", part.messageID, [part])
|
||||
break
|
||||
}
|
||||
const result = Binary.search(parts, part.id, (p) => p.id)
|
||||
if (result.found) {
|
||||
setStore("part", part.messageID, result.index, reconcile(part))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"part",
|
||||
part.messageID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, part)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
const [store, setStore] = globalSync.child(sdk.directory)
|
||||
|
||||
const load = {
|
||||
project: () => sdk.client.project.current().then((x) => setStore("project", x.data!)),
|
||||
@@ -142,6 +26,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
.slice(0, store.limit)
|
||||
setStore("session", sessions)
|
||||
}),
|
||||
status: () => sdk.client.session.status().then((x) => setStore("session_status", x.data!)),
|
||||
config: () => sdk.client.config.get().then((x) => setStore("config", x.data!)),
|
||||
changes: () => sdk.client.file.status().then((x) => setStore("changes", x.data!)),
|
||||
node: () => sdk.client.file.list({ query: { path: "/" } }).then((x) => setStore("node", x.data!)),
|
||||
@@ -216,7 +101,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
setStore("limit", (x) => x + count)
|
||||
await load.session()
|
||||
},
|
||||
more: createMemo(() => store.session.length === store.limit),
|
||||
more: createMemo(() => store.session.length >= store.limit),
|
||||
},
|
||||
load,
|
||||
absolute,
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
@import "@opencode-ai/ui/styles/tailwind";
|
||||
|
||||
:root {
|
||||
a {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
/* @refresh reload */
|
||||
import "@/index.css"
|
||||
import { render } from "solid-js/web"
|
||||
import { Router, Route } from "@solidjs/router"
|
||||
import { Router, Route, Navigate } from "@solidjs/router"
|
||||
import { MetaProvider } from "@solidjs/meta"
|
||||
import { Fonts, MarkedProvider } from "@opencode-ai/ui"
|
||||
import { SDKProvider } from "./context/sdk"
|
||||
import { SyncProvider } from "./context/sync"
|
||||
import { LocalProvider } from "./context/local"
|
||||
import { GlobalSyncProvider, useGlobalSync } from "./context/global-sync"
|
||||
import Layout from "@/pages/layout"
|
||||
import SessionLayout from "@/pages/session-layout"
|
||||
import DirectoryLayout from "@/pages/directory-layout"
|
||||
import Session from "@/pages/session"
|
||||
import { LayoutProvider } from "./context/layout"
|
||||
import { GlobalSDKProvider } from "./context/global-sdk"
|
||||
import { SessionProvider } from "./context/session"
|
||||
import { base64Encode } from "./utils"
|
||||
import { createMemo, Show } from "solid-js"
|
||||
|
||||
const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
|
||||
const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
|
||||
@@ -30,20 +33,38 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||
render(
|
||||
() => (
|
||||
<MarkedProvider>
|
||||
<SDKProvider url={url}>
|
||||
<SyncProvider>
|
||||
<LocalProvider>
|
||||
<GlobalSDKProvider url={url}>
|
||||
<GlobalSyncProvider>
|
||||
<LayoutProvider>
|
||||
<MetaProvider>
|
||||
<Fonts />
|
||||
<Router root={Layout}>
|
||||
<Route path={["/", "/session"]} component={SessionLayout}>
|
||||
<Route path="/:id?" component={Session} />
|
||||
<Route
|
||||
path="/"
|
||||
component={() => {
|
||||
const globalSync = useGlobalSync()
|
||||
const slug = createMemo(() => base64Encode(globalSync.data.defaultProject!.worktree))
|
||||
return <Navigate href={`${slug()}/session`} />
|
||||
}}
|
||||
/>
|
||||
<Route path="/:dir" component={DirectoryLayout}>
|
||||
<Route path="/" component={() => <Navigate href="session" />} />
|
||||
<Route
|
||||
path="/session/:id?"
|
||||
component={(p) => (
|
||||
<Show when={p.params.id || true} keyed>
|
||||
<SessionProvider>
|
||||
<Session />
|
||||
</SessionProvider>
|
||||
</Show>
|
||||
)}
|
||||
/>
|
||||
</Route>
|
||||
</Router>
|
||||
</MetaProvider>
|
||||
</LocalProvider>
|
||||
</SyncProvider>
|
||||
</SDKProvider>
|
||||
</LayoutProvider>
|
||||
</GlobalSyncProvider>
|
||||
</GlobalSDKProvider>
|
||||
</MarkedProvider>
|
||||
),
|
||||
root!,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createMemo, type ParentProps } from "solid-js"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { SDKProvider } from "@/context/sdk"
|
||||
import { SyncProvider } from "@/context/sync"
|
||||
import { LocalProvider } from "@/context/local"
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { base64Decode } from "@/utils"
|
||||
|
||||
export default function Layout(props: ParentProps) {
|
||||
const params = useParams()
|
||||
const sync = useGlobalSync()
|
||||
const directory = createMemo(() => {
|
||||
const decoded = base64Decode(params.dir)
|
||||
return sync.data.projects.find((x) => x.worktree === decoded)?.worktree ?? "/"
|
||||
})
|
||||
return (
|
||||
<SDKProvider directory={directory()}>
|
||||
<SyncProvider>
|
||||
<LocalProvider>{props.children}</LocalProvider>
|
||||
</SyncProvider>
|
||||
</SDKProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { base64Encode, getFilename } from "@/utils"
|
||||
import { For } from "solid-js"
|
||||
import { A } from "@solidjs/router"
|
||||
import { Button } from "@opencode-ai/ui"
|
||||
|
||||
export default function Home() {
|
||||
const sync = useGlobalSync()
|
||||
return (
|
||||
<div class="flex flex-col gap-3">
|
||||
<For each={sync.data.projects}>
|
||||
{(project) => (
|
||||
<Button as={A} href={base64Encode(project.worktree)}>
|
||||
{getFilename(project.worktree)}
|
||||
</Button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,116 +1,192 @@
|
||||
import { Button, Tooltip, DiffChanges, IconButton } from "@opencode-ai/ui"
|
||||
import { Button, Tooltip, DiffChanges, IconButton, Mark, Icon, Collapsible } from "@opencode-ai/ui"
|
||||
import { createMemo, For, ParentProps, Show } from "solid-js"
|
||||
import { DateTime } from "luxon"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { A, useParams } from "@solidjs/router"
|
||||
import { useLocal } from "@/context/local"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { base64Encode, getFilename } from "@/utils"
|
||||
|
||||
export default function Layout(props: ParentProps) {
|
||||
const params = useParams()
|
||||
const sync = useSync()
|
||||
const local = useLocal()
|
||||
const globalSync = useGlobalSync()
|
||||
const layout = useLayout()
|
||||
|
||||
const handleOpenProject = async () => {
|
||||
// layout.projects.open(dir.)
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="relative h-screen flex flex-col">
|
||||
<header class="hidden h-12 shrink-0 bg-background-strong border-b border-border-weak-base"></header>
|
||||
<div class="h-[calc(100vh-0rem)] flex">
|
||||
<header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base">
|
||||
<A
|
||||
href="/"
|
||||
classList={{
|
||||
"w-12 shrink-0 px-4 py-3.5": true,
|
||||
"flex items-center justify-start self-stretch": true,
|
||||
"border-r border-border-weak-base": true,
|
||||
}}
|
||||
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
|
||||
>
|
||||
<Mark class="shrink-0" />
|
||||
</A>
|
||||
</header>
|
||||
<div class="h-[calc(100vh-3rem)] flex">
|
||||
<div
|
||||
classList={{
|
||||
"@container w-14 pb-4 shrink-0 bg-background-weak": true,
|
||||
"flex flex-col items-start self-stretch justify-between": true,
|
||||
"@container w-12 pb-5 shrink-0 bg-background-base": true,
|
||||
"flex flex-col gap-5.5 items-start self-stretch justify-between": true,
|
||||
"border-r border-border-weak-base": true,
|
||||
"w-70": local.layout.sidebar.opened(),
|
||||
}}
|
||||
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
|
||||
>
|
||||
<div class="flex flex-col justify-center items-start gap-4 self-stretch py-2 overflow-hidden mx-auto @[4rem]:mx-0">
|
||||
<div class="h-8 shrink-0 flex items-center self-stretch px-3">
|
||||
<Tooltip placement="right" value="Collapse sidebar">
|
||||
<IconButton icon="layout-left" variant="ghost" size="large" onClick={local.layout.sidebar.toggle} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="w-full px-3">
|
||||
<Button as={A} href="/session" class="hidden @[4rem]:flex w-full" size="large" icon="edit-small-2">
|
||||
New Session
|
||||
<div class="grow flex flex-col items-start self-stretch gap-4 p-2 min-h-0">
|
||||
<Tooltip class="shrink-0" placement="right" value="Toggle sidebar" inactive={layout.sidebar.opened()}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="large"
|
||||
class="group/sidebar-toggle shrink-0 w-full text-left justify-start"
|
||||
onClick={layout.sidebar.toggle}
|
||||
>
|
||||
<div class="relative -ml-px flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||
<Icon
|
||||
name={layout.sidebar.opened() ? "layout-left" : "layout-right"}
|
||||
size="small"
|
||||
class="group-hover/sidebar-toggle:hidden"
|
||||
/>
|
||||
<Icon
|
||||
name={layout.sidebar.opened() ? "layout-left-partial" : "layout-right-partial"}
|
||||
size="small"
|
||||
class="hidden group-hover/sidebar-toggle:inline-block"
|
||||
/>
|
||||
<Icon
|
||||
name={layout.sidebar.opened() ? "layout-left-full" : "layout-right-full"}
|
||||
size="small"
|
||||
class="hidden group-active/sidebar-toggle:inline-block"
|
||||
/>
|
||||
</div>
|
||||
<Show when={layout.sidebar.opened()}>
|
||||
<div class="hidden group-hover/sidebar-toggle:block group-active/sidebar-toggle:block text-text-base">
|
||||
Toggle sidebar
|
||||
</div>
|
||||
</Show>
|
||||
</Button>
|
||||
<Tooltip placement="right" value="New session">
|
||||
<IconButton as={A} href="/session" icon="edit-small-2" size="large" class="@[4rem]:hidden" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="hidden @[4rem]:flex size-full overflow-y-auto no-scrollbar flex-col flex-1 px-3">
|
||||
<nav class="w-full">
|
||||
<For each={sync.data.session}>
|
||||
{(session) => {
|
||||
const updated = createMemo(() => DateTime.fromMillis(session.time.updated))
|
||||
</Tooltip>
|
||||
<div class="flex flex-col justify-center items-start gap-4 self-stretch min-h-0">
|
||||
<div class="hidden @[4rem]:flex size-full flex-col grow overflow-y-auto no-scrollbar">
|
||||
<For each={layout.projects.list()}>
|
||||
{(project) => {
|
||||
const [store] = globalSync.child(project.directory)
|
||||
const slug = createMemo(() => base64Encode(project.directory))
|
||||
return (
|
||||
<A
|
||||
data-active={session.id === params.id}
|
||||
href={`/session/${session.id}`}
|
||||
class="group/session focus:outline-none cursor-default"
|
||||
>
|
||||
<Tooltip placement="right" value={session.title}>
|
||||
<div
|
||||
class="w-full mb-1.5 px-3 py-1 rounded-md
|
||||
group-data-[active=true]/session:bg-surface-raised-base-hover
|
||||
group-hover/session:bg-surface-raised-base-hover
|
||||
group-focus/session:bg-surface-raised-base-hover"
|
||||
>
|
||||
<div class="flex items-center self-stretch gap-6 justify-between">
|
||||
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
|
||||
{session.title}
|
||||
</span>
|
||||
<span class="text-12-regular text-text-weak text-right whitespace-nowrap">
|
||||
{Math.abs(updated().diffNow().as("seconds")) < 60
|
||||
? "Now"
|
||||
: updated()
|
||||
.toRelative({ style: "short", unit: ["days", "hours", "minutes"] })
|
||||
?.replace(" ago", "")
|
||||
?.replace(/ days?/, "d")
|
||||
?.replace(" min.", "m")
|
||||
?.replace(" hr.", "h")}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center self-stretch">
|
||||
<span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span>
|
||||
<Show when={session.summary}>{(summary) => <DiffChanges changes={summary()} />}</Show>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</A>
|
||||
<Collapsible variant="ghost" defaultOpen class="gap-2">
|
||||
<Button
|
||||
as={"div"}
|
||||
variant="ghost"
|
||||
class="flex items-center justify-between gap-3 w-full h-8 pl-2 pr-2.25 self-stretch"
|
||||
>
|
||||
<Collapsible.Trigger class="p-0 text-left text-14-medium text-text-strong grow min-w-0 truncate">
|
||||
{getFilename(project.directory)}
|
||||
</Collapsible.Trigger>
|
||||
<IconButton as={A} href={`${slug()}/session`} icon="plus-small" size="normal" />
|
||||
</Button>
|
||||
<Collapsible.Content>
|
||||
<nav class="w-full flex flex-col gap-1.5">
|
||||
<For each={store.session}>
|
||||
{(session) => {
|
||||
const updated = createMemo(() => DateTime.fromMillis(session.time.updated))
|
||||
return (
|
||||
<A
|
||||
data-active={session.id === params.id}
|
||||
href={`${slug()}/session/${session.id}`}
|
||||
class="group/session focus:outline-none cursor-default"
|
||||
>
|
||||
<Tooltip placement="right" value={session.title}>
|
||||
<div
|
||||
class="w-full px-2 py-1 rounded-md
|
||||
group-data-[active=true]/session:bg-surface-raised-base-hover
|
||||
group-hover/session:bg-surface-raised-base-hover
|
||||
group-focus/session:bg-surface-raised-base-hover"
|
||||
>
|
||||
<div class="flex items-center self-stretch gap-6 justify-between">
|
||||
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
|
||||
{session.title}
|
||||
</span>
|
||||
<span class="text-12-regular text-text-weak text-right whitespace-nowrap">
|
||||
{Math.abs(updated().diffNow().as("seconds")) < 60
|
||||
? "Now"
|
||||
: updated()
|
||||
.toRelative({ style: "short", unit: ["days", "hours", "minutes"] })
|
||||
?.replace(" ago", "")
|
||||
?.replace(/ days?/, "d")
|
||||
?.replace(" min.", "m")
|
||||
?.replace(" hr.", "h")}
|
||||
</span>
|
||||
</div>
|
||||
<div class="hidden _flex justify-between items-center self-stretch">
|
||||
<span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span>
|
||||
<Show when={session.summary}>
|
||||
{(summary) => <DiffChanges changes={summary()} />}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</A>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</nav>
|
||||
{/* <Show when={sync.session.more()}> */}
|
||||
{/* <button */}
|
||||
{/* class="shrink-0 self-start p-3 text-12-medium text-text-weak hover:text-text-strong" */}
|
||||
{/* onClick={() => sync.session.fetch()} */}
|
||||
{/* > */}
|
||||
{/* Show more */}
|
||||
{/* </button> */}
|
||||
{/* </Show> */}
|
||||
</Collapsible.Content>
|
||||
</Collapsible>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</nav>
|
||||
<Show when={sync.session.more()}>
|
||||
<button
|
||||
class="shrink-0 self-start p-3 text-12-medium text-text-weak hover:text-text-strong"
|
||||
onClick={() => sync.session.fetch()}
|
||||
>
|
||||
Show more
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start shrink-0 px-3 py-1 mx-auto @[4rem]:mx-0">
|
||||
<Button
|
||||
as={"a"}
|
||||
href="https://opencode.ai/desktop-feedback"
|
||||
target="_blank"
|
||||
class="hidden @[4rem]:flex w-full text-12-medium text-text-base stroke-[1.5px]"
|
||||
variant="ghost"
|
||||
icon="speech-bubble"
|
||||
>
|
||||
Share feedback
|
||||
</Button>
|
||||
<Tooltip placement="right" value="Share feedback">
|
||||
<IconButton
|
||||
<div class="flex flex-col gap-1.5 self-stretch items-start shrink-0 px-2 py-3">
|
||||
<Tooltip placement="right" value="Open project" inactive={layout.sidebar.opened()}>
|
||||
<Button
|
||||
disabled
|
||||
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px]"
|
||||
variant="ghost"
|
||||
size="large"
|
||||
icon="folder-add-left"
|
||||
onClick={handleOpenProject}
|
||||
>
|
||||
<Show when={layout.sidebar.opened()}>Open project</Show>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip placement="right" value="Settings" inactive={layout.sidebar.opened()}>
|
||||
<Button
|
||||
disabled
|
||||
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px]"
|
||||
variant="ghost"
|
||||
size="large"
|
||||
icon="settings-gear"
|
||||
>
|
||||
<Show when={layout.sidebar.opened()}>Settings</Show>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip placement="right" value="Share feedback" inactive={layout.sidebar.opened()}>
|
||||
<Button
|
||||
as={"a"}
|
||||
href="https://opencode.ai/desktop-feedback"
|
||||
target="_blank"
|
||||
icon="speech-bubble"
|
||||
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px]"
|
||||
variant="ghost"
|
||||
size="large"
|
||||
class="@[4rem]:hidden stroke-[1.5px]"
|
||||
/>
|
||||
icon="bubble-5"
|
||||
>
|
||||
<Show when={layout.sidebar.opened()}>Share feedback</Show>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Show, type ParentProps } from "solid-js"
|
||||
import { SessionProvider } from "@/context/session"
|
||||
import { useParams } from "@solidjs/router"
|
||||
|
||||
export default function Layout(props: ParentProps) {
|
||||
const params = useParams()
|
||||
return (
|
||||
<Show when={params.id || true} keyed>
|
||||
<SessionProvider sessionId={params.id}>{props.children}</SessionProvider>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
Code,
|
||||
Tooltip,
|
||||
ProgressCircle,
|
||||
Button,
|
||||
} from "@opencode-ai/ui"
|
||||
import { FileIcon } from "@/ui"
|
||||
import { MessageProgress } from "@/components/message-progress"
|
||||
@@ -50,8 +49,12 @@ import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
|
||||
import { Markdown } from "@opencode-ai/ui"
|
||||
import { Spinner } from "@/components/spinner"
|
||||
import { useSession } from "@/context/session"
|
||||
import { StickyAccordionHeader } from "@/components/sticky-accordion-header"
|
||||
import { SessionReview } from "@/components/session-review"
|
||||
import { useLayout } from "@/context/layout"
|
||||
|
||||
export default function Page() {
|
||||
const layout = useLayout()
|
||||
const local = useLocal()
|
||||
const sync = useSync()
|
||||
const session = useSession()
|
||||
@@ -83,6 +86,15 @@ export default function Page() {
|
||||
setStore("fileSelectOpen", true)
|
||||
return
|
||||
}
|
||||
if (event.ctrlKey && event.key.toLowerCase() === "t") {
|
||||
event.preventDefault()
|
||||
const currentTheme = localStorage.getItem("theme") ?? "oc-1"
|
||||
const themes = ["oc-1", "oc-2-paper"]
|
||||
const nextTheme = themes[(themes.indexOf(currentTheme) + 1) % themes.length]
|
||||
localStorage.setItem("theme", nextTheme)
|
||||
document.documentElement.setAttribute("data-theme", nextTheme)
|
||||
return
|
||||
}
|
||||
|
||||
const focused = document.activeElement === inputRef
|
||||
if (focused) {
|
||||
@@ -165,10 +177,16 @@ export default function Page() {
|
||||
setStore("activeDraggable", undefined)
|
||||
}
|
||||
|
||||
const FileVisual = (props: { file: LocalFile }): JSX.Element => {
|
||||
const FileVisual = (props: { file: LocalFile; active?: boolean }): JSX.Element => {
|
||||
return (
|
||||
<div class="flex items-center gap-x-1.5">
|
||||
<FileIcon node={props.file} class="grayscale-100 group-data-[selected]/tab:grayscale-0" />
|
||||
<FileIcon
|
||||
node={props.file}
|
||||
classList={{
|
||||
"grayscale-100 group-data-[selected]/tab:grayscale-0": !props.active,
|
||||
"grayscale-0": props.active,
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
classList={{
|
||||
"text-14-medium": true,
|
||||
@@ -216,18 +234,15 @@ export default function Page() {
|
||||
// @ts-ignore
|
||||
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
|
||||
<div class="relative h-full">
|
||||
<Tabs.Trigger value={props.tab} class="group/tab pl-3 pr-1" onClick={() => props.onTabClick(props.tab)}>
|
||||
<Tabs.Trigger
|
||||
value={props.tab}
|
||||
closeButton={<IconButton icon="close" variant="ghost" onClick={() => props.onTabClose(props.tab)} />}
|
||||
hideCloseButton
|
||||
onClick={() => props.onTabClick(props.tab)}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={file()}>{(f) => <FileVisual file={f()} />}</Match>
|
||||
</Switch>
|
||||
<IconButton
|
||||
icon="close"
|
||||
class="mt-0.5 opacity-0 group-data-[selected]/tab:opacity-100
|
||||
hover:bg-transparent
|
||||
hover:opacity-100 group-hover/tab:opacity-100"
|
||||
variant="ghost"
|
||||
onClick={() => props.onTabClose(props.tab)}
|
||||
/>
|
||||
</Tabs.Trigger>
|
||||
</div>
|
||||
</div>
|
||||
@@ -277,38 +292,40 @@ export default function Page() {
|
||||
<Tabs value={session.layout.tabs.active ?? "chat"} onChange={session.layout.openTab}>
|
||||
<div class="sticky top-0 shrink-0 flex">
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="chat" class="flex gap-x-4 items-center">
|
||||
<div>Chat</div>
|
||||
<Tooltip
|
||||
value={`${new Intl.NumberFormat("en-US", {
|
||||
notation: "compact",
|
||||
compactDisplay: "short",
|
||||
}).format(session.usage.tokens() ?? 0)} Tokens`}
|
||||
class="flex items-center gap-1.5"
|
||||
>
|
||||
<ProgressCircle percentage={session.usage.context() ?? 0} />
|
||||
<div class="text-14-regular text-text-weak text-left w-7">{session.usage.context() ?? 0}%</div>
|
||||
</Tooltip>
|
||||
<Tabs.Trigger value="chat">
|
||||
<div class="flex gap-x-[17px] items-center">
|
||||
<div>Session</div>
|
||||
<Tooltip
|
||||
value={`${new Intl.NumberFormat("en-US", {
|
||||
notation: "compact",
|
||||
compactDisplay: "short",
|
||||
}).format(session.usage.tokens() ?? 0)} Tokens`}
|
||||
class="flex items-center gap-1.5"
|
||||
>
|
||||
<ProgressCircle percentage={session.usage.context() ?? 0} />
|
||||
<div class="text-14-regular text-text-weak text-left w-7">{session.usage.context() ?? 0}%</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tabs.Trigger>
|
||||
<Show when={local.layout.review.state() === "tab" && session.diffs().length}>
|
||||
<Tabs.Trigger value="review" class="flex gap-3 items-center group/tab pr-1">
|
||||
<Show when={session.diffs()}>
|
||||
<DiffChanges changes={session.diffs()} variant="bars" />
|
||||
</Show>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div>Review</div>
|
||||
<Show when={session.info()?.summary?.files}>
|
||||
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
|
||||
{session.info()?.summary?.files ?? 0}
|
||||
</div>
|
||||
<Show when={layout.review.state() === "tab" && session.diffs().length}>
|
||||
<Tabs.Trigger
|
||||
value="review"
|
||||
closeButton={
|
||||
<IconButton icon="collapse" size="normal" variant="ghost" onClick={layout.review.pane} />
|
||||
}
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<Show when={session.diffs()}>
|
||||
<DiffChanges changes={session.diffs()} variant="bars" />
|
||||
</Show>
|
||||
<IconButton
|
||||
icon="close"
|
||||
class="mt-0.5 -ml-1 opacity-0 group-data-[selected]/tab:opacity-100
|
||||
hover:bg-transparent hover:opacity-100 group-hover/tab:opacity-100"
|
||||
variant="ghost"
|
||||
onClick={local.layout.review.close}
|
||||
/>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div>Review</div>
|
||||
<Show when={session.info()?.summary?.files}>
|
||||
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
|
||||
{session.info()?.summary?.files ?? 0}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Trigger>
|
||||
</Show>
|
||||
@@ -332,111 +349,105 @@ export default function Page() {
|
||||
<Tabs.Content value="chat" class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden">
|
||||
<div
|
||||
classList={{
|
||||
"w-full grid flex-1 min-h-0": true,
|
||||
"grid-cols-2": local.layout.review.state() === "open",
|
||||
"w-full flex-1 min-h-0": true,
|
||||
grid: layout.review.state() === "tab",
|
||||
flex: layout.review.state() === "pane",
|
||||
}}
|
||||
>
|
||||
<div class="relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0 max-w-2xl mx-auto">
|
||||
<div class="relative shrink-0 px-6 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full max-w-xl mx-auto">
|
||||
<Switch>
|
||||
<Match when={session.id}>
|
||||
<div class="h-8 flex shrink-0 self-stretch items-center justify-end">
|
||||
<Show when={local.layout.review.state() === "closed" && session.diffs().length}>
|
||||
<Button icon="layout-right" onClick={local.layout.review.open}>
|
||||
Review
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
<div
|
||||
classList={{
|
||||
"flex-1 min-h-0 pb-20": true,
|
||||
"flex items-start justify-start": local.layout.review.state() === "open",
|
||||
"flex items-start justify-start": layout.review.state() === "pane",
|
||||
}}
|
||||
>
|
||||
<Show when={session.messages.user().length > 1}>
|
||||
<ul
|
||||
role="list"
|
||||
classList={{
|
||||
"mr-8 shrink-0 flex flex-col items-start": true,
|
||||
"absolute right-full w-60 @7xl:gap-2": true, // local.layout.review.state() !== "open",
|
||||
"": local.layout.review.state() === "open",
|
||||
}}
|
||||
>
|
||||
<For each={session.messages.user()}>
|
||||
{(message) => {
|
||||
const assistantMessages = createMemo(() => {
|
||||
if (!session.id) return []
|
||||
return sync.data.message[session.id]?.filter(
|
||||
(m) => m.role === "assistant" && m.parentID == message.id,
|
||||
) as AssistantMessageType[]
|
||||
})
|
||||
const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error)
|
||||
const working = createMemo(() => !message.summary?.body && !error())
|
||||
{(_) => {
|
||||
const expanded = createMemo(() => layout.review.state() === "tab" || !session.diffs().length)
|
||||
|
||||
const handleClick = () => session.messages.setActive(message.id)
|
||||
return (
|
||||
<ul
|
||||
role="list"
|
||||
classList={{
|
||||
"mr-8 shrink-0 flex flex-col items-start": true,
|
||||
"absolute right-full w-60 mt-3 @7xl:gap-2 @7xl:mt-1": expanded(),
|
||||
"mt-3": !expanded(),
|
||||
}}
|
||||
>
|
||||
<For each={session.messages.user()}>
|
||||
{(message) => {
|
||||
const working = createMemo(
|
||||
() => message.id === session.messages.last()?.id && session.working(),
|
||||
)
|
||||
const handleClick = () => session.messages.setActive(message.id)
|
||||
|
||||
return (
|
||||
<li
|
||||
classList={{
|
||||
"group/li flex items-center self-stretch justify-end": true,
|
||||
"@7xl:justify-start": local.layout.review.state() !== "open",
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
placement="right"
|
||||
gutter={8}
|
||||
value={
|
||||
<div class="flex items-center gap-2">
|
||||
<DiffChanges changes={message.summary?.diffs ?? []} variant="bars" />
|
||||
{message.summary?.title}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<button
|
||||
data-active={session.messages.active()?.id === message.id}
|
||||
onClick={handleClick}
|
||||
return (
|
||||
<li
|
||||
classList={{
|
||||
"group/tick flex items-center justify-start h-2 w-8 -mr-3": true,
|
||||
"data-[active=true]:[&>div]:bg-icon-strong-base data-[active=true]:[&>div]:w-full": true,
|
||||
"@7xl:hidden": local.layout.review.state() !== "open",
|
||||
"group/li flex items-center self-stretch justify-end": true,
|
||||
"@7xl:justify-start": expanded(),
|
||||
}}
|
||||
>
|
||||
<div class="h-px w-5 bg-icon-base group-hover/tick:w-full group-hover/tick:bg-icon-strong-base" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<button
|
||||
classList={{
|
||||
"hidden items-center self-stretch w-full gap-x-2 cursor-default": true,
|
||||
"@7xl:flex": local.layout.review.state() !== "open",
|
||||
}}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={working()}>
|
||||
<Spinner class="text-text-base shrink-0 w-[18px] aspect-square" />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<DiffChanges changes={message.summary?.diffs ?? []} variant="bars" />
|
||||
</Match>
|
||||
</Switch>
|
||||
<div
|
||||
data-active={session.messages.active()?.id === message.id}
|
||||
classList={{
|
||||
"text-14-regular text-text-weak whitespace-nowrap truncate min-w-0": true,
|
||||
"text-text-weak data-[active=true]:text-text-strong group-hover/li:text-text-base": true,
|
||||
}}
|
||||
>
|
||||
<Show when={message.summary?.title} fallback="New message">
|
||||
{message.summary?.title}
|
||||
</Show>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</ul>
|
||||
<Tooltip
|
||||
placement="right"
|
||||
gutter={8}
|
||||
value={
|
||||
<div class="flex items-center gap-2">
|
||||
<DiffChanges changes={message.summary?.diffs ?? []} variant="bars" />
|
||||
{message.summary?.title}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<button
|
||||
data-active={session.messages.active()?.id === message.id}
|
||||
onClick={handleClick}
|
||||
classList={{
|
||||
"group/tick flex items-center justify-start h-2 w-8 -mr-3": true,
|
||||
"data-[active=true]:[&>div]:bg-icon-strong-base data-[active=true]:[&>div]:w-full": true,
|
||||
"@7xl:hidden": expanded(),
|
||||
}}
|
||||
>
|
||||
<div class="h-px w-5 bg-icon-base group-hover/tick:w-full group-hover/tick:bg-icon-strong-base" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<button
|
||||
classList={{
|
||||
"hidden items-center self-stretch w-full gap-x-2 cursor-default": true,
|
||||
"@7xl:flex": expanded(),
|
||||
}}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={working()}>
|
||||
<Spinner class="text-text-base shrink-0 w-[18px] aspect-square" />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<DiffChanges changes={message.summary?.diffs ?? []} variant="bars" />
|
||||
</Match>
|
||||
</Switch>
|
||||
<div
|
||||
data-active={session.messages.active()?.id === message.id}
|
||||
classList={{
|
||||
"text-14-regular text-text-weak whitespace-nowrap truncate min-w-0": true,
|
||||
"text-text-weak data-[active=true]:text-text-strong group-hover/li:text-text-base": true,
|
||||
}}
|
||||
>
|
||||
<Show when={message.summary?.title} fallback="New message">
|
||||
{message.summary?.title}
|
||||
</Show>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</ul>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
<div ref={messageScrollElement} class="grow w-full min-w-0 h-full overflow-y-auto no-scrollbar">
|
||||
<div ref={messageScrollElement} class="grow size-full min-w-0 overflow-y-auto no-scrollbar">
|
||||
<For each={session.messages.user()}>
|
||||
{(message) => {
|
||||
const isActive = createMemo(() => session.messages.active()?.id === message.id)
|
||||
@@ -448,7 +459,6 @@ export default function Page() {
|
||||
) as AssistantMessageType[]
|
||||
})
|
||||
const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error)
|
||||
const [completed, setCompleted] = createSignal(!!message.summary?.body || !!error())
|
||||
const [detailsExpanded, setDetailsExpanded] = createSignal(false)
|
||||
const parts = createMemo(() => sync.data.part[message.id])
|
||||
const hasToolPart = createMemo(() =>
|
||||
@@ -456,7 +466,11 @@ export default function Page() {
|
||||
?.flatMap((m) => sync.data.part[m.id])
|
||||
.some((p) => p?.type === "tool"),
|
||||
)
|
||||
const working = createMemo(() => !message.summary?.body && !error())
|
||||
const working = createMemo(
|
||||
() => message.id === session.messages.last()?.id && session.working(),
|
||||
)
|
||||
const initialCompleted = !(message.id === session.messages.last()?.id && session.working())
|
||||
const [completed, setCompleted] = createSignal(initialCompleted)
|
||||
|
||||
// allowing time for the animations to finish
|
||||
createEffect(() => {
|
||||
@@ -464,9 +478,8 @@ export default function Page() {
|
||||
setTimeout(() => setTitled(!!title), 10_000)
|
||||
})
|
||||
createEffect(() => {
|
||||
const summary = message.summary?.body
|
||||
const complete = !!summary || !!error()
|
||||
setTimeout(() => setCompleted(complete), 1200)
|
||||
const completed = !working()
|
||||
setTimeout(() => setCompleted(completed), 1200)
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -476,7 +489,7 @@ export default function Page() {
|
||||
class="flex flex-col items-start self-stretch gap-8 pb-20"
|
||||
>
|
||||
{/* Title */}
|
||||
<div class="flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-10 pb-1">
|
||||
<div class="flex items-center gap-2 self-stretch sticky top-0 bg-background-stronger z-20 h-8">
|
||||
<div class="w-full text-14-medium text-text-strong">
|
||||
<Show
|
||||
when={titled()}
|
||||
@@ -494,9 +507,7 @@ export default function Page() {
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<div class="-mt-9">
|
||||
<Message message={message} parts={parts()} />
|
||||
</div>
|
||||
<Message message={message} parts={parts()} />
|
||||
{/* Summary */}
|
||||
<Show when={completed()}>
|
||||
<div class="w-full flex flex-col gap-6 items-start self-stretch">
|
||||
@@ -512,7 +523,8 @@ export default function Page() {
|
||||
<Markdown
|
||||
classList={{
|
||||
"text-14-regular": !!message.summary?.diffs?.length,
|
||||
"[&>*]:fade-up-text": !message.summary?.diffs?.length,
|
||||
"[&>*]:fade-up-text":
|
||||
!message.summary?.diffs?.length && !initialCompleted,
|
||||
}}
|
||||
text={summary()}
|
||||
/>
|
||||
@@ -523,7 +535,7 @@ export default function Page() {
|
||||
<For each={message.summary?.diffs ?? []}>
|
||||
{(diff) => (
|
||||
<Accordion.Item value={diff.file}>
|
||||
<Accordion.Header>
|
||||
<StickyAccordionHeader class="top-10 data-expanded:before:-top-10">
|
||||
<Accordion.Trigger>
|
||||
<div class="flex items-center justify-between w-full gap-5">
|
||||
<div class="grow flex items-center gap-5 min-w-0">
|
||||
@@ -548,8 +560,8 @@ export default function Page() {
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Trigger>
|
||||
</Accordion.Header>
|
||||
<Accordion.Content class="max-h-[360px] overflow-y-auto no-scrollbar">
|
||||
</StickyAccordionHeader>
|
||||
<Accordion.Content class="max-h-60 overflow-y-auto no-scrollbar">
|
||||
<Diff
|
||||
before={{
|
||||
name: diff.file!,
|
||||
@@ -652,130 +664,25 @@ export default function Page() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={local.layout.review.state() === "open"}>
|
||||
<Show when={layout.review.state() === "pane" && session.diffs().length}>
|
||||
<div
|
||||
classList={{
|
||||
"relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0 border-l border-border-weak-base": true,
|
||||
"relative grow px-6 py-3 flex-1 min-h-0 border-l border-border-weak-base": true,
|
||||
}}
|
||||
>
|
||||
<div class="h-8 w-full flex items-center justify-between shrink-0 self-stretch">
|
||||
<div class="flex items-center gap-x-3">
|
||||
<Tooltip value="Close">
|
||||
<IconButton icon="align-right" variant="ghost" onClick={local.layout.review.close} />
|
||||
</Tooltip>
|
||||
<Tooltip value="Open in tab">
|
||||
<IconButton
|
||||
icon="expand"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
local.layout.review.tab()
|
||||
session.layout.setActiveTab("review")
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-14-medium text-text-strong">All changes</div>
|
||||
<div class="h-full pb-40 overflow-y-auto no-scrollbar">
|
||||
<Accordion class="w-full" multiple>
|
||||
<For each={session.diffs()}>
|
||||
{(diff) => (
|
||||
<Accordion.Item value={diff.file} defaultOpen>
|
||||
<Accordion.Header>
|
||||
<Accordion.Trigger>
|
||||
<div class="flex items-center justify-between w-full gap-5">
|
||||
<div class="grow flex items-center gap-5 min-w-0">
|
||||
<FileIcon node={{ path: diff.file, type: "file" }} class="shrink-0 size-4" />
|
||||
<div class="flex grow min-w-0">
|
||||
<Show when={diff.file.includes("/")}>
|
||||
<span class="text-text-base truncate-start">
|
||||
{getDirectory(diff.file)}‎
|
||||
</span>
|
||||
</Show>
|
||||
<span class="text-text-strong shrink-0">{getFilename(diff.file)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shrink-0 flex gap-4 items-center justify-end">
|
||||
<DiffChanges changes={diff} />
|
||||
<Icon name="chevron-grabber-vertical" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Trigger>
|
||||
</Accordion.Header>
|
||||
<Accordion.Content>
|
||||
<Diff
|
||||
before={{
|
||||
name: diff.file!,
|
||||
contents: diff.before!,
|
||||
}}
|
||||
after={{
|
||||
name: diff.file!,
|
||||
contents: diff.after!,
|
||||
}}
|
||||
/>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
)}
|
||||
</For>
|
||||
</Accordion>
|
||||
</div>
|
||||
<SessionReview />
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Show when={local.layout.review.state() === "tab" && session.diffs().length}>
|
||||
<Tabs.Content value="review" class="select-text">
|
||||
<Show when={layout.review.state() === "tab" && session.diffs().length}>
|
||||
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden">
|
||||
<div
|
||||
classList={{
|
||||
"relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0": true,
|
||||
"relative px-6 py-3 flex-1 min-h-0 overflow-hidden": true,
|
||||
}}
|
||||
>
|
||||
<div class="h-8 w-full flex items-center justify-between shrink-0 self-stretch sticky top-0 bg-background-stronger z-100">
|
||||
<div class="flex items-center gap-x-3"></div>
|
||||
</div>
|
||||
<div class="text-14-medium text-text-strong">All changes</div>
|
||||
<div class="h-full pb-40 overflow-y-auto no-scrollbar">
|
||||
<Accordion class="w-full" multiple>
|
||||
<For each={session.diffs()}>
|
||||
{(diff) => (
|
||||
<Accordion.Item value={diff.file} defaultOpen>
|
||||
<Accordion.Header>
|
||||
<Accordion.Trigger>
|
||||
<div class="flex items-center justify-between w-full gap-5">
|
||||
<div class="grow flex items-center gap-5 min-w-0">
|
||||
<FileIcon node={{ path: diff.file, type: "file" }} class="shrink-0 size-4" />
|
||||
<div class="flex grow min-w-0">
|
||||
<Show when={diff.file.includes("/")}>
|
||||
<span class="text-text-base truncate-start">{getDirectory(diff.file)}‎</span>
|
||||
</Show>
|
||||
<span class="text-text-strong shrink-0">{getFilename(diff.file)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shrink-0 flex gap-4 items-center justify-end">
|
||||
<DiffChanges changes={diff} />
|
||||
<Icon name="chevron-grabber-vertical" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Trigger>
|
||||
</Accordion.Header>
|
||||
<Accordion.Content>
|
||||
<Diff
|
||||
diffStyle="split"
|
||||
before={{
|
||||
name: diff.file!,
|
||||
contents: diff.before!,
|
||||
}}
|
||||
after={{
|
||||
name: diff.file!,
|
||||
contents: diff.after!,
|
||||
}}
|
||||
/>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
)}
|
||||
</For>
|
||||
</Accordion>
|
||||
</div>
|
||||
<SessionReview split hideExpand class="pb-40" />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Show>
|
||||
@@ -821,8 +728,8 @@ export default function Page() {
|
||||
},
|
||||
)
|
||||
return (
|
||||
<div class="relative px-3 h-10 flex items-center bg-background-base border-x border-border-weak-base border-b border-b-transparent">
|
||||
<Show when={file()}>{(f) => <FileVisual file={f()} />}</Show>
|
||||
<div class="relative px-6 h-12 flex items-center bg-background-stronger border-x border-border-weak-base border-b border-b-transparent">
|
||||
<Show when={file()}>{(f) => <FileVisual active file={f()} />}</Show>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
@@ -830,7 +737,7 @@ export default function Page() {
|
||||
</DragOverlay>
|
||||
</DragDropProvider>
|
||||
<Show when={session.layout.tabs.active}>
|
||||
<div class="absolute inset-x-0 px-6 max-w-2xl flex flex-col justify-center items-center z-50 mx-auto bottom-8">
|
||||
<div class="absolute inset-x-0 px-6 max-w-2xl flex flex-col justify-center items-center z-50 mx-auto bottom-6">
|
||||
<PromptInput
|
||||
ref={(el) => {
|
||||
inputRef = el
|
||||
@@ -872,7 +779,13 @@ export default function Page() {
|
||||
items={local.file.searchFiles}
|
||||
key={(x) => x}
|
||||
onOpenChange={(open) => setStore("fileSelectOpen", open)}
|
||||
onSelect={(x) => (x ? session.layout.openTab("file://" + x) : undefined)}
|
||||
onSelect={(x) => {
|
||||
if (x) {
|
||||
local.file.open(x)
|
||||
return session.layout.openTab("file://" + x)
|
||||
}
|
||||
return undefined
|
||||
}}
|
||||
>
|
||||
{(i) => (
|
||||
<div
|
||||
|
||||
@@ -9,12 +9,13 @@ export type FileIconProps = JSX.GSVGAttributes<SVGSVGElement> & {
|
||||
}
|
||||
|
||||
export const FileIcon: Component<FileIconProps> = (props) => {
|
||||
const [local, rest] = splitProps(props, ["node", "class", "expanded"])
|
||||
const [local, rest] = splitProps(props, ["node", "class", "classList", "expanded"])
|
||||
const name = createMemo(() => chooseIconName(local.node.path, local.node.type, local.expanded || false))
|
||||
return (
|
||||
<svg
|
||||
{...rest}
|
||||
classList={{
|
||||
...(local.classList ?? {}),
|
||||
"shrink-0 size-4": true,
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export function base64Encode(value: string) {
|
||||
return btoa(value).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
|
||||
}
|
||||
|
||||
export function base64Decode(value: string) {
|
||||
return atob(value.replace(/-/g, "+").replace(/_/g, "/"))
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./path"
|
||||
export * from "./dom"
|
||||
export * from "./encode"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The AI coding agent built for the terminal"
|
||||
version = "1.0.53"
|
||||
version = "1.0.77"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/sst/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.53/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.77/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.53/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.77/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.53/opencode-linux-arm64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.77/opencode-linux-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.53/opencode-linux-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.77/opencode-linux-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.53/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.77/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,61 +1,84 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
#!/usr/bin/env node
|
||||
|
||||
if [ -n "$OPENCODE_BIN_PATH" ]; then
|
||||
resolved="$OPENCODE_BIN_PATH"
|
||||
else
|
||||
# Get the real path of this script, resolving any symlinks
|
||||
script_path="$0"
|
||||
while [ -L "$script_path" ]; do
|
||||
link_target="$(readlink "$script_path")"
|
||||
case "$link_target" in
|
||||
/*) script_path="$link_target" ;;
|
||||
*) script_path="$(dirname "$script_path")/$link_target" ;;
|
||||
esac
|
||||
done
|
||||
script_dir="$(dirname "$script_path")"
|
||||
script_dir="$(cd "$script_dir" && pwd)"
|
||||
|
||||
# Map platform names
|
||||
case "$(uname -s)" in
|
||||
Darwin) platform="darwin" ;;
|
||||
Linux) platform="linux" ;;
|
||||
MINGW*|CYGWIN*|MSYS*) platform="win32" ;;
|
||||
*) platform="$(uname -s | tr '[:upper:]' '[:lower:]')" ;;
|
||||
esac
|
||||
|
||||
# Map architecture names
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) arch="x64" ;;
|
||||
aarch64) arch="arm64" ;;
|
||||
armv7l) arch="arm" ;;
|
||||
*) arch="$(uname -m)" ;;
|
||||
esac
|
||||
|
||||
name="opencode-${platform}-${arch}"
|
||||
binary="opencode"
|
||||
[ "$platform" = "win32" ] && binary="opencode.exe"
|
||||
|
||||
# Search for the binary starting from real script location
|
||||
resolved=""
|
||||
current_dir="$script_dir"
|
||||
while [ "$current_dir" != "/" ]; do
|
||||
candidate="$current_dir/node_modules/$name/bin/$binary"
|
||||
if [ -f "$candidate" ]; then
|
||||
resolved="$candidate"
|
||||
break
|
||||
fi
|
||||
current_dir="$(dirname "$current_dir")"
|
||||
done
|
||||
|
||||
if [ -z "$resolved" ]; then
|
||||
printf "It seems that your package manager failed to install the right version of the opencode CLI for your platform. You can try manually installing the \"%s\" package\n" "$name" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
const childProcess = require("child_process")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
const os = require("os")
|
||||
|
||||
# Handle SIGINT gracefully
|
||||
trap '' INT
|
||||
function run(target) {
|
||||
const result = childProcess.spawnSync(target, process.argv.slice(2), {
|
||||
stdio: "inherit",
|
||||
})
|
||||
if (result.error) {
|
||||
console.error(result.error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
const code = typeof result.status === "number" ? result.status : 0
|
||||
process.exit(code)
|
||||
}
|
||||
|
||||
# Execute the binary with all arguments
|
||||
exec "$resolved" "$@"
|
||||
const envPath = process.env.OPENCODE_BIN_PATH
|
||||
if (envPath) {
|
||||
run(envPath)
|
||||
}
|
||||
|
||||
const scriptPath = fs.realpathSync(__filename)
|
||||
const scriptDir = path.dirname(scriptPath)
|
||||
|
||||
const platformMap = {
|
||||
darwin: "darwin",
|
||||
linux: "linux",
|
||||
win32: "windows",
|
||||
}
|
||||
const archMap = {
|
||||
x64: "x64",
|
||||
arm64: "arm64",
|
||||
arm: "arm",
|
||||
}
|
||||
|
||||
let platform = platformMap[os.platform()]
|
||||
if (!platform) {
|
||||
platform = os.platform()
|
||||
}
|
||||
let arch = archMap[os.arch()]
|
||||
if (!arch) {
|
||||
arch = os.arch()
|
||||
}
|
||||
const base = "opencode-" + platform + "-" + arch
|
||||
const binary = platform === "windows" ? "opencode.exe" : "opencode"
|
||||
|
||||
function findBinary(startDir) {
|
||||
let current = startDir
|
||||
for (;;) {
|
||||
const modules = path.join(current, "node_modules")
|
||||
if (fs.existsSync(modules)) {
|
||||
const entries = fs.readdirSync(modules)
|
||||
for (const entry of entries) {
|
||||
if (!entry.startsWith(base)) {
|
||||
continue
|
||||
}
|
||||
const candidate = path.join(modules, entry, "bin", binary)
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
const parent = path.dirname(current)
|
||||
if (parent === current) {
|
||||
return
|
||||
}
|
||||
current = parent
|
||||
}
|
||||
}
|
||||
|
||||
const resolved = findBinary(scriptDir)
|
||||
if (!resolved) {
|
||||
console.error(
|
||||
'It seems that your package manager failed to install the right version of the opencode CLI for your platform. You can try manually installing the "' +
|
||||
base +
|
||||
'" package',
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
run(resolved)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
if defined OPENCODE_BIN_PATH (
|
||||
set "resolved=%OPENCODE_BIN_PATH%"
|
||||
goto :execute
|
||||
)
|
||||
|
||||
rem Get the directory of this script
|
||||
set "script_dir=%~dp0"
|
||||
set "script_dir=%script_dir:~0,-1%"
|
||||
|
||||
rem Detect platform and architecture
|
||||
set "platform=windows"
|
||||
|
||||
rem Detect architecture
|
||||
if "%PROCESSOR_ARCHITECTURE%"=="AMD64" (
|
||||
set "arch=x64"
|
||||
) else if "%PROCESSOR_ARCHITECTURE%"=="ARM64" (
|
||||
set "arch=arm64"
|
||||
) else if "%PROCESSOR_ARCHITECTURE%"=="x86" (
|
||||
set "arch=x86"
|
||||
) else (
|
||||
set "arch=x64"
|
||||
)
|
||||
|
||||
set "name=opencode-!platform!-!arch!"
|
||||
set "binary=opencode.exe"
|
||||
|
||||
rem Search for the binary starting from script location
|
||||
set "resolved="
|
||||
set "current_dir=%script_dir%"
|
||||
|
||||
:search_loop
|
||||
set "candidate=%current_dir%\node_modules\%name%\bin\%binary%"
|
||||
if exist "%candidate%" (
|
||||
set "resolved=%candidate%"
|
||||
goto :execute
|
||||
)
|
||||
|
||||
rem Move up one directory
|
||||
for %%i in ("%current_dir%") do set "parent_dir=%%~dpi"
|
||||
set "parent_dir=%parent_dir:~0,-1%"
|
||||
|
||||
rem Check if we've reached the root
|
||||
if "%current_dir%"=="%parent_dir%" goto :not_found
|
||||
set "current_dir=%parent_dir%"
|
||||
goto :search_loop
|
||||
|
||||
:not_found
|
||||
echo It seems that your package manager failed to install the right version of the opencode CLI for your platform. You can try manually installing the "%name%" package >&2
|
||||
exit /b 1
|
||||
|
||||
:execute
|
||||
rem Execute the binary with all arguments in the same console window
|
||||
rem Use start /b /wait to ensure it runs in the current shell context for all shells
|
||||
start /b /wait "" "%resolved%" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.0.53",
|
||||
"version": "1.0.77",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
@@ -54,8 +54,8 @@
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opentui/core": "0.0.0-20251108-0c7899b1",
|
||||
"@opentui/solid": "0.0.0-20251108-0c7899b1",
|
||||
"@opentui/core": "0.1.46",
|
||||
"@opentui/solid": "0.1.46",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
|
||||
@@ -212,5 +212,19 @@ export default {
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
filetype: "swift",
|
||||
wasm: "https://github.com/alex-pinkus/tree-sitter-swift/releases/download/0.7.1/tree-sitter-swift.wasm",
|
||||
queries: {
|
||||
highlights: [
|
||||
// NOTE: Using parser repo queries instead of nvim-treesitter due to incompatible #lua-match? predicates
|
||||
// "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/highlights.scm
|
||||
"https://raw.githubusercontent.com/alex-pinkus/tree-sitter-swift/main/queries/highlights.scm",
|
||||
],
|
||||
locals: [
|
||||
"https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/swift/locals.scm",
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -17,38 +17,88 @@ import { Script } from "@opencode-ai/script"
|
||||
|
||||
const singleFlag = process.argv.includes("--single")
|
||||
|
||||
const allTargets = [
|
||||
["windows", "x64"],
|
||||
["linux", "arm64"],
|
||||
["linux", "x64"],
|
||||
["linux", "x64-baseline"],
|
||||
["darwin", "x64"],
|
||||
["darwin", "x64-baseline"],
|
||||
["darwin", "arm64"],
|
||||
const allTargets: {
|
||||
os: string
|
||||
arch: "arm64" | "x64"
|
||||
abi?: "musl"
|
||||
avx2?: false
|
||||
}[] = [
|
||||
{
|
||||
os: "linux",
|
||||
arch: "arm64",
|
||||
},
|
||||
{
|
||||
os: "linux",
|
||||
arch: "x64",
|
||||
},
|
||||
{
|
||||
os: "linux",
|
||||
arch: "x64",
|
||||
avx2: false,
|
||||
},
|
||||
{
|
||||
os: "linux",
|
||||
arch: "arm64",
|
||||
abi: "musl",
|
||||
},
|
||||
{
|
||||
os: "linux",
|
||||
arch: "x64",
|
||||
abi: "musl",
|
||||
},
|
||||
{
|
||||
os: "linux",
|
||||
arch: "x64",
|
||||
abi: "musl",
|
||||
avx2: false,
|
||||
},
|
||||
{
|
||||
os: "darwin",
|
||||
arch: "arm64",
|
||||
},
|
||||
{
|
||||
os: "darwin",
|
||||
arch: "x64",
|
||||
},
|
||||
{
|
||||
os: "darwin",
|
||||
arch: "x64",
|
||||
avx2: false,
|
||||
},
|
||||
{
|
||||
os: "win32",
|
||||
arch: "x64",
|
||||
},
|
||||
{
|
||||
os: "win32",
|
||||
arch: "x64",
|
||||
avx2: false,
|
||||
},
|
||||
]
|
||||
|
||||
const targets = singleFlag
|
||||
? allTargets.filter(([os, arch]) => os === process.platform && arch === process.arch)
|
||||
? allTargets.filter((item) => item.os === process.platform && item.arch === process.arch)
|
||||
: allTargets
|
||||
|
||||
await $`rm -rf dist`
|
||||
|
||||
const binaries: Record<string, string> = {}
|
||||
for (const [os, arch] of targets) {
|
||||
console.log(`building ${os}-${arch}`)
|
||||
const name = `${pkg.name}-${os}-${arch}`
|
||||
await $`bun install --os="*" --cpu="*" @opentui/core@${pkg.dependencies["@opentui/core"]}`
|
||||
await $`bun install --os="*" --cpu="*" @parcel/watcher@${pkg.dependencies["@parcel/watcher"]}`
|
||||
for (const item of targets) {
|
||||
const name = [
|
||||
pkg.name,
|
||||
// changing to win32 flags npm for some reason
|
||||
item.os === "win32" ? "windows" : item.os,
|
||||
item.arch,
|
||||
item.avx2 === false ? "baseline" : undefined,
|
||||
item.abi === undefined ? undefined : item.abi,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("-")
|
||||
console.log(`building ${name}`)
|
||||
await $`mkdir -p dist/${name}/bin`
|
||||
|
||||
const opentui = `@opentui/core-${os === "windows" ? "win32" : os}-${arch.replace("-baseline", "")}`
|
||||
await $`mkdir -p ../../node_modules/${opentui}`
|
||||
await $`npm pack ${opentui}@${pkg.dependencies["@opentui/core"]}`.cwd(path.join(dir, "../../node_modules"))
|
||||
await $`tar -xf ../../node_modules/${opentui.replace("@opentui/", "opentui-")}-*.tgz -C ../../node_modules/${opentui} --strip-components=1`
|
||||
|
||||
const watcher = `@parcel/watcher-${os === "windows" ? "win32" : os}-${arch.replace("-baseline", "")}${os === "linux" ? "-glibc" : ""}`
|
||||
await $`mkdir -p ../../node_modules/${watcher}`
|
||||
await $`npm pack ${watcher}`.cwd(path.join(dir, "../../node_modules")).quiet()
|
||||
await $`tar -xf ../../node_modules/${watcher.replace("@parcel/", "parcel-")}-*.tgz -C ../../node_modules/${watcher} --strip-components=1`
|
||||
|
||||
const parserWorker = fs.realpathSync(path.resolve(dir, "./node_modules/@opentui/core/parser.worker.js"))
|
||||
const workerPath = "./src/cli/cmd/tui/worker.ts"
|
||||
|
||||
@@ -58,7 +108,7 @@ for (const [os, arch] of targets) {
|
||||
plugins: [solidPlugin],
|
||||
sourcemap: "external",
|
||||
compile: {
|
||||
target: `bun-${os}-${arch}` as any,
|
||||
target: name.replace(pkg.name, "bun") as any,
|
||||
outfile: `dist/${name}/bin/opencode`,
|
||||
execArgv: [`--user-agent=opencode/${Script.version}`, `--env-file=""`, `--`],
|
||||
windows: {},
|
||||
@@ -66,7 +116,7 @@ for (const [os, arch] of targets) {
|
||||
entrypoints: ["./src/index.ts", parserWorker, workerPath],
|
||||
define: {
|
||||
OPENCODE_VERSION: `'${Script.version}'`,
|
||||
OTUI_TREE_SITTER_WORKER_PATH: "/$bunfs/root/" + path.relative(dir, parserWorker),
|
||||
OTUI_TREE_SITTER_WORKER_PATH: "/$bunfs/root/" + path.relative(dir, parserWorker).replaceAll("\\", "/"),
|
||||
OPENCODE_WORKER_PATH: workerPath,
|
||||
OPENCODE_CHANNEL: `'${Script.channel}'`,
|
||||
},
|
||||
@@ -78,8 +128,8 @@ for (const [os, arch] of targets) {
|
||||
{
|
||||
name,
|
||||
version: Script.version,
|
||||
os: [os === "windows" ? "win32" : os],
|
||||
cpu: [arch],
|
||||
os: [item.os],
|
||||
cpu: [item.arch],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
|
||||
@@ -50,79 +50,66 @@ function detectPlatformAndArch() {
|
||||
function findBinary() {
|
||||
const { platform, arch } = detectPlatformAndArch()
|
||||
const packageName = `opencode-${platform}-${arch}`
|
||||
const binary = platform === "windows" ? "opencode.exe" : "opencode"
|
||||
const binaryName = platform === "windows" ? "opencode.exe" : "opencode"
|
||||
|
||||
try {
|
||||
// Use require.resolve to find the package
|
||||
const packageJsonPath = require.resolve(`${packageName}/package.json`)
|
||||
const packageDir = path.dirname(packageJsonPath)
|
||||
const binaryPath = path.join(packageDir, "bin", binary)
|
||||
const binaryPath = path.join(packageDir, "bin", binaryName)
|
||||
|
||||
if (!fs.existsSync(binaryPath)) {
|
||||
throw new Error(`Binary not found at ${binaryPath}`)
|
||||
}
|
||||
|
||||
return binaryPath
|
||||
return { binaryPath, binaryName }
|
||||
} catch (error) {
|
||||
throw new Error(`Could not find package ${packageName}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function regenerateWindowsCmdWrappers() {
|
||||
console.log("Windows + npm detected: Forcing npm to rebuild bin links")
|
||||
function prepareBinDirectory(binaryName) {
|
||||
const binDir = path.join(__dirname, "bin")
|
||||
const targetPath = path.join(binDir, binaryName)
|
||||
|
||||
try {
|
||||
const { execSync } = require("child_process")
|
||||
const pkgPath = path.join(__dirname, "..")
|
||||
// Ensure bin directory exists
|
||||
if (!fs.existsSync(binDir)) {
|
||||
fs.mkdirSync(binDir, { recursive: true })
|
||||
}
|
||||
|
||||
// npm_config_global is string | undefined
|
||||
// if it exists, the value is true
|
||||
const isGlobal = process.env.npm_config_global === "true" || pkgPath.includes(path.join("npm", "node_modules"))
|
||||
// Remove existing binary/symlink if it exists
|
||||
if (fs.existsSync(targetPath)) {
|
||||
fs.unlinkSync(targetPath)
|
||||
}
|
||||
|
||||
// The npm rebuild command does 2 things - Execute lifecycle scripts and rebuild bin links
|
||||
// We want to skip lifecycle scripts to avoid infinite loops, so we use --ignore-scripts
|
||||
const cmd = `npm rebuild opencode-ai --ignore-scripts${isGlobal ? " -g" : ""}`
|
||||
const opts = {
|
||||
stdio: "inherit",
|
||||
shell: true,
|
||||
...(isGlobal ? {} : { cwd: path.join(pkgPath, "..", "..") }), // For local, run from project root
|
||||
}
|
||||
return { binDir, targetPath }
|
||||
}
|
||||
|
||||
console.log(`Running: ${cmd}`)
|
||||
execSync(cmd, opts)
|
||||
console.log("Successfully rebuilt npm bin links")
|
||||
} catch (error) {
|
||||
console.error("Error rebuilding npm links:", error.message)
|
||||
console.error("npm rebuild failed. You may need to manually run: npm rebuild opencode-ai --ignore-scripts")
|
||||
function symlinkBinary(sourcePath, binaryName) {
|
||||
const { targetPath } = prepareBinDirectory(binaryName)
|
||||
|
||||
fs.symlinkSync(sourcePath, targetPath)
|
||||
console.log(`opencode binary symlinked: ${targetPath} -> ${sourcePath}`)
|
||||
|
||||
// Verify the file exists after operation
|
||||
if (!fs.existsSync(targetPath)) {
|
||||
throw new Error(`Failed to symlink binary to ${targetPath}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
if (os.platform() === "win32") {
|
||||
// NPM eg format - npm/11.4.2 node/v24.4.1 win32 x64
|
||||
// Bun eg format - bun/1.2.19 npm/? node/v24.3.0 win32 x64
|
||||
if (process.env.npm_config_user_agent.startsWith("npm")) {
|
||||
await regenerateWindowsCmdWrappers()
|
||||
} else {
|
||||
console.log("Windows detected but not npm, skipping postinstall")
|
||||
}
|
||||
// On Windows, the .exe is already included in the package and bin field points to it
|
||||
// No postinstall setup needed
|
||||
console.log("Windows detected: binary setup not needed (using packaged .exe)")
|
||||
return
|
||||
}
|
||||
|
||||
const binaryPath = findBinary()
|
||||
const binScript = path.join(__dirname, "bin", "opencode")
|
||||
|
||||
// Remove existing bin script if it exists
|
||||
if (fs.existsSync(binScript)) {
|
||||
fs.unlinkSync(binScript)
|
||||
}
|
||||
|
||||
// Create symlink to the actual binary
|
||||
fs.symlinkSync(binaryPath, binScript)
|
||||
console.log(`opencode binary symlinked: ${binScript} -> ${binaryPath}`)
|
||||
const { binaryPath, binaryName } = findBinary()
|
||||
symlinkBinary(binaryPath, binaryName)
|
||||
} catch (error) {
|
||||
console.error("Failed to create opencode binary symlink:", error.message)
|
||||
console.error("Failed to setup opencode binary:", error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
function main() {
|
||||
if (os.platform() !== "win32") {
|
||||
console.log("Non-Windows platform detected, skipping preinstall")
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Windows detected: Modifying package.json bin entry")
|
||||
|
||||
// Read package.json
|
||||
const packageJsonPath = path.join(__dirname, "package.json")
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"))
|
||||
|
||||
// Modify bin to point to .cmd file on Windows
|
||||
packageJson.bin = {
|
||||
opencode: "./bin/opencode.cmd",
|
||||
}
|
||||
|
||||
// Write it back
|
||||
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
||||
console.log("Updated package.json bin to use opencode.cmd")
|
||||
|
||||
// Now you can also remove the Unix script if you want
|
||||
const unixScript = path.join(__dirname, "bin", "opencode")
|
||||
if (fs.existsSync(unixScript)) {
|
||||
console.log("Removing Unix shell script")
|
||||
fs.unlinkSync(unixScript)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
main()
|
||||
} catch (error) {
|
||||
console.error("Preinstall script error:", error.message)
|
||||
process.exit(0)
|
||||
}
|
||||
@@ -2,8 +2,9 @@
|
||||
import { $ } from "bun"
|
||||
import pkg from "../package.json"
|
||||
import { Script } from "@opencode-ai/script"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
const dir = new URL("..", import.meta.url).pathname
|
||||
const dir = fileURLToPath(new URL("..", import.meta.url))
|
||||
process.chdir(dir)
|
||||
|
||||
const { binaries } = await import("./build.ts")
|
||||
@@ -15,8 +16,8 @@ const { binaries } = await import("./build.ts")
|
||||
|
||||
await $`mkdir -p ./dist/${pkg.name}`
|
||||
await $`cp -r ./bin ./dist/${pkg.name}/bin`
|
||||
await $`cp ./script/preinstall.mjs ./dist/${pkg.name}/preinstall.mjs`
|
||||
await $`cp ./script/postinstall.mjs ./dist/${pkg.name}/postinstall.mjs`
|
||||
|
||||
await Bun.file(`./dist/${pkg.name}/package.json`).write(
|
||||
JSON.stringify(
|
||||
{
|
||||
@@ -25,7 +26,6 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
|
||||
[pkg.name]: `./bin/${pkg.name}`,
|
||||
},
|
||||
scripts: {
|
||||
preinstall: "bun ./preinstall.mjs || node ./preinstall.mjs",
|
||||
postinstall: "bun ./postinstall.mjs || node ./postinstall.mjs",
|
||||
},
|
||||
version: Script.version,
|
||||
@@ -36,7 +36,15 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
|
||||
),
|
||||
)
|
||||
for (const [name] of Object.entries(binaries)) {
|
||||
await $`cd dist/${name} && chmod 777 -R . && bun publish --access public --tag ${Script.channel}`
|
||||
try {
|
||||
process.chdir(`./dist/${name}`)
|
||||
if (process.platform !== "win32") {
|
||||
await $`chmod 755 -R .`
|
||||
}
|
||||
await $`bun publish --access public --tag ${Script.channel}`
|
||||
} finally {
|
||||
process.chdir(dir)
|
||||
}
|
||||
}
|
||||
await $`cd ./dist/${pkg.name} && bun publish --access public --tag ${Script.channel}`
|
||||
|
||||
|
||||
@@ -12,10 +12,11 @@ export namespace Agent {
|
||||
.object({
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
mode: z.union([z.literal("subagent"), z.literal("primary"), z.literal("all")]),
|
||||
mode: z.enum(["subagent", "primary", "all"]),
|
||||
builtIn: z.boolean(),
|
||||
topP: z.number().optional(),
|
||||
temperature: z.number().optional(),
|
||||
color: z.string().optional(),
|
||||
permission: z.object({
|
||||
edit: Config.Permission,
|
||||
bash: z.record(z.string(), Config.Permission),
|
||||
@@ -147,7 +148,7 @@ export namespace Agent {
|
||||
tools: {},
|
||||
builtIn: false,
|
||||
}
|
||||
const { name, model, prompt, tools, description, temperature, top_p, mode, permission, ...extra } = value
|
||||
const { name, model, prompt, tools, description, temperature, top_p, mode, permission, color, ...extra } = value
|
||||
item.options = {
|
||||
...item.options,
|
||||
...extra,
|
||||
@@ -167,6 +168,7 @@ export namespace Agent {
|
||||
if (temperature != undefined) item.temperature = temperature
|
||||
if (top_p != undefined) item.topP = top_p
|
||||
if (mode) item.mode = mode
|
||||
if (color) item.color = color
|
||||
// just here for consistency & to prevent it from being added as an option
|
||||
if (name) item.name = name
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { EventEmitter } from "events"
|
||||
|
||||
export const GlobalBus = new EventEmitter<{
|
||||
event: [
|
||||
{
|
||||
directory: string
|
||||
payload: any
|
||||
},
|
||||
]
|
||||
}>()
|
||||
@@ -2,6 +2,7 @@ import z from "zod"
|
||||
import type { ZodType } from "zod"
|
||||
import { Log } from "../util/log"
|
||||
import { Instance } from "../project/instance"
|
||||
import { GlobalBus } from "./global"
|
||||
|
||||
export namespace Bus {
|
||||
const log = Log.create({ service: "bus" })
|
||||
@@ -29,22 +30,26 @@ export namespace Bus {
|
||||
}
|
||||
|
||||
export function payloads() {
|
||||
return z.discriminatedUnion(
|
||||
"type",
|
||||
registry
|
||||
.entries()
|
||||
.map(([type, def]) => {
|
||||
return z
|
||||
.object({
|
||||
type: z.literal(type),
|
||||
properties: def.properties,
|
||||
})
|
||||
.meta({
|
||||
ref: "Event" + "." + def.type,
|
||||
})
|
||||
})
|
||||
.toArray() as any,
|
||||
)
|
||||
return z
|
||||
.discriminatedUnion(
|
||||
"type",
|
||||
registry
|
||||
.entries()
|
||||
.map(([type, def]) => {
|
||||
return z
|
||||
.object({
|
||||
type: z.literal(type),
|
||||
properties: def.properties,
|
||||
})
|
||||
.meta({
|
||||
ref: "Event" + "." + def.type,
|
||||
})
|
||||
})
|
||||
.toArray() as any,
|
||||
)
|
||||
.meta({
|
||||
ref: "Event",
|
||||
})
|
||||
}
|
||||
|
||||
export async function publish<Definition extends EventDefinition>(
|
||||
@@ -65,6 +70,10 @@ export namespace Bus {
|
||||
pending.push(sub(payload))
|
||||
}
|
||||
}
|
||||
GlobalBus.emit("event", {
|
||||
directory: Instance.directory,
|
||||
payload,
|
||||
})
|
||||
return Promise.all(pending)
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user