mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-16 17:41:16 -04:00
Compare commits
252 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac3d0cb5a3 | |||
| 06d69ab609 | |||
| c2cc486c7d | |||
| 8a6b8e5339 | |||
| cfd6a7ae96 | |||
| 4173ee0e0b | |||
| 22b5d7e570 | |||
| f1ec28176f | |||
| ab78a46396 | |||
| 2ed18ea1fe | |||
| 40eddce435 | |||
| 78f8cc9418 | |||
| 58f7da6e9f | |||
| 5a199b04cb | |||
| eb968a6651 | |||
| a813fcb41c | |||
| a58d1be822 | |||
| 07dc8d8ce4 | |||
| d377246491 | |||
| 7030f49a74 | |||
| c4e4f2a058 | |||
| 2729705594 | |||
| ea13b6e8aa | |||
| 85ab9798c6 | |||
| 33290c54cd | |||
| 5d613a038d | |||
| db78a59f03 | |||
| 7c3eeeb0fa | |||
| e8357a87b0 | |||
| 06c543e938 | |||
| 759ce8fb8e | |||
| 38847e13bb | |||
| e0c6459faa | |||
| 80b278ddab | |||
| ef7ef6538e | |||
| d23c21023a | |||
| dfa2a9f225 | |||
| 6f78a71fa7 | |||
| f8f1f46a4f | |||
| ab705dacfa | |||
| d1b93616f7 | |||
| 69215d456c | |||
| 54e52896a4 | |||
| b18fb16e9c | |||
| 1250486ddf | |||
| d645e8bbe1 | |||
| cad415872e | |||
| e8746ddb1d | |||
| 80020ade2e | |||
| 08ef97b162 | |||
| 1aedb265dd | |||
| 5c13b209aa | |||
| 43a9c50389 | |||
| 55224d64a2 | |||
| c325aa1142 | |||
| 6e020ef9ef | |||
| aca1eb6b5b | |||
| 3d095e7fe7 | |||
| 632f20558a | |||
| f96c4badd8 | |||
| cbe1c81470 | |||
| c25155586c | |||
| 08b94a6890 | |||
| 8cddc9ea55 | |||
| 578239e0d0 | |||
| 626fa1462b | |||
| 968239bb76 | |||
| 8c24879246 | |||
| 9127055ae7 | |||
| f5a6a4af7f | |||
| 6e00348bd7 | |||
| 95f7403daf | |||
| 14d1e20287 | |||
| b8e2895dfc | |||
| 6e028ec2dc | |||
| 8e0ddd1ac9 | |||
| da78b758d4 | |||
| 360765c591 | |||
| db0078bf17 | |||
| 98578d3a7b | |||
| bc3616d9c6 | |||
| 71306cbd1f | |||
| 0866034946 | |||
| 2ccaa10e79 | |||
| e92d5b592c | |||
| 00ec29dae6 | |||
| 438916de5f | |||
| 8d4a67324e | |||
| 0d683eaa8e | |||
| 8fd1b92e6e | |||
| 22e3240296 | |||
| e1d0b2ba6e | |||
| ccc27e23df | |||
| ad4bdd9f0f | |||
| 5479928a9d | |||
| 40836e9683 | |||
| 9a48f8e9e3 | |||
| 91b8ba2186 | |||
| d075c097ac | |||
| 88fd6a294b | |||
| ea8ef37d50 | |||
| d510bd52a4 | |||
| e0a854f035 | |||
| bd914a8c06 | |||
| a49102db01 | |||
| 21012fab4b | |||
| 9a71a73f50 | |||
| 2190e8c656 | |||
| 1fd496a5e2 | |||
| 74d584af34 | |||
| 46f415ecb0 | |||
| d0399045da | |||
| 4be0ba19ca | |||
| d5a5e6e062 | |||
| e8dad85233 | |||
| f197b8a0cd | |||
| efaf854e09 | |||
| 704276753b | |||
| 2c5437791b | |||
| 94ab87ffad | |||
| 416f419a81 | |||
| 3ba03a97dc | |||
| b1a22e08f5 | |||
| c551a4b6e3 | |||
| 524ea95a00 | |||
| 0e9664d300 | |||
| fffa718f5e | |||
| cce4f64e0b | |||
| ce6e9a822d | |||
| f66e6d7033 | |||
| de2de099b4 | |||
| 0233dd1b39 | |||
| 40b275d7e6 | |||
| e4a34beb8b | |||
| ac54535486 | |||
| 1a43e5fe87 | |||
| 46be47d0be | |||
| 4af9defb89 | |||
| 12b621068a | |||
| 07e7ebdb8e | |||
| 5092b5f07b | |||
| d8ef9f808d | |||
| d7192d6af9 | |||
| 68e6c540bb | |||
| b572c68100 | |||
| 25cb03dbe5 | |||
| d47510785a | |||
| 657f3d5089 | |||
| 49939c4d8d | |||
| 529eb6e147 | |||
| a7cae8f674 | |||
| 12ae80856e | |||
| 7e619a9302 | |||
| 2abafbcd2f | |||
| 8b08d340ac | |||
| 81983d4a2e | |||
| 7443b99295 | |||
| 306fc05c00 | |||
| 9d8d0e97ec | |||
| f4086ac459 | |||
| b9b5d42bd8 | |||
| 83ed1adcbd | |||
| 9b57db30d1 | |||
| df8e6e6014 | |||
| 472a6cc83e | |||
| 47d43aaf2d | |||
| da3dea0429 | |||
| 9862303eed | |||
| b14622352e | |||
| ea643f1e3f | |||
| f5fd54598f | |||
| 0f7b17b1b4 | |||
| 4d3e983edb | |||
| d3fc29bdec | |||
| fe58c649cb | |||
| af2a09940c | |||
| 7e016fdda6 | |||
| beb97d21ff | |||
| b0345284f9 | |||
| d71153eae6 | |||
| e60ded01df | |||
| 4b2a14c154 | |||
| b4717d8092 | |||
| dc8f8cc567 | |||
| 99110d12c4 | |||
| 74b1349cf6 | |||
| 3b3505cfe8 | |||
| 55bd6e487e | |||
| 1ee916a3c3 | |||
| a5d47f076e | |||
| acd1eb574d | |||
| a71dcc189e | |||
| 3789a31423 | |||
| bb6e350d68 | |||
| f9a441d4f4 | |||
| 1c05ebaea2 | |||
| 520c47e81d | |||
| e5b08da0f1 | |||
| fe2cc0cff1 | |||
| fbc8f6eba9 | |||
| 8cba7d7f53 | |||
| 6450ba1b79 | |||
| dc1c25cff5 | |||
| 3f3550a16e | |||
| 161e3db795 | |||
| 5a8a0f6a56 | |||
| 37f30993fa | |||
| ebc194ca9a | |||
| 4edb4fa4fa | |||
| 63176bb049 | |||
| 216a2d87cf | |||
| dd1f981d23 | |||
| bfc9b24b48 | |||
| 2691e1e666 | |||
| 3f16e0d89f | |||
| 994c55f709 | |||
| 2f32f2ceb5 | |||
| 076dfb3752 | |||
| 60aa0cb96e | |||
| e5973e2860 | |||
| dbd1987f0a | |||
| f270ea65c5 | |||
| 1698448016 | |||
| 564d3edfac | |||
| 679270d9e0 | |||
| 9f66a45970 | |||
| 779610d668 | |||
| 1fb611ef0a | |||
| 972f5ecc7d | |||
| 8d720f9463 | |||
| 92931437c4 | |||
| 08ca1237cc | |||
| 6473e15793 | |||
| 16cac69a72 | |||
| b2da41cfad | |||
| fcf2da9571 | |||
| 253b7ea784 | |||
| 3a9fd1bb36 | |||
| f84ac697dc | |||
| 76a79284d2 | |||
| 99a1e73fa1 | |||
| ba4c86448b | |||
| b36837ae93 | |||
| e03932e586 | |||
| 6b019a125a | |||
| 6a2fed7042 | |||
| 74baae597a | |||
| d78d31430d | |||
| 096e14d787 | |||
| bbb3120b59 | |||
| 9e4438f5bf | |||
| 87438fb38e |
@@ -9,6 +9,7 @@ on:
|
|||||||
- "nix/**"
|
- "nix/**"
|
||||||
- "packages/app/**"
|
- "packages/app/**"
|
||||||
- "packages/desktop/**"
|
- "packages/desktop/**"
|
||||||
|
- ".github/workflows/nix-desktop.yml"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "flake.nix"
|
- "flake.nix"
|
||||||
@@ -16,6 +17,7 @@ on:
|
|||||||
- "nix/**"
|
- "nix/**"
|
||||||
- "packages/app/**"
|
- "packages/app/**"
|
||||||
- "packages/desktop/**"
|
- "packages/desktop/**"
|
||||||
|
- ".github/workflows/nix-desktop.yml"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -25,6 +27,8 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
os:
|
os:
|
||||||
- blacksmith-4vcpu-ubuntu-2404
|
- blacksmith-4vcpu-ubuntu-2404
|
||||||
|
- blacksmith-4vcpu-ubuntu-2404-arm
|
||||||
|
- macos-15-intel
|
||||||
- macos-latest
|
- macos-latest
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
@@ -33,7 +37,7 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Nix
|
- name: Setup Nix
|
||||||
uses: DeterminateSystems/nix-installer-action@v21
|
uses: nixbuild/nix-quick-install-action@v34
|
||||||
|
|
||||||
- name: Build desktop via flake
|
- name: Build desktop via flake
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -10,22 +10,24 @@ on:
|
|||||||
- "bun.lock"
|
- "bun.lock"
|
||||||
- "package.json"
|
- "package.json"
|
||||||
- "packages/*/package.json"
|
- "packages/*/package.json"
|
||||||
|
- ".github/workflows/update-nix-hashes.yml"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "bun.lock"
|
- "bun.lock"
|
||||||
- "package.json"
|
- "package.json"
|
||||||
- "packages/*/package.json"
|
- "packages/*/package.json"
|
||||||
|
- ".github/workflows/update-nix-hashes.yml"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-linux:
|
update-flake:
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
env:
|
env:
|
||||||
SYSTEM: x86_64-linux
|
TITLE: flake.lock
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -33,39 +35,32 @@ jobs:
|
|||||||
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||||
|
|
||||||
- name: Setup Nix
|
- name: Setup Nix
|
||||||
uses: DeterminateSystems/nix-installer-action@v20
|
uses: nixbuild/nix-quick-install-action@v34
|
||||||
|
|
||||||
- name: Configure git
|
- name: Configure git
|
||||||
run: |
|
run: |
|
||||||
git config --global user.email "action@github.com"
|
git config --global user.email "action@github.com"
|
||||||
git config --global user.name "Github Action"
|
git config --global user.name "Github Action"
|
||||||
|
|
||||||
- name: Update flake.lock
|
- name: Update ${{ env.TITLE }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
echo "📦 Updating flake.lock..."
|
echo "Updating $TITLE..."
|
||||||
nix flake update
|
nix flake update
|
||||||
echo "✅ flake.lock updated successfully"
|
echo "$TITLE updated successfully"
|
||||||
|
|
||||||
- name: Update node_modules hash for x86_64-linux
|
- name: Commit ${{ env.TITLE }} changes
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
echo "🔄 Updating node_modules hash for x86_64-linux..."
|
|
||||||
nix/scripts/update-hashes.sh
|
|
||||||
echo "✅ node_modules hash for x86_64-linux updated successfully"
|
|
||||||
|
|
||||||
- name: Commit Linux hash changes
|
|
||||||
env:
|
env:
|
||||||
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo "🔍 Checking for changes in tracked Nix files..."
|
echo "Checking for changes in tracked files..."
|
||||||
|
|
||||||
summarize() {
|
summarize() {
|
||||||
local status="$1"
|
local status="$1"
|
||||||
{
|
{
|
||||||
echo "### Nix Hash Update (x86_64-linux)"
|
echo "### Nix $TITLE"
|
||||||
echo ""
|
echo ""
|
||||||
echo "- ref: ${GITHUB_REF_NAME}"
|
echo "- ref: ${GITHUB_REF_NAME}"
|
||||||
echo "- status: ${status}"
|
echo "- status: ${status}"
|
||||||
@@ -75,42 +70,53 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||||
}
|
}
|
||||||
|
FILES=(flake.lock flake.nix)
|
||||||
FILES=(flake.lock flake.nix nix/node-modules.nix nix/hashes.json)
|
|
||||||
STATUS="$(git status --short -- "${FILES[@]}" || true)"
|
STATUS="$(git status --short -- "${FILES[@]}" || true)"
|
||||||
if [ -z "$STATUS" ]; then
|
if [ -z "$STATUS" ]; then
|
||||||
echo "✅ No changes detected. Hashes are already up to date."
|
echo "No changes detected."
|
||||||
summarize "no changes"
|
summarize "no changes"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "📝 Changes detected:"
|
echo "Changes detected:"
|
||||||
echo "$STATUS"
|
echo "$STATUS"
|
||||||
echo "🔗 Staging files..."
|
echo "Staging files..."
|
||||||
git add "${FILES[@]}"
|
git add "${FILES[@]}"
|
||||||
echo "💾 Committing changes..."
|
echo "Committing changes..."
|
||||||
git commit -m "Update Nix flake.lock and x86_64-linux hash"
|
git commit -m "Update $TITLE"
|
||||||
echo "✅ Changes committed"
|
echo "Changes committed"
|
||||||
|
|
||||||
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
||||||
echo "🌳 Pulling latest from branch: $BRANCH"
|
echo "Pulling latest from branch: $BRANCH"
|
||||||
git pull --rebase origin "$BRANCH"
|
git pull --rebase --autostash origin "$BRANCH"
|
||||||
echo "🚀 Pushing changes to branch: $BRANCH"
|
echo "Pushing changes to branch: $BRANCH"
|
||||||
git push origin HEAD:"$BRANCH"
|
git push origin HEAD:"$BRANCH"
|
||||||
echo "✅ Changes pushed successfully"
|
echo "Changes pushed successfully"
|
||||||
|
|
||||||
summarize "committed $(git rev-parse --short HEAD)"
|
summarize "committed $(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
update-macos:
|
compute-node-modules-hash:
|
||||||
needs: update-linux
|
needs: update-flake
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
runs-on: macos-latest
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- system: x86_64-linux
|
||||||
|
host: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
- system: aarch64-linux
|
||||||
|
host: blacksmith-4vcpu-ubuntu-2404-arm
|
||||||
|
- system: x86_64-darwin
|
||||||
|
host: macos-15-intel
|
||||||
|
- system: aarch64-darwin
|
||||||
|
host: macos-latest
|
||||||
|
runs-on: ${{ matrix.host }}
|
||||||
env:
|
env:
|
||||||
SYSTEM: aarch64-darwin
|
SYSTEM: ${{ matrix.system }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -118,7 +124,105 @@ jobs:
|
|||||||
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||||
|
|
||||||
- name: Setup Nix
|
- name: Setup Nix
|
||||||
uses: DeterminateSystems/nix-installer-action@v20
|
uses: nixbuild/nix-quick-install-action@v34
|
||||||
|
|
||||||
|
- name: Compute node_modules hash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DUMMY="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||||
|
HASH_FILE="nix/hashes.json"
|
||||||
|
OUTPUT_FILE="hash-${SYSTEM}.txt"
|
||||||
|
|
||||||
|
export NIX_KEEP_OUTPUTS=1
|
||||||
|
export NIX_KEEP_DERIVATIONS=1
|
||||||
|
|
||||||
|
BUILD_LOG=$(mktemp)
|
||||||
|
TMP_JSON=$(mktemp)
|
||||||
|
trap 'rm -f "$BUILD_LOG" "$TMP_JSON"' EXIT
|
||||||
|
|
||||||
|
if [ ! -f "$HASH_FILE" ]; then
|
||||||
|
mkdir -p "$(dirname "$HASH_FILE")"
|
||||||
|
echo '{"nodeModules":{}}' > "$HASH_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set dummy hash to force nix to rebuild and reveal correct hash
|
||||||
|
jq --arg system "$SYSTEM" --arg value "$DUMMY" \
|
||||||
|
'.nodeModules = (.nodeModules // {}) | .nodeModules[$system] = $value' "$HASH_FILE" > "$TMP_JSON"
|
||||||
|
mv "$TMP_JSON" "$HASH_FILE"
|
||||||
|
|
||||||
|
MODULES_ATTR=".#packages.${SYSTEM}.default.node_modules"
|
||||||
|
DRV_PATH="$(nix eval --raw "${MODULES_ATTR}.drvPath")"
|
||||||
|
|
||||||
|
echo "Building node_modules for ${SYSTEM} to discover correct hash..."
|
||||||
|
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)
|
||||||
|
CORRECT_HASH=""
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Try to extract hash from build log
|
||||||
|
if [ -z "$CORRECT_HASH" ]; then
|
||||||
|
CORRECT_HASH="$(grep -E 'got:\s+sha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | awk '{print $2}' | head -n1 || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$CORRECT_HASH" ]; then
|
||||||
|
CORRECT_HASH="$(grep -A2 'hash mismatch' "$BUILD_LOG" | grep 'got:' | awk '{print $2}' | sed 's/sha256:/sha256-/' || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to hash from kept failed build directory
|
||||||
|
if [ -z "$CORRECT_HASH" ]; then
|
||||||
|
KEPT_DIR=$(grep -oE "build directory.*'[^']+'" "$BUILD_LOG" | grep -oE "'/[^']+'" | tr -d "'" | head -n1 || true)
|
||||||
|
if [ -z "$KEPT_DIR" ]; then
|
||||||
|
KEPT_DIR=$(grep -oE '/nix/var/nix/builds/[^ ]+' "$BUILD_LOG" | head -n1 || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$KEPT_DIR" ] && [ -d "$KEPT_DIR" ]; then
|
||||||
|
HASH_PATH="$KEPT_DIR"
|
||||||
|
[ -d "$KEPT_DIR/build" ] && HASH_PATH="$KEPT_DIR/build"
|
||||||
|
|
||||||
|
if [ -d "$HASH_PATH/node_modules" ]; then
|
||||||
|
CORRECT_HASH=$(nix hash path --sri "$HASH_PATH" 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$CORRECT_HASH" ]; then
|
||||||
|
echo "Failed to determine correct node_modules hash for ${SYSTEM}."
|
||||||
|
cat "$BUILD_LOG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$CORRECT_HASH" > "$OUTPUT_FILE"
|
||||||
|
echo "Hash for ${SYSTEM}: $CORRECT_HASH"
|
||||||
|
|
||||||
|
- name: Upload hash artifact
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: hash-${{ matrix.system }}
|
||||||
|
path: hash-${{ matrix.system }}.txt
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
commit-node-modules-hashes:
|
||||||
|
needs: compute-node-modules-hash
|
||||||
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
env:
|
||||||
|
TITLE: node_modules hashes
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ github.head_ref || github.ref_name }}
|
||||||
|
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||||
|
|
||||||
- name: Configure git
|
- name: Configure git
|
||||||
run: |
|
run: |
|
||||||
@@ -130,27 +234,71 @@ jobs:
|
|||||||
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
||||||
git pull origin "$BRANCH"
|
git pull --rebase --autostash origin "$BRANCH"
|
||||||
|
|
||||||
- name: Update node_modules hash for aarch64-darwin
|
- name: Download all hash artifacts
|
||||||
|
uses: actions/download-artifact@v7
|
||||||
|
with:
|
||||||
|
pattern: hash-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Merge hashes into hashes.json
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
echo "🔄 Updating node_modules hash for aarch64-darwin..."
|
|
||||||
nix/scripts/update-hashes.sh
|
|
||||||
echo "✅ node_modules hash for aarch64-darwin updated successfully"
|
|
||||||
|
|
||||||
- name: Commit macOS hash changes
|
HASH_FILE="nix/hashes.json"
|
||||||
|
|
||||||
|
if [ ! -f "$HASH_FILE" ]; then
|
||||||
|
mkdir -p "$(dirname "$HASH_FILE")"
|
||||||
|
echo '{"nodeModules":{}}' > "$HASH_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Merging hashes into ${HASH_FILE}..."
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
files=(hash-*.txt)
|
||||||
|
if [ ${#files[@]} -eq 0 ]; then
|
||||||
|
echo "No hash files found, nothing to update"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
EXPECTED_SYSTEMS="x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin"
|
||||||
|
for sys in $EXPECTED_SYSTEMS; do
|
||||||
|
if [ ! -f "hash-${sys}.txt" ]; then
|
||||||
|
echo "WARNING: Missing hash file for $sys"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for f in "${files[@]}"; do
|
||||||
|
system="${f#hash-}"
|
||||||
|
system="${system%.txt}"
|
||||||
|
hash=$(cat "$f")
|
||||||
|
if [ -z "$hash" ]; then
|
||||||
|
echo "WARNING: Empty hash for $system, skipping"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo " $system: $hash"
|
||||||
|
jq --arg sys "$system" --arg h "$hash" \
|
||||||
|
'.nodeModules = (.nodeModules // {}) | .nodeModules[$sys] = $h' "$HASH_FILE" > "${HASH_FILE}.tmp"
|
||||||
|
mv "${HASH_FILE}.tmp" "$HASH_FILE"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "All hashes merged:"
|
||||||
|
cat "$HASH_FILE"
|
||||||
|
|
||||||
|
- name: Commit ${{ env.TITLE }} changes
|
||||||
env:
|
env:
|
||||||
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo "🔍 Checking for changes in tracked Nix files..."
|
HASH_FILE="nix/hashes.json"
|
||||||
|
echo "Checking for changes..."
|
||||||
|
|
||||||
summarize() {
|
summarize() {
|
||||||
local status="$1"
|
local status="$1"
|
||||||
{
|
{
|
||||||
echo "### Nix Hash Update (aarch64-darwin)"
|
echo "### Nix $TITLE"
|
||||||
echo ""
|
echo ""
|
||||||
echo "- ref: ${GITHUB_REF_NAME}"
|
echo "- ref: ${GITHUB_REF_NAME}"
|
||||||
echo "- status: ${status}"
|
echo "- status: ${status}"
|
||||||
@@ -161,27 +309,22 @@ jobs:
|
|||||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||||
}
|
}
|
||||||
|
|
||||||
FILES=(nix/hashes.json)
|
FILES=("$HASH_FILE")
|
||||||
STATUS="$(git status --short -- "${FILES[@]}" || true)"
|
STATUS="$(git status --short -- "${FILES[@]}" || true)"
|
||||||
if [ -z "$STATUS" ]; then
|
if [ -z "$STATUS" ]; then
|
||||||
echo "✅ No changes detected. Hash is already up to date."
|
echo "No changes detected."
|
||||||
summarize "no changes"
|
summarize "no changes"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "📝 Changes detected:"
|
echo "Changes detected:"
|
||||||
echo "$STATUS"
|
echo "$STATUS"
|
||||||
echo "🔗 Staging files..."
|
|
||||||
git add "${FILES[@]}"
|
git add "${FILES[@]}"
|
||||||
echo "💾 Committing changes..."
|
git commit -m "Update $TITLE"
|
||||||
git commit -m "Update aarch64-darwin hash"
|
|
||||||
echo "✅ Changes committed"
|
|
||||||
|
|
||||||
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
||||||
echo "🌳 Pulling latest from branch: $BRANCH"
|
git pull --rebase --autostash origin "$BRANCH"
|
||||||
git pull --rebase origin "$BRANCH"
|
|
||||||
echo "🚀 Pushing changes to branch: $BRANCH"
|
|
||||||
git push origin HEAD:"$BRANCH"
|
git push origin HEAD:"$BRANCH"
|
||||||
echo "✅ Changes pushed successfully"
|
echo "Changes pushed successfully"
|
||||||
|
|
||||||
summarize "committed $(git rev-parse --short HEAD)"
|
summarize "committed $(git rev-parse --short HEAD)"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ opencode.json
|
|||||||
a.out
|
a.out
|
||||||
target
|
target
|
||||||
.scripts
|
.scripts
|
||||||
|
.direnv/
|
||||||
|
|
||||||
# Local dev files
|
# Local dev files
|
||||||
opencode-dev
|
opencode-dev
|
||||||
|
|||||||
@@ -1,203 +1,205 @@
|
|||||||
# Download Stats
|
# Download Stats
|
||||||
|
|
||||||
| Date | GitHub Downloads | npm Downloads | Total |
|
| Date | GitHub Downloads | npm Downloads | Total |
|
||||||
| ---------- | -------------------- | ------------------- | -------------------- |
|
| ---------- | -------------------- | -------------------- | -------------------- |
|
||||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
||||||
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
||||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||||
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||||
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||||
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
||||||
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
||||||
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
||||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||||
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
||||||
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
||||||
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
||||||
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
||||||
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
||||||
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
||||||
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
||||||
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
||||||
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
||||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
||||||
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
|
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
|
||||||
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
|
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
|
||||||
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
||||||
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
||||||
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
|
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
|
||||||
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
|
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
|
||||||
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
|
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
|
||||||
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
|
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
|
||||||
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
|
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
|
||||||
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
|
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
|
||||||
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
|
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
|
||||||
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
|
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
|
||||||
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
|
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
|
||||||
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
|
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
|
||||||
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
|
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
|
||||||
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
||||||
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
||||||
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
||||||
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
||||||
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
||||||
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
||||||
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
|
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
|
||||||
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
|
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
|
||||||
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
|
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
|
||||||
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
|
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
|
||||||
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
|
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
|
||||||
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
|
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
|
||||||
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
|
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
|
||||||
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
|
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
|
||||||
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
|
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
|
||||||
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
|
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
|
||||||
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
|
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
|
||||||
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
|
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
|
||||||
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
|
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
|
||||||
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
|
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
|
||||||
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
|
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
|
||||||
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
|
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
|
||||||
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
|
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
|
||||||
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
|
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
|
||||||
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
|
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
|
||||||
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
|
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
|
||||||
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
|
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
|
||||||
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
|
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
|
||||||
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
|
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
|
||||||
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
|
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
|
||||||
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
|
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
|
||||||
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
|
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
|
||||||
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
|
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
|
||||||
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
|
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
|
||||||
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
|
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
|
||||||
| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
|
| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
|
||||||
| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
|
| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
|
||||||
| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
|
| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
|
||||||
| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
|
| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
|
||||||
| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
|
| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
|
||||||
| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
|
| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
|
||||||
| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
|
| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
|
||||||
| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
|
| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
|
||||||
| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
|
| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
|
||||||
| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
|
| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
|
||||||
| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
|
| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
|
||||||
| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
|
| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
|
||||||
| 2025-10-02 | 440,852 (+7,261) | 348,099 (+6,357) | 788,951 (+13,618) |
|
| 2025-10-02 | 440,852 (+7,261) | 348,099 (+6,357) | 788,951 (+13,618) |
|
||||||
| 2025-10-03 | 446,829 (+5,977) | 359,937 (+11,838) | 806,766 (+17,815) |
|
| 2025-10-03 | 446,829 (+5,977) | 359,937 (+11,838) | 806,766 (+17,815) |
|
||||||
| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
|
| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
|
||||||
| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
|
| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
|
||||||
| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
|
| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
|
||||||
| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |
|
| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |
|
||||||
| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
|
| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
|
||||||
| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
|
| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
|
||||||
| 2025-10-10 | 484,374 (+5,171) | 406,015 (+5,489) | 890,389 (+10,660) |
|
| 2025-10-10 | 484,374 (+5,171) | 406,015 (+5,489) | 890,389 (+10,660) |
|
||||||
| 2025-10-11 | 488,427 (+4,053) | 414,699 (+8,684) | 903,126 (+12,737) |
|
| 2025-10-11 | 488,427 (+4,053) | 414,699 (+8,684) | 903,126 (+12,737) |
|
||||||
| 2025-10-12 | 492,125 (+3,698) | 418,745 (+4,046) | 910,870 (+7,744) |
|
| 2025-10-12 | 492,125 (+3,698) | 418,745 (+4,046) | 910,870 (+7,744) |
|
||||||
| 2025-10-14 | 505,130 (+13,005) | 429,286 (+10,541) | 934,416 (+23,546) |
|
| 2025-10-14 | 505,130 (+13,005) | 429,286 (+10,541) | 934,416 (+23,546) |
|
||||||
| 2025-10-15 | 512,717 (+7,587) | 439,290 (+10,004) | 952,007 (+17,591) |
|
| 2025-10-15 | 512,717 (+7,587) | 439,290 (+10,004) | 952,007 (+17,591) |
|
||||||
| 2025-10-16 | 517,719 (+5,002) | 447,137 (+7,847) | 964,856 (+12,849) |
|
| 2025-10-16 | 517,719 (+5,002) | 447,137 (+7,847) | 964,856 (+12,849) |
|
||||||
| 2025-10-17 | 526,239 (+8,520) | 457,467 (+10,330) | 983,706 (+18,850) |
|
| 2025-10-17 | 526,239 (+8,520) | 457,467 (+10,330) | 983,706 (+18,850) |
|
||||||
| 2025-10-18 | 531,564 (+5,325) | 465,272 (+7,805) | 996,836 (+13,130) |
|
| 2025-10-18 | 531,564 (+5,325) | 465,272 (+7,805) | 996,836 (+13,130) |
|
||||||
| 2025-10-19 | 536,209 (+4,645) | 469,078 (+3,806) | 1,005,287 (+8,451) |
|
| 2025-10-19 | 536,209 (+4,645) | 469,078 (+3,806) | 1,005,287 (+8,451) |
|
||||||
| 2025-10-20 | 541,264 (+5,055) | 472,952 (+3,874) | 1,014,216 (+8,929) |
|
| 2025-10-20 | 541,264 (+5,055) | 472,952 (+3,874) | 1,014,216 (+8,929) |
|
||||||
| 2025-10-21 | 548,721 (+7,457) | 479,703 (+6,751) | 1,028,424 (+14,208) |
|
| 2025-10-21 | 548,721 (+7,457) | 479,703 (+6,751) | 1,028,424 (+14,208) |
|
||||||
| 2025-10-22 | 557,949 (+9,228) | 491,395 (+11,692) | 1,049,344 (+20,920) |
|
| 2025-10-22 | 557,949 (+9,228) | 491,395 (+11,692) | 1,049,344 (+20,920) |
|
||||||
| 2025-10-23 | 564,716 (+6,767) | 498,736 (+7,341) | 1,063,452 (+14,108) |
|
| 2025-10-23 | 564,716 (+6,767) | 498,736 (+7,341) | 1,063,452 (+14,108) |
|
||||||
| 2025-10-24 | 572,692 (+7,976) | 506,905 (+8,169) | 1,079,597 (+16,145) |
|
| 2025-10-24 | 572,692 (+7,976) | 506,905 (+8,169) | 1,079,597 (+16,145) |
|
||||||
| 2025-10-25 | 578,927 (+6,235) | 516,129 (+9,224) | 1,095,056 (+15,459) |
|
| 2025-10-25 | 578,927 (+6,235) | 516,129 (+9,224) | 1,095,056 (+15,459) |
|
||||||
| 2025-10-26 | 584,409 (+5,482) | 521,179 (+5,050) | 1,105,588 (+10,532) |
|
| 2025-10-26 | 584,409 (+5,482) | 521,179 (+5,050) | 1,105,588 (+10,532) |
|
||||||
| 2025-10-27 | 589,999 (+5,590) | 526,001 (+4,822) | 1,116,000 (+10,412) |
|
| 2025-10-27 | 589,999 (+5,590) | 526,001 (+4,822) | 1,116,000 (+10,412) |
|
||||||
| 2025-10-28 | 595,776 (+5,777) | 532,438 (+6,437) | 1,128,214 (+12,214) |
|
| 2025-10-28 | 595,776 (+5,777) | 532,438 (+6,437) | 1,128,214 (+12,214) |
|
||||||
| 2025-10-29 | 606,259 (+10,483) | 542,064 (+9,626) | 1,148,323 (+20,109) |
|
| 2025-10-29 | 606,259 (+10,483) | 542,064 (+9,626) | 1,148,323 (+20,109) |
|
||||||
| 2025-10-30 | 613,746 (+7,487) | 542,064 (+0) | 1,155,810 (+7,487) |
|
| 2025-10-30 | 613,746 (+7,487) | 542,064 (+0) | 1,155,810 (+7,487) |
|
||||||
| 2025-10-30 | 617,846 (+4,100) | 555,026 (+12,962) | 1,172,872 (+17,062) |
|
| 2025-10-30 | 617,846 (+4,100) | 555,026 (+12,962) | 1,172,872 (+17,062) |
|
||||||
| 2025-10-31 | 626,612 (+8,766) | 564,579 (+9,553) | 1,191,191 (+18,319) |
|
| 2025-10-31 | 626,612 (+8,766) | 564,579 (+9,553) | 1,191,191 (+18,319) |
|
||||||
| 2025-11-01 | 636,100 (+9,488) | 581,806 (+17,227) | 1,217,906 (+26,715) |
|
| 2025-11-01 | 636,100 (+9,488) | 581,806 (+17,227) | 1,217,906 (+26,715) |
|
||||||
| 2025-11-02 | 644,067 (+7,967) | 590,004 (+8,198) | 1,234,071 (+16,165) |
|
| 2025-11-02 | 644,067 (+7,967) | 590,004 (+8,198) | 1,234,071 (+16,165) |
|
||||||
| 2025-11-03 | 653,130 (+9,063) | 597,139 (+7,135) | 1,250,269 (+16,198) |
|
| 2025-11-03 | 653,130 (+9,063) | 597,139 (+7,135) | 1,250,269 (+16,198) |
|
||||||
| 2025-11-04 | 663,912 (+10,782) | 608,056 (+10,917) | 1,271,968 (+21,699) |
|
| 2025-11-04 | 663,912 (+10,782) | 608,056 (+10,917) | 1,271,968 (+21,699) |
|
||||||
| 2025-11-05 | 675,074 (+11,162) | 619,690 (+11,634) | 1,294,764 (+22,796) |
|
| 2025-11-05 | 675,074 (+11,162) | 619,690 (+11,634) | 1,294,764 (+22,796) |
|
||||||
| 2025-11-06 | 686,252 (+11,178) | 630,885 (+11,195) | 1,317,137 (+22,373) |
|
| 2025-11-06 | 686,252 (+11,178) | 630,885 (+11,195) | 1,317,137 (+22,373) |
|
||||||
| 2025-11-07 | 696,646 (+10,394) | 642,146 (+11,261) | 1,338,792 (+21,655) |
|
| 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-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-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-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-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-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-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-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-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-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-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) |
|
| 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) |
|
||||||
| 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) |
|
| 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) |
|
||||||
| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) |
|
| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) |
|
||||||
| 2025-11-21 | 826,309 (+11,689) | 769,307 (+11,400) | 1,595,616 (+23,089) |
|
| 2025-11-21 | 826,309 (+11,689) | 769,307 (+11,400) | 1,595,616 (+23,089) |
|
||||||
| 2025-11-22 | 837,269 (+10,960) | 780,996 (+11,689) | 1,618,265 (+22,649) |
|
| 2025-11-22 | 837,269 (+10,960) | 780,996 (+11,689) | 1,618,265 (+22,649) |
|
||||||
| 2025-11-23 | 846,609 (+9,340) | 795,069 (+14,073) | 1,641,678 (+23,413) |
|
| 2025-11-23 | 846,609 (+9,340) | 795,069 (+14,073) | 1,641,678 (+23,413) |
|
||||||
| 2025-11-24 | 856,733 (+10,124) | 804,033 (+8,964) | 1,660,766 (+19,088) |
|
| 2025-11-24 | 856,733 (+10,124) | 804,033 (+8,964) | 1,660,766 (+19,088) |
|
||||||
| 2025-11-25 | 869,423 (+12,690) | 817,339 (+13,306) | 1,686,762 (+25,996) |
|
| 2025-11-25 | 869,423 (+12,690) | 817,339 (+13,306) | 1,686,762 (+25,996) |
|
||||||
| 2025-11-26 | 881,414 (+11,991) | 832,518 (+15,179) | 1,713,932 (+27,170) |
|
| 2025-11-26 | 881,414 (+11,991) | 832,518 (+15,179) | 1,713,932 (+27,170) |
|
||||||
| 2025-11-27 | 893,960 (+12,546) | 846,180 (+13,662) | 1,740,140 (+26,208) |
|
| 2025-11-27 | 893,960 (+12,546) | 846,180 (+13,662) | 1,740,140 (+26,208) |
|
||||||
| 2025-11-28 | 901,741 (+7,781) | 856,482 (+10,302) | 1,758,223 (+18,083) |
|
| 2025-11-28 | 901,741 (+7,781) | 856,482 (+10,302) | 1,758,223 (+18,083) |
|
||||||
| 2025-11-29 | 908,689 (+6,948) | 863,361 (+6,879) | 1,772,050 (+13,827) |
|
| 2025-11-29 | 908,689 (+6,948) | 863,361 (+6,879) | 1,772,050 (+13,827) |
|
||||||
| 2025-11-30 | 916,116 (+7,427) | 870,194 (+6,833) | 1,786,310 (+14,260) |
|
| 2025-11-30 | 916,116 (+7,427) | 870,194 (+6,833) | 1,786,310 (+14,260) |
|
||||||
| 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
|
| 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
|
||||||
| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
|
| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
|
||||||
| 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) |
|
| 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) |
|
||||||
| 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) |
|
| 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) |
|
||||||
| 2025-12-05 | 977,996 (+12,385) | 930,616 (+14,145) | 1,908,612 (+26,530) |
|
| 2025-12-05 | 977,996 (+12,385) | 930,616 (+14,145) | 1,908,612 (+26,530) |
|
||||||
| 2025-12-06 | 987,884 (+9,888) | 943,773 (+13,157) | 1,931,657 (+23,045) |
|
| 2025-12-06 | 987,884 (+9,888) | 943,773 (+13,157) | 1,931,657 (+23,045) |
|
||||||
| 2025-12-07 | 994,046 (+6,162) | 951,425 (+7,652) | 1,945,471 (+13,814) |
|
| 2025-12-07 | 994,046 (+6,162) | 951,425 (+7,652) | 1,945,471 (+13,814) |
|
||||||
| 2025-12-08 | 1,000,898 (+6,852) | 957,149 (+5,724) | 1,958,047 (+12,576) |
|
| 2025-12-08 | 1,000,898 (+6,852) | 957,149 (+5,724) | 1,958,047 (+12,576) |
|
||||||
| 2025-12-09 | 1,011,488 (+10,590) | 973,922 (+16,773) | 1,985,410 (+27,363) |
|
| 2025-12-09 | 1,011,488 (+10,590) | 973,922 (+16,773) | 1,985,410 (+27,363) |
|
||||||
| 2025-12-10 | 1,025,891 (+14,403) | 991,708 (+17,786) | 2,017,599 (+32,189) |
|
| 2025-12-10 | 1,025,891 (+14,403) | 991,708 (+17,786) | 2,017,599 (+32,189) |
|
||||||
| 2025-12-11 | 1,045,110 (+19,219) | 1,010,559 (+18,851) | 2,055,669 (+38,070) |
|
| 2025-12-11 | 1,045,110 (+19,219) | 1,010,559 (+18,851) | 2,055,669 (+38,070) |
|
||||||
| 2025-12-12 | 1,061,340 (+16,230) | 1,030,838 (+20,279) | 2,092,178 (+36,509) |
|
| 2025-12-12 | 1,061,340 (+16,230) | 1,030,838 (+20,279) | 2,092,178 (+36,509) |
|
||||||
| 2025-12-13 | 1,073,561 (+12,221) | 1,044,608 (+13,770) | 2,118,169 (+25,991) |
|
| 2025-12-13 | 1,073,561 (+12,221) | 1,044,608 (+13,770) | 2,118,169 (+25,991) |
|
||||||
| 2025-12-14 | 1,082,042 (+8,481) | 1,052,425 (+7,817) | 2,134,467 (+16,298) |
|
| 2025-12-14 | 1,082,042 (+8,481) | 1,052,425 (+7,817) | 2,134,467 (+16,298) |
|
||||||
| 2025-12-15 | 1,093,632 (+11,590) | 1,059,078 (+6,653) | 2,152,710 (+18,243) |
|
| 2025-12-15 | 1,093,632 (+11,590) | 1,059,078 (+6,653) | 2,152,710 (+18,243) |
|
||||||
| 2025-12-16 | 1,120,477 (+26,845) | 1,078,022 (+18,944) | 2,198,499 (+45,789) |
|
| 2025-12-16 | 1,120,477 (+26,845) | 1,078,022 (+18,944) | 2,198,499 (+45,789) |
|
||||||
| 2025-12-17 | 1,151,067 (+30,590) | 1,097,661 (+19,639) | 2,248,728 (+50,229) |
|
| 2025-12-17 | 1,151,067 (+30,590) | 1,097,661 (+19,639) | 2,248,728 (+50,229) |
|
||||||
| 2025-12-18 | 1,178,658 (+27,591) | 1,113,418 (+15,757) | 2,292,076 (+43,348) |
|
| 2025-12-18 | 1,178,658 (+27,591) | 1,113,418 (+15,757) | 2,292,076 (+43,348) |
|
||||||
| 2025-12-19 | 1,203,485 (+24,827) | 1,129,698 (+16,280) | 2,333,183 (+41,107) |
|
| 2025-12-19 | 1,203,485 (+24,827) | 1,129,698 (+16,280) | 2,333,183 (+41,107) |
|
||||||
| 2025-12-20 | 1,223,000 (+19,515) | 1,146,258 (+16,560) | 2,369,258 (+36,075) |
|
| 2025-12-20 | 1,223,000 (+19,515) | 1,146,258 (+16,560) | 2,369,258 (+36,075) |
|
||||||
| 2025-12-21 | 1,242,675 (+19,675) | 1,158,909 (+12,651) | 2,401,584 (+32,326) |
|
| 2025-12-21 | 1,242,675 (+19,675) | 1,158,909 (+12,651) | 2,401,584 (+32,326) |
|
||||||
| 2025-12-22 | 1,262,522 (+19,847) | 1,169,121 (+10,212) | 2,431,643 (+30,059) |
|
| 2025-12-22 | 1,262,522 (+19,847) | 1,169,121 (+10,212) | 2,431,643 (+30,059) |
|
||||||
| 2025-12-23 | 1,286,548 (+24,026) | 1,186,439 (+17,318) | 2,472,987 (+41,344) |
|
| 2025-12-23 | 1,286,548 (+24,026) | 1,186,439 (+17,318) | 2,472,987 (+41,344) |
|
||||||
| 2025-12-24 | 1,309,323 (+22,775) | 1,203,767 (+17,328) | 2,513,090 (+40,103) |
|
| 2025-12-24 | 1,309,323 (+22,775) | 1,203,767 (+17,328) | 2,513,090 (+40,103) |
|
||||||
| 2025-12-25 | 1,333,032 (+23,709) | 1,217,283 (+13,516) | 2,550,315 (+37,225) |
|
| 2025-12-25 | 1,333,032 (+23,709) | 1,217,283 (+13,516) | 2,550,315 (+37,225) |
|
||||||
| 2025-12-26 | 1,352,411 (+19,379) | 1,227,615 (+10,332) | 2,580,026 (+29,711) |
|
| 2025-12-26 | 1,352,411 (+19,379) | 1,227,615 (+10,332) | 2,580,026 (+29,711) |
|
||||||
| 2025-12-27 | 1,371,771 (+19,360) | 1,238,236 (+10,621) | 2,610,007 (+29,981) |
|
| 2025-12-27 | 1,371,771 (+19,360) | 1,238,236 (+10,621) | 2,610,007 (+29,981) |
|
||||||
| 2025-12-28 | 1,390,388 (+18,617) | 1,245,690 (+7,454) | 2,636,078 (+26,071) |
|
| 2025-12-28 | 1,390,388 (+18,617) | 1,245,690 (+7,454) | 2,636,078 (+26,071) |
|
||||||
| 2025-12-29 | 1,415,560 (+25,172) | 1,257,101 (+11,411) | 2,672,661 (+36,583) |
|
| 2025-12-29 | 1,415,560 (+25,172) | 1,257,101 (+11,411) | 2,672,661 (+36,583) |
|
||||||
| 2025-12-30 | 1,445,450 (+29,890) | 1,272,689 (+15,588) | 2,718,139 (+45,478) |
|
| 2025-12-30 | 1,445,450 (+29,890) | 1,272,689 (+15,588) | 2,718,139 (+45,478) |
|
||||||
| 2025-12-31 | 1,479,598 (+34,148) | 1,293,235 (+20,546) | 2,772,833 (+54,694) |
|
| 2025-12-31 | 1,479,598 (+34,148) | 1,293,235 (+20,546) | 2,772,833 (+54,694) |
|
||||||
| 2026-01-01 | 1,508,883 (+29,285) | 1,309,874 (+16,639) | 2,818,757 (+45,924) |
|
| 2026-01-01 | 1,508,883 (+29,285) | 1,309,874 (+16,639) | 2,818,757 (+45,924) |
|
||||||
| 2026-01-02 | 1,563,474 (+54,591) | 1,320,959 (+11,085) | 2,884,433 (+65,676) |
|
| 2026-01-02 | 1,563,474 (+54,591) | 1,320,959 (+11,085) | 2,884,433 (+65,676) |
|
||||||
| 2026-01-03 | 1,618,065 (+54,591) | 1,331,914 (+10,955) | 2,949,979 (+65,546) |
|
| 2026-01-03 | 1,618,065 (+54,591) | 1,331,914 (+10,955) | 2,949,979 (+65,546) |
|
||||||
| 2026-01-04 | 1,672,656 (+39,702) | 1,339,883 (+7,969) | 3,012,539 (+62,560) |
|
| 2026-01-04 | 1,672,656 (+39,702) | 1,339,883 (+7,969) | 3,012,539 (+62,560) |
|
||||||
| 2026-01-05 | 1,738,171 (+65,515) | 1,353,043 (+13,160) | 3,091,214 (+78,675) |
|
| 2026-01-05 | 1,738,171 (+65,515) | 1,353,043 (+13,160) | 3,091,214 (+78,675) |
|
||||||
| 2026-01-06 | 1,960,988 (+222,817) | 1,377,377 (+24,334) | 3,338,365 (+247,151) |
|
| 2026-01-06 | 1,960,988 (+222,817) | 1,377,377 (+24,334) | 3,338,365 (+247,151) |
|
||||||
| 2026-01-07 | 2,123,239 (+162,251) | 1,398,648 (+21,271) | 3,521,887 (+183,522) |
|
| 2026-01-07 | 2,123,239 (+162,251) | 1,398,648 (+21,271) | 3,521,887 (+183,522) |
|
||||||
| 2026-01-08 | 2,272,630 (+149,391) | 1,432,480 (+33,832) | 3,705,110 (+183,223) |
|
| 2026-01-08 | 2,272,630 (+149,391) | 1,432,480 (+33,832) | 3,705,110 (+183,223) |
|
||||||
| 2026-01-09 | 2,443,565 (+170,935) | 1,469,451 (+36,971) | 3,913,016 (+207,906) |
|
| 2026-01-09 | 2,443,565 (+170,935) | 1,469,451 (+36,971) | 3,913,016 (+207,906) |
|
||||||
| 2026-01-10 | 2,632,023 (+188,458) | 1,503,670 (+34,219) | 4,135,693 (+222,677) |
|
| 2026-01-10 | 2,632,023 (+188,458) | 1,503,670 (+34,219) | 4,135,693 (+222,677) |
|
||||||
| 2026-01-11 | 2,836,394 (+204,371) | 1,530,479 (+26,809) | 4,366,873 (+231,180) |
|
| 2026-01-11 | 2,836,394 (+204,371) | 1,530,479 (+26,809) | 4,366,873 (+231,180) |
|
||||||
| 2026-01-12 | 3,053,594 (+217,200) | 1,553,671 (+23,192) | 4,607,265 (+240,392) |
|
| 2026-01-12 | 3,053,594 (+217,200) | 1,553,671 (+23,192) | 4,607,265 (+240,392) |
|
||||||
| 2026-01-13 | 3,297,078 (+243,484) | 1,595,062 (+41,391) | 4,892,140 (+284,875) |
|
| 2026-01-13 | 3,297,078 (+243,484) | 1,595,062 (+41,391) | 4,892,140 (+284,875) |
|
||||||
| 2026-01-14 | 3,568,928 (+271,850) | 1,645,362 (+50,300) | 5,214,290 (+322,150) |
|
| 2026-01-14 | 3,568,928 (+271,850) | 1,645,362 (+50,300) | 5,214,290 (+322,150) |
|
||||||
|
| 2026-01-16 | 4,121,550 (+552,622) | 1,754,418 (+109,056) | 5,875,968 (+661,678) |
|
||||||
|
| 2026-01-17 | 4,389,558 (+268,008) | 1,805,315 (+50,897) | 6,194,873 (+318,905) |
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -81,6 +81,8 @@
|
|||||||
"@opencode-ai/console-mail": "workspace:*",
|
"@opencode-ai/console-mail": "workspace:*",
|
||||||
"@opencode-ai/console-resource": "workspace:*",
|
"@opencode-ai/console-resource": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
|
"@smithy/eventstream-codec": "4.2.7",
|
||||||
|
"@smithy/util-utf8": "4.2.0",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
"@solidjs/router": "catalog:",
|
"@solidjs/router": "catalog:",
|
||||||
"@solidjs/start": "catalog:",
|
"@solidjs/start": "catalog:",
|
||||||
@@ -95,13 +97,14 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
|
"@webgpu/types": "0.1.54",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"wrangler": "4.50.0",
|
"wrangler": "4.50.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -128,7 +131,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "2.0.0",
|
"@ai-sdk/anthropic": "2.0.0",
|
||||||
"@ai-sdk/openai": "2.0.2",
|
"@ai-sdk/openai": "2.0.2",
|
||||||
@@ -152,7 +155,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -176,7 +179,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -205,7 +208,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
@@ -234,7 +237,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -250,7 +253,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -290,8 +293,8 @@
|
|||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
"@openrouter/ai-sdk-provider": "1.5.2",
|
"@openrouter/ai-sdk-provider": "1.5.2",
|
||||||
"@opentui/core": "0.1.72",
|
"@opentui/core": "0.1.74",
|
||||||
"@opentui/solid": "0.1.72",
|
"@opentui/solid": "0.1.74",
|
||||||
"@parcel/watcher": "2.5.1",
|
"@parcel/watcher": "2.5.1",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
"@solid-primitives/event-bus": "1.1.2",
|
"@solid-primitives/event-bus": "1.1.2",
|
||||||
@@ -354,7 +357,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
@@ -374,9 +377,9 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.88.1",
|
"@hey-api/openapi-ts": "0.90.4",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
@@ -385,7 +388,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -398,7 +401,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -410,7 +413,7 @@
|
|||||||
"@solid-primitives/resize-observer": "2.1.3",
|
"@solid-primitives/resize-observer": "2.1.3",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
"dompurify": "catalog:",
|
"dompurify": "3.3.1",
|
||||||
"fuzzysort": "catalog:",
|
"fuzzysort": "catalog:",
|
||||||
"katex": "0.16.27",
|
"katex": "0.16.27",
|
||||||
"luxon": "catalog:",
|
"luxon": "catalog:",
|
||||||
@@ -421,6 +424,7 @@
|
|||||||
"shiki": "catalog:",
|
"shiki": "catalog:",
|
||||||
"solid-js": "catalog:",
|
"solid-js": "catalog:",
|
||||||
"solid-list": "catalog:",
|
"solid-list": "catalog:",
|
||||||
|
"strip-ansi": "7.1.2",
|
||||||
"virtua": "catalog:",
|
"virtua": "catalog:",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -438,7 +442,7 @@
|
|||||||
},
|
},
|
||||||
"packages/util": {
|
"packages/util": {
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -449,7 +453,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -505,7 +509,7 @@
|
|||||||
"@tailwindcss/vite": "4.1.11",
|
"@tailwindcss/vite": "4.1.11",
|
||||||
"@tsconfig/bun": "1.0.9",
|
"@tsconfig/bun": "1.0.9",
|
||||||
"@tsconfig/node22": "22.0.2",
|
"@tsconfig/node22": "22.0.2",
|
||||||
"@types/bun": "1.3.6",
|
"@types/bun": "1.3.5",
|
||||||
"@types/luxon": "3.7.1",
|
"@types/luxon": "3.7.1",
|
||||||
"@types/node": "22.13.9",
|
"@types/node": "22.13.9",
|
||||||
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
||||||
@@ -919,11 +923,11 @@
|
|||||||
|
|
||||||
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
|
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
|
||||||
|
|
||||||
"@hey-api/codegen-core": ["@hey-api/codegen-core@0.3.3", "", { "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-vArVDtrvdzFewu1hnjUm4jX1NBITlSCeO81EdWq676MxQbyxsGcDPAgohaSA+Wvr4HjPSvsg2/1s2zYxUtXebg=="],
|
"@hey-api/codegen-core": ["@hey-api/codegen-core@0.5.2", "", { "dependencies": { "ansi-colors": "4.1.3", "color-support": "1.1.3" }, "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-88cqrrB2cLXN8nMOHidQTcVOnZsJ5kebEbBefjMCifaUCwTA30ouSSWvTZqrOX4O104zjJyu7M8Gcv/NNYQuaA=="],
|
||||||
|
|
||||||
"@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.2.2", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.1", "lodash": "^4.17.21" } }, "sha512-oS+5yAdwnK20lSeFO1d53Ku+yaGCsY8PcrmSq2GtSs3bsBfRnHAbpPKSVzQcaxAOrzj5NB+f34WhZglVrNayBA=="],
|
"@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.2.2", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.1", "lodash": "^4.17.21" } }, "sha512-oS+5yAdwnK20lSeFO1d53Ku+yaGCsY8PcrmSq2GtSs3bsBfRnHAbpPKSVzQcaxAOrzj5NB+f34WhZglVrNayBA=="],
|
||||||
|
|
||||||
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.88.1", "", { "dependencies": { "@hey-api/codegen-core": "^0.3.3", "@hey-api/json-schema-ref-parser": "1.2.2", "ansi-colors": "4.1.3", "c12": "3.3.2", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.2" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-x/nDTupOnV9VuSeNIiJpgIpc915GHduhyseJeMTnI0JMsXaObmpa0rgPr3ASVEYMLgpvqozIEG1RTOOnal6zLQ=="],
|
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.90.4", "", { "dependencies": { "@hey-api/codegen-core": "^0.5.2", "@hey-api/json-schema-ref-parser": "1.2.2", "ansi-colors": "4.1.3", "c12": "3.3.3", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.3" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-9l++kjcb0ui4JqPlueZ6OZ9zKn6eK/8//Z2jHcIXb5MRwDRgubOOSpTU5llEv3uvWfT10VzcMp99dySWq0AASw=="],
|
||||||
|
|
||||||
"@hono/node-server": ["@hono/node-server@1.19.7", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw=="],
|
"@hono/node-server": ["@hono/node-server@1.19.7", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw=="],
|
||||||
|
|
||||||
@@ -1215,21 +1219,21 @@
|
|||||||
|
|
||||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||||
|
|
||||||
"@opentui/core": ["@opentui/core@0.1.72", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.72", "@opentui/core-darwin-x64": "0.1.72", "@opentui/core-linux-arm64": "0.1.72", "@opentui/core-linux-x64": "0.1.72", "@opentui/core-win32-arm64": "0.1.72", "@opentui/core-win32-x64": "0.1.72", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-l4WQzubBJ80Q0n77Lxuodjwwm8qj/sOa7IXxEAzzDDXY/7bsIhdSpVhRTt+KevBRlok5J+w/KMKYr8UzkA4/hA=="],
|
"@opentui/core": ["@opentui/core@0.1.74", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.74", "@opentui/core-darwin-x64": "0.1.74", "@opentui/core-linux-arm64": "0.1.74", "@opentui/core-linux-x64": "0.1.74", "@opentui/core-win32-arm64": "0.1.74", "@opentui/core-win32-x64": "0.1.74", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-g4W16ymv12JdgZ+9B4t7mpIICvzWy2+eHERfmDf80ALduOQCUedKQdULcBFhVCYUXIkDRtIy6CID5thMAah3FA=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.72", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RoU48kOrhLZYDBiXaDu1LXS2bwRdlJlFle8eUQiqJjLRbMIY34J/srBuL0JnAS3qKW4J34NepUQa0l0/S43Q3w=="],
|
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.74", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rfmlDLtm/u17CnuhJgCxPeYMvOST+A2MOdVOk46IurtHO849bdYqK6iudKNlFRs1FOrymgSKF9GlWBHAOKeRjg=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.72", "", { "os": "darwin", "cpu": "x64" }, "sha512-hHUQw8i2LWPToRW1rjAiRqmNf34iJPS9ve9CJDygvFs5JOqUxN5yrfLfKfE+1bQjfFDHnpqW1HUk96iLhkPj8Q=="],
|
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.74", "", { "os": "darwin", "cpu": "x64" }, "sha512-WAD8orsDV0ZdW/5GwjOOB4FY96772xbkz+rcV7WRzEFUVaqoBaC04IuqYzS9d5s+cjkbT5Cpj47hrVYkkVQKng=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.72", "", { "os": "linux", "cpu": "arm64" }, "sha512-63yml0OQ8tVa0JuDF9lBAWiChX6Q+iDO7lKv7c2n0352n/WyPr3iAgq4uSoH49HXuKeAXY/VwHGjvPzjXD/SDA=="],
|
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.74", "", { "os": "linux", "cpu": "arm64" }, "sha512-lgmHzrzLy4e+rgBS+lhtsMLLgIMLbtLNMm6EzVPyYVDlLDGjM7+ulXMem7AtpaRrWrUUl4REiG9BoQUsCFDwYA=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.72", "", { "os": "linux", "cpu": "x64" }, "sha512-51veiQXNLvzDsFzsEvt71uK7WhiRe2DnvlJSGBSe6aRRHHxjCFYHzYi7t6bitJqtDTUj+EaMPbH81oZ6xy7tyg=="],
|
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.74", "", { "os": "linux", "cpu": "x64" }, "sha512-8Mn2WbdBQ29xCThuPZezjDhd1N3+fXwKkGvCBOdTI0le6h2A/vCNbfUVjwfr/EGZSRXxCG+Yapol34BAULGpOA=="],
|
||||||
|
|
||||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.72", "", { "os": "win32", "cpu": "arm64" }, "sha512-1Ep6OcaYTy1RlLOln+LNN7DL1iNyLwLjG2M8aO0pVJKFvxeD5P7rdRzY065E4uhkHeJIHuduUqxvUjD0dyuwbw=="],
|
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.74", "", { "os": "win32", "cpu": "arm64" }, "sha512-dvYUXz03avnI6ZluyLp00HPmR0UT/IE/6QS97XBsgJlUTtpnbKkBtB5jD1NHwWkElaRj1Qv2QP36ngFoJqbl9g=="],
|
||||||
|
|
||||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.72", "", { "os": "win32", "cpu": "x64" }, "sha512-5QUv91UkOINlkEaPky3kaxmJvshcJMBAX7LZtIroduaKBGpWRA1aogNhPZzp+30WkvgOU7aOtUktAZuFXb9WdQ=="],
|
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.74", "", { "os": "win32", "cpu": "x64" }, "sha512-3wfWXaAKOIlDQz6ZZIESf2M+YGZ7uFHijjTEM8w/STRlLw8Y6+QyGYi1myHSM4d6RSO+/s2EMDxvjDf899W9vQ=="],
|
||||||
|
|
||||||
"@opentui/solid": ["@opentui/solid@0.1.72", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.72", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-hytoLPboL/MTY/BQUnf/HlBuNXTVONney0X+PIQI82wT7kMx7+HHI2wnowpM3dyvA7l6NfORSud2cs9kIUBFBw=="],
|
"@opentui/solid": ["@opentui/solid@0.1.74", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.74", "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-Vz82cI8T9YeJjGsVg4ULp6ral4N+xyt1j9A6Tbu3aaQgEKiB74LW03EXREehfjPr1irOFxtKfWPbx5NKH0Upag=="],
|
||||||
|
|
||||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||||
|
|
||||||
@@ -1521,7 +1525,7 @@
|
|||||||
|
|
||||||
"@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/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.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/eventstream-codec": ["@smithy/eventstream-codec@4.2.7", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ=="],
|
||||||
|
|
||||||
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.5", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw=="],
|
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.5", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw=="],
|
||||||
|
|
||||||
@@ -1773,7 +1777,7 @@
|
|||||||
|
|
||||||
"@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="],
|
"@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
||||||
|
|
||||||
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
||||||
|
|
||||||
@@ -1903,7 +1907,7 @@
|
|||||||
|
|
||||||
"@vitest/utils": ["@vitest/utils@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="],
|
"@vitest/utils": ["@vitest/utils@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="],
|
||||||
|
|
||||||
"@webgpu/types": ["@webgpu/types@0.1.66", "", {}, "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA=="],
|
"@webgpu/types": ["@webgpu/types@0.1.54", "", {}, "sha512-81oaalC8LFrXjhsczomEQ0u3jG+TqE6V9QHLA8GNZq/Rnot0KDugu3LhSYSlie8tSdooAN1Hov05asrUUp9qgg=="],
|
||||||
|
|
||||||
"@zip.js/zip.js": ["@zip.js/zip.js@2.7.62", "", {}, "sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA=="],
|
"@zip.js/zip.js": ["@zip.js/zip.js@2.7.62", "", {}, "sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA=="],
|
||||||
|
|
||||||
@@ -2075,7 +2079,7 @@
|
|||||||
|
|
||||||
"bun-pty": ["bun-pty@0.4.4", "", {}, "sha512-WK4G6uWsZgu1v4hKIlw6G1q2AOf8Rbga2Yr7RnxArVjjyb+mtVa/CFc9GOJf+OYSJSH8k7LonAtQOVeNAddRyg=="],
|
"bun-pty": ["bun-pty@0.4.4", "", {}, "sha512-WK4G6uWsZgu1v4hKIlw6G1q2AOf8Rbga2Yr7RnxArVjjyb+mtVa/CFc9GOJf+OYSJSH8k7LonAtQOVeNAddRyg=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
||||||
|
|
||||||
"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": ["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=="],
|
||||||
|
|
||||||
@@ -2091,7 +2095,7 @@
|
|||||||
|
|
||||||
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||||
|
|
||||||
"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=="],
|
"c12": ["c12@3.3.3", "", { "dependencies": { "chokidar": "^5.0.0", "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-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q=="],
|
||||||
|
|
||||||
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
||||||
|
|
||||||
@@ -3491,7 +3495,7 @@
|
|||||||
|
|
||||||
"selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
|
"selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
|
||||||
|
|
||||||
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||||
|
|
||||||
"send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
|
"send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
|
||||||
|
|
||||||
@@ -3965,6 +3969,8 @@
|
|||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.57", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.57", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
|
||||||
|
|
||||||
|
"@ai-sdk/amazon-bedrock/@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=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
"@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
||||||
@@ -4235,6 +4241,10 @@
|
|||||||
|
|
||||||
"@slack/web-api/p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
|
"@slack/web-api/p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
|
||||||
|
|
||||||
|
"@smithy/eventstream-codec/@smithy/types": ["@smithy/types@4.11.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA=="],
|
||||||
|
|
||||||
|
"@smithy/eventstream-serde-universal/@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=="],
|
||||||
|
|
||||||
"@solidjs/start/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
"@solidjs/start/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
||||||
|
|
||||||
"@solidjs/start/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
"@solidjs/start/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
||||||
@@ -4271,6 +4281,8 @@
|
|||||||
|
|
||||||
"astro/diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="],
|
"astro/diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="],
|
||||||
|
|
||||||
|
"astro/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||||
|
|
||||||
"astro/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
|
"astro/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
|
||||||
|
|
||||||
"astro/unstorage": ["unstorage@1.17.3", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q=="],
|
"astro/unstorage": ["unstorage@1.17.3", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q=="],
|
||||||
@@ -4291,6 +4303,10 @@
|
|||||||
|
|
||||||
"body-parser/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
|
"body-parser/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
|
||||||
|
|
||||||
|
"bun-webgpu/@webgpu/types": ["@webgpu/types@0.1.66", "", {}, "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA=="],
|
||||||
|
|
||||||
|
"c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||||
|
|
||||||
"clean-css/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
"clean-css/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||||
|
|
||||||
"compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
"compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||||
@@ -4307,6 +4323,8 @@
|
|||||||
|
|
||||||
"editorconfig/minimatch": ["minimatch@9.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="],
|
"editorconfig/minimatch": ["minimatch@9.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="],
|
||||||
|
|
||||||
|
"editorconfig/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||||
|
|
||||||
"engine.io-client/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
"engine.io-client/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
||||||
|
|
||||||
"es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
"es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||||
@@ -4333,6 +4351,8 @@
|
|||||||
|
|
||||||
"gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
"gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||||
|
|
||||||
|
"gel/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||||
|
|
||||||
"glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
|
"glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
|
||||||
|
|
||||||
"globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
"globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||||
@@ -4347,6 +4367,8 @@
|
|||||||
|
|
||||||
"jsonwebtoken/jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
|
"jsonwebtoken/jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
|
||||||
|
|
||||||
|
"jsonwebtoken/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||||
|
|
||||||
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
|
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
|
||||||
|
|
||||||
"lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
"lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||||
@@ -4431,6 +4453,8 @@
|
|||||||
|
|
||||||
"sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
"sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
|
|
||||||
|
"sharp/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||||
|
|
||||||
"shiki/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
|
"shiki/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
|
||||||
|
|
||||||
"shiki/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
"shiki/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
||||||
@@ -4909,6 +4933,8 @@
|
|||||||
|
|
||||||
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||||
|
|
||||||
|
"c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||||
|
|
||||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||||
|
|
||||||
"drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
|
"drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
|
||||||
|
|||||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1768302833,
|
"lastModified": 1768456270,
|
||||||
"narHash": "sha256-h5bRFy9bco+8QcK7rGoOiqMxMbmn21moTACofNLRMP4=",
|
"narHash": "sha256-NgaL2CCiUR6nsqUIY4yxkzz07iQUlUCany44CFv+OxY=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "61db79b0c6b838d9894923920b612048e1201926",
|
"rev": "f4606b01b39e09065df37905a2133905246db9ed",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
{
|
{
|
||||||
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
@@ -107,33 +108,10 @@
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
default = opencodePkg;
|
default = self.packages.${system}.opencode;
|
||||||
|
opencode = opencodePkg;
|
||||||
desktop = desktopPkg;
|
desktop = desktopPkg;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
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";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ const ZEN_MODELS = [
|
|||||||
new sst.Secret("ZEN_MODELS5"),
|
new sst.Secret("ZEN_MODELS5"),
|
||||||
new sst.Secret("ZEN_MODELS6"),
|
new sst.Secret("ZEN_MODELS6"),
|
||||||
new sst.Secret("ZEN_MODELS7"),
|
new sst.Secret("ZEN_MODELS7"),
|
||||||
|
new sst.Secret("ZEN_MODELS8"),
|
||||||
]
|
]
|
||||||
const ZEN_BLACK = new sst.Secret("ZEN_BLACK")
|
const ZEN_BLACK = new sst.Secret("ZEN_BLACK")
|
||||||
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
|
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
cargo,
|
cargo,
|
||||||
rustc,
|
rustc,
|
||||||
makeBinaryWrapper,
|
makeBinaryWrapper,
|
||||||
|
copyDesktopItems,
|
||||||
|
makeDesktopItem,
|
||||||
nodejs,
|
nodejs,
|
||||||
jq,
|
jq,
|
||||||
}:
|
}:
|
||||||
@@ -57,12 +59,28 @@ rustPlatform.buildRustPackage rec {
|
|||||||
pkg-config
|
pkg-config
|
||||||
bun
|
bun
|
||||||
makeBinaryWrapper
|
makeBinaryWrapper
|
||||||
|
copyDesktopItems
|
||||||
cargo
|
cargo
|
||||||
rustc
|
rustc
|
||||||
nodejs
|
nodejs
|
||||||
jq
|
jq
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# based on packages/desktop/src-tauri/release/appstream.metainfo.xml
|
||||||
|
desktopItems = lib.optionals stdenv.isLinux [
|
||||||
|
(makeDesktopItem {
|
||||||
|
name = "ai.opencode.opencode";
|
||||||
|
desktopName = "OpenCode";
|
||||||
|
comment = "Open source AI coding agent";
|
||||||
|
exec = "opencode-desktop";
|
||||||
|
icon = "opencode";
|
||||||
|
terminal = false;
|
||||||
|
type = "Application";
|
||||||
|
categories = [ "Development" "IDE" ];
|
||||||
|
startupWMClass = "opencode";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
openssl
|
openssl
|
||||||
]
|
]
|
||||||
@@ -121,6 +139,10 @@ rustPlatform.buildRustPackage rec {
|
|||||||
# It looks for them in the location specified in tauri.conf.json.
|
# It looks for them in the location specified in tauri.conf.json.
|
||||||
|
|
||||||
postInstall = lib.optionalString stdenv.isLinux ''
|
postInstall = lib.optionalString stdenv.isLinux ''
|
||||||
|
# Install icon
|
||||||
|
mkdir -p $out/share/icons/hicolor/128x128/apps
|
||||||
|
cp ../../../packages/desktop/src-tauri/icons/prod/128x128.png $out/share/icons/hicolor/128x128/apps/opencode.png
|
||||||
|
|
||||||
# Wrap the binary to ensure it finds the libraries
|
# Wrap the binary to ensure it finds the libraries
|
||||||
wrapProgram $out/bin/opencode-desktop \
|
wrapProgram $out/bin/opencode-desktop \
|
||||||
--prefix LD_LIBRARY_PATH : ${
|
--prefix LD_LIBRARY_PATH : ${
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-GKdu7nan/9ioBtgL3cUeuVLNKUDio10LeQrn7BPgbng=",
|
"x86_64-linux": "sha256-4zchRpxzvHnPMcwumgL9yaX0deIXS5IGPp131eYsSvg=",
|
||||||
"aarch64-darwin": "sha256-STLB1J65VjauvPM+BqCyTQQkHPoVmUhDvVEdH3WTJP4="
|
"aarch64-linux": "sha256-3/BSRsl5pI0Iz3qAFZxIkOehFLZ2Ox9UsbdDHYzqlVg=",
|
||||||
|
"aarch64-darwin": "sha256-86d/G1q6xiHSSlm+/irXoKLb/yLQbV348uuSrBV70+Q=",
|
||||||
|
"x86_64-darwin": "sha256-WYaP44PWRGtoG1DIuUJUH4DvuaCuFhlJZ9fPzGsiIfE="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
#!/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": {}
|
|
||||||
}
|
|
||||||
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 system="${2:-$SYSTEM}"
|
|
||||||
local temp
|
|
||||||
temp=$(mktemp)
|
|
||||||
|
|
||||||
if jq -e '.nodeModules | type == "object"' "$HASH_FILE" >/dev/null 2>&1; then
|
|
||||||
jq --arg system "$system" --arg value "$value" '.nodeModules[$system] = $value' "$HASH_FILE" >"$temp"
|
|
||||||
else
|
|
||||||
jq --arg system "$system" --arg value "$value" '.nodeModules = {($system): $value}' "$HASH_FILE" >"$temp"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv "$temp" "$HASH_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
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 system "$SYSTEM" --arg hash "$CORRECT_HASH" '.nodeModules[$system] == $hash' "$HASH_FILE" >/dev/null
|
|
||||||
|
|
||||||
echo "node_modules hash updated for ${SYSTEM}: $CORRECT_HASH"
|
|
||||||
|
|
||||||
rm -f "$BUILD_LOG"
|
|
||||||
unset BUILD_LOG
|
|
||||||
+2
-2
@@ -4,7 +4,7 @@
|
|||||||
"description": "AI-powered development tool",
|
"description": "AI-powered development tool",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "bun@1.3.6",
|
"packageManager": "bun@1.3.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
||||||
"typecheck": "bun turbo typecheck",
|
"typecheck": "bun turbo typecheck",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"packages/slack"
|
"packages/slack"
|
||||||
],
|
],
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"@types/bun": "1.3.6",
|
"@types/bun": "1.3.5",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"ulid": "3.0.1",
|
"ulid": "3.0.1",
|
||||||
|
|||||||
@@ -13,12 +13,11 @@
|
|||||||
<meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
|
<meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
|
||||||
<meta property="og:image" content="/social-share.png" />
|
<meta property="og:image" content="/social-share.png" />
|
||||||
<meta property="twitter:image" content="/social-share.png" />
|
<meta property="twitter:image" content="/social-share.png" />
|
||||||
<!-- Theme preload script - applies cached theme to avoid FOUC -->
|
|
||||||
<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
|
<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
|
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root" class="flex flex-col h-dvh"></div>
|
<div id="root" class="flex flex-col h-dvh p-px"></div>
|
||||||
<script src="/src/entry.tsx" type="module"></script>
|
<script src="/src/entry.tsx" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
|
import { Keybind } from "@opencode-ai/ui/keybind"
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
import { createMemo } from "solid-js"
|
import { createMemo, createSignal, onCleanup, Show } from "solid-js"
|
||||||
|
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { useFile } from "@/context/file"
|
import { useFile } from "@/context/file"
|
||||||
|
|
||||||
|
type EntryType = "command" | "file"
|
||||||
|
|
||||||
|
type Entry = {
|
||||||
|
id: string
|
||||||
|
type: EntryType
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
keybind?: string
|
||||||
|
category: "Commands" | "Files"
|
||||||
|
option?: CommandOption
|
||||||
|
path?: string
|
||||||
|
}
|
||||||
|
|
||||||
export function DialogSelectFile() {
|
export function DialogSelectFile() {
|
||||||
|
const command = useCommand()
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
const file = useFile()
|
const file = useFile()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
@@ -16,35 +32,148 @@ export function DialogSelectFile() {
|
|||||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||||
const tabs = createMemo(() => layout.tabs(sessionKey()))
|
const tabs = createMemo(() => layout.tabs(sessionKey()))
|
||||||
const view = createMemo(() => layout.view(sessionKey()))
|
const view = createMemo(() => layout.view(sessionKey()))
|
||||||
|
const state = { cleanup: undefined as (() => void) | void, committed: false }
|
||||||
|
const [grouped, setGrouped] = createSignal(false)
|
||||||
|
const common = ["session.new", "session.previous", "session.next", "terminal.toggle", "review.toggle"]
|
||||||
|
const limit = 5
|
||||||
|
|
||||||
|
const allowed = createMemo(() =>
|
||||||
|
command.options.filter(
|
||||||
|
(option) => !option.disabled && !option.id.startsWith("suggested.") && option.id !== "file.open",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const commandItem = (option: CommandOption): Entry => ({
|
||||||
|
id: "command:" + option.id,
|
||||||
|
type: "command",
|
||||||
|
title: option.title,
|
||||||
|
description: option.description,
|
||||||
|
keybind: option.keybind,
|
||||||
|
category: "Commands",
|
||||||
|
option,
|
||||||
|
})
|
||||||
|
|
||||||
|
const fileItem = (path: string): Entry => ({
|
||||||
|
id: "file:" + path,
|
||||||
|
type: "file",
|
||||||
|
title: path,
|
||||||
|
category: "Files",
|
||||||
|
path,
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = createMemo(() => allowed().map(commandItem))
|
||||||
|
|
||||||
|
const picks = createMemo(() => {
|
||||||
|
const all = allowed()
|
||||||
|
const order = new Map(common.map((id, index) => [id, index]))
|
||||||
|
const picked = all.filter((option) => order.has(option.id))
|
||||||
|
const base = picked.length ? picked : all.slice(0, limit)
|
||||||
|
const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
|
||||||
|
return sorted.map(commandItem)
|
||||||
|
})
|
||||||
|
|
||||||
|
const recent = createMemo(() => {
|
||||||
|
const all = tabs().all()
|
||||||
|
const active = tabs().active()
|
||||||
|
const order = active ? [active, ...all.filter((item) => item !== active)] : all
|
||||||
|
const seen = new Set<string>()
|
||||||
|
const items: Entry[] = []
|
||||||
|
|
||||||
|
for (const item of order) {
|
||||||
|
const path = file.pathFromTab(item)
|
||||||
|
if (!path) continue
|
||||||
|
if (seen.has(path)) continue
|
||||||
|
seen.add(path)
|
||||||
|
items.push(fileItem(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.slice(0, limit)
|
||||||
|
})
|
||||||
|
|
||||||
|
const items = async (filter: string) => {
|
||||||
|
const query = filter.trim()
|
||||||
|
setGrouped(query.length > 0)
|
||||||
|
if (!query) return [...picks(), ...recent()]
|
||||||
|
const files = await file.searchFiles(query)
|
||||||
|
const entries = files.map(fileItem)
|
||||||
|
return [...list(), ...entries]
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMove = (item: Entry | undefined) => {
|
||||||
|
state.cleanup?.()
|
||||||
|
if (!item) return
|
||||||
|
if (item.type !== "command") return
|
||||||
|
state.cleanup = item.option?.onHighlight?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = (path: string) => {
|
||||||
|
const value = file.tab(path)
|
||||||
|
tabs().open(value)
|
||||||
|
file.load(path)
|
||||||
|
view().reviewPanel.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelect = (item: Entry | undefined) => {
|
||||||
|
if (!item) return
|
||||||
|
state.committed = true
|
||||||
|
state.cleanup = undefined
|
||||||
|
dialog.close()
|
||||||
|
|
||||||
|
if (item.type === "command") {
|
||||||
|
item.option?.onSelect?.("palette")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.path) return
|
||||||
|
open(item.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
if (state.committed) return
|
||||||
|
state.cleanup?.()
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog title="Select file">
|
<Dialog class="pt-3 pb-0 !max-h-[480px]">
|
||||||
<List
|
<List
|
||||||
search={{ placeholder: "Search files", autofocus: true }}
|
search={{ placeholder: "Search files and commands", autofocus: true, hideIcon: true, class: "pl-3 pr-2 !mb-0" }}
|
||||||
emptyMessage="No files found"
|
emptyMessage="No results found"
|
||||||
items={file.searchFiles}
|
items={items}
|
||||||
key={(x) => x}
|
key={(item) => item.id}
|
||||||
onSelect={(path) => {
|
filterKeys={["title", "description", "category"]}
|
||||||
if (path) {
|
groupBy={(item) => item.category}
|
||||||
const value = file.tab(path)
|
onMove={handleMove}
|
||||||
tabs().open(value)
|
onSelect={handleSelect}
|
||||||
file.load(path)
|
|
||||||
view().reviewPanel.open()
|
|
||||||
}
|
|
||||||
dialog.close()
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{(i) => (
|
{(item) => (
|
||||||
<div class="w-full flex items-center justify-between rounded-md">
|
<Show
|
||||||
<div class="flex items-center gap-x-3 grow min-w-0">
|
when={item.type === "command"}
|
||||||
<FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
|
fallback={
|
||||||
<div class="flex items-center text-14-regular">
|
<div class="w-full flex items-center justify-between rounded-md">
|
||||||
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
|
<div class="flex items-center gap-x-3 grow min-w-0">
|
||||||
{getDirectory(i)}
|
<FileIcon node={{ path: item.path ?? "", type: "file" }} class="shrink-0 size-4" />
|
||||||
</span>
|
<div class="flex items-center text-14-regular">
|
||||||
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
|
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
|
||||||
|
{getDirectory(item.path ?? "")}
|
||||||
|
</span>
|
||||||
|
<span class="text-text-strong whitespace-nowrap">{getFilename(item.path ?? "")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div class="w-full flex items-center justify-between gap-4 pl-1">
|
||||||
|
<div class="flex items-center gap-2 min-w-0">
|
||||||
|
<span class="text-14-regular text-text-strong whitespace-nowrap">{item.title}</span>
|
||||||
|
<Show when={item.description}>
|
||||||
|
<span class="text-14-regular text-text-weak truncate">{item.description}</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
<Show when={item.keybind}>
|
||||||
|
<Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "")}</Keybind>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Show>
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -1,267 +1,203 @@
|
|||||||
import { createMemo, createResource, Show } from "solid-js"
|
import { createMemo, createResource, Show } from "solid-js"
|
||||||
import { A, useNavigate, useParams } from "@solidjs/router"
|
import { Portal } from "solid-js/web"
|
||||||
|
import { useParams } from "@solidjs/router"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { useCommand } from "@/context/command"
|
import { useCommand } from "@/context/command"
|
||||||
import { useServer } from "@/context/server"
|
// import { useServer } from "@/context/server"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
// import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useGlobalSDK } from "@/context/global-sdk"
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
|
import { base64Decode } from "@opencode-ai/util/encode"
|
||||||
import { iife } from "@opencode-ai/util/iife"
|
import { iife } from "@opencode-ai/util/iife"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
import { Select } from "@opencode-ai/ui/select"
|
|
||||||
import { Popover } from "@opencode-ai/ui/popover"
|
import { Popover } from "@opencode-ai/ui/popover"
|
||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
import { DialogSelectServer } from "@/components/dialog-select-server"
|
import { Keybind } from "@opencode-ai/ui/keybind"
|
||||||
import { SessionLspIndicator } from "@/components/session-lsp-indicator"
|
|
||||||
import { SessionMcpIndicator } from "@/components/session-mcp-indicator"
|
|
||||||
import type { Session } from "@opencode-ai/sdk/v2/client"
|
|
||||||
import { same } from "@/utils/same"
|
|
||||||
|
|
||||||
export function SessionHeader() {
|
export function SessionHeader() {
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const navigate = useNavigate()
|
|
||||||
const command = useCommand()
|
const command = useCommand()
|
||||||
const server = useServer()
|
// const server = useServer()
|
||||||
const dialog = useDialog()
|
// const dialog = useDialog()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
|
|
||||||
const projectDirectory = createMemo(() => base64Decode(params.dir ?? ""))
|
const projectDirectory = createMemo(() => base64Decode(params.dir ?? ""))
|
||||||
|
const project = createMemo(() => {
|
||||||
const sessions = createMemo(() => (sync.data.session ?? []).filter((s) => !s.parentID))
|
const directory = projectDirectory()
|
||||||
const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id))
|
if (!directory) return
|
||||||
const parentSession = createMemo(() => {
|
return layout.projects.list().find((p) => p.worktree === directory || p.sandboxes?.includes(directory))
|
||||||
const current = currentSession()
|
|
||||||
if (!current?.parentID) return undefined
|
|
||||||
return sync.data.session.find((s) => s.id === current.parentID)
|
|
||||||
})
|
})
|
||||||
|
const name = createMemo(() => {
|
||||||
|
const current = project()
|
||||||
|
if (current) return current.name || getFilename(current.worktree)
|
||||||
|
return getFilename(projectDirectory())
|
||||||
|
})
|
||||||
|
const hotkey = createMemo(() => command.keybind("file.open"))
|
||||||
|
|
||||||
|
const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id))
|
||||||
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
|
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
|
||||||
const worktrees = createMemo(() => layout.projects.list().map((p) => p.worktree), [], { equals: same })
|
|
||||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||||
const view = createMemo(() => layout.view(sessionKey()))
|
const view = createMemo(() => layout.view(sessionKey()))
|
||||||
|
|
||||||
function navigateToProject(directory: string) {
|
const centerMount = createMemo(() => document.getElementById("opencode-titlebar-center"))
|
||||||
navigate(`/${base64Encode(directory)}`)
|
const rightMount = createMemo(() => document.getElementById("opencode-titlebar-right"))
|
||||||
}
|
|
||||||
|
|
||||||
function navigateToSession(session: Session | undefined) {
|
|
||||||
if (!session) return
|
|
||||||
// Only navigate if we're actually changing to a different session
|
|
||||||
if (session.id === params.id) return
|
|
||||||
navigate(`/${params.dir}/session/${session.id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex">
|
<>
|
||||||
<button
|
<Show when={centerMount()}>
|
||||||
type="button"
|
{(mount) => (
|
||||||
class="xl:hidden w-12 shrink-0 flex items-center justify-center border-r border-border-weak-base hover:bg-surface-raised-base-hover active:bg-surface-raised-base-active transition-colors"
|
<Portal mount={mount()}>
|
||||||
onClick={layout.mobileSidebar.toggle}
|
<button
|
||||||
>
|
type="button"
|
||||||
<Icon name="menu" size="small" />
|
class="hidden md:flex w-[320px] p-1 pl-1.5 items-center gap-2 justify-between rounded-md border border-border-weak-base bg-surface-raised-base transition-colors cursor-default hover:bg-surface-raised-base-hover focus:bg-surface-raised-base-hover active:bg-surface-raised-base-active"
|
||||||
</button>
|
onClick={() => command.trigger("file.open")}
|
||||||
<div class="px-4 flex items-center justify-between gap-4 w-full">
|
|
||||||
<div class="flex items-center gap-3 min-w-0">
|
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
|
||||||
<div class="hidden xl:flex items-center gap-2">
|
|
||||||
<Select
|
|
||||||
options={worktrees()}
|
|
||||||
current={sync.project?.worktree ?? projectDirectory()}
|
|
||||||
label={(x) => getFilename(x)}
|
|
||||||
onSelect={(x) => (x ? navigateToProject(x) : undefined)}
|
|
||||||
class="text-14-regular text-text-base"
|
|
||||||
variant="ghost"
|
|
||||||
>
|
|
||||||
{/* @ts-ignore */}
|
|
||||||
{(i) => (
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<Icon name="folder" size="small" />
|
|
||||||
<div class="text-text-strong">{getFilename(i)}</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Select>
|
|
||||||
<div class="text-text-weaker">/</div>
|
|
||||||
</div>
|
|
||||||
<Show
|
|
||||||
when={parentSession()}
|
|
||||||
fallback={
|
|
||||||
<>
|
|
||||||
<Select
|
|
||||||
options={sessions()}
|
|
||||||
current={currentSession()}
|
|
||||||
placeholder="New session"
|
|
||||||
label={(x) => x.title}
|
|
||||||
value={(x) => x.id}
|
|
||||||
onSelect={navigateToSession}
|
|
||||||
class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md"
|
|
||||||
variant="ghost"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
<div class="flex items-center gap-2">
|
||||||
<Select
|
<Icon name="magnifying-glass" size="normal" class="icon-base" />
|
||||||
options={sessions()}
|
<span class="flex-1 min-w-0 text-14-regular text-text-weak truncate h-3.5 flex items-center overflow-visible">
|
||||||
current={parentSession()}
|
Search {name()}
|
||||||
placeholder="Back to parent session"
|
</span>
|
||||||
label={(x) => x.title}
|
|
||||||
value={(x) => x.id}
|
|
||||||
onSelect={(session) => {
|
|
||||||
// Only navigate if selecting a different session than current parent
|
|
||||||
const currentParent = parentSession()
|
|
||||||
if (session && currentParent && session.id !== currentParent.id) {
|
|
||||||
navigateToSession(session)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md"
|
|
||||||
variant="ghost"
|
|
||||||
/>
|
|
||||||
<div class="text-text-weaker">/</div>
|
|
||||||
<div class="flex items-center gap-1.5 min-w-0">
|
|
||||||
<Tooltip value="Back to parent session">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex items-center justify-center gap-1 p-1 rounded hover:bg-surface-raised-base-hover active:bg-surface-raised-base-active transition-colors flex-shrink-0"
|
|
||||||
onClick={() => navigateToSession(parentSession())}
|
|
||||||
>
|
|
||||||
<Icon name="arrow-left" size="small" class="text-icon-base" />
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
|
||||||
</div>
|
<Show when={hotkey()}>{(keybind) => <Keybind>{keybind()}</Keybind>}</Show>
|
||||||
<Show when={currentSession() && !parentSession()}>
|
</button>
|
||||||
<TooltipKeybind class="hidden xl:block" title="New session" keybind={command.keybind("session.new")}>
|
</Portal>
|
||||||
<IconButton as={A} href={`/${params.dir}/session`} icon="edit-small-2" variant="ghost" />
|
)}
|
||||||
</TooltipKeybind>
|
</Show>
|
||||||
</Show>
|
<Show when={rightMount()}>
|
||||||
</div>
|
{(mount) => (
|
||||||
<div class="flex items-center gap-3">
|
<Portal mount={mount()}>
|
||||||
<div class="hidden md:flex items-center gap-1">
|
<div class="flex items-center gap-3">
|
||||||
<Button
|
{/* <div class="hidden md:flex items-center gap-1"> */}
|
||||||
size="small"
|
{/* <Button */}
|
||||||
variant="ghost"
|
{/* size="small" */}
|
||||||
onClick={() => {
|
{/* variant="ghost" */}
|
||||||
dialog.show(() => <DialogSelectServer />)
|
{/* onClick={() => { */}
|
||||||
}}
|
{/* dialog.show(() => <DialogSelectServer />) */}
|
||||||
>
|
{/* }} */}
|
||||||
<div
|
{/* > */}
|
||||||
classList={{
|
{/* <div */}
|
||||||
"size-1.5 rounded-full": true,
|
{/* classList={{ */}
|
||||||
"bg-icon-success-base": server.healthy() === true,
|
{/* "size-1.5 rounded-full": true, */}
|
||||||
"bg-icon-critical-base": server.healthy() === false,
|
{/* "bg-icon-success-base": server.healthy() === true, */}
|
||||||
"bg-border-weak-base": server.healthy() === undefined,
|
{/* "bg-icon-critical-base": server.healthy() === false, */}
|
||||||
}}
|
{/* "bg-border-weak-base": server.healthy() === undefined, */}
|
||||||
/>
|
{/* }} */}
|
||||||
<Icon name="server" size="small" class="text-icon-weak" />
|
{/* /> */}
|
||||||
<span class="text-12-regular text-text-weak truncate max-w-[200px]">{server.name}</span>
|
{/* <Icon name="server" size="small" class="text-icon-weak" /> */}
|
||||||
</Button>
|
{/* <span class="text-12-regular text-text-weak truncate max-w-[200px]">{server.name}</span> */}
|
||||||
<SessionLspIndicator />
|
{/* </Button> */}
|
||||||
<SessionMcpIndicator />
|
{/* <SessionLspIndicator /> */}
|
||||||
</div>
|
{/* <SessionMcpIndicator /> */}
|
||||||
<div class="flex items-center gap-1">
|
{/* </div> */}
|
||||||
<Show when={currentSession()?.summary?.files}>
|
<div class="flex items-center gap-1">
|
||||||
<TooltipKeybind
|
<Show when={currentSession()?.summary?.files}>
|
||||||
class="hidden md:block shrink-0"
|
<TooltipKeybind
|
||||||
title="Toggle review"
|
class="hidden md:block shrink-0"
|
||||||
keybind={command.keybind("review.toggle")}
|
title="Toggle review"
|
||||||
>
|
keybind={command.keybind("review.toggle")}
|
||||||
<Button
|
>
|
||||||
variant="ghost"
|
<Button
|
||||||
class="group/review-toggle size-6 p-0"
|
variant="ghost"
|
||||||
onClick={() => view().reviewPanel.toggle()}
|
class="group/review-toggle size-6 p-0"
|
||||||
|
onClick={() => view().reviewPanel.toggle()}
|
||||||
|
>
|
||||||
|
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||||
|
<Icon
|
||||||
|
name={view().reviewPanel.opened() ? "layout-right" : "layout-left"}
|
||||||
|
size="small"
|
||||||
|
class="group-hover/review-toggle:hidden"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
name={view().reviewPanel.opened() ? "layout-right-partial" : "layout-left-partial"}
|
||||||
|
size="small"
|
||||||
|
class="hidden group-hover/review-toggle:inline-block"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
name={view().reviewPanel.opened() ? "layout-right-full" : "layout-left-full"}
|
||||||
|
size="small"
|
||||||
|
class="hidden group-active/review-toggle:inline-block"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</TooltipKeybind>
|
||||||
|
</Show>
|
||||||
|
<TooltipKeybind
|
||||||
|
class="hidden md:block shrink-0"
|
||||||
|
title="Toggle terminal"
|
||||||
|
keybind={command.keybind("terminal.toggle")}
|
||||||
>
|
>
|
||||||
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
<Button
|
||||||
<Icon
|
variant="ghost"
|
||||||
name={view().reviewPanel.opened() ? "layout-right" : "layout-left"}
|
class="group/terminal-toggle size-6 p-0"
|
||||||
size="small"
|
onClick={() => view().terminal.toggle()}
|
||||||
class="group-hover/review-toggle:hidden"
|
>
|
||||||
/>
|
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||||
<Icon
|
<Icon
|
||||||
name={view().reviewPanel.opened() ? "layout-right-partial" : "layout-left-partial"}
|
size="small"
|
||||||
size="small"
|
name={view().terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
|
||||||
class="hidden group-hover/review-toggle:inline-block"
|
class="group-hover/terminal-toggle:hidden"
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
name={view().reviewPanel.opened() ? "layout-right-full" : "layout-left-full"}
|
size="small"
|
||||||
size="small"
|
name="layout-bottom-partial"
|
||||||
class="hidden group-active/review-toggle:inline-block"
|
class="hidden group-hover/terminal-toggle:inline-block"
|
||||||
/>
|
/>
|
||||||
</div>
|
<Icon
|
||||||
</Button>
|
size="small"
|
||||||
</TooltipKeybind>
|
name={view().terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
|
||||||
</Show>
|
class="hidden group-active/terminal-toggle:inline-block"
|
||||||
<TooltipKeybind
|
/>
|
||||||
class="hidden md:block shrink-0"
|
</div>
|
||||||
title="Toggle terminal"
|
</Button>
|
||||||
keybind={command.keybind("terminal.toggle")}
|
</TooltipKeybind>
|
||||||
>
|
</div>
|
||||||
<Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={() => view().terminal.toggle()}>
|
<Show when={shareEnabled() && currentSession()}>
|
||||||
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
<Popover
|
||||||
<Icon
|
title="Share session"
|
||||||
size="small"
|
trigger={
|
||||||
name={view().terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
|
<Tooltip class="shrink-0" value="Share session">
|
||||||
class="group-hover/terminal-toggle:hidden"
|
<IconButton icon="share" variant="ghost" class="" />
|
||||||
/>
|
</Tooltip>
|
||||||
<Icon
|
}
|
||||||
size="small"
|
>
|
||||||
name="layout-bottom-partial"
|
{iife(() => {
|
||||||
class="hidden group-hover/terminal-toggle:inline-block"
|
const [url] = createResource(
|
||||||
/>
|
() => currentSession(),
|
||||||
<Icon
|
async (session) => {
|
||||||
size="small"
|
if (!session) return
|
||||||
name={view().terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
|
let shareURL = session.share?.url
|
||||||
class="hidden group-active/terminal-toggle:inline-block"
|
if (!shareURL) {
|
||||||
/>
|
shareURL = await globalSDK.client.session
|
||||||
</div>
|
.share({ sessionID: session.id, directory: projectDirectory() })
|
||||||
</Button>
|
.then((r) => r.data?.share?.url)
|
||||||
</TooltipKeybind>
|
.catch((e) => {
|
||||||
</div>
|
console.error("Failed to share session", e)
|
||||||
<Show when={shareEnabled() && currentSession()}>
|
return undefined
|
||||||
<Popover
|
})
|
||||||
title="Share session"
|
}
|
||||||
trigger={
|
return shareURL
|
||||||
<Tooltip class="shrink-0" value="Share session">
|
},
|
||||||
<IconButton icon="share" variant="ghost" class="" />
|
{ initialValue: "" },
|
||||||
</Tooltip>
|
)
|
||||||
}
|
return (
|
||||||
>
|
<Show when={url.latest}>
|
||||||
{iife(() => {
|
{(shareUrl) => <TextField value={shareUrl()} readOnly copyable class="w-72" />}
|
||||||
const [url] = createResource(
|
</Show>
|
||||||
() => currentSession(),
|
)
|
||||||
async (session) => {
|
})}
|
||||||
if (!session) return
|
</Popover>
|
||||||
let shareURL = session.share?.url
|
</Show>
|
||||||
if (!shareURL) {
|
</div>
|
||||||
shareURL = await globalSDK.client.session
|
</Portal>
|
||||||
.share({ sessionID: session.id, directory: projectDirectory() })
|
)}
|
||||||
.then((r) => r.data?.share?.url)
|
</Show>
|
||||||
.catch((e) => {
|
</>
|
||||||
console.error("Failed to share session", e)
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return shareURL
|
|
||||||
},
|
|
||||||
{ initialValue: "" },
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<Show when={url.latest}>
|
|
||||||
{(shareUrl) => <TextField value={shareUrl()} readOnly copyable class="w-72" />}
|
|
||||||
</Show>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Popover>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { createEffect, createMemo, Show } from "solid-js"
|
||||||
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
|
import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
|
import { useTheme } from "@opencode-ai/ui/theme"
|
||||||
|
|
||||||
|
import { useLayout } from "@/context/layout"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { useCommand } from "@/context/command"
|
||||||
|
|
||||||
|
export function Titlebar() {
|
||||||
|
const layout = useLayout()
|
||||||
|
const platform = usePlatform()
|
||||||
|
const command = useCommand()
|
||||||
|
const theme = useTheme()
|
||||||
|
|
||||||
|
const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
|
||||||
|
const reserve = createMemo(
|
||||||
|
() => platform.platform === "desktop" && (platform.os === "windows" || platform.os === "linux"),
|
||||||
|
)
|
||||||
|
const web = createMemo(() => platform.platform === "web")
|
||||||
|
|
||||||
|
const getWin = () => {
|
||||||
|
if (platform.platform !== "desktop") return
|
||||||
|
|
||||||
|
const tauri = (
|
||||||
|
window as unknown as {
|
||||||
|
__TAURI__?: { window?: { getCurrentWindow?: () => { startDragging?: () => Promise<void> } } }
|
||||||
|
}
|
||||||
|
).__TAURI__
|
||||||
|
if (!tauri?.window?.getCurrentWindow) return
|
||||||
|
|
||||||
|
return tauri.window.getCurrentWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (platform.platform !== "desktop") return
|
||||||
|
|
||||||
|
const scheme = theme.colorScheme()
|
||||||
|
const value = scheme === "system" ? null : scheme
|
||||||
|
|
||||||
|
const tauri = (window as unknown as { __TAURI__?: { webviewWindow?: { getCurrentWebviewWindow?: () => unknown } } })
|
||||||
|
.__TAURI__
|
||||||
|
const get = tauri?.webviewWindow?.getCurrentWebviewWindow
|
||||||
|
if (!get) return
|
||||||
|
|
||||||
|
const win = get() as { setTheme?: (theme?: "light" | "dark" | null) => Promise<void> }
|
||||||
|
if (!win.setTheme) return
|
||||||
|
|
||||||
|
void win.setTheme(value).catch(() => undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
const interactive = (target: EventTarget | null) => {
|
||||||
|
if (!(target instanceof Element)) return false
|
||||||
|
|
||||||
|
const selector =
|
||||||
|
"button, a, input, textarea, select, option, [role='button'], [role='menuitem'], [contenteditable='true'], [contenteditable='']"
|
||||||
|
|
||||||
|
return !!target.closest(selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
const drag = (e: MouseEvent) => {
|
||||||
|
if (platform.platform !== "desktop") return
|
||||||
|
if (e.buttons !== 1) return
|
||||||
|
if (interactive(e.target)) return
|
||||||
|
|
||||||
|
const win = getWin()
|
||||||
|
if (!win?.startDragging) return
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
void win.startDragging().catch(() => undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header class="h-10 shrink-0 bg-background-base flex items-center relative">
|
||||||
|
<div
|
||||||
|
classList={{
|
||||||
|
"flex items-center w-full min-w-0 pr-2": true,
|
||||||
|
"pl-2": !mac(),
|
||||||
|
}}
|
||||||
|
onMouseDown={drag}
|
||||||
|
>
|
||||||
|
<Show when={mac()}>
|
||||||
|
<div class="w-[72px] h-full shrink-0" data-tauri-drag-region />
|
||||||
|
<div class="xl:hidden w-10 shrink-0 flex items-center justify-center">
|
||||||
|
<IconButton icon="menu" variant="ghost" class="size-8 rounded-md" onClick={layout.mobileSidebar.toggle} />
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<Show when={!mac()}>
|
||||||
|
<div class="xl:hidden w-[48px] shrink-0 flex items-center justify-center">
|
||||||
|
<IconButton icon="menu" variant="ghost" class="size-8 rounded-md" onClick={layout.mobileSidebar.toggle} />
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<TooltipKeybind
|
||||||
|
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0"}
|
||||||
|
placement="bottom"
|
||||||
|
title="Toggle sidebar"
|
||||||
|
keybind={command.keybind("sidebar.toggle")}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
icon={layout.sidebar.opened() ? "layout-left" : "layout-right"}
|
||||||
|
variant="ghost"
|
||||||
|
class="size-8 rounded-md"
|
||||||
|
onClick={layout.sidebar.toggle}
|
||||||
|
/>
|
||||||
|
</TooltipKeybind>
|
||||||
|
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
|
||||||
|
<div class="flex-1 h-full" data-tauri-drag-region />
|
||||||
|
<div id="opencode-titlebar-right" class="flex items-center gap-3 shrink-0" />
|
||||||
|
<Show when={reserve()}>
|
||||||
|
<div class="w-[120px] h-full shrink-0" data-tauri-drag-region />
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||||
|
<div id="opencode-titlebar-center" class="pointer-events-auto" />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import { createMemo, createSignal, onCleanup, onMount, Show, type Accessor } from "solid-js"
|
import { createMemo, createSignal, onCleanup, onMount, type Accessor } from "solid-js"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
|
||||||
import { List } from "@opencode-ai/ui/list"
|
|
||||||
|
|
||||||
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
|
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
|
||||||
|
|
||||||
@@ -107,74 +105,27 @@ export function formatKeybind(config: string): string {
|
|||||||
if (kb.meta) parts.push(IS_MAC ? "⌘" : "Meta")
|
if (kb.meta) parts.push(IS_MAC ? "⌘" : "Meta")
|
||||||
|
|
||||||
if (kb.key) {
|
if (kb.key) {
|
||||||
const displayKey = kb.key.length === 1 ? kb.key.toUpperCase() : kb.key.charAt(0).toUpperCase() + kb.key.slice(1)
|
const arrows: Record<string, string> = {
|
||||||
|
arrowup: "↑",
|
||||||
|
arrowdown: "↓",
|
||||||
|
arrowleft: "←",
|
||||||
|
arrowright: "→",
|
||||||
|
}
|
||||||
|
const displayKey =
|
||||||
|
arrows[kb.key.toLowerCase()] ??
|
||||||
|
(kb.key.length === 1 ? kb.key.toUpperCase() : kb.key.charAt(0).toUpperCase() + kb.key.slice(1))
|
||||||
parts.push(displayKey)
|
parts.push(displayKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
return IS_MAC ? parts.join("") : parts.join("+")
|
return IS_MAC ? parts.join("") : parts.join("+")
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogCommand(props: { options: CommandOption[] }) {
|
|
||||||
const dialog = useDialog()
|
|
||||||
let cleanup: (() => void) | void
|
|
||||||
let committed = false
|
|
||||||
|
|
||||||
const handleMove = (option: CommandOption | undefined) => {
|
|
||||||
cleanup?.()
|
|
||||||
cleanup = option?.onHighlight?.()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSelect = (option: CommandOption | undefined) => {
|
|
||||||
if (option) {
|
|
||||||
committed = true
|
|
||||||
cleanup = undefined
|
|
||||||
dialog.close()
|
|
||||||
option.onSelect?.("palette")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
if (!committed) {
|
|
||||||
cleanup?.()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog title="Commands">
|
|
||||||
<List
|
|
||||||
search={{ placeholder: "Search commands", autofocus: true }}
|
|
||||||
emptyMessage="No commands found"
|
|
||||||
items={() => props.options.filter((x) => !x.id.startsWith("suggested.") || !x.disabled)}
|
|
||||||
key={(x) => x?.id}
|
|
||||||
filterKeys={["title", "description", "category"]}
|
|
||||||
groupBy={(x) => x.category ?? ""}
|
|
||||||
onMove={handleMove}
|
|
||||||
onSelect={handleSelect}
|
|
||||||
>
|
|
||||||
{(option) => (
|
|
||||||
<div class="w-full flex items-center justify-between gap-4">
|
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
|
||||||
<span class="text-14-regular text-text-strong whitespace-nowrap">{option.title}</span>
|
|
||||||
<Show when={option.description}>
|
|
||||||
<span class="text-14-regular text-text-weak truncate">{option.description}</span>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
<Show when={option.keybind}>
|
|
||||||
<span class="text-12-regular text-text-subtle shrink-0">{formatKeybind(option.keybind!)}</span>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const { use: useCommand, provider: CommandProvider } = createSimpleContext({
|
export const { use: useCommand, provider: CommandProvider } = createSimpleContext({
|
||||||
name: "Command",
|
name: "Command",
|
||||||
init: () => {
|
init: () => {
|
||||||
|
const dialog = useDialog()
|
||||||
const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
|
const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
|
||||||
const [suspendCount, setSuspendCount] = createSignal(0)
|
const [suspendCount, setSuspendCount] = createSignal(0)
|
||||||
const dialog = useDialog()
|
|
||||||
|
|
||||||
const options = createMemo(() => {
|
const options = createMemo(() => {
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
@@ -202,14 +153,21 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||||||
|
|
||||||
const suspended = () => suspendCount() > 0
|
const suspended = () => suspendCount() > 0
|
||||||
|
|
||||||
const showPalette = () => {
|
const run = (id: string, source?: "palette" | "keybind" | "slash") => {
|
||||||
if (!dialog.active) {
|
for (const option of options()) {
|
||||||
dialog.show(() => <DialogCommand options={options().filter((x) => !x.disabled)} />)
|
if (option.id === id || option.id === "suggested." + id) {
|
||||||
|
option.onSelect?.(source)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showPalette = () => {
|
||||||
|
run("file.open", "palette")
|
||||||
|
}
|
||||||
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (suspended()) return
|
if (suspended() || dialog.active) return
|
||||||
|
|
||||||
const paletteKeybinds = parseKeybind("mod+shift+p")
|
const paletteKeybinds = parseKeybind("mod+shift+p")
|
||||||
if (matchKeybind(paletteKeybinds, event)) {
|
if (matchKeybind(paletteKeybinds, event)) {
|
||||||
@@ -248,12 +206,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
trigger(id: string, source?: "palette" | "keybind" | "slash") {
|
trigger(id: string, source?: "palette" | "keybind" | "slash") {
|
||||||
for (const option of options()) {
|
run(id, source)
|
||||||
if (option.id === id || option.id === "suggested." + id) {
|
|
||||||
option.onSelect?.(source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
keybind(id: string) {
|
keybind(id: string) {
|
||||||
const option = options().find((x) => x.id === id || x.id === "suggested." + id)
|
const option = options().find((x) => x.id === id || x.id === "suggested." + id)
|
||||||
|
|||||||
@@ -19,15 +19,29 @@ import {
|
|||||||
type QuestionRequest,
|
type QuestionRequest,
|
||||||
createOpencodeClient,
|
createOpencodeClient,
|
||||||
} from "@opencode-ai/sdk/v2/client"
|
} from "@opencode-ai/sdk/v2/client"
|
||||||
import { createStore, produce, reconcile } from "solid-js/store"
|
import { createStore, produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
||||||
import { Binary } from "@opencode-ai/util/binary"
|
import { Binary } from "@opencode-ai/util/binary"
|
||||||
import { retry } from "@opencode-ai/util/retry"
|
import { retry } from "@opencode-ai/util/retry"
|
||||||
import { useGlobalSDK } from "./global-sdk"
|
import { useGlobalSDK } from "./global-sdk"
|
||||||
import { ErrorPage, type InitError } from "../pages/error"
|
import { ErrorPage, type InitError } from "../pages/error"
|
||||||
import { batch, createContext, useContext, onCleanup, onMount, type ParentProps, Switch, Match } from "solid-js"
|
import {
|
||||||
|
batch,
|
||||||
|
createContext,
|
||||||
|
createEffect,
|
||||||
|
getOwner,
|
||||||
|
runWithOwner,
|
||||||
|
useContext,
|
||||||
|
onCleanup,
|
||||||
|
onMount,
|
||||||
|
type Accessor,
|
||||||
|
type ParentProps,
|
||||||
|
Switch,
|
||||||
|
Match,
|
||||||
|
} from "solid-js"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { usePlatform } from "./platform"
|
import { usePlatform } from "./platform"
|
||||||
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
status: "loading" | "partial" | "complete"
|
status: "loading" | "partial" | "complete"
|
||||||
@@ -68,9 +82,18 @@ type State = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VcsCache = {
|
||||||
|
store: Store<{ value: VcsInfo | undefined }>
|
||||||
|
setStore: SetStoreFunction<{ value: VcsInfo | undefined }>
|
||||||
|
ready: Accessor<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
function createGlobalSync() {
|
function createGlobalSync() {
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
|
const owner = getOwner()
|
||||||
|
if (!owner) throw new Error("GlobalSync must be created within owner")
|
||||||
|
const vcsCache = new Map<string, VcsCache>()
|
||||||
const [globalStore, setGlobalStore] = createStore<{
|
const [globalStore, setGlobalStore] = createStore<{
|
||||||
ready: boolean
|
ready: boolean
|
||||||
error?: InitError
|
error?: InitError
|
||||||
@@ -86,35 +109,51 @@ function createGlobalSync() {
|
|||||||
provider_auth: {},
|
provider_auth: {},
|
||||||
})
|
})
|
||||||
|
|
||||||
const children: Record<string, ReturnType<typeof createStore<State>>> = {}
|
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
|
||||||
|
|
||||||
function child(directory: string) {
|
function child(directory: string) {
|
||||||
if (!directory) console.error("No directory provided")
|
if (!directory) console.error("No directory provided")
|
||||||
if (!children[directory]) {
|
if (!children[directory]) {
|
||||||
children[directory] = createStore<State>({
|
const cache = runWithOwner(owner, () =>
|
||||||
project: "",
|
persisted(
|
||||||
provider: { all: [], connected: [], default: {} },
|
Persist.workspace(directory, "vcs", ["vcs.v1"]),
|
||||||
config: {},
|
createStore({ value: undefined as VcsInfo | undefined }),
|
||||||
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
),
|
||||||
status: "loading" as const,
|
)
|
||||||
agent: [],
|
if (!cache) throw new Error("Failed to create persisted cache")
|
||||||
command: [],
|
vcsCache.set(directory, { store: cache[0], setStore: cache[1], ready: cache[3] })
|
||||||
session: [],
|
|
||||||
sessionTotal: 0,
|
const init = () => {
|
||||||
session_status: {},
|
children[directory] = createStore<State>({
|
||||||
session_diff: {},
|
project: "",
|
||||||
todo: {},
|
provider: { all: [], connected: [], default: {} },
|
||||||
permission: {},
|
config: {},
|
||||||
question: {},
|
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
||||||
mcp: {},
|
status: "loading" as const,
|
||||||
lsp: [],
|
agent: [],
|
||||||
vcs: undefined,
|
command: [],
|
||||||
limit: 5,
|
session: [],
|
||||||
message: {},
|
sessionTotal: 0,
|
||||||
part: {},
|
session_status: {},
|
||||||
})
|
session_diff: {},
|
||||||
bootstrapInstance(directory)
|
todo: {},
|
||||||
|
permission: {},
|
||||||
|
question: {},
|
||||||
|
mcp: {},
|
||||||
|
lsp: [],
|
||||||
|
vcs: cache[0].value,
|
||||||
|
limit: 5,
|
||||||
|
message: {},
|
||||||
|
part: {},
|
||||||
|
})
|
||||||
|
bootstrapInstance(directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
runWithOwner(owner, init)
|
||||||
}
|
}
|
||||||
return children[directory]
|
const childStore = children[directory]
|
||||||
|
if (!childStore) throw new Error("Failed to create store")
|
||||||
|
return childStore
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSessions(directory: string) {
|
async function loadSessions(directory: string) {
|
||||||
@@ -124,12 +163,19 @@ function createGlobalSync() {
|
|||||||
return globalSDK.client.session
|
return globalSDK.client.session
|
||||||
.list({ directory, roots: true })
|
.list({ directory, roots: true })
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
|
|
||||||
const nonArchived = (x.data ?? [])
|
const nonArchived = (x.data ?? [])
|
||||||
.filter((s) => !!s?.id)
|
.filter((s) => !!s?.id)
|
||||||
.filter((s) => !s.time?.archived)
|
.filter((s) => !s.time?.archived)
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => a.id.localeCompare(b.id))
|
.sort((a, b) => a.id.localeCompare(b.id))
|
||||||
|
|
||||||
|
const sandboxWorkspace = globalStore.project.some((p) => (p.sandboxes ?? []).includes(directory))
|
||||||
|
if (sandboxWorkspace) {
|
||||||
|
setStore("session", reconcile(nonArchived, { key: "id" }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
|
||||||
// Include up to the limit, plus any updated in the last 4 hours
|
// Include up to the limit, plus any updated in the last 4 hours
|
||||||
const sessions = nonArchived.filter((s, i) => {
|
const sessions = nonArchived.filter((s, i) => {
|
||||||
if (i < limit) return true
|
if (i < limit) return true
|
||||||
@@ -150,6 +196,8 @@ function createGlobalSync() {
|
|||||||
async function bootstrapInstance(directory: string) {
|
async function bootstrapInstance(directory: string) {
|
||||||
if (!directory) return
|
if (!directory) return
|
||||||
const [store, setStore] = child(directory)
|
const [store, setStore] = child(directory)
|
||||||
|
const cache = vcsCache.get(directory)
|
||||||
|
if (!cache) return
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: globalSDK.url,
|
baseUrl: globalSDK.url,
|
||||||
fetch: platform.fetch,
|
fetch: platform.fetch,
|
||||||
@@ -157,6 +205,13 @@ function createGlobalSync() {
|
|||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!cache.ready()) return
|
||||||
|
const cached = cache.store.value
|
||||||
|
if (!cached?.branch) return
|
||||||
|
setStore("vcs", (value) => value ?? cached)
|
||||||
|
})
|
||||||
|
|
||||||
const blockingRequests = {
|
const blockingRequests = {
|
||||||
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
|
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
|
||||||
provider: () =>
|
provider: () =>
|
||||||
@@ -186,7 +241,11 @@ function createGlobalSync() {
|
|||||||
loadSessions(directory),
|
loadSessions(directory),
|
||||||
sdk.mcp.status().then((x) => setStore("mcp", x.data!)),
|
sdk.mcp.status().then((x) => setStore("mcp", x.data!)),
|
||||||
sdk.lsp.status().then((x) => setStore("lsp", x.data!)),
|
sdk.lsp.status().then((x) => setStore("lsp", x.data!)),
|
||||||
sdk.vcs.get().then((x) => setStore("vcs", x.data)),
|
sdk.vcs.get().then((x) => {
|
||||||
|
const next = x.data ?? store.vcs
|
||||||
|
setStore("vcs", next)
|
||||||
|
if (next?.branch) cache.setStore("value", next)
|
||||||
|
}),
|
||||||
sdk.permission.list().then((x) => {
|
sdk.permission.list().then((x) => {
|
||||||
const grouped: Record<string, PermissionRequest[]> = {}
|
const grouped: Record<string, PermissionRequest[]> = {}
|
||||||
for (const perm of x.data ?? []) {
|
for (const perm of x.data ?? []) {
|
||||||
@@ -292,6 +351,23 @@ function createGlobalSync() {
|
|||||||
bootstrapInstance(directory)
|
bootstrapInstance(directory)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case "session.created": {
|
||||||
|
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)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
if (!event.properties.info.parentID) {
|
||||||
|
setStore("sessionTotal", store.sessionTotal + 1)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
case "session.updated": {
|
case "session.updated": {
|
||||||
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
|
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
|
||||||
if (event.properties.info.time.archived) {
|
if (event.properties.info.time.archived) {
|
||||||
@@ -303,6 +379,8 @@ function createGlobalSync() {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (event.properties.info.parentID) break
|
||||||
|
setStore("sessionTotal", (value) => Math.max(0, value - 1))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (result.found) {
|
if (result.found) {
|
||||||
@@ -399,7 +477,10 @@ function createGlobalSync() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "vcs.branch.updated": {
|
case "vcs.branch.updated": {
|
||||||
setStore("vcs", { branch: event.properties.branch })
|
const next = { branch: event.properties.branch }
|
||||||
|
setStore("vcs", next)
|
||||||
|
const cache = vcsCache.get(directory)
|
||||||
|
if (cache) cache.setStore("value", next)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "permission.asked": {
|
case "permission.asked": {
|
||||||
|
|||||||
@@ -47,12 +47,34 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
const globalSdk = useGlobalSDK()
|
const globalSdk = useGlobalSDK()
|
||||||
const globalSync = useGlobalSync()
|
const globalSync = useGlobalSync()
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
|
|
||||||
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||||
|
typeof value === "object" && value !== null && !Array.isArray(value)
|
||||||
|
|
||||||
|
const migrate = (value: unknown) => {
|
||||||
|
if (!isRecord(value)) return value
|
||||||
|
const sidebar = value.sidebar
|
||||||
|
if (!isRecord(sidebar)) return value
|
||||||
|
if (typeof sidebar.workspaces !== "boolean") return value
|
||||||
|
return {
|
||||||
|
...value,
|
||||||
|
sidebar: {
|
||||||
|
...sidebar,
|
||||||
|
workspaces: {},
|
||||||
|
workspacesDefault: sidebar.workspaces,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = Persist.global("layout", ["layout.v6"])
|
||||||
const [store, setStore, _, ready] = persisted(
|
const [store, setStore, _, ready] = persisted(
|
||||||
Persist.global("layout", ["layout.v6"]),
|
{ ...target, migrate },
|
||||||
createStore({
|
createStore({
|
||||||
sidebar: {
|
sidebar: {
|
||||||
opened: false,
|
opened: false,
|
||||||
width: 280,
|
width: 344,
|
||||||
|
workspaces: {} as Record<string, boolean>,
|
||||||
|
workspacesDefault: false,
|
||||||
},
|
},
|
||||||
terminal: {
|
terminal: {
|
||||||
height: 280,
|
height: 280,
|
||||||
@@ -304,6 +326,16 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
resize(width: number) {
|
resize(width: number) {
|
||||||
setStore("sidebar", "width", width)
|
setStore("sidebar", "width", width)
|
||||||
},
|
},
|
||||||
|
workspaces(directory: string) {
|
||||||
|
return createMemo(() => store.sidebar.workspaces[directory] ?? store.sidebar.workspacesDefault ?? false)
|
||||||
|
},
|
||||||
|
setWorkspaces(directory: string, value: boolean) {
|
||||||
|
setStore("sidebar", "workspaces", directory, value)
|
||||||
|
},
|
||||||
|
toggleWorkspaces(directory: string) {
|
||||||
|
const current = store.sidebar.workspaces[directory] ?? store.sidebar.workspacesDefault ?? false
|
||||||
|
setStore("sidebar", "workspaces", directory, !current)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
terminal: {
|
terminal: {
|
||||||
height: createMemo(() => store.terminal.height),
|
height: createMemo(() => store.terminal.height),
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ export type Platform = {
|
|||||||
/** Platform discriminator */
|
/** Platform discriminator */
|
||||||
platform: "web" | "desktop"
|
platform: "web" | "desktop"
|
||||||
|
|
||||||
|
/** Desktop OS (Tauri only) */
|
||||||
|
os?: "macos" | "windows" | "linux"
|
||||||
|
|
||||||
/** App version */
|
/** App version */
|
||||||
version?: string
|
version?: string
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
|
|||||||
createStore({
|
createStore({
|
||||||
list: [] as string[],
|
list: [] as string[],
|
||||||
projects: {} as Record<string, StoredProject[]>,
|
projects: {} as Record<string, StoredProject[]>,
|
||||||
|
lastProject: {} as Record<string, string>,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -197,6 +198,16 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
|
|||||||
result.splice(toIndex, 0, item)
|
result.splice(toIndex, 0, item)
|
||||||
setStore("projects", key, result)
|
setStore("projects", key, result)
|
||||||
},
|
},
|
||||||
|
last() {
|
||||||
|
const key = origin()
|
||||||
|
if (!key) return
|
||||||
|
return store.lastProject[key]
|
||||||
|
},
|
||||||
|
touch(directory: string) {
|
||||||
|
const key = origin()
|
||||||
|
if (!key) return
|
||||||
|
setStore("lastProject", key, directory)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const [store, setStore] = globalSync.child(sdk.directory)
|
const [store, setStore] = globalSync.child(sdk.directory)
|
||||||
const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/")
|
const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/")
|
||||||
const chunk = 200
|
const chunk = 400
|
||||||
const inflight = new Map<string, Promise<void>>()
|
const inflight = new Map<string, Promise<void>>()
|
||||||
const inflightDiff = new Map<string, Promise<void>>()
|
const inflightDiff = new Map<string, Promise<void>>()
|
||||||
const inflightTodo = new Map<string, Promise<void>>()
|
const inflightTodo = new Map<string, Promise<void>>()
|
||||||
|
|||||||
@@ -5,3 +5,7 @@
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*[data-tauri-drag-region] {
|
||||||
|
app-region: drag;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useGlobalSync } from "@/context/global-sync"
|
|
||||||
import { createMemo, For, Match, Show, Switch } from "solid-js"
|
import { createMemo, For, Match, Show, Switch } from "solid-js"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Logo } from "@opencode-ai/ui/logo"
|
import { Logo } from "@opencode-ai/ui/logo"
|
||||||
@@ -12,6 +11,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|||||||
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
||||||
import { DialogSelectServer } from "@/components/dialog-select-server"
|
import { DialogSelectServer } from "@/components/dialog-select-server"
|
||||||
import { useServer } from "@/context/server"
|
import { useServer } from "@/context/server"
|
||||||
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const sync = useGlobalSync()
|
const sync = useGlobalSync()
|
||||||
@@ -24,6 +24,7 @@ export default function Home() {
|
|||||||
|
|
||||||
function openProject(directory: string) {
|
function openProject(directory: string) {
|
||||||
layout.projects.open(directory)
|
layout.projects.open(directory)
|
||||||
|
server.projects.touch(directory)
|
||||||
navigate(`/${base64Encode(directory)}`)
|
navigate(`/${base64Encode(directory)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1024
-540
File diff suppressed because it is too large
Load Diff
@@ -419,7 +419,6 @@ export default function Page() {
|
|||||||
{
|
{
|
||||||
id: "session.new",
|
id: "session.new",
|
||||||
title: "New session",
|
title: "New session",
|
||||||
description: "Create a new session",
|
|
||||||
category: "Session",
|
category: "Session",
|
||||||
keybind: "mod+shift+s",
|
keybind: "mod+shift+s",
|
||||||
slash: "new",
|
slash: "new",
|
||||||
@@ -428,7 +427,7 @@ export default function Page() {
|
|||||||
{
|
{
|
||||||
id: "file.open",
|
id: "file.open",
|
||||||
title: "Open file",
|
title: "Open file",
|
||||||
description: "Search and open a file",
|
description: "Search files and commands",
|
||||||
category: "File",
|
category: "File",
|
||||||
keybind: "mod+p",
|
keybind: "mod+p",
|
||||||
slash: "open",
|
slash: "open",
|
||||||
@@ -437,7 +436,7 @@ export default function Page() {
|
|||||||
{
|
{
|
||||||
id: "terminal.toggle",
|
id: "terminal.toggle",
|
||||||
title: "Toggle terminal",
|
title: "Toggle terminal",
|
||||||
description: "Show or hide the terminal",
|
description: "",
|
||||||
category: "View",
|
category: "View",
|
||||||
keybind: "ctrl+`",
|
keybind: "ctrl+`",
|
||||||
slash: "terminal",
|
slash: "terminal",
|
||||||
@@ -446,7 +445,7 @@ export default function Page() {
|
|||||||
{
|
{
|
||||||
id: "review.toggle",
|
id: "review.toggle",
|
||||||
title: "Toggle review",
|
title: "Toggle review",
|
||||||
description: "Show or hide the review panel",
|
description: "",
|
||||||
category: "View",
|
category: "View",
|
||||||
keybind: "mod+shift+r",
|
keybind: "mod+shift+r",
|
||||||
onSelect: () => view().reviewPanel.toggle(),
|
onSelect: () => view().reviewPanel.toggle(),
|
||||||
@@ -885,6 +884,19 @@ export default function Page() {
|
|||||||
window.history.replaceState(null, "", `#${anchor(id)}`)
|
window.history.replaceState(null, "", `#${anchor(id)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scrollToElement = (el: HTMLElement, behavior: ScrollBehavior) => {
|
||||||
|
const root = scroller
|
||||||
|
if (!root) {
|
||||||
|
el.scrollIntoView({ behavior, block: "start" })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = el.getBoundingClientRect()
|
||||||
|
const b = root.getBoundingClientRect()
|
||||||
|
const top = a.top - b.top + root.scrollTop
|
||||||
|
root.scrollTo({ top, behavior })
|
||||||
|
}
|
||||||
|
|
||||||
const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
|
const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
|
||||||
setActiveMessage(message)
|
setActiveMessage(message)
|
||||||
|
|
||||||
@@ -896,7 +908,7 @@ export default function Page() {
|
|||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const el = document.getElementById(anchor(message.id))
|
const el = document.getElementById(anchor(message.id))
|
||||||
if (el) el.scrollIntoView({ behavior, block: "start" })
|
if (el) scrollToElement(el, behavior)
|
||||||
})
|
})
|
||||||
|
|
||||||
updateHash(message.id)
|
updateHash(message.id)
|
||||||
@@ -904,7 +916,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const el = document.getElementById(anchor(message.id))
|
const el = document.getElementById(anchor(message.id))
|
||||||
if (el) el.scrollIntoView({ behavior, block: "start" })
|
if (el) scrollToElement(el, behavior)
|
||||||
updateHash(message.id)
|
updateHash(message.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -956,7 +968,7 @@ export default function Page() {
|
|||||||
|
|
||||||
const hashTarget = document.getElementById(hash)
|
const hashTarget = document.getElementById(hash)
|
||||||
if (hashTarget) {
|
if (hashTarget) {
|
||||||
hashTarget.scrollIntoView({ behavior: "auto", block: "start" })
|
scrollToElement(hashTarget, "auto")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -20,6 +20,8 @@
|
|||||||
"@opencode-ai/console-mail": "workspace:*",
|
"@opencode-ai/console-mail": "workspace:*",
|
||||||
"@opencode-ai/console-resource": "workspace:*",
|
"@opencode-ai/console-resource": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
|
"@smithy/eventstream-codec": "4.2.7",
|
||||||
|
"@smithy/util-utf8": "4.2.0",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
"@solidjs/router": "catalog:",
|
"@solidjs/router": "catalog:",
|
||||||
"@solidjs/start": "catalog:",
|
"@solidjs/start": "catalog:",
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
|
"@webgpu/types": "0.1.54",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"wrangler": "4.50.0"
|
"wrangler": "4.50.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
.spotlight-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 50dvh;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spotlight-container canvas {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,820 @@
|
|||||||
|
import { createSignal, createEffect, onMount, onCleanup, Accessor } from "solid-js"
|
||||||
|
import "./spotlight.css"
|
||||||
|
|
||||||
|
export interface ParticlesConfig {
|
||||||
|
enabled: boolean
|
||||||
|
amount: number
|
||||||
|
size: [number, number]
|
||||||
|
speed: number
|
||||||
|
opacity: number
|
||||||
|
drift: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpotlightConfig {
|
||||||
|
placement: [number, number]
|
||||||
|
color: string
|
||||||
|
speed: number
|
||||||
|
spread: number
|
||||||
|
length: number
|
||||||
|
width: number
|
||||||
|
pulsating: false | [number, number]
|
||||||
|
distance: number
|
||||||
|
saturation: number
|
||||||
|
noiseAmount: number
|
||||||
|
distortion: number
|
||||||
|
opacity: number
|
||||||
|
particles: ParticlesConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultConfig: SpotlightConfig = {
|
||||||
|
placement: [0.5, -0.15],
|
||||||
|
color: "#ffffff",
|
||||||
|
speed: 0.8,
|
||||||
|
spread: 0.5,
|
||||||
|
length: 4.0,
|
||||||
|
width: 0.15,
|
||||||
|
pulsating: [0.95, 1.1],
|
||||||
|
distance: 3.5,
|
||||||
|
saturation: 0.35,
|
||||||
|
noiseAmount: 0.15,
|
||||||
|
distortion: 0.05,
|
||||||
|
opacity: 0.325,
|
||||||
|
particles: {
|
||||||
|
enabled: true,
|
||||||
|
amount: 70,
|
||||||
|
size: [1.25, 1.5],
|
||||||
|
speed: 0.75,
|
||||||
|
opacity: 0.9,
|
||||||
|
drift: 1.5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpotlightAnimationState {
|
||||||
|
time: number
|
||||||
|
intensity: number
|
||||||
|
pulseValue: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpotlightProps {
|
||||||
|
config: Accessor<SpotlightConfig>
|
||||||
|
class?: string
|
||||||
|
onAnimationFrame?: (state: SpotlightAnimationState) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexToRgb = (hex: string): [number, number, number] => {
|
||||||
|
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||||
|
return m ? [parseInt(m[1], 16) / 255, parseInt(m[2], 16) / 255, parseInt(m[3], 16) / 255] : [1, 1, 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAnchorAndDir = (
|
||||||
|
placement: [number, number],
|
||||||
|
w: number,
|
||||||
|
h: number,
|
||||||
|
): { anchor: [number, number]; dir: [number, number] } => {
|
||||||
|
const [px, py] = placement
|
||||||
|
const outside = 0.2
|
||||||
|
|
||||||
|
let anchorX = px * w
|
||||||
|
let anchorY = py * h
|
||||||
|
let dirX = 0
|
||||||
|
let dirY = 0
|
||||||
|
|
||||||
|
const centerX = 0.5
|
||||||
|
const centerY = 0.5
|
||||||
|
|
||||||
|
if (py <= 0.25) {
|
||||||
|
anchorY = -outside * h + py * h
|
||||||
|
dirY = 1
|
||||||
|
dirX = (centerX - px) * 0.5
|
||||||
|
} else if (py >= 0.75) {
|
||||||
|
anchorY = (1 + outside) * h - (1 - py) * h
|
||||||
|
dirY = -1
|
||||||
|
dirX = (centerX - px) * 0.5
|
||||||
|
} else if (px <= 0.25) {
|
||||||
|
anchorX = -outside * w + px * w
|
||||||
|
dirX = 1
|
||||||
|
dirY = (centerY - py) * 0.5
|
||||||
|
} else if (px >= 0.75) {
|
||||||
|
anchorX = (1 + outside) * w - (1 - px) * w
|
||||||
|
dirX = -1
|
||||||
|
dirY = (centerY - py) * 0.5
|
||||||
|
} else {
|
||||||
|
dirY = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const len = Math.sqrt(dirX * dirX + dirY * dirY)
|
||||||
|
if (len > 0) {
|
||||||
|
dirX /= len
|
||||||
|
dirY /= len
|
||||||
|
}
|
||||||
|
|
||||||
|
return { anchor: [anchorX, anchorY], dir: [dirX, dirY] }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UniformData {
|
||||||
|
iTime: number
|
||||||
|
iResolution: [number, number]
|
||||||
|
lightPos: [number, number]
|
||||||
|
lightDir: [number, number]
|
||||||
|
color: [number, number, number]
|
||||||
|
speed: number
|
||||||
|
lightSpread: number
|
||||||
|
lightLength: number
|
||||||
|
sourceWidth: number
|
||||||
|
pulsating: number
|
||||||
|
pulsatingMin: number
|
||||||
|
pulsatingMax: number
|
||||||
|
fadeDistance: number
|
||||||
|
saturation: number
|
||||||
|
noiseAmount: number
|
||||||
|
distortion: number
|
||||||
|
particlesEnabled: number
|
||||||
|
particleAmount: number
|
||||||
|
particleSizeMin: number
|
||||||
|
particleSizeMax: number
|
||||||
|
particleSpeed: number
|
||||||
|
particleOpacity: number
|
||||||
|
particleDrift: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const WGSL_SHADER = `
|
||||||
|
struct Uniforms {
|
||||||
|
iTime: f32,
|
||||||
|
_pad0: f32,
|
||||||
|
iResolution: vec2<f32>,
|
||||||
|
lightPos: vec2<f32>,
|
||||||
|
lightDir: vec2<f32>,
|
||||||
|
color: vec3<f32>,
|
||||||
|
speed: f32,
|
||||||
|
lightSpread: f32,
|
||||||
|
lightLength: f32,
|
||||||
|
sourceWidth: f32,
|
||||||
|
pulsating: f32,
|
||||||
|
pulsatingMin: f32,
|
||||||
|
pulsatingMax: f32,
|
||||||
|
fadeDistance: f32,
|
||||||
|
saturation: f32,
|
||||||
|
noiseAmount: f32,
|
||||||
|
distortion: f32,
|
||||||
|
particlesEnabled: f32,
|
||||||
|
particleAmount: f32,
|
||||||
|
particleSizeMin: f32,
|
||||||
|
particleSizeMax: f32,
|
||||||
|
particleSpeed: f32,
|
||||||
|
particleOpacity: f32,
|
||||||
|
particleDrift: f32,
|
||||||
|
_pad1: f32,
|
||||||
|
_pad2: f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) position: vec4<f32>,
|
||||||
|
@location(0) vUv: vec2<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
||||||
|
var positions = array<vec2<f32>, 3>(
|
||||||
|
vec2<f32>(-1.0, -1.0),
|
||||||
|
vec2<f32>(3.0, -1.0),
|
||||||
|
vec2<f32>(-1.0, 3.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
var output: VertexOutput;
|
||||||
|
let pos = positions[vertexIndex];
|
||||||
|
output.position = vec4<f32>(pos, 0.0, 1.0);
|
||||||
|
output.vUv = pos * 0.5 + 0.5;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash(p: vec2<f32>) -> f32 {
|
||||||
|
let p3 = fract(p.xyx * 0.1031);
|
||||||
|
return fract((p3.x + p3.y) * p3.z + dot(p3, p3.yzx + 33.33));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash2(p: vec2<f32>) -> vec2<f32> {
|
||||||
|
let n = sin(dot(p, vec2<f32>(41.0, 289.0)));
|
||||||
|
return fract(vec2<f32>(n * 262144.0, n * 32768.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fastNoise(st: vec2<f32>) -> f32 {
|
||||||
|
return fract(sin(dot(st, vec2<f32>(12.9898, 78.233))) * 43758.5453);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lightStrengthCombined(lightSource: vec2<f32>, lightRefDirection: vec2<f32>, coord: vec2<f32>) -> f32 {
|
||||||
|
let sourceToCoord = coord - lightSource;
|
||||||
|
let distSq = dot(sourceToCoord, sourceToCoord);
|
||||||
|
let distance = sqrt(distSq);
|
||||||
|
|
||||||
|
let baseSize = min(uniforms.iResolution.x, uniforms.iResolution.y);
|
||||||
|
let maxDistance = max(baseSize * uniforms.lightLength, 0.001);
|
||||||
|
if (distance > maxDistance) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let invDist = 1.0 / max(distance, 0.001);
|
||||||
|
let dirNorm = sourceToCoord * invDist;
|
||||||
|
let cosAngle = dot(dirNorm, lightRefDirection);
|
||||||
|
|
||||||
|
if (cosAngle < 0.0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let side = dot(dirNorm, vec2<f32>(-lightRefDirection.y, lightRefDirection.x));
|
||||||
|
let time = uniforms.iTime;
|
||||||
|
let speed = uniforms.speed;
|
||||||
|
|
||||||
|
let asymNoise = fastNoise(vec2<f32>(side * 6.0 + time * 0.12, distance * 0.004 + cosAngle * 2.0));
|
||||||
|
let asymShift = (asymNoise - 0.5) * uniforms.distortion * 0.6;
|
||||||
|
|
||||||
|
let distortPhase = time * 1.4 + distance * 0.006 + cosAngle * 4.5 + side * 1.7;
|
||||||
|
let distortedAngle = cosAngle + uniforms.distortion * sin(distortPhase) * 0.22 + asymShift;
|
||||||
|
|
||||||
|
let flickerSeed = cosAngle * 9.0 + side * 4.0 + time * speed * 0.35;
|
||||||
|
let flicker = 0.86 + fastNoise(vec2<f32>(flickerSeed, distance * 0.01)) * 0.28;
|
||||||
|
|
||||||
|
let asymSpread = max(uniforms.lightSpread * (0.9 + (asymNoise - 0.5) * 0.25), 0.001);
|
||||||
|
let spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / asymSpread);
|
||||||
|
let lengthFalloff = clamp(1.0 - distance / maxDistance, 0.0, 1.0);
|
||||||
|
|
||||||
|
let fadeMaxDist = max(baseSize * uniforms.fadeDistance, 0.001);
|
||||||
|
let fadeFalloff = clamp((fadeMaxDist - distance) / fadeMaxDist, 0.0, 1.0);
|
||||||
|
|
||||||
|
var pulse: f32 = 1.0;
|
||||||
|
if (uniforms.pulsating > 0.5) {
|
||||||
|
let pulseCenter = (uniforms.pulsatingMin + uniforms.pulsatingMax) * 0.5;
|
||||||
|
let pulseAmplitude = (uniforms.pulsatingMax - uniforms.pulsatingMin) * 0.5;
|
||||||
|
pulse = pulseCenter + pulseAmplitude * sin(time * speed * 3.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeSpeed = time * speed;
|
||||||
|
let wave = 0.5
|
||||||
|
+ 0.25 * sin(cosAngle * 28.0 + side * 8.0 + timeSpeed * 1.2)
|
||||||
|
+ 0.18 * cos(cosAngle * 22.0 - timeSpeed * 0.95 + side * 6.0)
|
||||||
|
+ 0.12 * sin(cosAngle * 35.0 + timeSpeed * 1.6 + asymNoise * 3.0);
|
||||||
|
let minStrength = 0.14 + asymNoise * 0.06;
|
||||||
|
let baseStrength = max(clamp(wave * (0.85 + asymNoise * 0.3), 0.0, 1.0), minStrength);
|
||||||
|
|
||||||
|
let lightStrength = baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse * flicker;
|
||||||
|
let ambientLight = (0.06 + asymNoise * 0.04) * lengthFalloff * fadeFalloff * spreadFactor;
|
||||||
|
|
||||||
|
return max(lightStrength, ambientLight);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn particle(coord: vec2<f32>, particlePos: vec2<f32>, size: f32) -> f32 {
|
||||||
|
let delta = coord - particlePos;
|
||||||
|
let distSq = dot(delta, delta);
|
||||||
|
let sizeSq = size * size;
|
||||||
|
|
||||||
|
if (distSq > sizeSq * 9.0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let d = sqrt(distSq);
|
||||||
|
let core = smoothstep(size, size * 0.35, d);
|
||||||
|
let glow = smoothstep(size * 3.0, 0.0, d) * 0.55;
|
||||||
|
return core + glow;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn renderParticles(coord: vec2<f32>, lightSource: vec2<f32>, lightDir: vec2<f32>) -> f32 {
|
||||||
|
if (uniforms.particlesEnabled < 0.5 || uniforms.particleAmount < 1.0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var particleSum: f32 = 0.0;
|
||||||
|
let particleCount = i32(uniforms.particleAmount);
|
||||||
|
let time = uniforms.iTime * uniforms.particleSpeed;
|
||||||
|
let perpDir = vec2<f32>(-lightDir.y, lightDir.x);
|
||||||
|
let baseSize = min(uniforms.iResolution.x, uniforms.iResolution.y);
|
||||||
|
let maxDist = max(baseSize * uniforms.lightLength, 1.0);
|
||||||
|
let spreadScale = uniforms.lightSpread * baseSize * 0.65;
|
||||||
|
let coneHalfWidth = uniforms.lightSpread * baseSize * 0.55;
|
||||||
|
|
||||||
|
for (var i: i32 = 0; i < particleCount; i = i + 1) {
|
||||||
|
let fi = f32(i);
|
||||||
|
let seed = vec2<f32>(fi * 127.1, fi * 311.7);
|
||||||
|
let rnd = hash2(seed);
|
||||||
|
|
||||||
|
let lifeDuration = 2.0 + hash(seed + vec2<f32>(19.0, 73.0)) * 3.0;
|
||||||
|
let lifeOffset = hash(seed + vec2<f32>(91.0, 37.0)) * lifeDuration;
|
||||||
|
let lifeProgress = fract((time + lifeOffset) / lifeDuration);
|
||||||
|
|
||||||
|
let fadeIn = smoothstep(0.0, 0.2, lifeProgress);
|
||||||
|
let fadeOut = 1.0 - smoothstep(0.8, 1.0, lifeProgress);
|
||||||
|
let lifeFade = fadeIn * fadeOut;
|
||||||
|
if (lifeFade < 0.01) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let alongLight = rnd.x * maxDist * 0.8;
|
||||||
|
let perpOffset = (rnd.y - 0.5) * spreadScale;
|
||||||
|
|
||||||
|
let floatPhase = rnd.y * 6.28318 + fi * 0.37;
|
||||||
|
let floatSpeed = 0.35 + rnd.x * 0.9;
|
||||||
|
let drift = vec2<f32>(
|
||||||
|
sin(time * floatSpeed + floatPhase),
|
||||||
|
cos(time * floatSpeed * 0.85 + floatPhase * 1.3)
|
||||||
|
) * uniforms.particleDrift * baseSize * 0.08;
|
||||||
|
|
||||||
|
let wobble = vec2<f32>(
|
||||||
|
sin(time * 1.4 + floatPhase * 2.1),
|
||||||
|
cos(time * 1.1 + floatPhase * 1.6)
|
||||||
|
) * uniforms.particleDrift * baseSize * 0.03;
|
||||||
|
|
||||||
|
let flowOffset = (rnd.x - 0.5) * baseSize * 0.12 + fract(time * 0.06 + rnd.y) * baseSize * 0.1;
|
||||||
|
|
||||||
|
let basePos = lightSource + lightDir * (alongLight + flowOffset) + perpDir * perpOffset + drift + wobble;
|
||||||
|
|
||||||
|
let toParticle = basePos - lightSource;
|
||||||
|
let projLen = dot(toParticle, lightDir);
|
||||||
|
if (projLen < 0.0 || projLen > maxDist) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sideDist = abs(dot(toParticle, perpDir));
|
||||||
|
if (sideDist > coneHalfWidth) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = mix(uniforms.particleSizeMin, uniforms.particleSizeMax, rnd.x);
|
||||||
|
let twinkle = 0.7 + 0.3 * sin(time * (1.5 + rnd.y * 2.0) + floatPhase);
|
||||||
|
let distFade = 1.0 - smoothstep(maxDist * 0.2, maxDist * 0.95, projLen);
|
||||||
|
if (distFade < 0.01) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = particle(coord, basePos, size);
|
||||||
|
if (p > 0.0) {
|
||||||
|
particleSum = particleSum + p * lifeFade * twinkle * distFade * uniforms.particleOpacity;
|
||||||
|
if (particleSum >= 1.0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return min(particleSum, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fragmentMain(@builtin(position) fragCoord: vec4<f32>, @location(0) vUv: vec2<f32>) -> @location(0) vec4<f32> {
|
||||||
|
let coord = vec2<f32>(fragCoord.x, fragCoord.y);
|
||||||
|
|
||||||
|
let normalizedX = (coord.x / uniforms.iResolution.x) - 0.5;
|
||||||
|
let widthOffset = -normalizedX * uniforms.sourceWidth * uniforms.iResolution.x;
|
||||||
|
|
||||||
|
let perpDir = vec2<f32>(-uniforms.lightDir.y, uniforms.lightDir.x);
|
||||||
|
let adjustedLightPos = uniforms.lightPos + perpDir * widthOffset;
|
||||||
|
|
||||||
|
let lightValue = lightStrengthCombined(adjustedLightPos, uniforms.lightDir, coord);
|
||||||
|
|
||||||
|
if (lightValue < 0.001) {
|
||||||
|
let particles = renderParticles(coord, adjustedLightPos, uniforms.lightDir);
|
||||||
|
if (particles < 0.001) {
|
||||||
|
return vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
let particleBrightness = particles * 1.8;
|
||||||
|
return vec4<f32>(uniforms.color * particleBrightness, particles * 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fragColor = vec4<f32>(lightValue, lightValue, lightValue, lightValue);
|
||||||
|
|
||||||
|
if (uniforms.noiseAmount > 0.01) {
|
||||||
|
let n = fastNoise(coord * 0.5 + uniforms.iTime * 0.5);
|
||||||
|
let grain = mix(1.0, n, uniforms.noiseAmount * 0.5);
|
||||||
|
fragColor = vec4<f32>(fragColor.rgb * grain, fragColor.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
let brightness = 1.0 - (coord.y / uniforms.iResolution.y);
|
||||||
|
fragColor = vec4<f32>(
|
||||||
|
fragColor.x * (0.15 + brightness * 0.85),
|
||||||
|
fragColor.y * (0.35 + brightness * 0.65),
|
||||||
|
fragColor.z * (0.55 + brightness * 0.45),
|
||||||
|
fragColor.a
|
||||||
|
);
|
||||||
|
|
||||||
|
if (abs(uniforms.saturation - 1.0) > 0.01) {
|
||||||
|
let gray = dot(fragColor.rgb, vec3<f32>(0.299, 0.587, 0.114));
|
||||||
|
fragColor = vec4<f32>(mix(vec3<f32>(gray), fragColor.rgb, uniforms.saturation), fragColor.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragColor = vec4<f32>(fragColor.rgb * uniforms.color, fragColor.a);
|
||||||
|
|
||||||
|
let particles = renderParticles(coord, adjustedLightPos, uniforms.lightDir);
|
||||||
|
if (particles > 0.001) {
|
||||||
|
let particleBrightness = particles * 1.8;
|
||||||
|
fragColor = vec4<f32>(fragColor.rgb + uniforms.color * particleBrightness, max(fragColor.a, particles * 0.9));
|
||||||
|
}
|
||||||
|
|
||||||
|
return fragColor;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const UNIFORM_BUFFER_SIZE = 144
|
||||||
|
|
||||||
|
function updateUniformBuffer(buffer: Float32Array, data: UniformData): void {
|
||||||
|
buffer[0] = data.iTime
|
||||||
|
buffer[2] = data.iResolution[0]
|
||||||
|
buffer[3] = data.iResolution[1]
|
||||||
|
buffer[4] = data.lightPos[0]
|
||||||
|
buffer[5] = data.lightPos[1]
|
||||||
|
buffer[6] = data.lightDir[0]
|
||||||
|
buffer[7] = data.lightDir[1]
|
||||||
|
buffer[8] = data.color[0]
|
||||||
|
buffer[9] = data.color[1]
|
||||||
|
buffer[10] = data.color[2]
|
||||||
|
buffer[11] = data.speed
|
||||||
|
buffer[12] = data.lightSpread
|
||||||
|
buffer[13] = data.lightLength
|
||||||
|
buffer[14] = data.sourceWidth
|
||||||
|
buffer[15] = data.pulsating
|
||||||
|
buffer[16] = data.pulsatingMin
|
||||||
|
buffer[17] = data.pulsatingMax
|
||||||
|
buffer[18] = data.fadeDistance
|
||||||
|
buffer[19] = data.saturation
|
||||||
|
buffer[20] = data.noiseAmount
|
||||||
|
buffer[21] = data.distortion
|
||||||
|
buffer[22] = data.particlesEnabled
|
||||||
|
buffer[23] = data.particleAmount
|
||||||
|
buffer[24] = data.particleSizeMin
|
||||||
|
buffer[25] = data.particleSizeMax
|
||||||
|
buffer[26] = data.particleSpeed
|
||||||
|
buffer[27] = data.particleOpacity
|
||||||
|
buffer[28] = data.particleDrift
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Spotlight(props: SpotlightProps) {
|
||||||
|
let containerRef: HTMLDivElement | undefined
|
||||||
|
let canvasRef: HTMLCanvasElement | null = null
|
||||||
|
let deviceRef: GPUDevice | null = null
|
||||||
|
let contextRef: GPUCanvasContext | null = null
|
||||||
|
let pipelineRef: GPURenderPipeline | null = null
|
||||||
|
let uniformBufferRef: GPUBuffer | null = null
|
||||||
|
let bindGroupRef: GPUBindGroup | null = null
|
||||||
|
let animationIdRef: number | null = null
|
||||||
|
let cleanupFunctionRef: (() => void) | null = null
|
||||||
|
let uniformDataRef: UniformData | null = null
|
||||||
|
let uniformArrayRef: Float32Array | null = null
|
||||||
|
let configRef: SpotlightConfig = props.config()
|
||||||
|
let frameCount = 0
|
||||||
|
|
||||||
|
const [isVisible, setIsVisible] = createSignal(false)
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
configRef = props.config()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!containerRef) return
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
const entry = entries[0]
|
||||||
|
setIsVisible(entry.isIntersecting)
|
||||||
|
},
|
||||||
|
{ threshold: 0.1 },
|
||||||
|
)
|
||||||
|
|
||||||
|
observer.observe(containerRef)
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
observer.disconnect()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const visible = isVisible()
|
||||||
|
const config = props.config()
|
||||||
|
if (!visible || !containerRef) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanupFunctionRef) {
|
||||||
|
cleanupFunctionRef()
|
||||||
|
cleanupFunctionRef = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeWebGPU = async () => {
|
||||||
|
if (!containerRef) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||||
|
|
||||||
|
if (!containerRef) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!navigator.gpu) {
|
||||||
|
console.warn("WebGPU is not supported in this browser")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const adapter = await navigator.gpu.requestAdapter({
|
||||||
|
powerPreference: "high-performance",
|
||||||
|
})
|
||||||
|
if (!adapter) {
|
||||||
|
console.warn("Failed to get WebGPU adapter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const device = await adapter.requestDevice()
|
||||||
|
deviceRef = device
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas")
|
||||||
|
canvas.style.width = "100%"
|
||||||
|
canvas.style.height = "100%"
|
||||||
|
canvasRef = canvas
|
||||||
|
|
||||||
|
while (containerRef.firstChild) {
|
||||||
|
containerRef.removeChild(containerRef.firstChild)
|
||||||
|
}
|
||||||
|
containerRef.appendChild(canvas)
|
||||||
|
|
||||||
|
const context = canvas.getContext("webgpu")
|
||||||
|
if (!context) {
|
||||||
|
console.warn("Failed to get WebGPU context")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contextRef = context
|
||||||
|
|
||||||
|
const presentationFormat = navigator.gpu.getPreferredCanvasFormat()
|
||||||
|
context.configure({
|
||||||
|
device,
|
||||||
|
format: presentationFormat,
|
||||||
|
alphaMode: "premultiplied",
|
||||||
|
})
|
||||||
|
|
||||||
|
const shaderModule = device.createShaderModule({
|
||||||
|
code: WGSL_SHADER,
|
||||||
|
})
|
||||||
|
|
||||||
|
const uniformBuffer = device.createBuffer({
|
||||||
|
size: UNIFORM_BUFFER_SIZE,
|
||||||
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||||
|
})
|
||||||
|
uniformBufferRef = uniformBuffer
|
||||||
|
|
||||||
|
const bindGroupLayout = device.createBindGroupLayout({
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
binding: 0,
|
||||||
|
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
||||||
|
buffer: { type: "uniform" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const bindGroup = device.createBindGroup({
|
||||||
|
layout: bindGroupLayout,
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
binding: 0,
|
||||||
|
resource: { buffer: uniformBuffer },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
bindGroupRef = bindGroup
|
||||||
|
|
||||||
|
const pipelineLayout = device.createPipelineLayout({
|
||||||
|
bindGroupLayouts: [bindGroupLayout],
|
||||||
|
})
|
||||||
|
|
||||||
|
const pipeline = device.createRenderPipeline({
|
||||||
|
layout: pipelineLayout,
|
||||||
|
vertex: {
|
||||||
|
module: shaderModule,
|
||||||
|
entryPoint: "vertexMain",
|
||||||
|
},
|
||||||
|
fragment: {
|
||||||
|
module: shaderModule,
|
||||||
|
entryPoint: "fragmentMain",
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
format: presentationFormat,
|
||||||
|
blend: {
|
||||||
|
color: {
|
||||||
|
srcFactor: "src-alpha",
|
||||||
|
dstFactor: "one-minus-src-alpha",
|
||||||
|
operation: "add",
|
||||||
|
},
|
||||||
|
alpha: {
|
||||||
|
srcFactor: "one",
|
||||||
|
dstFactor: "one-minus-src-alpha",
|
||||||
|
operation: "add",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
primitive: {
|
||||||
|
topology: "triangle-list",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
pipelineRef = pipeline
|
||||||
|
|
||||||
|
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef
|
||||||
|
const dpr = Math.min(window.devicePixelRatio, 2)
|
||||||
|
const w = wCSS * dpr
|
||||||
|
const h = hCSS * dpr
|
||||||
|
const { anchor, dir } = getAnchorAndDir(config.placement, w, h)
|
||||||
|
|
||||||
|
uniformDataRef = {
|
||||||
|
iTime: 0,
|
||||||
|
iResolution: [w, h],
|
||||||
|
lightPos: anchor,
|
||||||
|
lightDir: dir,
|
||||||
|
color: hexToRgb(config.color),
|
||||||
|
speed: config.speed,
|
||||||
|
lightSpread: config.spread,
|
||||||
|
lightLength: config.length,
|
||||||
|
sourceWidth: config.width,
|
||||||
|
pulsating: config.pulsating !== false ? 1.0 : 0.0,
|
||||||
|
pulsatingMin: config.pulsating !== false ? config.pulsating[0] : 1.0,
|
||||||
|
pulsatingMax: config.pulsating !== false ? config.pulsating[1] : 1.0,
|
||||||
|
fadeDistance: config.distance,
|
||||||
|
saturation: config.saturation,
|
||||||
|
noiseAmount: config.noiseAmount,
|
||||||
|
distortion: config.distortion,
|
||||||
|
particlesEnabled: config.particles.enabled ? 1.0 : 0.0,
|
||||||
|
particleAmount: config.particles.amount,
|
||||||
|
particleSizeMin: config.particles.size[0],
|
||||||
|
particleSizeMax: config.particles.size[1],
|
||||||
|
particleSpeed: config.particles.speed,
|
||||||
|
particleOpacity: config.particles.opacity,
|
||||||
|
particleDrift: config.particles.drift,
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePlacement = () => {
|
||||||
|
if (!containerRef || !canvasRef || !uniformDataRef) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const dpr = Math.min(window.devicePixelRatio, 2)
|
||||||
|
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef
|
||||||
|
const w = Math.floor(wCSS * dpr)
|
||||||
|
const h = Math.floor(hCSS * dpr)
|
||||||
|
|
||||||
|
canvasRef.width = w
|
||||||
|
canvasRef.height = h
|
||||||
|
|
||||||
|
uniformDataRef.iResolution = [w, h]
|
||||||
|
|
||||||
|
const { anchor, dir } = getAnchorAndDir(configRef.placement, w, h)
|
||||||
|
uniformDataRef.lightPos = anchor
|
||||||
|
uniformDataRef.lightDir = dir
|
||||||
|
}
|
||||||
|
|
||||||
|
const loop = (t: number) => {
|
||||||
|
if (!deviceRef || !contextRef || !pipelineRef || !uniformBufferRef || !bindGroupRef || !uniformDataRef) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeSeconds = t * 0.001
|
||||||
|
uniformDataRef.iTime = timeSeconds
|
||||||
|
frameCount++
|
||||||
|
|
||||||
|
if (props.onAnimationFrame && frameCount % 2 === 0) {
|
||||||
|
const pulsatingMin = configRef.pulsating !== false ? configRef.pulsating[0] : 1.0
|
||||||
|
const pulsatingMax = configRef.pulsating !== false ? configRef.pulsating[1] : 1.0
|
||||||
|
const pulseCenter = (pulsatingMin + pulsatingMax) * 0.5
|
||||||
|
const pulseAmplitude = (pulsatingMax - pulsatingMin) * 0.5
|
||||||
|
const pulseValue =
|
||||||
|
configRef.pulsating !== false
|
||||||
|
? pulseCenter + pulseAmplitude * Math.sin(timeSeconds * configRef.speed * 3.0)
|
||||||
|
: 1.0
|
||||||
|
|
||||||
|
const baseIntensity1 = 0.45 + 0.15 * Math.sin(timeSeconds * configRef.speed * 1.5)
|
||||||
|
const baseIntensity2 = 0.3 + 0.2 * Math.cos(timeSeconds * configRef.speed * 1.1)
|
||||||
|
const intensity = Math.max((baseIntensity1 + baseIntensity2) * pulseValue, 0.55)
|
||||||
|
|
||||||
|
props.onAnimationFrame({
|
||||||
|
time: timeSeconds,
|
||||||
|
intensity,
|
||||||
|
pulseValue: Math.max(pulseValue, 0.9),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!uniformArrayRef) {
|
||||||
|
uniformArrayRef = new Float32Array(36)
|
||||||
|
}
|
||||||
|
updateUniformBuffer(uniformArrayRef, uniformDataRef)
|
||||||
|
deviceRef.queue.writeBuffer(uniformBufferRef, 0, uniformArrayRef.buffer)
|
||||||
|
|
||||||
|
const commandEncoder = deviceRef.createCommandEncoder()
|
||||||
|
|
||||||
|
const textureView = contextRef.getCurrentTexture().createView()
|
||||||
|
|
||||||
|
const renderPass = commandEncoder.beginRenderPass({
|
||||||
|
colorAttachments: [
|
||||||
|
{
|
||||||
|
view: textureView,
|
||||||
|
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
||||||
|
loadOp: "clear",
|
||||||
|
storeOp: "store",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
renderPass.setPipeline(pipelineRef)
|
||||||
|
renderPass.setBindGroup(0, bindGroupRef)
|
||||||
|
renderPass.draw(3)
|
||||||
|
renderPass.end()
|
||||||
|
|
||||||
|
deviceRef.queue.submit([commandEncoder.finish()])
|
||||||
|
|
||||||
|
animationIdRef = requestAnimationFrame(loop)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("WebGPU rendering error:", error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("resize", updatePlacement)
|
||||||
|
updatePlacement()
|
||||||
|
animationIdRef = requestAnimationFrame(loop)
|
||||||
|
|
||||||
|
cleanupFunctionRef = () => {
|
||||||
|
if (animationIdRef) {
|
||||||
|
cancelAnimationFrame(animationIdRef)
|
||||||
|
animationIdRef = null
|
||||||
|
}
|
||||||
|
|
||||||
|
window.removeEventListener("resize", updatePlacement)
|
||||||
|
|
||||||
|
if (uniformBufferRef) {
|
||||||
|
uniformBufferRef.destroy()
|
||||||
|
uniformBufferRef = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceRef) {
|
||||||
|
deviceRef.destroy()
|
||||||
|
deviceRef = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canvasRef && canvasRef.parentNode) {
|
||||||
|
canvasRef.parentNode.removeChild(canvasRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
canvasRef = null
|
||||||
|
contextRef = null
|
||||||
|
pipelineRef = null
|
||||||
|
bindGroupRef = null
|
||||||
|
uniformDataRef = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeWebGPU()
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
if (cleanupFunctionRef) {
|
||||||
|
cleanupFunctionRef()
|
||||||
|
cleanupFunctionRef = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!uniformDataRef || !containerRef) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = props.config()
|
||||||
|
|
||||||
|
uniformDataRef.color = hexToRgb(config.color)
|
||||||
|
uniformDataRef.speed = config.speed
|
||||||
|
uniformDataRef.lightSpread = config.spread
|
||||||
|
uniformDataRef.lightLength = config.length
|
||||||
|
uniformDataRef.sourceWidth = config.width
|
||||||
|
uniformDataRef.pulsating = config.pulsating !== false ? 1.0 : 0.0
|
||||||
|
uniformDataRef.pulsatingMin = config.pulsating !== false ? config.pulsating[0] : 1.0
|
||||||
|
uniformDataRef.pulsatingMax = config.pulsating !== false ? config.pulsating[1] : 1.0
|
||||||
|
uniformDataRef.fadeDistance = config.distance
|
||||||
|
uniformDataRef.saturation = config.saturation
|
||||||
|
uniformDataRef.noiseAmount = config.noiseAmount
|
||||||
|
uniformDataRef.distortion = config.distortion
|
||||||
|
uniformDataRef.particlesEnabled = config.particles.enabled ? 1.0 : 0.0
|
||||||
|
uniformDataRef.particleAmount = config.particles.amount
|
||||||
|
uniformDataRef.particleSizeMin = config.particles.size[0]
|
||||||
|
uniformDataRef.particleSizeMax = config.particles.size[1]
|
||||||
|
uniformDataRef.particleSpeed = config.particles.speed
|
||||||
|
uniformDataRef.particleOpacity = config.particles.opacity
|
||||||
|
uniformDataRef.particleDrift = config.particles.drift
|
||||||
|
|
||||||
|
const dpr = Math.min(window.devicePixelRatio, 2)
|
||||||
|
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef
|
||||||
|
const { anchor, dir } = getAnchorAndDir(config.placement, wCSS * dpr, hCSS * dpr)
|
||||||
|
uniformDataRef.lightPos = anchor
|
||||||
|
uniformDataRef.lightDir = dir
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
class={`spotlight-container ${props.class ?? ""}`.trim()}
|
||||||
|
style={{ opacity: props.config().opacity }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@ export const config = {
|
|||||||
github: {
|
github: {
|
||||||
repoUrl: "https://github.com/anomalyco/opencode",
|
repoUrl: "https://github.com/anomalyco/opencode",
|
||||||
starsFormatted: {
|
starsFormatted: {
|
||||||
compact: "60K",
|
compact: "70K",
|
||||||
full: "60,000",
|
full: "70,000",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export const config = {
|
|||||||
// Static stats (used on landing page)
|
// Static stats (used on landing page)
|
||||||
stats: {
|
stats: {
|
||||||
contributors: "500",
|
contributors: "500",
|
||||||
commits: "6,500",
|
commits: "7,000",
|
||||||
monthlyUsers: "650,000",
|
monthlyUsers: "650,000",
|
||||||
},
|
},
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ export const github = query(async () => {
|
|||||||
fetch(`${apiBaseUrl}/releases`, { headers }).then((res) => res.json()),
|
fetch(`${apiBaseUrl}/releases`, { headers }).then((res) => res.json()),
|
||||||
fetch(`${apiBaseUrl}/contributors?per_page=1`, { headers }),
|
fetch(`${apiBaseUrl}/contributors?per_page=1`, { headers }),
|
||||||
])
|
])
|
||||||
|
if (!Array.isArray(releases) || releases.length === 0) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
const [release] = releases
|
const [release] = releases
|
||||||
const contributorCount = Number.parseInt(
|
const linkHeader = contributors.headers.get("Link")
|
||||||
contributors.headers
|
const contributorCount = linkHeader
|
||||||
.get("Link")!
|
? Number.parseInt(linkHeader.match(/&page=(\d+)>; rel="last"/)?.at(1) ?? "0")
|
||||||
.match(/&page=(\d+)>; rel="last"/)!
|
: 0
|
||||||
.at(1)!,
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
stars: meta.stargazers_count,
|
stars: meta.stargazers_count,
|
||||||
release: {
|
release: {
|
||||||
|
|||||||
@@ -1,3 +1,114 @@
|
|||||||
|
::view-transition-group(*) {
|
||||||
|
animation-duration: 250ms;
|
||||||
|
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-old(root),
|
||||||
|
::view-transition-new(root) {
|
||||||
|
animation-duration: 250ms;
|
||||||
|
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-image-pair(root) {
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-old(root) {
|
||||||
|
animation: none;
|
||||||
|
mix-blend-mode: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-new(root) {
|
||||||
|
animation: none;
|
||||||
|
mix-blend-mode: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-out {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in-up {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(8px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes reveal-terms {
|
||||||
|
from {
|
||||||
|
mask-position: 0% 200%;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
mask-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes hide-terms {
|
||||||
|
from {
|
||||||
|
mask-position: 0% 50%;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
mask-position: 0% 200%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-old(terms-20),
|
||||||
|
::view-transition-old(terms-100),
|
||||||
|
::view-transition-old(terms-200) {
|
||||||
|
mask-image: linear-gradient(to bottom, transparent, black 25% 75%, transparent);
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: 100% 200%;
|
||||||
|
animation: hide-terms 200ms cubic-bezier(0.25, 0, 0.5, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-new(terms-20),
|
||||||
|
::view-transition-new(terms-100),
|
||||||
|
::view-transition-new(terms-200) {
|
||||||
|
mask-image: linear-gradient(to bottom, transparent, black 25% 75%, transparent);
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: 0% 200%;
|
||||||
|
mask-size: 100% 200%;
|
||||||
|
animation: reveal-terms 300ms cubic-bezier(0.25, 0, 0.5, 1) 50ms forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-old(actions-20),
|
||||||
|
::view-transition-old(actions-100),
|
||||||
|
::view-transition-old(actions-200) {
|
||||||
|
animation: fade-out 80ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-new(actions-20),
|
||||||
|
::view-transition-new(actions-100),
|
||||||
|
::view-transition-new(actions-200) {
|
||||||
|
animation: fade-in-up 300ms cubic-bezier(0.16, 1, 0.3, 1) 300ms forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-group(card-20),
|
||||||
|
::view-transition-group(card-100),
|
||||||
|
::view-transition-group(card-200) {
|
||||||
|
animation-duration: 250ms;
|
||||||
|
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
[data-page="black"] {
|
[data-page="black"] {
|
||||||
background: #000;
|
background: #000;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -8,13 +119,18 @@
|
|||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
[data-component="header-gradient"] {
|
[data-component="header-logo"] {
|
||||||
|
filter: drop-shadow(0 8px 24px rgba(0, 0, 0, 0.25)) drop-shadow(0 4px 16px rgba(0, 0, 0, 0.1));
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-light-rays {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
inset: 0 0 auto 0;
|
||||||
left: 0;
|
height: 30dvh;
|
||||||
width: 100%;
|
pointer-events: none;
|
||||||
height: 288px;
|
z-index: 0;
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0%, rgba(0, 0, 0, 0) 100%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="header"] {
|
[data-component="header"] {
|
||||||
@@ -48,27 +164,35 @@
|
|||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
color: rgba(255, 255, 255, 0.92);
|
color: rgba(255, 255, 255, 0.92);
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 160%;
|
line-height: 1.45;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
font-size: 22px;
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: rgba(255, 255, 255, 0.59);
|
color: rgba(255, 255, 255, 0.59);
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 160%;
|
line-height: 1.45;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
font-size: 22px;
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,30 +200,36 @@
|
|||||||
[data-slot="hero-black"] {
|
[data-slot="hero-black"] {
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
margin-top: 60px;
|
margin-top: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
--hero-black-fill-from: hsl(0 0% 100%);
|
|
||||||
--hero-black-fill-to: hsl(0 0% 100% / 0%);
|
|
||||||
--hero-black-stroke-from: hsl(0 0% 100% / 60%);
|
|
||||||
--hero-black-stroke-to: hsl(0 0% 100% / 0%);
|
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 590px;
|
max-width: 590px;
|
||||||
height: auto;
|
height: auto;
|
||||||
filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.1));
|
overflow: visible;
|
||||||
|
filter: drop-shadow(0 0 20px rgba(255, 255, 255, calc(0.1 + var(--hero-black-glow-intensity, 0) * 0.15)))
|
||||||
|
drop-shadow(0 -5px 30px rgba(255, 255, 255, calc(var(--hero-black-glow-intensity, 0) * 0.2)));
|
||||||
mask-image: linear-gradient(to bottom, black, transparent);
|
mask-image: linear-gradient(to bottom, black, transparent);
|
||||||
stroke-width: 1.5;
|
stroke-width: 1.5;
|
||||||
|
|
||||||
[data-slot="black-fill"] {
|
[data-slot="black-base"] {
|
||||||
fill: url(#hero-black-fill-gradient);
|
fill: url(#hero-black-fill-gradient);
|
||||||
|
stroke: url(#hero-black-stroke-gradient);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="black-stroke"] {
|
[data-slot="black-glow"] {
|
||||||
fill: url(#hero-black-stroke-gradient);
|
fill: url(#hero-black-top-glow);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="black-shimmer"] {
|
||||||
|
fill: url(#hero-black-shimmer-gradient);
|
||||||
|
pointer-events: none;
|
||||||
|
mix-blend-mode: overlay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,14 +237,14 @@
|
|||||||
[data-slot="cta"] {
|
[data-slot="cta"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 32px;
|
gap: 16px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: -32px;
|
margin-top: -40px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
margin-top: -16px;
|
margin-top: -20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="heading"] {
|
[data-slot="heading"] {
|
||||||
@@ -129,7 +259,6 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="subheading"] {
|
[data-slot="subheading"] {
|
||||||
color: rgba(255, 255, 255, 0.59);
|
color: rgba(255, 255, 255, 0.59);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
@@ -142,7 +271,6 @@
|
|||||||
line-height: 160%;
|
line-height: 160%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="button"] {
|
[data-slot="button"] {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@@ -154,7 +282,7 @@
|
|||||||
background: rgba(255, 255, 255, 0.92);
|
background: rgba(255, 255, 255, 0.92);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #000;
|
color: #000;
|
||||||
font-family: var(--font-mono);
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -168,16 +296,14 @@
|
|||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="back-soon"] {
|
[data-slot="back-soon"] {
|
||||||
color: rgba(255, 255, 255, 0.59);
|
color: rgba(255, 255, 255, 0.59);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 160%;
|
line-height: 160%; /* 20.8px */
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="follow-us"] {
|
[data-slot="follow-us"] {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@@ -201,98 +327,38 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 680px;
|
max-width: 660px;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
box-sizing: border-box;
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="pricing-card"] {
|
[data-slot="pricing-card"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
gap: 12px;
|
||||||
|
padding: 24px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.17);
|
border: 1px solid rgba(255, 255, 255, 0.17);
|
||||||
border-radius: 5px;
|
background: black;
|
||||||
|
background-clip: padding-box;
|
||||||
|
border-radius: 4px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background: #000;
|
transition: border-color 0.15s ease;
|
||||||
|
cursor: pointer;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
|
||||||
transition: border-color 200ms ease;
|
|
||||||
|
|
||||||
&:hover:not([data-selected="true"]) {
|
@media (max-width: 480px) {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:active) {
|
||||||
border-color: rgba(255, 255, 255, 0.35);
|
border-color: rgba(255, 255, 255, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="card-trigger"] {
|
[data-slot="icon"] {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
width: 100%;
|
|
||||||
padding: 24px;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: inherit;
|
|
||||||
text-align: left;
|
|
||||||
transition: padding 200ms ease;
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-selected="true"] {
|
|
||||||
[data-slot="amount"] {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="terms"] {
|
|
||||||
animation: reveal 500ms cubic-bezier(0.25, 0, 0.5, 1) forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="actions"] {
|
|
||||||
[data-slot="continue"] {
|
|
||||||
animation-delay: 200ms;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-collapsed="true"] {
|
|
||||||
[data-slot="card-trigger"] {
|
|
||||||
padding: 20px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="plan-header"] {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="amount"] {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-selected="false"][data-collapsed="false"] {
|
|
||||||
[data-slot="amount"] {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="period"],
|
|
||||||
[data-slot="multiplier"] {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="plan-header"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
gap: 12px;
|
|
||||||
transition: gap 200ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="plan-icon"] {
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
color: rgba(255, 255, 255, 0.59);
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="price"] {
|
[data-slot="price"] {
|
||||||
@@ -300,31 +366,81 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
line-height: 24px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="amount"] {
|
[data-slot="amount"] {
|
||||||
color: rgba(255, 255, 255, 0.92);
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 24px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="content"] {
|
[data-slot="period"] {
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="period"],
|
|
||||||
[data-slot="multiplier"] {
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="billing"] {
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
color: rgba(255, 255, 255, 0.59);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="multiplier"] {
|
[data-slot="multiplier"] {
|
||||||
color: rgba(255, 255, 255, 0.39);
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "·";
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="selected-plan"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 32px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 660px;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
background-color: rgba(0, 0, 0, 0.75);
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
margin: 0 20px;
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="selected-card"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.17);
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
[data-slot="icon"] {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="price"] {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="amount"] {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="period"] {
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="multiplier"] {
|
||||||
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "·";
|
content: "·";
|
||||||
@@ -334,32 +450,30 @@
|
|||||||
|
|
||||||
[data-slot="terms"] {
|
[data-slot="terms"] {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0 24px 24px 24px;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100%;
|
|
||||||
opacity: 0;
|
|
||||||
mask-image: linear-gradient(to bottom, black 0%, black 50%, transparent 100%);
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: 100% 200%;
|
|
||||||
mask-position: 0% 320%;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="terms"] li {
|
li {
|
||||||
color: rgba(255, 255, 255, 0.59);
|
color: rgba(255, 255, 255, 0.59);
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
line-height: 1.2;
|
line-height: 1.5;
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "▪";
|
content: "▪";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
color: rgba(255, 255, 255, 0.39);
|
color: rgba(255, 255, 255, 0.39);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,48 +481,45 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
padding: 0 24px 24px 24px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="actions"] button,
|
button,
|
||||||
[data-slot="actions"] a {
|
a {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition-property: background-color, border-color;
|
|
||||||
transition-duration: 200ms;
|
|
||||||
transition-timing-function: cubic-bezier(0.25, 0, 0.5, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="cancel"] {
|
|
||||||
border: 1px solid var(--border-base, rgba(255, 255, 255, 0.17));
|
|
||||||
background: var(--surface-raised-base, rgba(255, 255, 255, 0.06));
|
|
||||||
background-clip: border-box;
|
|
||||||
color: rgba(255, 255, 255, 0.92);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--surface-raised-base, rgba(255, 255, 255, 0.08));
|
|
||||||
border-color: rgba(255, 255, 255, 0.25);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="continue"] {
|
[data-slot="cancel"] {
|
||||||
background: rgb(255, 255, 255);
|
background: rgba(255, 255, 255, 0.05);
|
||||||
color: rgb(0, 0, 0);
|
border: 1px solid rgba(255, 255, 255, 0.17);
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
transition-property: background-color, border-color;
|
||||||
|
transition-duration: 150ms;
|
||||||
|
transition-timing-function: cubic-bezier(0.25, 0, 0.5, 1);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgb(255, 255, 255, 0.9);
|
background-color: rgba(255, 255, 255, 0.08);
|
||||||
|
border-color: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="continue"] {
|
||||||
|
background: rgb(255, 255, 255);
|
||||||
|
color: rgb(0, 0, 0);
|
||||||
|
transition: background-color 150ms cubic-bezier(0.25, 0, 0.5, 1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,7 +530,8 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 160%;
|
line-height: 160%; /* 20.8px */
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: rgba(255, 255, 255, 0.39);
|
color: rgba(255, 255, 255, 0.39);
|
||||||
@@ -436,7 +548,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: -18px;
|
margin-top: -18px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 540px;
|
max-width: 660px;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
@@ -469,6 +581,8 @@
|
|||||||
|
|
||||||
[data-slot="icon"] {
|
[data-slot="icon"] {
|
||||||
color: rgba(255, 255, 255, 0.59);
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
isolation: isolate;
|
||||||
|
transform: translateZ(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="price"] {
|
[data-slot="price"] {
|
||||||
@@ -491,7 +605,7 @@
|
|||||||
|
|
||||||
[data-slot="multiplier"] {
|
[data-slot="multiplier"] {
|
||||||
color: rgba(255, 255, 255, 0.39);
|
color: rgba(255, 255, 255, 0.39);
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "·";
|
content: "·";
|
||||||
@@ -510,39 +624,6 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="tax-id-section"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
[data-slot="label"] {
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="input"] {
|
|
||||||
width: 100%;
|
|
||||||
height: 44px;
|
|
||||||
padding: 0 12px;
|
|
||||||
background: #1a1a1a;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.17);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #ffffff;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 14px;
|
|
||||||
outline: none;
|
|
||||||
transition: border-color 0.15s ease;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: rgba(255, 255, 255, 0.35);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="checkout-form"] {
|
[data-slot="checkout-form"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -583,52 +664,6 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="success"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 24px;
|
|
||||||
|
|
||||||
[data-slot="title"] {
|
|
||||||
color: rgba(255, 255, 255, 0.92);
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 400;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="details"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt {
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
color: rgba(255, 255, 255, 0.92);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
margin: 0;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="charge-notice"] {
|
|
||||||
color: #d4a500;
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="loading"] {
|
[data-slot="loading"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -645,6 +680,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
view-transition-name: fine-print;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: rgba(255, 255, 255, 0.39);
|
color: rgba(255, 255, 255, 0.39);
|
||||||
@@ -739,7 +775,7 @@
|
|||||||
span,
|
span,
|
||||||
a {
|
a {
|
||||||
color: rgba(255, 255, 255, 0.39);
|
color: rgba(255, 255, 255, 0.39);
|
||||||
font-family: var(--font-mono);
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -749,7 +785,7 @@
|
|||||||
|
|
||||||
[data-slot="github-stars"] {
|
[data-slot="github-stars"] {
|
||||||
color: rgba(255, 255, 255, 0.25);
|
color: rgba(255, 255, 255, 0.25);
|
||||||
font-family: var(--font-mono);
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -764,10 +800,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="anomaly-alt"] {
|
[data-slot="anomaly-alt"] {
|
||||||
color: rgba(255, 255, 255, 0.39);
|
color: rgba(255, 255, 255, 0.39);
|
||||||
font-family: var(--font-mono);
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -777,7 +812,7 @@
|
|||||||
|
|
||||||
a {
|
a {
|
||||||
color: rgba(255, 255, 255, 0.39);
|
color: rgba(255, 255, 255, 0.39);
|
||||||
font-family: "JetBrains Mono Nerd Font", monospace;
|
font-family: "JetBrains Mono Nerd Font";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -791,15 +826,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::view-transition-group(*) {
|
|
||||||
animation-duration: 200ms;
|
|
||||||
animation-timing-function: cubic-bezier(0.25, 0, 0.5, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes reveal {
|
|
||||||
100% {
|
|
||||||
mask-position: 0% 0%;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { A, createAsync, RouteSectionProps } from "@solidjs/router"
|
import { A, createAsync, RouteSectionProps } from "@solidjs/router"
|
||||||
import { Title, Meta, Link } from "@solidjs/meta"
|
import { Title, Meta, Link } from "@solidjs/meta"
|
||||||
import { createMemo } from "solid-js"
|
import { createMemo, createSignal } from "solid-js"
|
||||||
import { github } from "~/lib/github"
|
import { github } from "~/lib/github"
|
||||||
import { config } from "~/config"
|
import { config } from "~/config"
|
||||||
|
import Spotlight, { defaultConfig, type SpotlightAnimationState } from "~/component/spotlight"
|
||||||
import "./black.css"
|
import "./black.css"
|
||||||
|
|
||||||
export default function BlackLayout(props: RouteSectionProps) {
|
export default function BlackLayout(props: RouteSectionProps) {
|
||||||
@@ -16,6 +17,50 @@ export default function BlackLayout(props: RouteSectionProps) {
|
|||||||
: config.github.starsFormatted.compact,
|
: config.github.starsFormatted.compact,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [spotlightAnimationState, setSpotlightAnimationState] = createSignal<SpotlightAnimationState>({
|
||||||
|
time: 0,
|
||||||
|
intensity: 0.5,
|
||||||
|
pulseValue: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const svgLightingValues = createMemo(() => {
|
||||||
|
const state = spotlightAnimationState()
|
||||||
|
const t = state.time
|
||||||
|
|
||||||
|
const wave1 = Math.sin(t * 1.5) * 0.5 + 0.5
|
||||||
|
const wave2 = Math.sin(t * 2.3 + 1.2) * 0.5 + 0.5
|
||||||
|
const wave3 = Math.sin(t * 0.8 + 2.5) * 0.5 + 0.5
|
||||||
|
|
||||||
|
const shimmerPos = Math.sin(t * 0.7) * 0.5 + 0.5
|
||||||
|
const glowIntensity = Math.max(state.intensity * state.pulseValue * 0.35, 0.15)
|
||||||
|
const fillOpacity = Math.max(0.1 + wave1 * 0.08 * state.pulseValue, 0.12)
|
||||||
|
const strokeBrightness = Math.max(55 + wave2 * 25 * state.pulseValue, 60)
|
||||||
|
|
||||||
|
const shimmerIntensity = Math.max(wave3 * 0.15 * state.pulseValue, 0.08)
|
||||||
|
|
||||||
|
return {
|
||||||
|
glowIntensity,
|
||||||
|
fillOpacity,
|
||||||
|
strokeBrightness,
|
||||||
|
shimmerPos,
|
||||||
|
shimmerIntensity,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const svgLightingStyle = createMemo(() => {
|
||||||
|
const values = svgLightingValues()
|
||||||
|
return {
|
||||||
|
"--hero-black-glow-intensity": values.glowIntensity.toFixed(3),
|
||||||
|
"--hero-black-stroke-brightness": `${values.strokeBrightness.toFixed(0)}%`,
|
||||||
|
} as Record<string, string>
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleAnimationFrame = (state: SpotlightAnimationState) => {
|
||||||
|
setSpotlightAnimationState(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
const spotlightConfig = () => defaultConfig
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-page="black">
|
<div data-page="black">
|
||||||
<Title>OpenCode Black | Access all the world's best coding models</Title>
|
<Title>OpenCode Black | Access all the world's best coding models</Title>
|
||||||
@@ -39,7 +84,9 @@ export default function BlackLayout(props: RouteSectionProps) {
|
|||||||
content="Get access to Claude, GPT, Gemini and more with OpenCode Black subscription plans."
|
content="Get access to Claude, GPT, Gemini and more with OpenCode Black subscription plans."
|
||||||
/>
|
/>
|
||||||
<Meta name="twitter:image" content="/social-share-black.png" />
|
<Meta name="twitter:image" content="/social-share-black.png" />
|
||||||
<div data-component="header-gradient" />
|
|
||||||
|
<Spotlight config={spotlightConfig} class="header-spotlight" onAnimationFrame={handleAnimationFrame} />
|
||||||
|
|
||||||
<header data-component="header">
|
<header data-component="header">
|
||||||
<A href="/" data-component="header-logo">
|
<A href="/" data-component="header-logo">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="179" height="32" viewBox="0 0 179 32" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="179" height="32" viewBox="0 0 179 32" fill="none">
|
||||||
@@ -112,15 +159,8 @@ export default function BlackLayout(props: RouteSectionProps) {
|
|||||||
<h1>Access all the world's best coding models</h1>
|
<h1>Access all the world's best coding models</h1>
|
||||||
<p>Including Claude, GPT, Gemini and more</p>
|
<p>Including Claude, GPT, Gemini and more</p>
|
||||||
</div>
|
</div>
|
||||||
<div data-slot="hero-black">
|
<div data-slot="hero-black" style={svgLightingStyle()}>
|
||||||
<svg width="591" height="90" viewBox="0 0 591 90" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="591" height="90" viewBox="0 0 591 90" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
|
||||||
d="M425.56 0.75C429.464 0.750017 432.877 1.27807 435.78 2.35645C438.656 3.42455 441.138 4.86975 443.215 6.69727C445.268 8.50382 446.995 10.5587 448.394 12.8604C449.77 15.0464 450.986 17.2741 452.04 19.5439L452.357 20.2275L451.672 20.542L443.032 24.502L442.311 24.833L442.021 24.0938C441.315 22.2906 440.494 20.6079 439.557 19.0459L439.552 19.0391L439.548 19.0322C438.626 17.419 437.517 16.0443 436.223 14.9023L436.206 14.8867L436.189 14.8701C434.989 13.6697 433.518 12.7239 431.766 12.0381L431.755 12.0342V12.0332C430.111 11.3607 428.053 11.0098 425.56 11.0098C419.142 11.0098 414.433 13.4271 411.308 18.2295C408.212 23.109 406.629 29.6717 406.629 37.9805V51.6602C406.629 59.9731 408.214 66.5377 411.312 71.418C414.438 76.2157 419.145 78.6299 425.56 78.6299C428.054 78.6299 430.111 78.2782 431.756 77.6055L431.766 77.6016L432.413 77.333C433.893 76.6811 435.154 75.8593 436.206 74.873C437.512 73.644 438.625 72.2626 439.548 70.7275C440.489 69.0801 441.314 67.3534 442.021 65.5469L442.311 64.8076L443.032 65.1387L451.672 69.0986L452.348 69.4082L452.044 70.0869C450.99 72.439 449.773 74.7099 448.395 76.8994C446.995 79.1229 445.266 81.1379 443.215 82.9434C441.138 84.7708 438.656 86.2151 435.78 87.2832C432.877 88.3616 429.464 88.8896 425.56 88.8896C415.111 88.8896 407.219 85.0777 402.019 77.4004L402.016 77.3965C396.939 69.7818 394.449 58.891 394.449 44.8203C394.449 30.7495 396.939 19.8589 402.016 12.2441L402.019 12.2393C407.219 4.56202 415.111 0.75 425.56 0.75ZM29.9404 2.19043C37.2789 2.19051 43.125 4.19131 47.3799 8.2793C51.6307 12.3635 53.7305 17.8115 53.7305 24.54C53.7305 29.6953 52.4605 33.8451 49.835 36.8994L49.8359 36.9004C47.7064 39.4558 45.0331 41.367 41.835 42.6445C45.893 43.8751 49.3115 45.9006 52.0703 48.7295C55.2954 51.9546 56.8496 56.6143 56.8496 62.5801C56.8496 66.0251 56.2751 69.2753 55.1211 72.3252C53.9689 75.3702 52.3185 78.014 50.1689 80.249L50.1699 80.25C48.0996 82.4858 45.6172 84.2628 42.7314 85.582L42.7227 85.5859C39.9002 86.8312 36.8362 87.4502 33.54 87.4502H0.75V2.19043H29.9404ZM148.123 2.19043V77.1904H187.843V87.4502H136.543V2.19043H148.123ZM298.121 2.19043L298.283 2.71973L323.963 86.4805L324.261 87.4502H312.006L311.848 86.9131L304.927 63.5703H276.646L269.726 86.9131L269.566 87.4502H257.552L257.85 86.4805L283.529 2.71973L283.691 2.19043H298.121ZM539.782 2.19043V44.9209L549.845 32.2344L549.851 32.2275L549.855 32.2207L574.575 2.46094L574.801 2.19043H588.874L587.849 3.41992L558.795 38.2832L588.749 86.3027L589.464 87.4502H575.934L575.714 87.0938L550.937 46.9316L539.782 60.0947V87.4502H528.202V2.19043H539.782ZM12.3301 77.1904H30.54C35.0749 77.1904 38.5307 76.1729 40.9961 74.2305C43.4059 72.3317 44.6699 69.3811 44.6699 65.2197V60.2998C44.6699 56.2239 43.4093 53.3106 40.9961 51.4092L40.9854 51.4004C38.5207 49.3838 35.0691 48.3301 30.54 48.3301H12.3301V77.1904ZM279.485 53.3096H302.087L290.786 14.4482L279.485 53.3096ZM12.3301 38.5498H28.8604C33 38.5498 36.1378 37.6505 38.3633 35.9443C40.5339 34.2015 41.6698 31.5679 41.6699 27.9004V23.2197C41.6699 19.5455 40.5299 16.9088 38.3516 15.166C36.1272 13.3865 32.9938 12.4502 28.8604 12.4502H12.3301V38.5498Z"
|
|
||||||
fill="url(#hero-black-fill-gradient)"
|
|
||||||
fill-opacity="0.1"
|
|
||||||
stroke="url(#hero-black-stroke-gradient)"
|
|
||||||
stroke-width="1.5"
|
|
||||||
/>
|
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="hero-black-fill-gradient"
|
id="hero-black-fill-gradient"
|
||||||
@@ -130,9 +170,10 @@ export default function BlackLayout(props: RouteSectionProps) {
|
|||||||
y2="87.0326"
|
y2="87.0326"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="var(--hero-black-fill-from)" />
|
<stop stop-color="white" />
|
||||||
<stop offset="1" stop-color="var(--hero-black-fill-to)" />
|
<stop offset="1" stop-color="white" stop-opacity="0" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="hero-black-stroke-gradient"
|
id="hero-black-stroke-gradient"
|
||||||
x1="290.82"
|
x1="290.82"
|
||||||
@@ -141,10 +182,80 @@ export default function BlackLayout(props: RouteSectionProps) {
|
|||||||
y2="87.0325"
|
y2="87.0325"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="var(--hero-black-stroke-from)" />
|
<stop stop-color={`hsl(0 0% ${svgLightingValues().strokeBrightness}%)`} />
|
||||||
<stop offset="1" stop-color="var(--hero-black-stroke-to)" />
|
<stop offset="1" stop-color="white" stop-opacity="0" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
|
||||||
|
<linearGradient
|
||||||
|
id="hero-black-shimmer-gradient"
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="591"
|
||||||
|
y2="0"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<stop offset={Math.max(0, svgLightingValues().shimmerPos - 0.12)} stop-color="transparent" />
|
||||||
|
<stop
|
||||||
|
offset={svgLightingValues().shimmerPos}
|
||||||
|
stop-color={`rgba(255, 255, 255, ${svgLightingValues().shimmerIntensity})`}
|
||||||
|
/>
|
||||||
|
<stop offset={Math.min(1, svgLightingValues().shimmerPos + 0.12)} stop-color="transparent" />
|
||||||
|
</linearGradient>
|
||||||
|
|
||||||
|
<linearGradient
|
||||||
|
id="hero-black-top-glow"
|
||||||
|
x1="290.82"
|
||||||
|
y1="0"
|
||||||
|
x2="290.82"
|
||||||
|
y2="45"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<stop offset="0" stop-color={`rgba(255, 255, 255, ${svgLightingValues().glowIntensity})`} />
|
||||||
|
<stop offset="1" stop-color="transparent" />
|
||||||
|
</linearGradient>
|
||||||
|
|
||||||
|
<linearGradient
|
||||||
|
id="hero-black-shimmer-mask"
|
||||||
|
x1="290.82"
|
||||||
|
y1="0"
|
||||||
|
x2="290.82"
|
||||||
|
y2="50"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<stop offset="0" stop-color="white" />
|
||||||
|
<stop offset="0.8" stop-color="white" stop-opacity="0.5" />
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0" />
|
||||||
|
</linearGradient>
|
||||||
|
|
||||||
|
<mask id="shimmer-top-mask">
|
||||||
|
<rect x="0" y="0" width="591" height="90" fill="url(#hero-black-shimmer-mask)" />
|
||||||
|
</mask>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
|
<path
|
||||||
|
d="M425.56 0.75C429.464 0.750017 432.877 1.27807 435.78 2.35645C438.656 3.42455 441.138 4.86975 443.215 6.69727C445.268 8.50382 446.995 10.5587 448.394 12.8604C449.77 15.0464 450.986 17.2741 452.04 19.5439L452.357 20.2275L451.672 20.542L443.032 24.502L442.311 24.833L442.021 24.0938C441.315 22.2906 440.494 20.6079 439.557 19.0459L439.552 19.0391L439.548 19.0322C438.626 17.419 437.517 16.0443 436.223 14.9023L436.206 14.8867L436.189 14.8701C434.989 13.6697 433.518 12.7239 431.766 12.0381L431.755 12.0342V12.0332C430.111 11.3607 428.053 11.0098 425.56 11.0098C419.142 11.0098 414.433 13.4271 411.308 18.2295C408.212 23.109 406.629 29.6717 406.629 37.9805V51.6602C406.629 59.9731 408.214 66.5377 411.312 71.418C414.438 76.2157 419.145 78.6299 425.56 78.6299C428.054 78.6299 430.111 78.2782 431.756 77.6055L431.766 77.6016L432.413 77.333C433.893 76.6811 435.154 75.8593 436.206 74.873C437.512 73.644 438.625 72.2626 439.548 70.7275C440.489 69.0801 441.314 67.3534 442.021 65.5469L442.311 64.8076L443.032 65.1387L451.672 69.0986L452.348 69.4082L452.044 70.0869C450.99 72.439 449.773 74.7099 448.395 76.8994C446.995 79.1229 445.266 81.1379 443.215 82.9434C441.138 84.7708 438.656 86.2151 435.78 87.2832C432.877 88.3616 429.464 88.8896 425.56 88.8896C415.111 88.8896 407.219 85.0777 402.019 77.4004L402.016 77.3965C396.939 69.7818 394.449 58.891 394.449 44.8203C394.449 30.7495 396.939 19.8589 402.016 12.2441L402.019 12.2393C407.219 4.56202 415.111 0.75 425.56 0.75ZM29.9404 2.19043C37.2789 2.19051 43.125 4.19131 47.3799 8.2793C51.6307 12.3635 53.7305 17.8115 53.7305 24.54C53.7305 29.6953 52.4605 33.8451 49.835 36.8994L49.8359 36.9004C47.7064 39.4558 45.0331 41.367 41.835 42.6445C45.893 43.8751 49.3115 45.9006 52.0703 48.7295C55.2954 51.9546 56.8496 56.6143 56.8496 62.5801C56.8496 66.0251 56.2751 69.2753 55.1211 72.3252C53.9689 75.3702 52.3185 78.014 50.1689 80.249L50.1699 80.25C48.0996 82.4858 45.6172 84.2628 42.7314 85.582L42.7227 85.5859C39.9002 86.8312 36.8362 87.4502 33.54 87.4502H0.75V2.19043H29.9404ZM148.123 2.19043V77.1904H187.843V87.4502H136.543V2.19043H148.123ZM298.121 2.19043L298.283 2.71973L323.963 86.4805L324.261 87.4502H312.006L311.848 86.9131L304.927 63.5703H276.646L269.726 86.9131L269.566 87.4502H257.552L257.85 86.4805L283.529 2.71973L283.691 2.19043H298.121ZM539.782 2.19043V44.9209L549.845 32.2344L549.851 32.2275L549.855 32.2207L574.575 2.46094L574.801 2.19043H588.874L587.849 3.41992L558.795 38.2832L588.749 86.3027L589.464 87.4502H575.934L575.714 87.0938L550.937 46.9316L539.782 60.0947V87.4502H528.202V2.19043H539.782ZM12.3301 77.1904H30.54C35.0749 77.1904 38.5307 76.1729 40.9961 74.2305C43.4059 72.3317 44.6699 69.3811 44.6699 65.2197V60.2998C44.6699 56.2239 43.4093 53.3106 40.9961 51.4092L40.9854 51.4004C38.5207 49.3838 35.0691 48.3301 30.54 48.3301H12.3301V77.1904ZM279.485 53.3096H302.087L290.786 14.4482L279.485 53.3096ZM12.3301 38.5498H28.8604C33 38.5498 36.1378 37.6505 38.3633 35.9443C40.5339 34.2015 41.6698 31.5679 41.6699 27.9004V23.2197C41.6699 19.5455 40.5299 16.9088 38.3516 15.166C36.1272 13.3865 32.9938 12.4502 28.8604 12.4502H12.3301V38.5498Z"
|
||||||
|
fill="url(#hero-black-fill-gradient)"
|
||||||
|
fill-opacity={svgLightingValues().fillOpacity}
|
||||||
|
stroke="url(#hero-black-stroke-gradient)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
data-slot="black-base"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<path
|
||||||
|
d="M425.56 0.75C429.464 0.750017 432.877 1.27807 435.78 2.35645C438.656 3.42455 441.138 4.86975 443.215 6.69727C445.268 8.50382 446.995 10.5587 448.394 12.8604C449.77 15.0464 450.986 17.2741 452.04 19.5439L452.357 20.2275L451.672 20.542L443.032 24.502L442.311 24.833L442.021 24.0938C441.315 22.2906 440.494 20.6079 439.557 19.0459L439.552 19.0391L439.548 19.0322C438.626 17.419 437.517 16.0443 436.223 14.9023L436.206 14.8867L436.189 14.8701C434.989 13.6697 433.518 12.7239 431.766 12.0381L431.755 12.0342V12.0332C430.111 11.3607 428.053 11.0098 425.56 11.0098C419.142 11.0098 414.433 13.4271 411.308 18.2295C408.212 23.109 406.629 29.6717 406.629 37.9805V51.6602C406.629 59.9731 408.214 66.5377 411.312 71.418C414.438 76.2157 419.145 78.6299 425.56 78.6299C428.054 78.6299 430.111 78.2782 431.756 77.6055L431.766 77.6016L432.413 77.333C433.893 76.6811 435.154 75.8593 436.206 74.873C437.512 73.644 438.625 72.2626 439.548 70.7275C440.489 69.0801 441.314 67.3534 442.021 65.5469L442.311 64.8076L443.032 65.1387L451.672 69.0986L452.348 69.4082L452.044 70.0869C450.99 72.439 449.773 74.7099 448.395 76.8994C446.995 79.1229 445.266 81.1379 443.215 82.9434C441.138 84.7708 438.656 86.2151 435.78 87.2832C432.877 88.3616 429.464 88.8896 425.56 88.8896C415.111 88.8896 407.219 85.0777 402.019 77.4004L402.016 77.3965C396.939 69.7818 394.449 58.891 394.449 44.8203C394.449 30.7495 396.939 19.8589 402.016 12.2441L402.019 12.2393C407.219 4.56202 415.111 0.75 425.56 0.75ZM29.9404 2.19043C37.2789 2.19051 43.125 4.19131 47.3799 8.2793C51.6307 12.3635 53.7305 17.8115 53.7305 24.54C53.7305 29.6953 52.4605 33.8451 49.835 36.8994L49.8359 36.9004C47.7064 39.4558 45.0331 41.367 41.835 42.6445C45.893 43.8751 49.3115 45.9006 52.0703 48.7295C55.2954 51.9546 56.8496 56.6143 56.8496 62.5801C56.8496 66.0251 56.2751 69.2753 55.1211 72.3252C53.9689 75.3702 52.3185 78.014 50.1689 80.249L50.1699 80.25C48.0996 82.4858 45.6172 84.2628 42.7314 85.582L42.7227 85.5859C39.9002 86.8312 36.8362 87.4502 33.54 87.4502H0.75V2.19043H29.9404ZM148.123 2.19043V77.1904H187.843V87.4502H136.543V2.19043H148.123ZM298.121 2.19043L298.283 2.71973L323.963 86.4805L324.261 87.4502H312.006L311.848 86.9131L304.927 63.5703H276.646L269.726 86.9131L269.566 87.4502H257.552L257.85 86.4805L283.529 2.71973L283.691 2.19043H298.121ZM539.782 2.19043V44.9209L549.845 32.2344L549.851 32.2275L549.855 32.2207L574.575 2.46094L574.801 2.19043H588.874L587.849 3.41992L558.795 38.2832L588.749 86.3027L589.464 87.4502H575.934L575.714 87.0938L550.937 46.9316L539.782 60.0947V87.4502H528.202V2.19043H539.782ZM12.3301 77.1904H30.54C35.0749 77.1904 38.5307 76.1729 40.9961 74.2305C43.4059 72.3317 44.6699 69.3811 44.6699 65.2197V60.2998C44.6699 56.2239 43.4093 53.3106 40.9961 51.4092L40.9854 51.4004C38.5207 49.3838 35.0691 48.3301 30.54 48.3301H12.3301V77.1904ZM279.485 53.3096H302.087L290.786 14.4482L279.485 53.3096ZM12.3301 38.5498H28.8604C33 38.5498 36.1378 37.6505 38.3633 35.9443C40.5339 34.2015 41.6698 31.5679 41.6699 27.9004V23.2197C41.6699 19.5455 40.5299 16.9088 38.3516 15.166C36.1272 13.3865 32.9938 12.4502 28.8604 12.4502H12.3301V38.5498Z"
|
||||||
|
fill="url(#hero-black-top-glow)"
|
||||||
|
stroke="none"
|
||||||
|
data-slot="black-glow"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<path
|
||||||
|
d="M425.56 0.75C429.464 0.750017 432.877 1.27807 435.78 2.35645C438.656 3.42455 441.138 4.86975 443.215 6.69727C445.268 8.50382 446.995 10.5587 448.394 12.8604C449.77 15.0464 450.986 17.2741 452.04 19.5439L452.357 20.2275L451.672 20.542L443.032 24.502L442.311 24.833L442.021 24.0938C441.315 22.2906 440.494 20.6079 439.557 19.0459L439.552 19.0391L439.548 19.0322C438.626 17.419 437.517 16.0443 436.223 14.9023L436.206 14.8867L436.189 14.8701C434.989 13.6697 433.518 12.7239 431.766 12.0381L431.755 12.0342V12.0332C430.111 11.3607 428.053 11.0098 425.56 11.0098C419.142 11.0098 414.433 13.4271 411.308 18.2295C408.212 23.109 406.629 29.6717 406.629 37.9805V51.6602C406.629 59.9731 408.214 66.5377 411.312 71.418C414.438 76.2157 419.145 78.6299 425.56 78.6299C428.054 78.6299 430.111 78.2782 431.756 77.6055L431.766 77.6016L432.413 77.333C433.893 76.6811 435.154 75.8593 436.206 74.873C437.512 73.644 438.625 72.2626 439.548 70.7275C440.489 69.0801 441.314 67.3534 442.021 65.5469L442.311 64.8076L443.032 65.1387L451.672 69.0986L452.348 69.4082L452.044 70.0869C450.99 72.439 449.773 74.7099 448.395 76.8994C446.995 79.1229 445.266 81.1379 443.215 82.9434C441.138 84.7708 438.656 86.2151 435.78 87.2832C432.877 88.3616 429.464 88.8896 425.56 88.8896C415.111 88.8896 407.219 85.0777 402.019 77.4004L402.016 77.3965C396.939 69.7818 394.449 58.891 394.449 44.8203C394.449 30.7495 396.939 19.8589 402.016 12.2441L402.019 12.2393C407.219 4.56202 415.111 0.75 425.56 0.75ZM29.9404 2.19043C37.2789 2.19051 43.125 4.19131 47.3799 8.2793C51.6307 12.3635 53.7305 17.8115 53.7305 24.54C53.7305 29.6953 52.4605 33.8451 49.835 36.8994L49.8359 36.9004C47.7064 39.4558 45.0331 41.367 41.835 42.6445C45.893 43.8751 49.3115 45.9006 52.0703 48.7295C55.2954 51.9546 56.8496 56.6143 56.8496 62.5801C56.8496 66.0251 56.2751 69.2753 55.1211 72.3252C53.9689 75.3702 52.3185 78.014 50.1689 80.249L50.1699 80.25C48.0996 82.4858 45.6172 84.2628 42.7314 85.582L42.7227 85.5859C39.9002 86.8312 36.8362 87.4502 33.54 87.4502H0.75V2.19043H29.9404ZM148.123 2.19043V77.1904H187.843V87.4502H136.543V2.19043H148.123ZM298.121 2.19043L298.283 2.71973L323.963 86.4805L324.261 87.4502H312.006L311.848 86.9131L304.927 63.5703H276.646L269.726 86.9131L269.566 87.4502H257.552L257.85 86.4805L283.529 2.71973L283.691 2.19043H298.121ZM539.782 2.19043V44.9209L549.845 32.2344L549.851 32.2275L549.855 32.2207L574.575 2.46094L574.801 2.19043H588.874L587.849 3.41992L558.795 38.2832L588.749 86.3027L589.464 87.4502H575.934L575.714 87.0938L550.937 46.9316L539.782 60.0947V87.4502H528.202V2.19043H539.782ZM12.3301 77.1904H30.54C35.0749 77.1904 38.5307 76.1729 40.9961 74.2305C43.4059 72.3317 44.6699 69.3811 44.6699 65.2197V60.2998C44.6699 56.2239 43.4093 53.3106 40.9961 51.4092L40.9854 51.4004C38.5207 49.3838 35.0691 48.3301 30.54 48.3301H12.3301V77.1904ZM279.485 53.3096H302.087L290.786 14.4482L279.485 53.3096ZM12.3301 38.5498H28.8604C33 38.5498 36.1378 37.6505 38.3633 35.9443C40.5339 34.2015 41.6698 31.5679 41.6699 27.9004V23.2197C41.6699 19.5455 40.5299 16.9088 38.3516 15.166C36.1272 13.3865 32.9938 12.4502 28.8604 12.4502H12.3301V38.5498Z"
|
||||||
|
fill="url(#hero-black-shimmer-gradient)"
|
||||||
|
stroke="none"
|
||||||
|
data-slot="black-shimmer"
|
||||||
|
mask="url(#shimmer-top-mask)"
|
||||||
|
style={{ "mix-blend-mode": "overlay" }}
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { A, useSearchParams } from "@solidjs/router"
|
import { A, useSearchParams } from "@solidjs/router"
|
||||||
import { Title } from "@solidjs/meta"
|
import { Title } from "@solidjs/meta"
|
||||||
import { createMemo, createSignal, For, onMount, Show } from "solid-js"
|
import { createMemo, createSignal, For, Match, onMount, Show, Switch } from "solid-js"
|
||||||
import { PlanIcon, plans } from "./common"
|
import { PlanIcon, plans } from "./common"
|
||||||
|
|
||||||
export default function Black() {
|
export default function Black() {
|
||||||
const [params] = useSearchParams()
|
const [params] = useSearchParams()
|
||||||
const [selected, setSelected] = createSignal<string | null>((params.plan as string) || null)
|
const [selected, setSelected] = createSignal<string | null>((params.plan as string) || null)
|
||||||
const [mounted, setMounted] = createSignal(false)
|
const [mounted, setMounted] = createSignal(false)
|
||||||
|
const selectedPlan = createMemo(() => plans.find((p) => p.id === selected()))
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
requestAnimationFrame(() => setMounted(true))
|
requestAnimationFrame(() => setMounted(true))
|
||||||
@@ -37,110 +38,68 @@ export default function Black() {
|
|||||||
<>
|
<>
|
||||||
<Title>opencode</Title>
|
<Title>opencode</Title>
|
||||||
<section data-slot="cta">
|
<section data-slot="cta">
|
||||||
<div data-slot="pricing">
|
<Switch>
|
||||||
<For each={plans}>
|
<Match when={!selected()}>
|
||||||
{(plan) => {
|
<div data-slot="pricing">
|
||||||
const isSelected = createMemo(() => selected() === plan.id)
|
<For each={plans}>
|
||||||
const isCollapsed = createMemo(() => selected() !== null && selected() !== plan.id)
|
{(plan) => (
|
||||||
|
|
||||||
return (
|
|
||||||
<article
|
|
||||||
data-slot="pricing-card"
|
|
||||||
data-plan-id={plan.id}
|
|
||||||
data-selected={isSelected() ? "true" : "false"}
|
|
||||||
data-collapsed={isCollapsed() ? "true" : "false"}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-slot="card-trigger"
|
|
||||||
onClick={() => select(plan.id)}
|
onClick={() => select(plan.id)}
|
||||||
disabled={isSelected()}
|
data-slot="pricing-card"
|
||||||
|
style={{ "view-transition-name": `card-${plan.id}` }}
|
||||||
>
|
>
|
||||||
<div
|
<div data-slot="icon">
|
||||||
data-slot="plan-header"
|
<PlanIcon plan={plan.id} />
|
||||||
style={{
|
|
||||||
"view-transition-name": `plan-header-${plan.id}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div data-slot="plan-icon">
|
|
||||||
<PlanIcon plan={plan.id} />
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
data-slot="price"
|
|
||||||
style={{
|
|
||||||
"view-transition-name": `price-${plan.id}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-slot="amount"
|
|
||||||
style={{
|
|
||||||
"view-transition-name": `amount-${plan.id}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
${plan.id}
|
|
||||||
</span>
|
|
||||||
<Show when={!isSelected()}>
|
|
||||||
<span
|
|
||||||
data-slot="period"
|
|
||||||
style={{
|
|
||||||
"view-transition-name": `period-${plan.id}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
per month
|
|
||||||
</span>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show when={isSelected()}>
|
|
||||||
<span
|
|
||||||
data-slot="billing"
|
|
||||||
style={{
|
|
||||||
"view-transition-name": `billing-${plan.id}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
per person billed monthly
|
|
||||||
</span>
|
|
||||||
</Show>
|
|
||||||
{plan.multiplier && (
|
|
||||||
<span
|
|
||||||
data-slot="multiplier"
|
|
||||||
style={{
|
|
||||||
"view-transition-name": `multiplier-${plan.id}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{plan.multiplier}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p data-slot="price">
|
||||||
|
<span data-slot="amount">${plan.id}</span> <span data-slot="period">per month</span>
|
||||||
|
<Show when={plan.multiplier}>
|
||||||
|
<span data-slot="multiplier">{plan.multiplier}</span>
|
||||||
|
</Show>
|
||||||
|
</p>
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
<Show when={isSelected()}>
|
</For>
|
||||||
<div data-slot="content">
|
</div>
|
||||||
<ul data-slot="terms">
|
</Match>
|
||||||
<li>You will be added to the waitlist and activated in batches</li>
|
<Match when={selectedPlan()}>
|
||||||
<li>Card won't be charged until subscription is active</li>
|
{(plan) => (
|
||||||
<li>Not unlimited - limits apply and may be adjusted dynamically</li>
|
<div data-slot="selected-plan">
|
||||||
<li>Heavily automated usage will hit limits quickly</li>
|
<div data-slot="selected-card" style={{ "view-transition-name": `card-${plan().id}` }}>
|
||||||
<li>Plans may be discontinued</li>
|
<div data-slot="icon">
|
||||||
<li>Can cancel subscription at anytime</li>
|
<PlanIcon plan={plan().id} />
|
||||||
<li>Cannot issue refunds for consumed subscriptions</li>
|
</div>
|
||||||
</ul>
|
<p data-slot="price">
|
||||||
<div data-slot="actions">
|
<span data-slot="amount">${plan().id}</span>{" "}
|
||||||
<button type="button" onClick={cancel} data-slot="cancel">
|
<span data-slot="period">per person billed monthly</span>
|
||||||
Cancel
|
<Show when={plan().multiplier}>
|
||||||
</button>
|
<span data-slot="multiplier">{plan().multiplier}</span>
|
||||||
<a href={`/black/subscribe/${plan.id}`} data-slot="continue">
|
</Show>
|
||||||
Continue
|
</p>
|
||||||
</a>
|
<ul data-slot="terms" style={{ "view-transition-name": `terms-${plan().id}` }}>
|
||||||
</div>
|
<li>Your subscription will not start immediately</li>
|
||||||
</div>
|
<li>You will be added to the waitlist and activated soon</li>
|
||||||
</Show>
|
<li>Your card will be only charged when your subscription is activated</li>
|
||||||
</article>
|
<li>Usage limits apply, heavily automated use may reach limits sooner</li>
|
||||||
)
|
<li>Subscriptions for individuals, contact Enterprise for teams</li>
|
||||||
}}
|
<li>Limits may be adjusted and plans may be discontinued in the future</li>
|
||||||
</For>
|
<li>Cancel your subscription at anytime</li>
|
||||||
</div>
|
</ul>
|
||||||
<p data-slot="fine-print">
|
<div data-slot="actions" style={{ "view-transition-name": `actions-${plan().id}` }}>
|
||||||
|
<button type="button" onClick={() => cancel()} data-slot="cancel">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<a href={`/black/subscribe/${plan().id}`} data-slot="continue">
|
||||||
|
Continue
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
<p data-slot="fine-print" style={{ "view-transition-name": "fine-print" }}>
|
||||||
Prices shown don't include applicable tax · <A href="/legal/terms-of-service">Terms of Service</A>
|
Prices shown don't include applicable tax · <A href="/legal/terms-of-service">Terms of Service</A>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
padding-bottom: 5rem;
|
padding-bottom: 5rem;
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
--color-background: hsl(0, 9%, 7%);
|
--color-background: hsl(0, 9%, 7%);
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
padding-bottom: 5rem;
|
padding-bottom: 5rem;
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
--color-background: hsl(0, 9%, 7%);
|
--color-background: hsl(0, 9%, 7%);
|
||||||
|
|||||||
@@ -183,7 +183,12 @@ export async function POST(input: APIEvent) {
|
|||||||
.set({
|
.set({
|
||||||
customerID,
|
customerID,
|
||||||
subscriptionID,
|
subscriptionID,
|
||||||
subscriptionCouponID: couponID,
|
subscription: {
|
||||||
|
status: "subscribed",
|
||||||
|
coupon: couponID,
|
||||||
|
seats: 1,
|
||||||
|
plan: "200",
|
||||||
|
},
|
||||||
paymentMethodID: paymentMethod.id,
|
paymentMethodID: paymentMethod.id,
|
||||||
paymentMethodLast4: paymentMethod.card?.last4 ?? null,
|
paymentMethodLast4: paymentMethod.card?.last4 ?? null,
|
||||||
paymentMethodType: paymentMethod.type,
|
paymentMethodType: paymentMethod.type,
|
||||||
@@ -408,7 +413,7 @@ export async function POST(input: APIEvent) {
|
|||||||
await Database.transaction(async (tx) => {
|
await Database.transaction(async (tx) => {
|
||||||
await tx
|
await tx
|
||||||
.update(BillingTable)
|
.update(BillingTable)
|
||||||
.set({ subscriptionID: null, subscriptionCouponID: null })
|
.set({ subscriptionID: null, subscription: null })
|
||||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
|
|
||||||
await tx.delete(SubscriptionTable).where(eq(SubscriptionTable.workspaceID, workspaceID))
|
await tx.delete(SubscriptionTable).where(eq(SubscriptionTable.workspaceID, workspaceID))
|
||||||
|
|||||||
@@ -5,4 +5,58 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-4);
|
gap: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-slot="usage"] {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-6);
|
||||||
|
margin-top: var(--space-4);
|
||||||
|
|
||||||
|
@media (max-width: 40rem) {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="usage-item"] {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="usage-header"] {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="usage-label"] {
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="usage-value"] {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="progress"] {
|
||||||
|
height: 8px;
|
||||||
|
background-color: var(--color-bg-surface);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="progress-bar"] {
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="reset-time"] {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,58 @@
|
|||||||
import { action, useParams, useAction, useSubmission, json } from "@solidjs/router"
|
import { action, useParams, useAction, useSubmission, json, query, createAsync } from "@solidjs/router"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
|
import { Show } from "solid-js"
|
||||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||||
|
import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js"
|
||||||
|
import { SubscriptionTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||||
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
|
import { Black } from "@opencode-ai/console-core/black.js"
|
||||||
import { withActor } from "~/context/auth.withActor"
|
import { withActor } from "~/context/auth.withActor"
|
||||||
import { queryBillingInfo } from "../../common"
|
import { queryBillingInfo } from "../../common"
|
||||||
import styles from "./black-section.module.css"
|
import styles from "./black-section.module.css"
|
||||||
|
|
||||||
|
const querySubscription = query(async (workspaceID: string) => {
|
||||||
|
"use server"
|
||||||
|
return withActor(async () => {
|
||||||
|
const row = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
rollingUsage: SubscriptionTable.rollingUsage,
|
||||||
|
fixedUsage: SubscriptionTable.fixedUsage,
|
||||||
|
timeRollingUpdated: SubscriptionTable.timeRollingUpdated,
|
||||||
|
timeFixedUpdated: SubscriptionTable.timeFixedUpdated,
|
||||||
|
})
|
||||||
|
.from(SubscriptionTable)
|
||||||
|
.where(and(eq(SubscriptionTable.workspaceID, Actor.workspace()), isNull(SubscriptionTable.timeDeleted)))
|
||||||
|
.then((r) => r[0]),
|
||||||
|
)
|
||||||
|
if (!row) return null
|
||||||
|
|
||||||
|
return {
|
||||||
|
rollingUsage: Black.analyzeRollingUsage({
|
||||||
|
usage: row.rollingUsage ?? 0,
|
||||||
|
timeUpdated: row.timeRollingUpdated ?? new Date(),
|
||||||
|
}),
|
||||||
|
weeklyUsage: Black.analyzeWeeklyUsage({
|
||||||
|
usage: row.fixedUsage ?? 0,
|
||||||
|
timeUpdated: row.timeFixedUpdated ?? new Date(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}, workspaceID)
|
||||||
|
}, "subscription.get")
|
||||||
|
|
||||||
|
function formatResetTime(seconds: number) {
|
||||||
|
const days = Math.floor(seconds / 86400)
|
||||||
|
if (days >= 1) {
|
||||||
|
const hours = Math.floor((seconds % 86400) / 3600)
|
||||||
|
return `${days} ${days === 1 ? "day" : "days"} ${hours} ${hours === 1 ? "hour" : "hours"}`
|
||||||
|
}
|
||||||
|
const hours = Math.floor(seconds / 3600)
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60)
|
||||||
|
if (hours >= 1) return `${hours} ${hours === 1 ? "hour" : "hours"} ${minutes} ${minutes === 1 ? "minute" : "minutes"}`
|
||||||
|
if (minutes === 0) return "a few seconds"
|
||||||
|
return `${minutes} ${minutes === 1 ? "minute" : "minutes"}`
|
||||||
|
}
|
||||||
|
|
||||||
const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
|
const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
|
||||||
"use server"
|
"use server"
|
||||||
return json(
|
return json(
|
||||||
@@ -26,6 +74,7 @@ export function BlackSection() {
|
|||||||
const params = useParams()
|
const params = useParams()
|
||||||
const sessionAction = useAction(createSessionUrl)
|
const sessionAction = useAction(createSessionUrl)
|
||||||
const sessionSubmission = useSubmission(createSessionUrl)
|
const sessionSubmission = useSubmission(createSessionUrl)
|
||||||
|
const subscription = createAsync(() => querySubscription(params.id!))
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
sessionRedirecting: false,
|
sessionRedirecting: false,
|
||||||
})
|
})
|
||||||
@@ -53,6 +102,32 @@ export function BlackSection() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Show when={subscription()}>
|
||||||
|
{(sub) => (
|
||||||
|
<div data-slot="usage">
|
||||||
|
<div data-slot="usage-item">
|
||||||
|
<div data-slot="usage-header">
|
||||||
|
<span data-slot="usage-label">5-hour Usage</span>
|
||||||
|
<span data-slot="usage-value">{sub().rollingUsage.usagePercent}%</span>
|
||||||
|
</div>
|
||||||
|
<div data-slot="progress">
|
||||||
|
<div data-slot="progress-bar" style={{ width: `${sub().rollingUsage.usagePercent}%` }} />
|
||||||
|
</div>
|
||||||
|
<span data-slot="reset-time">Resets in {formatResetTime(sub().rollingUsage.resetInSec)}</span>
|
||||||
|
</div>
|
||||||
|
<div data-slot="usage-item">
|
||||||
|
<div data-slot="usage-header">
|
||||||
|
<span data-slot="usage-label">Weekly Usage</span>
|
||||||
|
<span data-slot="usage-value">{sub().weeklyUsage.usagePercent}%</span>
|
||||||
|
</div>
|
||||||
|
<div data-slot="progress">
|
||||||
|
<div data-slot="progress-bar" style={{ width: `${sub().weeklyUsage.usagePercent}%` }} />
|
||||||
|
</div>
|
||||||
|
<span data-slot="reset-time">Resets in {formatResetTime(sub().weeklyUsage.resetInSec)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Actor } from "@opencode-ai/console-core/actor.js"
|
|||||||
import { action, json, query } from "@solidjs/router"
|
import { action, json, query } from "@solidjs/router"
|
||||||
import { withActor } from "~/context/auth.withActor"
|
import { withActor } from "~/context/auth.withActor"
|
||||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||||
import { User } from "@opencode-ai/console-core/user.js"
|
|
||||||
import { and, Database, desc, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js"
|
import { and, Database, desc, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js"
|
||||||
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
|
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
|
||||||
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||||
@@ -96,11 +95,22 @@ export const queryBillingInfo = query(async (workspaceID: string) => {
|
|||||||
return withActor(async () => {
|
return withActor(async () => {
|
||||||
const billing = await Billing.get()
|
const billing = await Billing.get()
|
||||||
return {
|
return {
|
||||||
...billing,
|
customerID: billing.customerID,
|
||||||
|
paymentMethodID: billing.paymentMethodID,
|
||||||
|
paymentMethodType: billing.paymentMethodType,
|
||||||
|
paymentMethodLast4: billing.paymentMethodLast4,
|
||||||
|
balance: billing.balance,
|
||||||
|
reload: billing.reload,
|
||||||
reloadAmount: billing.reloadAmount ?? Billing.RELOAD_AMOUNT,
|
reloadAmount: billing.reloadAmount ?? Billing.RELOAD_AMOUNT,
|
||||||
reloadAmountMin: Billing.RELOAD_AMOUNT_MIN,
|
reloadAmountMin: Billing.RELOAD_AMOUNT_MIN,
|
||||||
reloadTrigger: billing.reloadTrigger ?? Billing.RELOAD_TRIGGER,
|
reloadTrigger: billing.reloadTrigger ?? Billing.RELOAD_TRIGGER,
|
||||||
reloadTriggerMin: Billing.RELOAD_TRIGGER_MIN,
|
reloadTriggerMin: Billing.RELOAD_TRIGGER_MIN,
|
||||||
|
monthlyLimit: billing.monthlyLimit,
|
||||||
|
monthlyUsage: billing.monthlyUsage,
|
||||||
|
timeMonthlyUsageUpdated: billing.timeMonthlyUsageUpdated,
|
||||||
|
reloadError: billing.reloadError,
|
||||||
|
timeReloadError: billing.timeReloadError,
|
||||||
|
subscriptionID: billing.subscriptionID,
|
||||||
}
|
}
|
||||||
}, workspaceID)
|
}, workspaceID)
|
||||||
}, "billing.get")
|
}, "billing.get")
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Billing } from "@opencode-ai/console-core/billing.js"
|
|||||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
|
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
|
||||||
import { ZenData } from "@opencode-ai/console-core/model.js"
|
import { ZenData } from "@opencode-ai/console-core/model.js"
|
||||||
import { BlackData } from "@opencode-ai/console-core/black.js"
|
import { Black, BlackData } from "@opencode-ai/console-core/black.js"
|
||||||
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||||
import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
|
import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
|
||||||
import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
|
import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
|
||||||
@@ -81,12 +81,13 @@ export async function handler(
|
|||||||
const isTrial = await trialLimiter?.isTrial()
|
const isTrial = await trialLimiter?.isTrial()
|
||||||
const rateLimiter = createRateLimiter(modelInfo.rateLimit, ip)
|
const rateLimiter = createRateLimiter(modelInfo.rateLimit, ip)
|
||||||
await rateLimiter?.check()
|
await rateLimiter?.check()
|
||||||
const stickyTracker = createStickyTracker(modelInfo.stickyProvider ?? false, sessionId)
|
const stickyTracker = createStickyTracker(modelInfo.stickyProvider, sessionId)
|
||||||
const stickyProvider = await stickyTracker?.get()
|
const stickyProvider = await stickyTracker?.get()
|
||||||
const authInfo = await authenticate(modelInfo)
|
const authInfo = await authenticate(modelInfo)
|
||||||
|
|
||||||
const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
|
const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
|
||||||
const providerInfo = selectProvider(
|
const providerInfo = selectProvider(
|
||||||
|
model,
|
||||||
zenData,
|
zenData,
|
||||||
authInfo,
|
authInfo,
|
||||||
modelInfo,
|
modelInfo,
|
||||||
@@ -101,7 +102,7 @@ export async function handler(
|
|||||||
logger.metric({ provider: providerInfo.id })
|
logger.metric({ provider: providerInfo.id })
|
||||||
|
|
||||||
const startTimestamp = Date.now()
|
const startTimestamp = Date.now()
|
||||||
const reqUrl = providerInfo.modifyUrl(providerInfo.api, providerInfo.model, isStream)
|
const reqUrl = providerInfo.modifyUrl(providerInfo.api, isStream)
|
||||||
const reqBody = JSON.stringify(
|
const reqBody = JSON.stringify(
|
||||||
providerInfo.modifyBody({
|
providerInfo.modifyBody({
|
||||||
...createBodyConverter(opts.format, providerInfo.format)(body),
|
...createBodyConverter(opts.format, providerInfo.format)(body),
|
||||||
@@ -135,7 +136,7 @@ export async function handler(
|
|||||||
// ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found.
|
// ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found.
|
||||||
res.status !== 404 &&
|
res.status !== 404 &&
|
||||||
// ie. cannot change codex model providers mid-session
|
// ie. cannot change codex model providers mid-session
|
||||||
!modelInfo.stickyProvider &&
|
modelInfo.stickyProvider !== "strict" &&
|
||||||
modelInfo.fallbackProvider &&
|
modelInfo.fallbackProvider &&
|
||||||
providerInfo.id !== modelInfo.fallbackProvider
|
providerInfo.id !== modelInfo.fallbackProvider
|
||||||
) {
|
) {
|
||||||
@@ -194,17 +195,19 @@ export async function handler(
|
|||||||
// Handle streaming response
|
// Handle streaming response
|
||||||
const streamConverter = createStreamPartConverter(providerInfo.format, opts.format)
|
const streamConverter = createStreamPartConverter(providerInfo.format, opts.format)
|
||||||
const usageParser = providerInfo.createUsageParser()
|
const usageParser = providerInfo.createUsageParser()
|
||||||
|
const binaryDecoder = providerInfo.createBinaryStreamDecoder()
|
||||||
const stream = new ReadableStream({
|
const stream = new ReadableStream({
|
||||||
start(c) {
|
start(c) {
|
||||||
const reader = res.body?.getReader()
|
const reader = res.body?.getReader()
|
||||||
const decoder = new TextDecoder()
|
const decoder = new TextDecoder()
|
||||||
const encoder = new TextEncoder()
|
const encoder = new TextEncoder()
|
||||||
|
|
||||||
let buffer = ""
|
let buffer = ""
|
||||||
let responseLength = 0
|
let responseLength = 0
|
||||||
|
|
||||||
function pump(): Promise<void> {
|
function pump(): Promise<void> {
|
||||||
return (
|
return (
|
||||||
reader?.read().then(async ({ done, value }) => {
|
reader?.read().then(async ({ done, value: rawValue }) => {
|
||||||
if (done) {
|
if (done) {
|
||||||
logger.metric({
|
logger.metric({
|
||||||
response_length: responseLength,
|
response_length: responseLength,
|
||||||
@@ -230,6 +233,10 @@ export async function handler(
|
|||||||
"timestamp.first_byte": now,
|
"timestamp.first_byte": now,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const value = binaryDecoder ? binaryDecoder(rawValue) : rawValue
|
||||||
|
if (!value) return
|
||||||
|
|
||||||
responseLength += value.length
|
responseLength += value.length
|
||||||
buffer += decoder.decode(value, { stream: true })
|
buffer += decoder.decode(value, { stream: true })
|
||||||
dataDumper?.provideStream(buffer)
|
dataDumper?.provideStream(buffer)
|
||||||
@@ -331,6 +338,7 @@ export async function handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectProvider(
|
function selectProvider(
|
||||||
|
reqModel: string,
|
||||||
zenData: ZenData,
|
zenData: ZenData,
|
||||||
authInfo: AuthInfo,
|
authInfo: AuthInfo,
|
||||||
modelInfo: ModelInfo,
|
modelInfo: ModelInfo,
|
||||||
@@ -339,7 +347,7 @@ export async function handler(
|
|||||||
retry: RetryOptions,
|
retry: RetryOptions,
|
||||||
stickyProvider: string | undefined,
|
stickyProvider: string | undefined,
|
||||||
) {
|
) {
|
||||||
const provider = (() => {
|
const modelProvider = (() => {
|
||||||
if (authInfo?.provider?.credentials) {
|
if (authInfo?.provider?.credentials) {
|
||||||
return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider)
|
return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider)
|
||||||
}
|
}
|
||||||
@@ -372,18 +380,19 @@ export async function handler(
|
|||||||
return providers[index || 0]
|
return providers[index || 0]
|
||||||
})()
|
})()
|
||||||
|
|
||||||
if (!provider) throw new ModelError("No provider available")
|
if (!modelProvider) throw new ModelError("No provider available")
|
||||||
if (!(provider.id in zenData.providers)) throw new ModelError(`Provider ${provider.id} not supported`)
|
if (!(modelProvider.id in zenData.providers)) throw new ModelError(`Provider ${modelProvider.id} not supported`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...provider,
|
...modelProvider,
|
||||||
...zenData.providers[provider.id],
|
...zenData.providers[modelProvider.id],
|
||||||
...(() => {
|
...(() => {
|
||||||
const format = zenData.providers[provider.id].format
|
const format = zenData.providers[modelProvider.id].format
|
||||||
if (format === "anthropic") return anthropicHelper
|
const providerModel = modelProvider.model
|
||||||
if (format === "google") return googleHelper
|
if (format === "anthropic") return anthropicHelper({ reqModel, providerModel })
|
||||||
if (format === "openai") return openaiHelper
|
if (format === "google") return googleHelper({ reqModel, providerModel })
|
||||||
return oaCompatHelper
|
if (format === "openai") return openaiHelper({ reqModel, providerModel })
|
||||||
|
return oaCompatHelper({ reqModel, providerModel })
|
||||||
})(),
|
})(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -495,27 +504,28 @@ export async function handler(
|
|||||||
|
|
||||||
// Check weekly limit
|
// Check weekly limit
|
||||||
if (sub.fixedUsage && sub.timeFixedUpdated) {
|
if (sub.fixedUsage && sub.timeFixedUpdated) {
|
||||||
const week = getWeekBounds(now)
|
const result = Black.analyzeWeeklyUsage({
|
||||||
if (sub.timeFixedUpdated >= week.start && sub.fixedUsage >= centsToMicroCents(black.fixedLimit * 100)) {
|
usage: sub.fixedUsage,
|
||||||
const retryAfter = Math.ceil((week.end.getTime() - now.getTime()) / 1000)
|
timeUpdated: sub.timeFixedUpdated,
|
||||||
|
})
|
||||||
|
if (result.status === "rate-limited")
|
||||||
throw new SubscriptionError(
|
throw new SubscriptionError(
|
||||||
`Subscription quota exceeded. Retry in ${formatRetryTime(retryAfter)}.`,
|
`Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
|
||||||
retryAfter,
|
result.resetInSec,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check rolling limit
|
// Check rolling limit
|
||||||
if (sub.rollingUsage && sub.timeRollingUpdated) {
|
if (sub.rollingUsage && sub.timeRollingUpdated) {
|
||||||
const rollingWindowMs = black.rollingWindow * 3600 * 1000
|
const result = Black.analyzeRollingUsage({
|
||||||
const windowStart = new Date(now.getTime() - rollingWindowMs)
|
usage: sub.rollingUsage,
|
||||||
if (sub.timeRollingUpdated >= windowStart && sub.rollingUsage >= centsToMicroCents(black.rollingLimit * 100)) {
|
timeUpdated: sub.timeRollingUpdated,
|
||||||
const retryAfter = Math.ceil((sub.timeRollingUpdated.getTime() + rollingWindowMs - now.getTime()) / 1000)
|
})
|
||||||
|
if (result.status === "rate-limited")
|
||||||
throw new SubscriptionError(
|
throw new SubscriptionError(
|
||||||
`Subscription quota exceeded. Retry in ${formatRetryTime(retryAfter)}.`,
|
`Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
|
||||||
retryAfter,
|
result.resetInSec,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { EventStreamCodec } from "@smithy/eventstream-codec"
|
||||||
import { ProviderHelper, CommonRequest, CommonResponse, CommonChunk } from "./provider"
|
import { ProviderHelper, CommonRequest, CommonResponse, CommonChunk } from "./provider"
|
||||||
|
import { fromUtf8, toUtf8 } from "@smithy/util-utf8"
|
||||||
|
|
||||||
type Usage = {
|
type Usage = {
|
||||||
cache_creation?: {
|
cache_creation?: {
|
||||||
@@ -14,65 +16,169 @@ type Usage = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const anthropicHelper = {
|
export const anthropicHelper: ProviderHelper = ({ reqModel, providerModel }) => {
|
||||||
format: "anthropic",
|
const isBedrockModelArn = providerModel.startsWith("arn:aws:bedrock:")
|
||||||
modifyUrl: (providerApi: string) => providerApi + "/messages",
|
const isBedrockModelID = providerModel.startsWith("global.anthropic.")
|
||||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
const isBedrock = isBedrockModelArn || isBedrockModelID
|
||||||
headers.set("x-api-key", apiKey)
|
const isSonnet = reqModel.includes("sonnet")
|
||||||
headers.set("anthropic-version", headers.get("anthropic-version") ?? "2023-06-01")
|
return {
|
||||||
if (body.model.startsWith("claude-sonnet-")) {
|
format: "anthropic",
|
||||||
headers.set("anthropic-beta", "context-1m-2025-08-07")
|
modifyUrl: (providerApi: string, isStream?: boolean) =>
|
||||||
}
|
isBedrock
|
||||||
},
|
? `${providerApi}/model/${isBedrockModelArn ? encodeURIComponent(providerModel) : providerModel}/${isStream ? "invoke-with-response-stream" : "invoke"}`
|
||||||
modifyBody: (body: Record<string, any>) => {
|
: providerApi + "/messages",
|
||||||
return {
|
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
||||||
|
if (isBedrock) {
|
||||||
|
headers.set("Authorization", `Bearer ${apiKey}`)
|
||||||
|
} else {
|
||||||
|
headers.set("x-api-key", apiKey)
|
||||||
|
headers.set("anthropic-version", headers.get("anthropic-version") ?? "2023-06-01")
|
||||||
|
if (body.model.startsWith("claude-sonnet-")) {
|
||||||
|
headers.set("anthropic-beta", "context-1m-2025-08-07")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifyBody: (body: Record<string, any>) => ({
|
||||||
...body,
|
...body,
|
||||||
service_tier: "standard_only",
|
...(isBedrock
|
||||||
}
|
? {
|
||||||
},
|
anthropic_version: "bedrock-2023-05-31",
|
||||||
streamSeparator: "\n\n",
|
anthropic_beta: isSonnet ? "context-1m-2025-08-07" : undefined,
|
||||||
createUsageParser: () => {
|
model: undefined,
|
||||||
let usage: Usage
|
stream: undefined,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
service_tier: "standard_only",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
createBinaryStreamDecoder: () => {
|
||||||
|
if (!isBedrock) return undefined
|
||||||
|
|
||||||
return {
|
const decoder = new TextDecoder()
|
||||||
parse: (chunk: string) => {
|
const encoder = new TextEncoder()
|
||||||
const data = chunk.split("\n")[1]
|
const codec = new EventStreamCodec(toUtf8, fromUtf8)
|
||||||
if (!data.startsWith("data: ")) return
|
let buffer = new Uint8Array(0)
|
||||||
|
return (value: Uint8Array) => {
|
||||||
|
const newBuffer = new Uint8Array(buffer.length + value.length)
|
||||||
|
newBuffer.set(buffer)
|
||||||
|
newBuffer.set(value, buffer.length)
|
||||||
|
buffer = newBuffer
|
||||||
|
|
||||||
let json
|
const messages = []
|
||||||
try {
|
while (buffer.length >= 4) {
|
||||||
json = JSON.parse(data.slice(6))
|
// first 4 bytes are the total length (big-endian)
|
||||||
} catch (e) {
|
const totalLength = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength).getUint32(0, false)
|
||||||
return
|
|
||||||
|
// wait for more chunks
|
||||||
|
if (buffer.length < totalLength) break
|
||||||
|
|
||||||
|
try {
|
||||||
|
const subView = buffer.subarray(0, totalLength)
|
||||||
|
const decoded = codec.decode(subView)
|
||||||
|
buffer = buffer.slice(totalLength)
|
||||||
|
|
||||||
|
/* Example of Bedrock data
|
||||||
|
```
|
||||||
|
{
|
||||||
|
bytes: 'eyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtb3B1cy00LTUtMjAyNTExMDEiLCJpZCI6Im1zZ19iZHJrXzAxMjVGdHRGb2lkNGlwWmZ4SzZMbktxeCIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwidXNhZ2UiOnsiaW5wdXRfdG9rZW5zIjo0LCJjYWNoZV9jcmVhdGlvbl9pbnB1dF90b2tlbnMiOjEsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjoxMTk2MywiY2FjaGVfY3JlYXRpb24iOnsiZXBoZW1lcmFsXzVtX2lucHV0X3Rva2VucyI6MSwiZXBoZW1lcmFsXzFoX2lucHV0X3Rva2VucyI6MH0sIm91dHB1dF90b2tlbnMiOjF9fX0=',
|
||||||
|
p: '...'
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
const usageUpdate = json.usage ?? json.message?.usage
|
Decoded bytes
|
||||||
if (!usageUpdate) return
|
```
|
||||||
usage = {
|
{
|
||||||
...usage,
|
type: 'message_start',
|
||||||
...usageUpdate,
|
message: {
|
||||||
cache_creation: {
|
model: 'claude-opus-4-5-20251101',
|
||||||
...usage?.cache_creation,
|
id: 'msg_bdrk_0125FttFoid4ipZfxK6LnKqx',
|
||||||
...usageUpdate.cache_creation,
|
type: 'message',
|
||||||
},
|
role: 'assistant',
|
||||||
server_tool_use: {
|
content: [],
|
||||||
...usage?.server_tool_use,
|
stop_reason: null,
|
||||||
...usageUpdate.server_tool_use,
|
stop_sequence: null,
|
||||||
},
|
usage: {
|
||||||
|
input_tokens: 4,
|
||||||
|
cache_creation_input_tokens: 1,
|
||||||
|
cache_read_input_tokens: 11963,
|
||||||
|
cache_creation: [Object],
|
||||||
|
output_tokens: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
```
|
||||||
retrieve: () => usage,
|
*/
|
||||||
}
|
|
||||||
},
|
/* Example of Anthropic data
|
||||||
normalizeUsage: (usage: Usage) => ({
|
```
|
||||||
inputTokens: usage.input_tokens ?? 0,
|
event: message_delta
|
||||||
outputTokens: usage.output_tokens ?? 0,
|
data: {"type":"message_start","message":{"model":"claude-opus-4-5-20251101","id":"msg_01ETvwVWSKULxzPdkQ1xAnk2","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":11543,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":11543,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}}
|
||||||
reasoningTokens: undefined,
|
```
|
||||||
cacheReadTokens: usage.cache_read_input_tokens ?? undefined,
|
*/
|
||||||
cacheWrite5mTokens: usage.cache_creation?.ephemeral_5m_input_tokens ?? undefined,
|
if (decoded.headers[":message-type"]?.value === "event") {
|
||||||
cacheWrite1hTokens: usage.cache_creation?.ephemeral_1h_input_tokens ?? undefined,
|
const data = decoder.decode(decoded.body, { stream: true })
|
||||||
}),
|
|
||||||
} satisfies ProviderHelper
|
const parsedDataResult = JSON.parse(data)
|
||||||
|
delete parsedDataResult.p
|
||||||
|
const binary = atob(parsedDataResult.bytes)
|
||||||
|
const uint8 = Uint8Array.from(binary, (c) => c.charCodeAt(0))
|
||||||
|
const bytes = decoder.decode(uint8)
|
||||||
|
const eventName = JSON.parse(bytes).type
|
||||||
|
messages.push([`event: ${eventName}`, "\n", `data: ${bytes}`, "\n\n"].join(""))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("@@@EE@@@")
|
||||||
|
console.log(e)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return encoder.encode(messages.join(""))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
streamSeparator: "\n\n",
|
||||||
|
createUsageParser: () => {
|
||||||
|
let usage: Usage
|
||||||
|
|
||||||
|
return {
|
||||||
|
parse: (chunk: string) => {
|
||||||
|
const data = chunk.split("\n")[1]
|
||||||
|
if (!data.startsWith("data: ")) return
|
||||||
|
|
||||||
|
let json
|
||||||
|
try {
|
||||||
|
json = JSON.parse(data.slice(6))
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const usageUpdate = json.usage ?? json.message?.usage
|
||||||
|
if (!usageUpdate) return
|
||||||
|
usage = {
|
||||||
|
...usage,
|
||||||
|
...usageUpdate,
|
||||||
|
cache_creation: {
|
||||||
|
...usage?.cache_creation,
|
||||||
|
...usageUpdate.cache_creation,
|
||||||
|
},
|
||||||
|
server_tool_use: {
|
||||||
|
...usage?.server_tool_use,
|
||||||
|
...usageUpdate.server_tool_use,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
retrieve: () => usage,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
normalizeUsage: (usage: Usage) => ({
|
||||||
|
inputTokens: usage.input_tokens ?? 0,
|
||||||
|
outputTokens: usage.output_tokens ?? 0,
|
||||||
|
reasoningTokens: undefined,
|
||||||
|
cacheReadTokens: usage.cache_read_input_tokens ?? undefined,
|
||||||
|
cacheWrite5mTokens: usage.cache_creation?.ephemeral_5m_input_tokens ?? undefined,
|
||||||
|
cacheWrite1hTokens: usage.cache_creation?.ephemeral_1h_input_tokens ?? undefined,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function fromAnthropicRequest(body: any): CommonRequest {
|
export function fromAnthropicRequest(body: any): CommonRequest {
|
||||||
if (!body || typeof body !== "object") return body
|
if (!body || typeof body !== "object") return body
|
||||||
|
|||||||
@@ -26,16 +26,17 @@ type Usage = {
|
|||||||
thoughtsTokenCount?: number
|
thoughtsTokenCount?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const googleHelper = {
|
export const googleHelper: ProviderHelper = ({ providerModel }) => ({
|
||||||
format: "google",
|
format: "google",
|
||||||
modifyUrl: (providerApi: string, model?: string, isStream?: boolean) =>
|
modifyUrl: (providerApi: string, isStream?: boolean) =>
|
||||||
`${providerApi}/models/${model}:${isStream ? "streamGenerateContent?alt=sse" : "generateContent"}`,
|
`${providerApi}/models/${providerModel}:${isStream ? "streamGenerateContent?alt=sse" : "generateContent"}`,
|
||||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
||||||
headers.set("x-goog-api-key", apiKey)
|
headers.set("x-goog-api-key", apiKey)
|
||||||
},
|
},
|
||||||
modifyBody: (body: Record<string, any>) => {
|
modifyBody: (body: Record<string, any>) => {
|
||||||
return body
|
return body
|
||||||
},
|
},
|
||||||
|
createBinaryStreamDecoder: () => undefined,
|
||||||
streamSeparator: "\r\n\r\n",
|
streamSeparator: "\r\n\r\n",
|
||||||
createUsageParser: () => {
|
createUsageParser: () => {
|
||||||
let usage: Usage
|
let usage: Usage
|
||||||
@@ -71,4 +72,4 @@ export const googleHelper = {
|
|||||||
cacheWrite1hTokens: undefined,
|
cacheWrite1hTokens: undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
} satisfies ProviderHelper
|
})
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type Usage = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const oaCompatHelper = {
|
export const oaCompatHelper: ProviderHelper = () => ({
|
||||||
format: "oa-compat",
|
format: "oa-compat",
|
||||||
modifyUrl: (providerApi: string) => providerApi + "/chat/completions",
|
modifyUrl: (providerApi: string) => providerApi + "/chat/completions",
|
||||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
||||||
@@ -33,6 +33,7 @@ export const oaCompatHelper = {
|
|||||||
...(body.stream ? { stream_options: { include_usage: true } } : {}),
|
...(body.stream ? { stream_options: { include_usage: true } } : {}),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
createBinaryStreamDecoder: () => undefined,
|
||||||
streamSeparator: "\n\n",
|
streamSeparator: "\n\n",
|
||||||
createUsageParser: () => {
|
createUsageParser: () => {
|
||||||
let usage: Usage
|
let usage: Usage
|
||||||
@@ -68,7 +69,7 @@ export const oaCompatHelper = {
|
|||||||
cacheWrite1hTokens: undefined,
|
cacheWrite1hTokens: undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
} satisfies ProviderHelper
|
})
|
||||||
|
|
||||||
export function fromOaCompatibleRequest(body: any): CommonRequest {
|
export function fromOaCompatibleRequest(body: any): CommonRequest {
|
||||||
if (!body || typeof body !== "object") return body
|
if (!body || typeof body !== "object") return body
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type Usage = {
|
|||||||
total_tokens?: number
|
total_tokens?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const openaiHelper = {
|
export const openaiHelper: ProviderHelper = () => ({
|
||||||
format: "openai",
|
format: "openai",
|
||||||
modifyUrl: (providerApi: string) => providerApi + "/responses",
|
modifyUrl: (providerApi: string) => providerApi + "/responses",
|
||||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
||||||
@@ -21,6 +21,7 @@ export const openaiHelper = {
|
|||||||
modifyBody: (body: Record<string, any>) => {
|
modifyBody: (body: Record<string, any>) => {
|
||||||
return body
|
return body
|
||||||
},
|
},
|
||||||
|
createBinaryStreamDecoder: () => undefined,
|
||||||
streamSeparator: "\n\n",
|
streamSeparator: "\n\n",
|
||||||
createUsageParser: () => {
|
createUsageParser: () => {
|
||||||
let usage: Usage
|
let usage: Usage
|
||||||
@@ -58,7 +59,7 @@ export const openaiHelper = {
|
|||||||
cacheWrite1hTokens: undefined,
|
cacheWrite1hTokens: undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
} satisfies ProviderHelper
|
})
|
||||||
|
|
||||||
export function fromOpenaiRequest(body: any): CommonRequest {
|
export function fromOpenaiRequest(body: any): CommonRequest {
|
||||||
if (!body || typeof body !== "object") return body
|
if (!body || typeof body !== "object") return body
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ export type UsageInfo = {
|
|||||||
cacheWrite1hTokens?: number
|
cacheWrite1hTokens?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProviderHelper = {
|
export type ProviderHelper = (input: { reqModel: string; providerModel: string }) => {
|
||||||
format: ZenData.Format
|
format: ZenData.Format
|
||||||
modifyUrl: (providerApi: string, model?: string, isStream?: boolean) => string
|
modifyUrl: (providerApi: string, isStream?: boolean) => string
|
||||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => void
|
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => void
|
||||||
modifyBody: (body: Record<string, any>) => Record<string, any>
|
modifyBody: (body: Record<string, any>) => Record<string, any>
|
||||||
|
createBinaryStreamDecoder: () => ((chunk: Uint8Array) => Uint8Array | undefined) | undefined
|
||||||
streamSeparator: string
|
streamSeparator: string
|
||||||
createUsageParser: () => {
|
createUsageParser: () => {
|
||||||
parse: (chunk: string) => void
|
parse: (chunk: string) => void
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Resource } from "@opencode-ai/console-resource"
|
import { Resource } from "@opencode-ai/console-resource"
|
||||||
|
|
||||||
export function createStickyTracker(stickyProvider: boolean, session: string) {
|
export function createStickyTracker(stickyProvider: "strict" | "prefer" | undefined, session: string) {
|
||||||
if (!stickyProvider) return
|
if (!stickyProvider) return
|
||||||
if (!session) return
|
if (!session) return
|
||||||
const key = `sticky:${session}`
|
const key = `sticky:${session}`
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"types": ["vite/client"],
|
"types": ["vite/client", "@webgpu/types"],
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["./src/*"]
|
"~/*": ["./src/*"]
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `billing` ADD `subscription` json;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `billing` DROP COLUMN `subscription_coupon_id`;
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -372,6 +372,20 @@
|
|||||||
"when": 1768343920467,
|
"when": 1768343920467,
|
||||||
"tag": "0052_aromatic_agent_zero",
|
"tag": "0052_aromatic_agent_zero",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 53,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1768599366758,
|
||||||
|
"tag": "0053_gigantic_hardball",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 54,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1768603665356,
|
||||||
|
"tag": "0054_numerous_annihilus",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
"promote-models-to-dev": "script/promote-models.ts dev",
|
"promote-models-to-dev": "script/promote-models.ts dev",
|
||||||
"promote-models-to-prod": "script/promote-models.ts production",
|
"promote-models-to-prod": "script/promote-models.ts production",
|
||||||
"pull-models-from-dev": "script/pull-models.ts dev",
|
"pull-models-from-dev": "script/pull-models.ts dev",
|
||||||
|
"pull-models-from-prod": "script/pull-models.ts production",
|
||||||
"update-black": "script/update-black.ts",
|
"update-black": "script/update-black.ts",
|
||||||
"promote-black-to-dev": "script/promote-black.ts dev",
|
"promote-black-to-dev": "script/promote-black.ts dev",
|
||||||
"promote-black-to-prod": "script/promote-black.ts production",
|
"promote-black-to-prod": "script/promote-black.ts production",
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { subscribe } from "diagnostics_channel"
|
||||||
|
import { Billing } from "../src/billing.js"
|
||||||
|
import { and, Database, eq } from "../src/drizzle/index.js"
|
||||||
|
import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js"
|
||||||
|
|
||||||
|
const workspaceID = process.argv[2]
|
||||||
|
|
||||||
|
if (!workspaceID) {
|
||||||
|
console.error("Usage: bun script/foo.ts <workspaceID>")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Removing from Black waitlist`)
|
||||||
|
|
||||||
|
const billing = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
subscriptionPlan: BillingTable.subscriptionPlan,
|
||||||
|
timeSubscriptionBooked: BillingTable.timeSubscriptionBooked,
|
||||||
|
})
|
||||||
|
.from(BillingTable)
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!billing?.timeSubscriptionBooked) {
|
||||||
|
console.error(`Error: Workspace is not on the waitlist`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({
|
||||||
|
subscriptionPlan: null,
|
||||||
|
timeSubscriptionBooked: null,
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID)),
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(`Done`)
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { Billing } from "../src/billing.js"
|
||||||
|
import { and, Database, eq, isNull, sql } from "../src/drizzle/index.js"
|
||||||
|
import { UserTable } from "../src/schema/user.sql.js"
|
||||||
|
import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js"
|
||||||
|
import { Identifier } from "../src/identifier.js"
|
||||||
|
import { centsToMicroCents } from "../src/util/price.js"
|
||||||
|
import { AuthTable } from "../src/schema/auth.sql.js"
|
||||||
|
|
||||||
|
const plan = "200"
|
||||||
|
const workspaceID = process.argv[2]
|
||||||
|
const seats = parseInt(process.argv[3])
|
||||||
|
|
||||||
|
console.log(`Gifting ${seats} seats of Black to workspace ${workspaceID}`)
|
||||||
|
|
||||||
|
if (!workspaceID || !seats) throw new Error("Usage: bun foo.ts <workspaceID> <seats>")
|
||||||
|
|
||||||
|
// Get workspace user
|
||||||
|
const users = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
id: UserTable.id,
|
||||||
|
role: UserTable.role,
|
||||||
|
email: AuthTable.subject,
|
||||||
|
})
|
||||||
|
.from(UserTable)
|
||||||
|
.leftJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email")))
|
||||||
|
.where(and(eq(UserTable.workspaceID, workspaceID), isNull(UserTable.timeDeleted))),
|
||||||
|
)
|
||||||
|
if (users.length === 0) throw new Error(`Error: No users found in workspace ${workspaceID}`)
|
||||||
|
if (users.length !== seats)
|
||||||
|
throw new Error(`Error: Workspace ${workspaceID} has ${users.length} users, expected ${seats}`)
|
||||||
|
const adminUser = users.find((user) => user.role === "admin")
|
||||||
|
if (!adminUser) throw new Error(`Error: No admin user found in workspace ${workspaceID}`)
|
||||||
|
if (!adminUser.email) throw new Error(`Error: Admin user ${adminUser.id} has no email`)
|
||||||
|
|
||||||
|
// Get Billing
|
||||||
|
const billing = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
customerID: BillingTable.customerID,
|
||||||
|
subscriptionID: BillingTable.subscriptionID,
|
||||||
|
})
|
||||||
|
.from(BillingTable)
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
if (!billing) throw new Error(`Error: Workspace ${workspaceID} has no billing record`)
|
||||||
|
if (billing.subscriptionID) throw new Error(`Error: Workspace ${workspaceID} already has a subscription`)
|
||||||
|
|
||||||
|
// Look up the Stripe customer by email
|
||||||
|
const customerID =
|
||||||
|
billing.customerID ??
|
||||||
|
(await (() =>
|
||||||
|
Billing.stripe()
|
||||||
|
.customers.create({
|
||||||
|
email: adminUser.email,
|
||||||
|
metadata: {
|
||||||
|
workspaceID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((customer) => customer.id))())
|
||||||
|
console.log(`Customer ID: ${customerID}`)
|
||||||
|
|
||||||
|
const couponID = "JAIr0Pe1"
|
||||||
|
const subscription = await Billing.stripe().subscriptions.create({
|
||||||
|
customer: customerID!,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
price: `price_1SmfyI2StuRr0lbXovxJNeZn`,
|
||||||
|
discounts: [{ coupon: couponID }],
|
||||||
|
quantity: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
console.log(`Subscription ID: ${subscription.id}`)
|
||||||
|
|
||||||
|
await Database.transaction(async (tx) => {
|
||||||
|
// Set customer id, subscription id, and payment method on workspace billing
|
||||||
|
await tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({
|
||||||
|
customerID,
|
||||||
|
subscriptionID: subscription.id,
|
||||||
|
subscription: { status: "subscribed", coupon: couponID, seats, plan },
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
|
|
||||||
|
// Create a row in subscription table
|
||||||
|
for (const user of users) {
|
||||||
|
await tx.insert(SubscriptionTable).values({
|
||||||
|
workspaceID,
|
||||||
|
id: Identifier.create("subscription"),
|
||||||
|
userID: user.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// // Create a row in payments table
|
||||||
|
// await tx.insert(PaymentTable).values({
|
||||||
|
// workspaceID,
|
||||||
|
// id: Identifier.create("payment"),
|
||||||
|
// amount: centsToMicroCents(amountInCents),
|
||||||
|
// customerID,
|
||||||
|
// invoiceID,
|
||||||
|
// paymentID,
|
||||||
|
// enrichment: {
|
||||||
|
// type: "subscription",
|
||||||
|
// couponID,
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`done`)
|
||||||
+8
-3
@@ -12,7 +12,7 @@ const email = process.argv[3]
|
|||||||
console.log(`Onboarding workspace ${workspaceID} for email ${email}`)
|
console.log(`Onboarding workspace ${workspaceID} for email ${email}`)
|
||||||
|
|
||||||
if (!workspaceID || !email) {
|
if (!workspaceID || !email) {
|
||||||
console.error("Usage: bun onboard-zen-black.ts <workspaceID> <email>")
|
console.error("Usage: bun foo.ts <workspaceID> <email>")
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ const existingSubscription = await Database.use((tx) =>
|
|||||||
tx
|
tx
|
||||||
.select({ workspaceID: BillingTable.workspaceID })
|
.select({ workspaceID: BillingTable.workspaceID })
|
||||||
.from(BillingTable)
|
.from(BillingTable)
|
||||||
.where(eq(BillingTable.subscriptionID, subscriptionID))
|
.where(sql`JSON_EXTRACT(${BillingTable.subscription}, '$.id') = ${subscriptionID}`)
|
||||||
.then((rows) => rows[0]),
|
.then((rows) => rows[0]),
|
||||||
)
|
)
|
||||||
if (existingSubscription) {
|
if (existingSubscription) {
|
||||||
@@ -128,10 +128,15 @@ await Database.transaction(async (tx) => {
|
|||||||
.set({
|
.set({
|
||||||
customerID,
|
customerID,
|
||||||
subscriptionID,
|
subscriptionID,
|
||||||
subscriptionCouponID: couponID,
|
|
||||||
paymentMethodID,
|
paymentMethodID,
|
||||||
paymentMethodLast4,
|
paymentMethodLast4,
|
||||||
paymentMethodType,
|
paymentMethodType,
|
||||||
|
subscription: {
|
||||||
|
status: "subscribed",
|
||||||
|
coupon: couponID,
|
||||||
|
seats: 1,
|
||||||
|
plan: "200",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
|
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import { Billing } from "../src/billing.js"
|
||||||
|
import { and, Database, desc, eq, isNotNull, lt, sql } from "../src/drizzle/index.js"
|
||||||
|
import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js"
|
||||||
|
|
||||||
|
const fromWrkID = process.argv[2]
|
||||||
|
const toWrkID = process.argv[3]
|
||||||
|
|
||||||
|
if (!fromWrkID || !toWrkID) {
|
||||||
|
console.error("Usage: bun foo.ts <fromWrkID> <toWrkID>")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Transferring subscription from ${fromWrkID} to ${toWrkID}`)
|
||||||
|
|
||||||
|
// Look up the FROM workspace billing
|
||||||
|
const fromBilling = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
customerID: BillingTable.customerID,
|
||||||
|
subscriptionID: BillingTable.subscriptionID,
|
||||||
|
subscription: BillingTable.subscription,
|
||||||
|
paymentMethodID: BillingTable.paymentMethodID,
|
||||||
|
paymentMethodType: BillingTable.paymentMethodType,
|
||||||
|
paymentMethodLast4: BillingTable.paymentMethodLast4,
|
||||||
|
})
|
||||||
|
.from(BillingTable)
|
||||||
|
.where(eq(BillingTable.workspaceID, fromWrkID))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
if (!fromBilling) throw new Error(`Error: FROM workspace has no billing record`)
|
||||||
|
if (!fromBilling.customerID) throw new Error(`Error: FROM workspace has no Stripe customer ID`)
|
||||||
|
if (!fromBilling.subscriptionID) throw new Error(`Error: FROM workspace has no subscription`)
|
||||||
|
|
||||||
|
const fromSubscription = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({ userID: SubscriptionTable.userID })
|
||||||
|
.from(SubscriptionTable)
|
||||||
|
.where(eq(SubscriptionTable.workspaceID, fromWrkID))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
if (!fromSubscription) throw new Error(`Error: FROM workspace has no subscription`)
|
||||||
|
|
||||||
|
// Look up the previous customer ID in FROM workspace
|
||||||
|
const subscriptionPayment = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
customerID: PaymentTable.customerID,
|
||||||
|
timeCreated: PaymentTable.timeCreated,
|
||||||
|
})
|
||||||
|
.from(PaymentTable)
|
||||||
|
.where(and(eq(PaymentTable.workspaceID, fromWrkID), sql`JSON_EXTRACT(enrichment, '$.type') = 'subscription'`))
|
||||||
|
.then((rows) => {
|
||||||
|
if (rows.length > 1) {
|
||||||
|
console.error(`Error: Multiple subscription payments found for workspace ${fromWrkID}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
return rows[0]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const fromPrevPayment = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({ customerID: PaymentTable.customerID })
|
||||||
|
.from(PaymentTable)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(PaymentTable.workspaceID, fromWrkID),
|
||||||
|
isNotNull(PaymentTable.customerID),
|
||||||
|
lt(PaymentTable.timeCreated, subscriptionPayment.timeCreated),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.orderBy(desc(PaymentTable.timeCreated))
|
||||||
|
.limit(1)
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
if (!fromPrevPayment?.customerID) throw new Error(`Error: FROM workspace has no previous Stripe customer to revert to`)
|
||||||
|
if (fromPrevPayment.customerID === fromBilling.customerID)
|
||||||
|
throw new Error(`Error: FROM workspace has the same Stripe customer ID as the current one`)
|
||||||
|
|
||||||
|
const fromPrevPaymentMethods = await Billing.stripe().customers.listPaymentMethods(fromPrevPayment.customerID, {})
|
||||||
|
if (fromPrevPaymentMethods.data.length === 0)
|
||||||
|
throw new Error(`Error: FROM workspace has no previous Stripe payment methods`)
|
||||||
|
|
||||||
|
// Look up the TO workspace billing
|
||||||
|
const toBilling = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
customerID: BillingTable.customerID,
|
||||||
|
subscriptionID: BillingTable.subscriptionID,
|
||||||
|
})
|
||||||
|
.from(BillingTable)
|
||||||
|
.where(eq(BillingTable.workspaceID, toWrkID))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
if (!toBilling) throw new Error(`Error: TO workspace has no billing record`)
|
||||||
|
if (toBilling.subscriptionID) throw new Error(`Error: TO workspace already has a subscription`)
|
||||||
|
|
||||||
|
console.log(`FROM:`)
|
||||||
|
console.log(` Old Customer ID: ${fromBilling.customerID}`)
|
||||||
|
console.log(` New Customer ID: ${fromPrevPayment.customerID}`)
|
||||||
|
console.log(`TO:`)
|
||||||
|
console.log(` Old Customer ID: ${toBilling.customerID}`)
|
||||||
|
console.log(` New Customer ID: ${fromBilling.customerID}`)
|
||||||
|
|
||||||
|
// Clear workspaceID from Stripe customer metadata
|
||||||
|
await Billing.stripe().customers.update(fromPrevPayment.customerID, {
|
||||||
|
metadata: {
|
||||||
|
workspaceID: fromWrkID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await Billing.stripe().customers.update(fromBilling.customerID, {
|
||||||
|
metadata: {
|
||||||
|
workspaceID: toWrkID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Database.transaction(async (tx) => {
|
||||||
|
await tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({
|
||||||
|
customerID: fromPrevPayment.customerID,
|
||||||
|
subscriptionID: null,
|
||||||
|
subscription: null,
|
||||||
|
paymentMethodID: fromPrevPaymentMethods.data[0].id,
|
||||||
|
paymentMethodLast4: fromPrevPaymentMethods.data[0].card?.last4 ?? null,
|
||||||
|
paymentMethodType: fromPrevPaymentMethods.data[0].type,
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, fromWrkID))
|
||||||
|
|
||||||
|
await tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({
|
||||||
|
customerID: fromBilling.customerID,
|
||||||
|
subscriptionID: fromBilling.subscriptionID,
|
||||||
|
subscription: fromBilling.subscription,
|
||||||
|
paymentMethodID: fromBilling.paymentMethodID,
|
||||||
|
paymentMethodLast4: fromBilling.paymentMethodLast4,
|
||||||
|
paymentMethodType: fromBilling.paymentMethodType,
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, toWrkID))
|
||||||
|
|
||||||
|
await tx
|
||||||
|
.update(SubscriptionTable)
|
||||||
|
.set({
|
||||||
|
workspaceID: toWrkID,
|
||||||
|
userID: fromSubscription.userID,
|
||||||
|
})
|
||||||
|
.where(eq(SubscriptionTable.workspaceID, fromWrkID))
|
||||||
|
|
||||||
|
await tx
|
||||||
|
.update(PaymentTable)
|
||||||
|
.set({
|
||||||
|
workspaceID: toWrkID,
|
||||||
|
})
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(PaymentTable.workspaceID, fromWrkID),
|
||||||
|
sql`JSON_EXTRACT(enrichment, '$.type') = 'subscription'`,
|
||||||
|
eq(PaymentTable.amount, 20000000000),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`done`)
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { Billing } from "../src/billing.js"
|
import { Billing } from "../src/billing.js"
|
||||||
|
import { Database, eq } from "../src/drizzle/index.js"
|
||||||
|
import { WorkspaceTable } from "../src/schema/workspace.sql.js"
|
||||||
|
|
||||||
// get input from command line
|
// get input from command line
|
||||||
const workspaceID = process.argv[2]
|
const workspaceID = process.argv[2]
|
||||||
@@ -9,6 +11,19 @@ if (!workspaceID || !dollarAmount) {
|
|||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check workspace exists
|
||||||
|
const workspace = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select()
|
||||||
|
.from(WorkspaceTable)
|
||||||
|
.where(eq(WorkspaceTable.id, workspaceID))
|
||||||
|
.then((rows) => rows[0]),
|
||||||
|
)
|
||||||
|
if (!workspace) {
|
||||||
|
console.error("Error: Workspace not found")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
const amountInDollars = parseFloat(dollarAmount)
|
const amountInDollars = parseFloat(dollarAmount)
|
||||||
if (isNaN(amountInDollars) || amountInDollars <= 0) {
|
if (isNaN(amountInDollars) || amountInDollars <= 0) {
|
||||||
console.error("Error: dollarAmount must be a positive number")
|
console.error("Error: dollarAmount must be a positive number")
|
||||||
|
|||||||
@@ -55,8 +55,9 @@ if (identifier.startsWith("wrk_")) {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get all payments for these workspaces
|
for (const user of users) {
|
||||||
await Promise.all(users.map((u: { workspaceID: string }) => printWorkspace(u.workspaceID)))
|
await printWorkspace(user.workspaceID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function printWorkspace(workspaceID: string) {
|
async function printWorkspace(workspaceID: string) {
|
||||||
@@ -113,8 +114,13 @@ async function printWorkspace(workspaceID: string) {
|
|||||||
.select({
|
.select({
|
||||||
balance: BillingTable.balance,
|
balance: BillingTable.balance,
|
||||||
customerID: BillingTable.customerID,
|
customerID: BillingTable.customerID,
|
||||||
|
reload: BillingTable.reload,
|
||||||
subscriptionID: BillingTable.subscriptionID,
|
subscriptionID: BillingTable.subscriptionID,
|
||||||
subscriptionCouponID: BillingTable.subscriptionCouponID,
|
subscription: {
|
||||||
|
plan: BillingTable.subscriptionPlan,
|
||||||
|
booked: BillingTable.timeSubscriptionBooked,
|
||||||
|
enrichment: BillingTable.subscription,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.from(BillingTable)
|
.from(BillingTable)
|
||||||
.where(eq(BillingTable.workspaceID, workspace.id))
|
.where(eq(BillingTable.workspaceID, workspace.id))
|
||||||
@@ -123,6 +129,16 @@ async function printWorkspace(workspaceID: string) {
|
|||||||
rows.map((row) => ({
|
rows.map((row) => ({
|
||||||
...row,
|
...row,
|
||||||
balance: `$${(row.balance / 100000000).toFixed(2)}`,
|
balance: `$${(row.balance / 100000000).toFixed(2)}`,
|
||||||
|
subscription: row.subscriptionID
|
||||||
|
? [
|
||||||
|
`Black ${row.subscription.enrichment!.plan}`,
|
||||||
|
row.subscription.enrichment!.seats > 1 ? `X ${row.subscription.enrichment!.seats} seats` : "",
|
||||||
|
row.subscription.enrichment!.coupon ? `(coupon: ${row.subscription.enrichment!.coupon})` : "",
|
||||||
|
`(ref: ${row.subscriptionID})`,
|
||||||
|
].join(" ")
|
||||||
|
: row.subscription.booked
|
||||||
|
? `Waitlist ${row.subscription.plan} plan`
|
||||||
|
: undefined,
|
||||||
}))[0],
|
}))[0],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -133,6 +149,7 @@ async function printWorkspace(workspaceID: string) {
|
|||||||
amount: PaymentTable.amount,
|
amount: PaymentTable.amount,
|
||||||
paymentID: PaymentTable.paymentID,
|
paymentID: PaymentTable.paymentID,
|
||||||
invoiceID: PaymentTable.invoiceID,
|
invoiceID: PaymentTable.invoiceID,
|
||||||
|
customerID: PaymentTable.customerID,
|
||||||
timeCreated: PaymentTable.timeCreated,
|
timeCreated: PaymentTable.timeCreated,
|
||||||
timeRefunded: PaymentTable.timeRefunded,
|
timeRefunded: PaymentTable.timeRefunded,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,33 +8,25 @@ const stage = process.argv[2]
|
|||||||
if (!stage) throw new Error("Stage is required")
|
if (!stage) throw new Error("Stage is required")
|
||||||
|
|
||||||
const root = path.resolve(process.cwd(), "..", "..", "..")
|
const root = path.resolve(process.cwd(), "..", "..", "..")
|
||||||
|
const PARTS = 8
|
||||||
|
|
||||||
// read the secret
|
// read the secret
|
||||||
const ret = await $`bun sst secret list`.cwd(root).text()
|
const ret = await $`bun sst secret list`.cwd(root).text()
|
||||||
const lines = ret.split("\n")
|
const lines = ret.split("\n")
|
||||||
const value1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[1]
|
const values = Array.from({ length: PARTS }, (_, i) => {
|
||||||
const value2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1]
|
const value = lines
|
||||||
const value3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1]
|
.find((line) => line.startsWith(`ZEN_MODELS${i + 1}`))
|
||||||
const value4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1]
|
?.split("=")
|
||||||
const value5 = lines.find((line) => line.startsWith("ZEN_MODELS5"))?.split("=")[1]
|
.slice(1)
|
||||||
const value6 = lines.find((line) => line.startsWith("ZEN_MODELS6"))?.split("=")[1]
|
.join("=")
|
||||||
const value7 = lines.find((line) => line.startsWith("ZEN_MODELS7"))?.split("=")[1]
|
if (!value) throw new Error(`ZEN_MODELS${i + 1} not found`)
|
||||||
if (!value1) throw new Error("ZEN_MODELS1 not found")
|
return value
|
||||||
if (!value2) throw new Error("ZEN_MODELS2 not found")
|
})
|
||||||
if (!value3) throw new Error("ZEN_MODELS3 not found")
|
|
||||||
if (!value4) throw new Error("ZEN_MODELS4 not found")
|
|
||||||
if (!value5) throw new Error("ZEN_MODELS5 not found")
|
|
||||||
if (!value6) throw new Error("ZEN_MODELS6 not found")
|
|
||||||
if (!value7) throw new Error("ZEN_MODELS7 not found")
|
|
||||||
|
|
||||||
// validate value
|
// validate value
|
||||||
ZenData.validate(JSON.parse(value1 + value2 + value3 + value4 + value5 + value6 + value7))
|
ZenData.validate(JSON.parse(values.join("")))
|
||||||
|
|
||||||
// update the secret
|
// update the secret
|
||||||
await $`bun sst secret set ZEN_MODELS1 ${value1} --stage ${stage}`
|
for (let i = 0; i < PARTS; i++) {
|
||||||
await $`bun sst secret set ZEN_MODELS2 ${value2} --stage ${stage}`
|
await $`bun sst secret set ZEN_MODELS${i + 1} --stage ${stage} -- ${values[i]}`
|
||||||
await $`bun sst secret set ZEN_MODELS3 ${value3} --stage ${stage}`
|
}
|
||||||
await $`bun sst secret set ZEN_MODELS4 ${value4} --stage ${stage}`
|
|
||||||
await $`bun sst secret set ZEN_MODELS5 ${value5} --stage ${stage}`
|
|
||||||
await $`bun sst secret set ZEN_MODELS6 ${value6} --stage ${stage}`
|
|
||||||
await $`bun sst secret set ZEN_MODELS7 ${value7} --stage ${stage}`
|
|
||||||
|
|||||||
@@ -8,32 +8,25 @@ const stage = process.argv[2]
|
|||||||
if (!stage) throw new Error("Stage is required")
|
if (!stage) throw new Error("Stage is required")
|
||||||
|
|
||||||
const root = path.resolve(process.cwd(), "..", "..", "..")
|
const root = path.resolve(process.cwd(), "..", "..", "..")
|
||||||
|
const PARTS = 8
|
||||||
|
|
||||||
// read the secret
|
// read the secret
|
||||||
const ret = await $`bun sst secret list --stage ${stage}`.cwd(root).text()
|
const ret = await $`bun sst secret list --stage ${stage}`.cwd(root).text()
|
||||||
const lines = ret.split("\n")
|
const lines = ret.split("\n")
|
||||||
const value1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[1]
|
const values = Array.from({ length: PARTS }, (_, i) => {
|
||||||
const value2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1]
|
const value = lines
|
||||||
const value3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1]
|
.find((line) => line.startsWith(`ZEN_MODELS${i + 1}`))
|
||||||
const value4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1]
|
?.split("=")
|
||||||
const value5 = lines.find((line) => line.startsWith("ZEN_MODELS5"))?.split("=")[1]
|
.slice(1)
|
||||||
const value6 = lines.find((line) => line.startsWith("ZEN_MODELS6"))?.split("=")[1]
|
.join("=")
|
||||||
const value7 = lines.find((line) => line.startsWith("ZEN_MODELS7"))?.split("=")[1]
|
if (!value) throw new Error(`ZEN_MODELS${i + 1} not found`)
|
||||||
if (!value1) throw new Error("ZEN_MODELS1 not found")
|
return value
|
||||||
if (!value2) throw new Error("ZEN_MODELS2 not found")
|
})
|
||||||
if (!value3) throw new Error("ZEN_MODELS3 not found")
|
|
||||||
if (!value4) throw new Error("ZEN_MODELS4 not found")
|
|
||||||
if (!value5) throw new Error("ZEN_MODELS5 not found")
|
|
||||||
if (!value6) throw new Error("ZEN_MODELS6 not found")
|
|
||||||
if (!value7) throw new Error("ZEN_MODELS7 not found")
|
|
||||||
// validate value
|
// validate value
|
||||||
ZenData.validate(JSON.parse(value1 + value2 + value3 + value4 + value5 + value6 + value7))
|
ZenData.validate(JSON.parse(values.join("")))
|
||||||
|
|
||||||
// update the secret
|
// update the secret
|
||||||
await $`bun sst secret set ZEN_MODELS1 ${value1}`
|
for (let i = 0; i < PARTS; i++) {
|
||||||
await $`bun sst secret set ZEN_MODELS2 ${value2}`
|
await $`bun sst secret set ZEN_MODELS${i + 1} -- ${values[i]}`
|
||||||
await $`bun sst secret set ZEN_MODELS3 ${value3}`
|
}
|
||||||
await $`bun sst secret set ZEN_MODELS4 ${value4}`
|
|
||||||
await $`bun sst secret set ZEN_MODELS5 ${value5}`
|
|
||||||
await $`bun sst secret set ZEN_MODELS6 ${value6}`
|
|
||||||
await $`bun sst secret set ZEN_MODELS7 ${value7}`
|
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import { Billing } from "../src/billing.js"
|
|
||||||
import { and, Database, eq } from "../src/drizzle/index.js"
|
|
||||||
import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js"
|
|
||||||
|
|
||||||
const workspaceID = process.argv[2]
|
|
||||||
|
|
||||||
if (!workspaceID) {
|
|
||||||
console.error("Usage: bun remove-black.ts <workspaceID>")
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Removing subscription from workspace ${workspaceID}`)
|
|
||||||
|
|
||||||
// Look up the workspace billing
|
|
||||||
const billing = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select({
|
|
||||||
customerID: BillingTable.customerID,
|
|
||||||
subscriptionID: BillingTable.subscriptionID,
|
|
||||||
})
|
|
||||||
.from(BillingTable)
|
|
||||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
|
||||||
.then((rows) => rows[0]),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!billing) {
|
|
||||||
console.error(`Error: No billing record found for workspace ${workspaceID}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!billing.subscriptionID) {
|
|
||||||
console.error(`Error: Workspace ${workspaceID} does not have a subscription`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(` Customer ID: ${billing.customerID}`)
|
|
||||||
console.log(` Subscription ID: ${billing.subscriptionID}`)
|
|
||||||
|
|
||||||
// Clear workspaceID from Stripe customer metadata
|
|
||||||
if (billing.customerID) {
|
|
||||||
//await Billing.stripe().customers.update(billing.customerID, {
|
|
||||||
// metadata: {
|
|
||||||
// workspaceID: "",
|
|
||||||
// },
|
|
||||||
//})
|
|
||||||
//console.log(`Cleared workspaceID from Stripe customer metadata`)
|
|
||||||
}
|
|
||||||
|
|
||||||
await Database.transaction(async (tx) => {
|
|
||||||
// Clear subscription-related fields from billing table
|
|
||||||
await tx
|
|
||||||
.update(BillingTable)
|
|
||||||
.set({
|
|
||||||
// customerID: null,
|
|
||||||
subscriptionID: null,
|
|
||||||
subscriptionCouponID: null,
|
|
||||||
// paymentMethodID: null,
|
|
||||||
// paymentMethodLast4: null,
|
|
||||||
// paymentMethodType: null,
|
|
||||||
})
|
|
||||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
|
||||||
|
|
||||||
// Delete from subscription table
|
|
||||||
await tx.delete(SubscriptionTable).where(eq(SubscriptionTable.workspaceID, workspaceID))
|
|
||||||
|
|
||||||
// Delete from payments table
|
|
||||||
await tx
|
|
||||||
.delete(PaymentTable)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(PaymentTable.workspaceID, workspaceID),
|
|
||||||
eq(PaymentTable.enrichment, { type: "subscription" }),
|
|
||||||
eq(PaymentTable.amount, 20000000000),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`Successfully removed subscription from workspace ${workspaceID}`)
|
|
||||||
@@ -7,34 +7,24 @@ import { ZenData } from "../src/model"
|
|||||||
|
|
||||||
const root = path.resolve(process.cwd(), "..", "..", "..")
|
const root = path.resolve(process.cwd(), "..", "..", "..")
|
||||||
const models = await $`bun sst secret list`.cwd(root).text()
|
const models = await $`bun sst secret list`.cwd(root).text()
|
||||||
|
const PARTS = 8
|
||||||
|
|
||||||
// read the line starting with "ZEN_MODELS"
|
// read the line starting with "ZEN_MODELS"
|
||||||
const lines = models.split("\n")
|
const lines = models.split("\n")
|
||||||
const oldValue1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[1]
|
const oldValues = Array.from({ length: PARTS }, (_, i) => {
|
||||||
const oldValue2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1]
|
const value = lines
|
||||||
const oldValue3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1]
|
.find((line) => line.startsWith(`ZEN_MODELS${i + 1}`))
|
||||||
const oldValue4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1]
|
?.split("=")
|
||||||
const oldValue5 = lines.find((line) => line.startsWith("ZEN_MODELS5"))?.split("=")[1]
|
.slice(1)
|
||||||
const oldValue6 = lines.find((line) => line.startsWith("ZEN_MODELS6"))?.split("=")[1]
|
.join("=")
|
||||||
const oldValue7 = lines.find((line) => line.startsWith("ZEN_MODELS7"))?.split("=")[1]
|
if (!value) throw new Error(`ZEN_MODELS${i + 1} not found`)
|
||||||
if (!oldValue1) throw new Error("ZEN_MODELS1 not found")
|
return value
|
||||||
if (!oldValue2) throw new Error("ZEN_MODELS2 not found")
|
})
|
||||||
if (!oldValue3) throw new Error("ZEN_MODELS3 not found")
|
|
||||||
if (!oldValue4) throw new Error("ZEN_MODELS4 not found")
|
|
||||||
if (!oldValue5) throw new Error("ZEN_MODELS5 not found")
|
|
||||||
if (!oldValue6) throw new Error("ZEN_MODELS6 not found")
|
|
||||||
if (!oldValue7) throw new Error("ZEN_MODELS7 not found")
|
|
||||||
|
|
||||||
// store the prettified json to a temp file
|
// store the prettified json to a temp file
|
||||||
const filename = `models-${Date.now()}.json`
|
const filename = `models-${Date.now()}.json`
|
||||||
const tempFile = Bun.file(path.join(os.tmpdir(), filename))
|
const tempFile = Bun.file(path.join(os.tmpdir(), filename))
|
||||||
await tempFile.write(
|
await tempFile.write(JSON.stringify(JSON.parse(oldValues.join("")), null, 2))
|
||||||
JSON.stringify(
|
|
||||||
JSON.parse(oldValue1 + oldValue2 + oldValue3 + oldValue4 + oldValue5 + oldValue6 + oldValue7),
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
console.log("tempFile", tempFile.name)
|
console.log("tempFile", tempFile.name)
|
||||||
|
|
||||||
// open temp file in vim and read the file on close
|
// open temp file in vim and read the file on close
|
||||||
@@ -43,19 +33,11 @@ const newValue = JSON.stringify(JSON.parse(await tempFile.text()))
|
|||||||
ZenData.validate(JSON.parse(newValue))
|
ZenData.validate(JSON.parse(newValue))
|
||||||
|
|
||||||
// update the secret
|
// update the secret
|
||||||
const chunk = Math.ceil(newValue.length / 7)
|
const chunk = Math.ceil(newValue.length / PARTS)
|
||||||
const newValue1 = newValue.slice(0, chunk)
|
const newValues = Array.from({ length: PARTS }, (_, i) =>
|
||||||
const newValue2 = newValue.slice(chunk, chunk * 2)
|
newValue.slice(chunk * i, i === PARTS - 1 ? undefined : chunk * (i + 1)),
|
||||||
const newValue3 = newValue.slice(chunk * 2, chunk * 3)
|
)
|
||||||
const newValue4 = newValue.slice(chunk * 3, chunk * 4)
|
|
||||||
const newValue5 = newValue.slice(chunk * 4, chunk * 5)
|
|
||||||
const newValue6 = newValue.slice(chunk * 5, chunk * 6)
|
|
||||||
const newValue7 = newValue.slice(chunk * 6)
|
|
||||||
|
|
||||||
await $`bun sst secret set ZEN_MODELS1 ${newValue1}`
|
for (let i = 0; i < PARTS; i++) {
|
||||||
await $`bun sst secret set ZEN_MODELS2 ${newValue2}`
|
await $`bun sst secret set ZEN_MODELS${i + 1} -- ${newValues[i]}`
|
||||||
await $`bun sst secret set ZEN_MODELS3 ${newValue3}`
|
}
|
||||||
await $`bun sst secret set ZEN_MODELS4 ${newValue4}`
|
|
||||||
await $`bun sst secret set ZEN_MODELS5 ${newValue5}`
|
|
||||||
await $`bun sst secret set ZEN_MODELS6 ${newValue6}`
|
|
||||||
await $`bun sst secret set ZEN_MODELS7 ${newValue7}`
|
|
||||||
|
|||||||
@@ -25,22 +25,7 @@ export namespace Billing {
|
|||||||
export const get = async () => {
|
export const get = async () => {
|
||||||
return Database.use(async (tx) =>
|
return Database.use(async (tx) =>
|
||||||
tx
|
tx
|
||||||
.select({
|
.select()
|
||||||
customerID: BillingTable.customerID,
|
|
||||||
subscriptionID: BillingTable.subscriptionID,
|
|
||||||
paymentMethodID: BillingTable.paymentMethodID,
|
|
||||||
paymentMethodType: BillingTable.paymentMethodType,
|
|
||||||
paymentMethodLast4: BillingTable.paymentMethodLast4,
|
|
||||||
balance: BillingTable.balance,
|
|
||||||
reload: BillingTable.reload,
|
|
||||||
reloadAmount: BillingTable.reloadAmount,
|
|
||||||
reloadTrigger: BillingTable.reloadTrigger,
|
|
||||||
monthlyLimit: BillingTable.monthlyLimit,
|
|
||||||
monthlyUsage: BillingTable.monthlyUsage,
|
|
||||||
timeMonthlyUsageUpdated: BillingTable.timeMonthlyUsageUpdated,
|
|
||||||
reloadError: BillingTable.reloadError,
|
|
||||||
timeReloadError: BillingTable.timeReloadError,
|
|
||||||
})
|
|
||||||
.from(BillingTable)
|
.from(BillingTable)
|
||||||
.where(eq(BillingTable.workspaceID, Actor.workspace()))
|
.where(eq(BillingTable.workspaceID, Actor.workspace()))
|
||||||
.then((r) => r[0]),
|
.then((r) => r[0]),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { fn } from "./util/fn"
|
import { fn } from "./util/fn"
|
||||||
import { Resource } from "@opencode-ai/console-resource"
|
import { Resource } from "@opencode-ai/console-resource"
|
||||||
|
import { centsToMicroCents } from "./util/price"
|
||||||
|
import { getWeekBounds } from "./util/date"
|
||||||
|
|
||||||
export namespace BlackData {
|
export namespace BlackData {
|
||||||
const Schema = z.object({
|
const Schema = z.object({
|
||||||
@@ -18,3 +20,73 @@ export namespace BlackData {
|
|||||||
return Schema.parse(json)
|
return Schema.parse(json)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace Black {
|
||||||
|
export const analyzeRollingUsage = fn(
|
||||||
|
z.object({
|
||||||
|
usage: z.number().int(),
|
||||||
|
timeUpdated: z.date(),
|
||||||
|
}),
|
||||||
|
({ usage, timeUpdated }) => {
|
||||||
|
const now = new Date()
|
||||||
|
const black = BlackData.get()
|
||||||
|
const rollingWindowMs = black.rollingWindow * 3600 * 1000
|
||||||
|
const rollingLimitInMicroCents = centsToMicroCents(black.rollingLimit * 100)
|
||||||
|
const windowStart = new Date(now.getTime() - rollingWindowMs)
|
||||||
|
if (timeUpdated < windowStart) {
|
||||||
|
return {
|
||||||
|
status: "ok" as const,
|
||||||
|
resetInSec: black.rollingWindow * 3600,
|
||||||
|
usagePercent: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const windowEnd = new Date(timeUpdated.getTime() + rollingWindowMs)
|
||||||
|
if (usage < rollingLimitInMicroCents) {
|
||||||
|
return {
|
||||||
|
status: "ok" as const,
|
||||||
|
resetInSec: Math.ceil((windowEnd.getTime() - now.getTime()) / 1000),
|
||||||
|
usagePercent: Math.ceil(Math.min(100, (usage / rollingLimitInMicroCents) * 100)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: "rate-limited" as const,
|
||||||
|
resetInSec: Math.ceil((windowEnd.getTime() - now.getTime()) / 1000),
|
||||||
|
usagePercent: 100,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export const analyzeWeeklyUsage = fn(
|
||||||
|
z.object({
|
||||||
|
usage: z.number().int(),
|
||||||
|
timeUpdated: z.date(),
|
||||||
|
}),
|
||||||
|
({ usage, timeUpdated }) => {
|
||||||
|
const black = BlackData.get()
|
||||||
|
const now = new Date()
|
||||||
|
const week = getWeekBounds(now)
|
||||||
|
const fixedLimitInMicroCents = centsToMicroCents(black.fixedLimit * 100)
|
||||||
|
if (timeUpdated < week.start) {
|
||||||
|
return {
|
||||||
|
status: "ok" as const,
|
||||||
|
resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
|
||||||
|
usagePercent: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (usage < fixedLimitInMicroCents) {
|
||||||
|
return {
|
||||||
|
status: "ok" as const,
|
||||||
|
resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
|
||||||
|
usagePercent: Math.ceil(Math.min(100, (usage / fixedLimitInMicroCents) * 100)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "rate-limited" as const,
|
||||||
|
resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
|
||||||
|
usagePercent: 100,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export namespace ZenData {
|
|||||||
cost200K: ModelCostSchema.optional(),
|
cost200K: ModelCostSchema.optional(),
|
||||||
allowAnonymous: z.boolean().optional(),
|
allowAnonymous: z.boolean().optional(),
|
||||||
byokProvider: z.enum(["openai", "anthropic", "google"]).optional(),
|
byokProvider: z.enum(["openai", "anthropic", "google"]).optional(),
|
||||||
stickyProvider: z.boolean().optional(),
|
stickyProvider: z.enum(["strict", "prefer"]).optional(),
|
||||||
trial: TrialSchema.optional(),
|
trial: TrialSchema.optional(),
|
||||||
rateLimit: z.number().optional(),
|
rateLimit: z.number().optional(),
|
||||||
fallbackProvider: z.string().optional(),
|
fallbackProvider: z.string().optional(),
|
||||||
@@ -74,7 +74,8 @@ export namespace ZenData {
|
|||||||
Resource.ZEN_MODELS4.value +
|
Resource.ZEN_MODELS4.value +
|
||||||
Resource.ZEN_MODELS5.value +
|
Resource.ZEN_MODELS5.value +
|
||||||
Resource.ZEN_MODELS6.value +
|
Resource.ZEN_MODELS6.value +
|
||||||
Resource.ZEN_MODELS7.value,
|
Resource.ZEN_MODELS7.value +
|
||||||
|
Resource.ZEN_MODELS8.value,
|
||||||
)
|
)
|
||||||
return ModelsSchema.parse(json)
|
return ModelsSchema.parse(json)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,8 +21,13 @@ export const BillingTable = mysqlTable(
|
|||||||
reloadError: varchar("reload_error", { length: 255 }),
|
reloadError: varchar("reload_error", { length: 255 }),
|
||||||
timeReloadError: utc("time_reload_error"),
|
timeReloadError: utc("time_reload_error"),
|
||||||
timeReloadLockedTill: utc("time_reload_locked_till"),
|
timeReloadLockedTill: utc("time_reload_locked_till"),
|
||||||
|
subscription: json("subscription").$type<{
|
||||||
|
status: "subscribed"
|
||||||
|
coupon?: string
|
||||||
|
seats: number
|
||||||
|
plan: "20" | "100" | "200"
|
||||||
|
}>(),
|
||||||
subscriptionID: varchar("subscription_id", { length: 28 }),
|
subscriptionID: varchar("subscription_id", { length: 28 }),
|
||||||
subscriptionCouponID: varchar("subscription_coupon_id", { length: 28 }),
|
|
||||||
subscriptionPlan: mysqlEnum("subscription_plan", ["20", "100", "200"] as const),
|
subscriptionPlan: mysqlEnum("subscription_plan", ["20", "100", "200"] as const),
|
||||||
timeSubscriptionBooked: utc("time_subscription_booked"),
|
timeSubscriptionBooked: utc("time_subscription_booked"),
|
||||||
},
|
},
|
||||||
|
|||||||
Vendored
+4
@@ -134,6 +134,10 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
|
"ZEN_MODELS8": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"ZEN_SESSION_SECRET": {
|
"ZEN_SESSION_SECRET": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+4
@@ -134,6 +134,10 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
|
"ZEN_MODELS8": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"ZEN_SESSION_SECRET": {
|
"ZEN_SESSION_SECRET": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
|
|||||||
+4
@@ -134,6 +134,10 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
|
"ZEN_MODELS8": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"ZEN_SESSION_SECRET": {
|
"ZEN_SESSION_SECRET": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
|
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root" class="flex flex-col h-screen"></div>
|
<div id="root" class="flex flex-col h-dvh"></div>
|
||||||
<script src="/src/index.tsx" type="module"></script>
|
<script src="/src/index.tsx" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ semver = "1.0.27"
|
|||||||
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
||||||
uuid = { version = "1.19.0", features = ["v4"] }
|
uuid = { version = "1.19.0", features = ["v4"] }
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
gtk = "0.18.2"
|
gtk = "0.18.2"
|
||||||
webkit2gtk = "=2.0.1"
|
webkit2gtk = "=2.0.1"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"core:default",
|
"core:default",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
"core:window:allow-start-dragging",
|
"core:window:allow-start-dragging",
|
||||||
|
"core:window:allow-set-theme",
|
||||||
"core:webview:allow-set-webview-zoom",
|
"core:webview:allow-set-webview-zoom",
|
||||||
"core:window:allow-is-focused",
|
"core:window:allow-is-focused",
|
||||||
"core:window:allow-show",
|
"core:window:allow-show",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use std::{
|
|||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use tauri::{AppHandle, LogicalSize, Manager, RunEvent, State, WebviewUrl, WebviewWindow};
|
use tauri::{AppHandle, LogicalSize, Manager, RunEvent, State, WebviewWindowBuilder};
|
||||||
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
|
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
|
||||||
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
||||||
use tauri_plugin_store::StoreExt;
|
use tauri_plugin_store::StoreExt;
|
||||||
@@ -223,7 +223,7 @@ async fn check_server_health(url: &str, password: Option<&str>) -> bool {
|
|||||||
pub fn run() {
|
pub fn run() {
|
||||||
let updater_enabled = option_env!("TAURI_SIGNING_PRIVATE_KEY").is_some();
|
let updater_enabled = option_env!("TAURI_SIGNING_PRIVATE_KEY").is_some();
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(all(target_os = "macos", not(debug_assertions)))]
|
||||||
let _ = std::process::Command::new("killall")
|
let _ = std::process::Command::new("killall")
|
||||||
.arg("opencode-cli")
|
.arg("opencode-cli")
|
||||||
.output();
|
.output();
|
||||||
@@ -237,7 +237,14 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
.plugin(tauri_plugin_window_state::Builder::new().build())
|
.plugin(
|
||||||
|
tauri_plugin_window_state::Builder::new()
|
||||||
|
.with_state_flags(
|
||||||
|
tauri_plugin_window_state::StateFlags::all()
|
||||||
|
- tauri_plugin_window_state::StateFlags::DECORATIONS,
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
.plugin(tauri_plugin_store::Builder::new().build())
|
.plugin(tauri_plugin_store::Builder::new().build())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
@@ -268,29 +275,30 @@ pub fn run() {
|
|||||||
.map(|m| m.size().to_logical(m.scale_factor()))
|
.map(|m| m.size().to_logical(m.scale_factor()))
|
||||||
.unwrap_or(LogicalSize::new(1920, 1080));
|
.unwrap_or(LogicalSize::new(1920, 1080));
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
let config = app
|
||||||
let mut window_builder =
|
.config()
|
||||||
WebviewWindow::builder(&app, "main", WebviewUrl::App("/".into()))
|
.app
|
||||||
.title("OpenCode")
|
.windows
|
||||||
.inner_size(size.width as f64, size.height as f64)
|
.iter()
|
||||||
.decorations(true)
|
.find(|w| w.label == "main")
|
||||||
.zoom_hotkeys_enabled(true)
|
.expect("main window config missing");
|
||||||
.disable_drag_drop_handler()
|
|
||||||
.initialization_script(format!(
|
let window_builder = WebviewWindowBuilder::from_config(&app, config)
|
||||||
r#"
|
.expect("Failed to create window builder from config")
|
||||||
|
.inner_size(size.width as f64, size.height as f64)
|
||||||
|
.initialization_script(format!(
|
||||||
|
r#"
|
||||||
window.__OPENCODE__ ??= {{}};
|
window.__OPENCODE__ ??= {{}};
|
||||||
window.__OPENCODE__.updaterEnabled = {updater_enabled};
|
window.__OPENCODE__.updaterEnabled = {updater_enabled};
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
let window_builder = window_builder
|
||||||
window_builder = window_builder
|
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
.hidden_title(true);
|
||||||
.hidden_title(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
window_builder.build().expect("Failed to create window");
|
let _window = window_builder.build().expect("Failed to create window");
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
app.manage(ServerState::new(None, rx));
|
app.manage(ServerState::new(None, rx));
|
||||||
|
|||||||
@@ -11,6 +11,20 @@
|
|||||||
"frontendDist": "../dist"
|
"frontendDist": "../dist"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"label": "main",
|
||||||
|
"create": false,
|
||||||
|
"title": "OpenCode",
|
||||||
|
"url": "/",
|
||||||
|
"decorations": true,
|
||||||
|
"dragDropEnabled": false,
|
||||||
|
"zoomHotkeysEnabled": true,
|
||||||
|
"titleBarStyle": "Overlay",
|
||||||
|
"hiddenTitle": true,
|
||||||
|
"trafficLightPosition": { "x": 12.0, "y": 18.0 }
|
||||||
|
}
|
||||||
|
],
|
||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": null
|
||||||
|
|||||||
@@ -2,6 +2,27 @@
|
|||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "OpenCode",
|
"productName": "OpenCode",
|
||||||
"identifier": "ai.opencode.desktop",
|
"identifier": "ai.opencode.desktop",
|
||||||
|
"app": {
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"label": "main",
|
||||||
|
"create": false,
|
||||||
|
"title": "OpenCode",
|
||||||
|
"url": "/",
|
||||||
|
"decorations": true,
|
||||||
|
"dragDropEnabled": false,
|
||||||
|
"zoomHotkeysEnabled": true,
|
||||||
|
"titleBarStyle": "Overlay",
|
||||||
|
"hiddenTitle": true,
|
||||||
|
"trafficLightPosition": { "x": 12.0, "y": 18.0 }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"withGlobalTauri": true,
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
},
|
||||||
|
"macOSPrivateApi": true
|
||||||
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"createUpdaterArtifacts": true,
|
"createUpdaterArtifacts": true,
|
||||||
"icon": [
|
"icon": [
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { AsyncStorage } from "@solid-primitives/storage"
|
|||||||
import { fetch as tauriFetch } from "@tauri-apps/plugin-http"
|
import { fetch as tauriFetch } from "@tauri-apps/plugin-http"
|
||||||
import { Store } from "@tauri-apps/plugin-store"
|
import { Store } from "@tauri-apps/plugin-store"
|
||||||
import { Logo } from "@opencode-ai/ui/logo"
|
import { Logo } from "@opencode-ai/ui/logo"
|
||||||
import { createSignal, Show, Accessor, JSX, createResource } from "solid-js"
|
import { createSignal, Show, Accessor, JSX, createResource, onMount, onCleanup } from "solid-js"
|
||||||
|
|
||||||
import { UPDATER_ENABLED } from "./updater"
|
import { UPDATER_ENABLED } from "./updater"
|
||||||
import { createMenu } from "./menu"
|
import { createMenu } from "./menu"
|
||||||
@@ -26,10 +26,27 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isWindows = ostype() === "windows"
|
||||||
|
if (isWindows) {
|
||||||
|
const originalGetComputedStyle = window.getComputedStyle
|
||||||
|
window.getComputedStyle = ((elt: Element, pseudoElt?: string | null) => {
|
||||||
|
if (!(elt instanceof Element)) {
|
||||||
|
// WebView2 can call into Floating UI with non-elements; fall back to a safe element.
|
||||||
|
return originalGetComputedStyle(document.documentElement, pseudoElt ?? undefined)
|
||||||
|
}
|
||||||
|
return originalGetComputedStyle(elt, pseudoElt ?? undefined)
|
||||||
|
}) as typeof window.getComputedStyle
|
||||||
|
}
|
||||||
|
|
||||||
let update: Update | null = null
|
let update: Update | null = null
|
||||||
|
|
||||||
const createPlatform = (password: Accessor<string | null>): Platform => ({
|
const createPlatform = (password: Accessor<string | null>): Platform => ({
|
||||||
platform: "desktop",
|
platform: "desktop",
|
||||||
|
os: (() => {
|
||||||
|
const type = ostype()
|
||||||
|
if (type === "macos" || type === "windows" || type === "linux") return type
|
||||||
|
return undefined
|
||||||
|
})(),
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
|
|
||||||
async openDirectoryPickerDialog(opts) {
|
async openDirectoryPickerDialog(opts) {
|
||||||
@@ -296,12 +313,24 @@ render(() => {
|
|||||||
const [serverPassword, setServerPassword] = createSignal<string | null>(null)
|
const [serverPassword, setServerPassword] = createSignal<string | null>(null)
|
||||||
const platform = createPlatform(() => serverPassword())
|
const platform = createPlatform(() => serverPassword())
|
||||||
|
|
||||||
|
function handleClick(e: MouseEvent) {
|
||||||
|
const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null
|
||||||
|
if (link?.href) {
|
||||||
|
e.preventDefault()
|
||||||
|
platform.openLink(link.href)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
document.addEventListener("click", handleClick)
|
||||||
|
onCleanup(() => {
|
||||||
|
document.removeEventListener("click", handleClick)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlatformProvider value={platform}>
|
<PlatformProvider value={platform}>
|
||||||
<AppBaseProviders>
|
<AppBaseProviders>
|
||||||
{ostype() === "macos" && (
|
|
||||||
<div class="mx-px bg-background-base border-b border-border-weak-base h-8" data-tauri-drag-region />
|
|
||||||
)}
|
|
||||||
<ServerGate>
|
<ServerGate>
|
||||||
{(data) => {
|
{(data) => {
|
||||||
setServerPassword(data().password)
|
setServerPassword(data().password)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
Vendored
+4
@@ -134,6 +134,10 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
|
"ZEN_MODELS8": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"ZEN_SESSION_SECRET": {
|
"ZEN_SESSION_SECRET": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
id = "opencode"
|
id = "opencode"
|
||||||
name = "OpenCode"
|
name = "OpenCode"
|
||||||
description = "The open source coding agent."
|
description = "The open source coding agent."
|
||||||
version = "1.1.20"
|
version = "1.1.25"
|
||||||
schema_version = 1
|
schema_version = 1
|
||||||
authors = ["Anomaly"]
|
authors = ["Anomaly"]
|
||||||
repository = "https://github.com/anomalyco/opencode"
|
repository = "https://github.com/anomalyco/opencode"
|
||||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
|||||||
icon = "./icons/opencode.svg"
|
icon = "./icons/opencode.svg"
|
||||||
|
|
||||||
[agent_servers.opencode.targets.darwin-aarch64]
|
[agent_servers.opencode.targets.darwin-aarch64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.20/opencode-darwin-arm64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.25/opencode-darwin-arm64.zip"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.darwin-x86_64]
|
[agent_servers.opencode.targets.darwin-x86_64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.20/opencode-darwin-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.25/opencode-darwin-x64.zip"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.linux-aarch64]
|
[agent_servers.opencode.targets.linux-aarch64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.20/opencode-linux-arm64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.25/opencode-linux-arm64.tar.gz"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.linux-x86_64]
|
[agent_servers.opencode.targets.linux-x86_64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.20/opencode-linux-x64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.25/opencode-linux-x64.tar.gz"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.windows-x86_64]
|
[agent_servers.opencode.targets.windows-x86_64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.20/opencode-windows-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.25/opencode-windows-x64.zip"
|
||||||
cmd = "./opencode.exe"
|
cmd = "./opencode.exe"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
Vendored
+4
@@ -134,6 +134,10 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
|
"ZEN_MODELS8": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"ZEN_SESSION_SECRET": {
|
"ZEN_SESSION_SECRET": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.1.20",
|
"version": "1.1.25",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -82,8 +82,8 @@
|
|||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
"@openrouter/ai-sdk-provider": "1.5.2",
|
"@openrouter/ai-sdk-provider": "1.5.2",
|
||||||
"@opentui/core": "0.1.72",
|
"@opentui/core": "0.1.74",
|
||||||
"@opentui/solid": "0.1.72",
|
"@opentui/solid": "0.1.74",
|
||||||
"@parcel/watcher": "2.5.1",
|
"@parcel/watcher": "2.5.1",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
"@solid-primitives/event-bus": "1.1.2",
|
"@solid-primitives/event-bus": "1.1.2",
|
||||||
|
|||||||
@@ -731,6 +731,9 @@ export namespace ACP {
|
|||||||
const defaultAgentName = await AgentModule.defaultAgent()
|
const defaultAgentName = await AgentModule.defaultAgent()
|
||||||
const currentModeId = availableModes.find((m) => m.name === defaultAgentName)?.id ?? availableModes[0].id
|
const currentModeId = availableModes.find((m) => m.name === defaultAgentName)?.id ?? availableModes[0].id
|
||||||
|
|
||||||
|
// Persist the default mode so prompt() uses it immediately
|
||||||
|
this.sessionManager.setMode(sessionId, currentModeId)
|
||||||
|
|
||||||
const mcpServers: Record<string, Config.Mcp> = {}
|
const mcpServers: Record<string, Config.Mcp> = {}
|
||||||
for (const server of params.mcpServers) {
|
for (const server of params.mcpServers) {
|
||||||
if ("type" in server) {
|
if ("type" in server) {
|
||||||
|
|||||||
@@ -255,7 +255,20 @@ export namespace Agent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function defaultAgent() {
|
export async function defaultAgent() {
|
||||||
return state().then((x) => Object.keys(x)[0])
|
const cfg = await Config.get()
|
||||||
|
const agents = await state()
|
||||||
|
|
||||||
|
if (cfg.default_agent) {
|
||||||
|
const agent = agents[cfg.default_agent]
|
||||||
|
if (!agent) throw new Error(`default agent "${cfg.default_agent}" not found`)
|
||||||
|
if (agent.mode === "subagent") throw new Error(`default agent "${cfg.default_agent}" is a subagent`)
|
||||||
|
if (agent.hidden === true) throw new Error(`default agent "${cfg.default_agent}" is hidden`)
|
||||||
|
return agent.name
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryVisible = Object.values(agents).find((a) => a.mode !== "subagent" && a.hidden !== true)
|
||||||
|
if (!primaryVisible) throw new Error("no primary visible agent found")
|
||||||
|
return primaryVisible.name
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generate(input: { description: string; model?: { providerID: string; modelID: string } }) {
|
export async function generate(input: { description: string; model?: { providerID: string; modelID: string } }) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import z from "zod"
|
|||||||
import { Global } from "../global"
|
import { Global } from "../global"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import { Filesystem } from "../util/filesystem"
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
import { readableStreamToText } from "bun"
|
import { readableStreamToText } from "bun"
|
||||||
import { createRequire } from "module"
|
import { createRequire } from "module"
|
||||||
@@ -71,7 +72,10 @@ export namespace BunProc {
|
|||||||
await Bun.write(pkgjson.name!, JSON.stringify(result, null, 2))
|
await Bun.write(pkgjson.name!, JSON.stringify(result, null, 2))
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
if (parsed.dependencies[pkg] === version) return mod
|
const dependencies = parsed.dependencies ?? {}
|
||||||
|
if (!parsed.dependencies) parsed.dependencies = dependencies
|
||||||
|
const modExists = await Filesystem.exists(mod)
|
||||||
|
if (dependencies[pkg] === version && modExists) return mod
|
||||||
|
|
||||||
const proxied = !!(
|
const proxied = !!(
|
||||||
process.env.HTTP_PROXY ||
|
process.env.HTTP_PROXY ||
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ export const AgentCommand = cmd({
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function getAvailableTools(agent: Agent.Info) {
|
async function getAvailableTools(agent: Agent.Info) {
|
||||||
const providerID = agent.model?.providerID ?? (await Provider.defaultModel()).providerID
|
const model = agent.model ?? (await Provider.defaultModel())
|
||||||
return ToolRegistry.tools(providerID, agent)
|
return ToolRegistry.tools(model, agent)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveTools(agent: Agent.Info, availableTools: Awaited<ReturnType<typeof getAvailableTools>>) {
|
async function resolveTools(agent: Agent.Info, availableTools: Awaited<ReturnType<typeof getAvailableTools>>) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Installation } from "../../installation"
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { Global } from "../../global"
|
import { Global } from "../../global"
|
||||||
import { modify, applyEdits } from "jsonc-parser"
|
import { modify, applyEdits } from "jsonc-parser"
|
||||||
|
import { Bus } from "../../bus"
|
||||||
|
|
||||||
function getAuthStatusIcon(status: MCP.AuthStatus): string {
|
function getAuthStatusIcon(status: MCP.AuthStatus): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@@ -227,6 +228,16 @@ export const McpAuthCommand = cmd({
|
|||||||
const spinner = prompts.spinner()
|
const spinner = prompts.spinner()
|
||||||
spinner.start("Starting OAuth flow...")
|
spinner.start("Starting OAuth flow...")
|
||||||
|
|
||||||
|
// Subscribe to browser open failure events to show URL for manual opening
|
||||||
|
const unsubscribe = Bus.subscribe(MCP.BrowserOpenFailed, (evt) => {
|
||||||
|
if (evt.properties.mcpName === serverName) {
|
||||||
|
spinner.stop("Could not open browser automatically")
|
||||||
|
prompts.log.warn("Please open this URL in your browser to authenticate:")
|
||||||
|
prompts.log.info(evt.properties.url)
|
||||||
|
spinner.start("Waiting for authorization...")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const status = await MCP.authenticate(serverName)
|
const status = await MCP.authenticate(serverName)
|
||||||
|
|
||||||
@@ -256,6 +267,8 @@ export const McpAuthCommand = cmd({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.stop("Authentication failed", 1)
|
spinner.stop("Authentication failed", 1)
|
||||||
prompts.log.error(error instanceof Error ? error.message : String(error))
|
prompts.log.error(error instanceof Error ? error.message : String(error))
|
||||||
|
} finally {
|
||||||
|
unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
prompts.outro("Done")
|
prompts.outro("Done")
|
||||||
|
|||||||
@@ -200,11 +200,6 @@ function App() {
|
|||||||
renderer.console.onCopySelection = async (text: string) => {
|
renderer.console.onCopySelection = async (text: string) => {
|
||||||
if (!text || text.length === 0) return
|
if (!text || text.length === 0) return
|
||||||
|
|
||||||
const base64 = Buffer.from(text).toString("base64")
|
|
||||||
const osc52 = `\x1b]52;c;${base64}\x07`
|
|
||||||
const finalOsc52 = process.env["TMUX"] ? `\x1bPtmux;\x1b${osc52}\x1b\\` : osc52
|
|
||||||
// @ts-expect-error writeOut is not in type definitions
|
|
||||||
renderer.writeOut(finalOsc52)
|
|
||||||
await Clipboard.copy(text)
|
await Clipboard.copy(text)
|
||||||
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
|
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
|
||||||
.catch(toast.error)
|
.catch(toast.error)
|
||||||
@@ -627,11 +622,6 @@ function App() {
|
|||||||
}
|
}
|
||||||
const text = renderer.getSelection()?.getSelectedText()
|
const text = renderer.getSelection()?.getSelectedText()
|
||||||
if (text && text.length > 0) {
|
if (text && text.length > 0) {
|
||||||
const base64 = Buffer.from(text).toString("base64")
|
|
||||||
const osc52 = `\x1b]52;c;${base64}\x07`
|
|
||||||
const finalOsc52 = process.env["TMUX"] ? `\x1bPtmux;\x1b${osc52}\x1b\\` : osc52
|
|
||||||
/* @ts-expect-error */
|
|
||||||
renderer.writeOut(finalOsc52)
|
|
||||||
await Clipboard.copy(text)
|
await Clipboard.copy(text)
|
||||||
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
|
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
|
||||||
.catch(toast.error)
|
.catch(toast.error)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { map, pipe, flatMap, entries, filter, sortBy, take } from "remeda"
|
|||||||
import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
|
import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
|
||||||
import { useDialog } from "@tui/ui/dialog"
|
import { useDialog } from "@tui/ui/dialog"
|
||||||
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
|
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
|
||||||
import { Keybind } from "@/util/keybind"
|
import { useKeybind } from "../context/keybind"
|
||||||
import * as fuzzysort from "fuzzysort"
|
import * as fuzzysort from "fuzzysort"
|
||||||
|
|
||||||
export function useConnected() {
|
export function useConnected() {
|
||||||
@@ -19,6 +19,7 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
const keybind = useKeybind()
|
||||||
const [ref, setRef] = createSignal<DialogSelectRef<unknown>>()
|
const [ref, setRef] = createSignal<DialogSelectRef<unknown>>()
|
||||||
const [query, setQuery] = createSignal("")
|
const [query, setQuery] = createSignal("")
|
||||||
|
|
||||||
@@ -207,14 +208,14 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
<DialogSelect
|
<DialogSelect
|
||||||
keybind={[
|
keybind={[
|
||||||
{
|
{
|
||||||
keybind: Keybind.parse("ctrl+a")[0],
|
keybind: keybind.all.model_provider_list?.[0],
|
||||||
title: connected() ? "Connect provider" : "View all providers",
|
title: connected() ? "Connect provider" : "View all providers",
|
||||||
onTrigger() {
|
onTrigger() {
|
||||||
dialog.replace(() => <DialogProvider />)
|
dialog.replace(() => <DialogProvider />)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keybind: Keybind.parse("ctrl+f")[0],
|
keybind: keybind.all.model_favorite_toggle?.[0],
|
||||||
title: "Favorite",
|
title: "Favorite",
|
||||||
disabled: !connected(),
|
disabled: !connected(),
|
||||||
onTrigger: (option) => {
|
onTrigger: (option) => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useRoute } from "@tui/context/route"
|
|||||||
import { useSync } from "@tui/context/sync"
|
import { useSync } from "@tui/context/sync"
|
||||||
import { createMemo, createSignal, createResource, onMount, Show } from "solid-js"
|
import { createMemo, createSignal, createResource, onMount, Show } from "solid-js"
|
||||||
import { Locale } from "@/util/locale"
|
import { Locale } from "@/util/locale"
|
||||||
import { Keybind } from "@/util/keybind"
|
import { useKeybind } from "../context/keybind"
|
||||||
import { useTheme } from "../context/theme"
|
import { useTheme } from "../context/theme"
|
||||||
import { useSDK } from "../context/sdk"
|
import { useSDK } from "../context/sdk"
|
||||||
import { DialogSessionRename } from "./dialog-session-rename"
|
import { DialogSessionRename } from "./dialog-session-rename"
|
||||||
@@ -14,9 +14,10 @@ import "opentui-spinner/solid"
|
|||||||
|
|
||||||
export function DialogSessionList() {
|
export function DialogSessionList() {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const sync = useSync()
|
|
||||||
const { theme } = useTheme()
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const sync = useSync()
|
||||||
|
const keybind = useKeybind()
|
||||||
|
const { theme } = useTheme()
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const kv = useKV()
|
const kv = useKV()
|
||||||
|
|
||||||
@@ -29,8 +30,6 @@ export function DialogSessionList() {
|
|||||||
return result.data ?? []
|
return result.data ?? []
|
||||||
})
|
})
|
||||||
|
|
||||||
const deleteKeybind = "ctrl+d"
|
|
||||||
|
|
||||||
const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined))
|
const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined))
|
||||||
|
|
||||||
const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
||||||
@@ -52,7 +51,7 @@ export function DialogSessionList() {
|
|||||||
const status = sync.data.session_status?.[x.id]
|
const status = sync.data.session_status?.[x.id]
|
||||||
const isWorking = status?.type === "busy"
|
const isWorking = status?.type === "busy"
|
||||||
return {
|
return {
|
||||||
title: isDeleting ? `Press ${deleteKeybind} again to confirm` : x.title,
|
title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title,
|
||||||
bg: isDeleting ? theme.error : undefined,
|
bg: isDeleting ? theme.error : undefined,
|
||||||
value: x.id,
|
value: x.id,
|
||||||
category,
|
category,
|
||||||
@@ -89,7 +88,7 @@ export function DialogSessionList() {
|
|||||||
}}
|
}}
|
||||||
keybind={[
|
keybind={[
|
||||||
{
|
{
|
||||||
keybind: Keybind.parse(deleteKeybind)[0],
|
keybind: keybind.all.session_delete?.[0],
|
||||||
title: "delete",
|
title: "delete",
|
||||||
onTrigger: async (option) => {
|
onTrigger: async (option) => {
|
||||||
if (toDelete() === option.value) {
|
if (toDelete() === option.value) {
|
||||||
@@ -103,7 +102,7 @@ export function DialogSessionList() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keybind: Keybind.parse("ctrl+r")[0],
|
keybind: keybind.all.session_rename?.[0],
|
||||||
title: "rename",
|
title: "rename",
|
||||||
onTrigger: async (option) => {
|
onTrigger: async (option) => {
|
||||||
dialog.replace(() => <DialogSessionRename session={option.value} />)
|
dialog.replace(() => <DialogSessionRename session={option.value} />)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { useDialog } from "@tui/ui/dialog"
|
|||||||
import { DialogSelect } from "@tui/ui/dialog-select"
|
import { DialogSelect } from "@tui/ui/dialog-select"
|
||||||
import { createMemo, createSignal } from "solid-js"
|
import { createMemo, createSignal } from "solid-js"
|
||||||
import { Locale } from "@/util/locale"
|
import { Locale } from "@/util/locale"
|
||||||
import { Keybind } from "@/util/keybind"
|
|
||||||
import { useTheme } from "../context/theme"
|
import { useTheme } from "../context/theme"
|
||||||
|
import { useKeybind } from "../context/keybind"
|
||||||
import { usePromptStash, type StashEntry } from "./prompt/stash"
|
import { usePromptStash, type StashEntry } from "./prompt/stash"
|
||||||
|
|
||||||
function getRelativeTime(timestamp: number): string {
|
function getRelativeTime(timestamp: number): string {
|
||||||
@@ -30,6 +30,7 @@ export function DialogStash(props: { onSelect: (entry: StashEntry) => void }) {
|
|||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const stash = usePromptStash()
|
const stash = usePromptStash()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
const keybind = useKeybind()
|
||||||
|
|
||||||
const [toDelete, setToDelete] = createSignal<number>()
|
const [toDelete, setToDelete] = createSignal<number>()
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ export function DialogStash(props: { onSelect: (entry: StashEntry) => void }) {
|
|||||||
const isDeleting = toDelete() === index
|
const isDeleting = toDelete() === index
|
||||||
const lineCount = (entry.input.match(/\n/g)?.length ?? 0) + 1
|
const lineCount = (entry.input.match(/\n/g)?.length ?? 0) + 1
|
||||||
return {
|
return {
|
||||||
title: isDeleting ? "Press ctrl+d again to confirm" : getStashPreview(entry.input),
|
title: isDeleting ? `Press ${keybind.print("stash_delete")} again to confirm` : getStashPreview(entry.input),
|
||||||
bg: isDeleting ? theme.error : undefined,
|
bg: isDeleting ? theme.error : undefined,
|
||||||
value: index,
|
value: index,
|
||||||
description: getRelativeTime(entry.timestamp),
|
description: getRelativeTime(entry.timestamp),
|
||||||
@@ -69,7 +70,7 @@ export function DialogStash(props: { onSelect: (entry: StashEntry) => void }) {
|
|||||||
}}
|
}}
|
||||||
keybind={[
|
keybind={[
|
||||||
{
|
{
|
||||||
keybind: Keybind.parse("ctrl+d")[0],
|
keybind: keybind.all.stash_delete?.[0],
|
||||||
title: "delete",
|
title: "delete",
|
||||||
onTrigger: (option) => {
|
onTrigger: (option) => {
|
||||||
if (toDelete() === option.value) {
|
if (toDelete() === option.value) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { TextAttributes } from "@opentui/core"
|
|||||||
import { useTheme } from "../context/theme"
|
import { useTheme } from "../context/theme"
|
||||||
import { useSync } from "@tui/context/sync"
|
import { useSync } from "@tui/context/sync"
|
||||||
import { For, Match, Switch, Show, createMemo } from "solid-js"
|
import { For, Match, Switch, Show, createMemo } from "solid-js"
|
||||||
|
import { Installation } from "@/installation"
|
||||||
|
|
||||||
export type DialogStatusProps = {}
|
export type DialogStatusProps = {}
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ export function DialogStatus() {
|
|||||||
</text>
|
</text>
|
||||||
<text fg={theme.textMuted}>esc</text>
|
<text fg={theme.textMuted}>esc</text>
|
||||||
</box>
|
</box>
|
||||||
|
<text fg={theme.textMuted}>OpenCode v{Installation.VERSION}</text>
|
||||||
<Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text fg={theme.text}>No MCP Servers</text>}>
|
<Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text fg={theme.text}>No MCP Servers</text>}>
|
||||||
<box>
|
<box>
|
||||||
<text fg={theme.text}>{Object.keys(sync.data.mcp).length} MCP Servers</text>
|
<text fg={theme.text}>{Object.keys(sync.data.mcp).length} MCP Servers</text>
|
||||||
|
|||||||
@@ -1,24 +1,85 @@
|
|||||||
import { TextAttributes } from "@opentui/core"
|
import { TextAttributes, RGBA } from "@opentui/core"
|
||||||
import { For } from "solid-js"
|
import { For, type JSX } from "solid-js"
|
||||||
import { useTheme } from "@tui/context/theme"
|
import { useTheme, tint } from "@tui/context/theme"
|
||||||
|
|
||||||
const LOGO_LEFT = [` `, `█▀▀█ █▀▀█ █▀▀█ █▀▀▄`, `█░░█ █░░█ █▀▀▀ █░░█`, `▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀`]
|
// Shadow markers (rendered chars in parens):
|
||||||
|
// _ = full shadow cell (space with bg=shadow)
|
||||||
|
// ^ = letter top, shadow bottom (▀ with fg=letter, bg=shadow)
|
||||||
|
// ~ = shadow top only (▀ with fg=shadow)
|
||||||
|
const SHADOW_MARKER = /[_^~]/
|
||||||
|
|
||||||
const LOGO_RIGHT = [` ▄ `, `█▀▀▀ █▀▀█ █▀▀█ █▀▀█`, `█░░░ █░░█ █░░█ █▀▀▀`, `▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`]
|
const LOGO_LEFT = [` `, `█▀▀█ █▀▀█ █▀▀█ █▀▀▄`, `█__█ █__█ █^^^ █__█`, `▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀~~▀`]
|
||||||
|
|
||||||
|
const LOGO_RIGHT = [` ▄ `, `█▀▀▀ █▀▀█ █▀▀█ █▀▀█`, `█___ █__█ █__█ █^^^`, `▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`]
|
||||||
|
|
||||||
export function Logo() {
|
export function Logo() {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
const renderLine = (line: string, fg: RGBA, bold: boolean): JSX.Element[] => {
|
||||||
|
const shadow = tint(theme.background, fg, 0.25)
|
||||||
|
const attrs = bold ? TextAttributes.BOLD : undefined
|
||||||
|
const elements: JSX.Element[] = []
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
while (i < line.length) {
|
||||||
|
const rest = line.slice(i)
|
||||||
|
const markerIndex = rest.search(SHADOW_MARKER)
|
||||||
|
|
||||||
|
if (markerIndex === -1) {
|
||||||
|
elements.push(
|
||||||
|
<text fg={fg} attributes={attrs} selectable={false}>
|
||||||
|
{rest}
|
||||||
|
</text>,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (markerIndex > 0) {
|
||||||
|
elements.push(
|
||||||
|
<text fg={fg} attributes={attrs} selectable={false}>
|
||||||
|
{rest.slice(0, markerIndex)}
|
||||||
|
</text>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const marker = rest[markerIndex]
|
||||||
|
switch (marker) {
|
||||||
|
case "_":
|
||||||
|
elements.push(
|
||||||
|
<text fg={fg} bg={shadow} attributes={attrs} selectable={false}>
|
||||||
|
{" "}
|
||||||
|
</text>,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case "^":
|
||||||
|
elements.push(
|
||||||
|
<text fg={fg} bg={shadow} attributes={attrs} selectable={false}>
|
||||||
|
▀
|
||||||
|
</text>,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case "~":
|
||||||
|
elements.push(
|
||||||
|
<text fg={shadow} attributes={attrs} selectable={false}>
|
||||||
|
▀
|
||||||
|
</text>,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
i += markerIndex + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box>
|
<box>
|
||||||
<For each={LOGO_LEFT}>
|
<For each={LOGO_LEFT}>
|
||||||
{(line, index) => (
|
{(line, index) => (
|
||||||
<box flexDirection="row" gap={1}>
|
<box flexDirection="row" gap={1}>
|
||||||
<text fg={theme.textMuted} selectable={false}>
|
<box flexDirection="row">{renderLine(line, theme.textMuted, false)}</box>
|
||||||
{line}
|
<box flexDirection="row">{renderLine(LOGO_RIGHT[index()], theme.text, true)}</box>
|
||||||
</text>
|
|
||||||
<text fg={theme.text} attributes={TextAttributes.BOLD} selectable={false}>
|
|
||||||
{LOGO_RIGHT[index()]}
|
|
||||||
</text>
|
|
||||||
</box>
|
</box>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import type { FilePart } from "@opencode-ai/sdk/v2"
|
|||||||
import { TuiEvent } from "../../event"
|
import { TuiEvent } from "../../event"
|
||||||
import { iife } from "@/util/iife"
|
import { iife } from "@/util/iife"
|
||||||
import { Locale } from "@/util/locale"
|
import { Locale } from "@/util/locale"
|
||||||
|
import { formatDuration } from "@/util/format"
|
||||||
import { createColors, createFrames } from "../../ui/spinner.ts"
|
import { createColors, createFrames } from "../../ui/spinner.ts"
|
||||||
import { useDialog } from "@tui/ui/dialog"
|
import { useDialog } from "@tui/ui/dialog"
|
||||||
import { DialogProvider as DialogProviderConnect } from "../dialog-provider"
|
import { DialogProvider as DialogProviderConnect } from "../dialog-provider"
|
||||||
@@ -144,9 +145,9 @@ export function Prompt(props: PromptProps) {
|
|||||||
const isPrimaryAgent = local.agent.list().some((x) => x.name === msg.agent)
|
const isPrimaryAgent = local.agent.list().some((x) => x.name === msg.agent)
|
||||||
if (msg.agent && isPrimaryAgent) {
|
if (msg.agent && isPrimaryAgent) {
|
||||||
local.agent.set(msg.agent)
|
local.agent.set(msg.agent)
|
||||||
|
if (msg.model) local.model.set(msg.model)
|
||||||
|
if (msg.variant) local.model.variant.set(msg.variant)
|
||||||
}
|
}
|
||||||
if (msg.model) local.model.set(msg.model)
|
|
||||||
if (msg.variant) local.model.variant.set(msg.variant)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1037,7 +1038,8 @@ export function Prompt(props: PromptProps) {
|
|||||||
if (!r) return ""
|
if (!r) return ""
|
||||||
const baseMessage = message()
|
const baseMessage = message()
|
||||||
const truncatedHint = isTruncated() ? " (click to expand)" : ""
|
const truncatedHint = isTruncated() ? " (click to expand)" : ""
|
||||||
const retryInfo = ` [retrying ${seconds() > 0 ? `in ${seconds()}s ` : ""}attempt #${r.attempt}]`
|
const duration = formatDuration(seconds())
|
||||||
|
const retryInfo = ` [retrying ${duration ? `in ${duration} ` : ""}attempt #${r.attempt}]`
|
||||||
return baseMessage + truncatedHint + retryInfo
|
return baseMessage + truncatedHint + retryInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user