mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-02 16:26:14 -04:00
Compare commits
159 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a7774115c5 | |||
| 288bc88e40 | |||
| 6d36dbf9de | |||
| 4ab4baf3a4 | |||
| 90f05eb9c2 | |||
| b63b6d04c6 | |||
| 8addaa7e08 | |||
| a96bf8e62d | |||
| c8bda598f5 | |||
| c857cff585 | |||
| fd9d2db755 | |||
| b19fd14f80 | |||
| a0f469095c | |||
| 0ccb26df94 | |||
| 71fd5966ad | |||
| c02230de4f | |||
| aa2e2c76c0 | |||
| 7c2d4ee79a | |||
| e3a2728fa3 | |||
| 18260b037b | |||
| ad83dd3ad9 | |||
| 6f37315cd1 | |||
| d81dce6a82 | |||
| 0bd11e970b | |||
| 7e29e1dd23 | |||
| 491a2adf8d | |||
| c07d6487a8 | |||
| 9990e84d37 | |||
| 0b86adbe99 | |||
| 834a2c09d5 | |||
| f13c17e654 | |||
| a0611d92e4 | |||
| 0b001c3e80 | |||
| 53b7cb62c4 | |||
| c5e096c76a | |||
| e1fc4a756b | |||
| e5bc4cbbcf | |||
| 459d5ec19b | |||
| 8baa222621 | |||
| ce1397cc34 | |||
| dc7c5ced4c | |||
| b8e8fe7e31 | |||
| 890085758f | |||
| 85f15893bc | |||
| 98be75b17c | |||
| b5cc27b8ea | |||
| 05937b52cc | |||
| 62b82570e1 | |||
| 4bf75c0b44 | |||
| a8a06c4983 | |||
| b0b7fd143b | |||
| 140498eb4f | |||
| ca5126e24d | |||
| fb2b3e567c | |||
| c672a1963b | |||
| 54bff6b120 | |||
| ab3f198fab | |||
| 0057ef6336 | |||
| 4f604b3839 | |||
| a20489584e | |||
| a6b066bd47 | |||
| 37fdcac05a | |||
| 299bf1dca8 | |||
| d685aa38ef | |||
| 995b23787c | |||
| ed8e663e13 | |||
| 38cee3b848 | |||
| 6d116d4b54 | |||
| 7c4f111b34 | |||
| f2fac29270 | |||
| 12892f0e12 | |||
| 9714a3558e | |||
| e49a1d1f39 | |||
| 528565510d | |||
| 36cfda933d | |||
| ecf5040966 | |||
| 7d56603c26 | |||
| 02b7cc8313 | |||
| c9a52c9a85 | |||
| dea668b0ea | |||
| 1bc3e92376 | |||
| 3f5acc3dff | |||
| 0588011476 | |||
| bba72c82ae | |||
| e95181a551 | |||
| 74e8c2e50f | |||
| cdabafa264 | |||
| 0a92af60a0 | |||
| c7808a4b01 | |||
| 7f978e07ff | |||
| a4ae1bb9eb | |||
| 96a39803cc | |||
| 16f8f20b31 | |||
| 06b1684ddb | |||
| c6e830c954 | |||
| fc78c28df6 | |||
| 930a1bf358 | |||
| 6cf7f18cc9 | |||
| 3f59570ee6 | |||
| 304e956b5d | |||
| 538eaa42aa | |||
| 67c41fd389 | |||
| 83ea19770a | |||
| 3ace8543b2 | |||
| eb855e1e31 | |||
| 5e53f054c6 | |||
| 5d5e184329 | |||
| 2fbb49ac30 | |||
| c56b407e1d | |||
| bdaa0e8b8c | |||
| 4e549b1c05 | |||
| 7be8e16c33 | |||
| d1588f93a1 | |||
| 576696a370 | |||
| 2c6f9043e8 | |||
| 9f771ef0ae | |||
| 356715f67d | |||
| 540421267a | |||
| e253398936 | |||
| ee87e1f139 | |||
| 8887616457 | |||
| 905c034885 | |||
| 92f7c4943f | |||
| 10bde356b1 | |||
| f7cc46cd9f | |||
| d9ffe07391 | |||
| c0702ed8bd | |||
| b927b9dca6 | |||
| 4b7231be68 | |||
| 70a6fe96ea | |||
| 6e5971dff2 | |||
| d48d6b3577 | |||
| 4b1668c3ef | |||
| d85eb1b880 | |||
| 9637d70407 | |||
| cfbbdc2e14 | |||
| feb65201f6 | |||
| f1f07a56d8 | |||
| 0fe313bd87 | |||
| 1fd676528d | |||
| 0a2801444b | |||
| c9adbc7c21 | |||
| ba8de38435 | |||
| 8166612467 | |||
| 4d20e1c3c6 | |||
| 4bb7ea9127 | |||
| 969af4d541 | |||
| 271b679058 | |||
| 83b16cb18e | |||
| 431ffc94f5 | |||
| 47b4cc6d53 | |||
| ce9b758d0a | |||
| f8a1a0b26f | |||
| 6ecaf83f76 | |||
| 30b1ae5d4b | |||
| 9cd465f9c0 | |||
| 7088bfabd7 | |||
| dbdbfb8543 | |||
| 521803aaa3 |
@@ -13,9 +13,9 @@ runs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lockb', 'bun.lock') }}
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('package.json') }}-${{ hashFiles('bun.lockb', 'bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
${{ runner.os }}-bun-${{ hashFiles('package.json') }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: "sync-zed-extension"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
zed:
|
||||
name: Release Zed Extension
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Get version tag
|
||||
id: get_tag
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "release" ]; then
|
||||
TAG="${{ github.event.release.tag_name }}"
|
||||
else
|
||||
TAG=$(git tag --list 'v[0-9]*.*' --sort=-version:refname | head -n 1)
|
||||
fi
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "Using tag: ${TAG}"
|
||||
|
||||
- name: Sync Zed extension
|
||||
run: |
|
||||
./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
+1
-14
@@ -1,17 +1,4 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugin": ["opencode-openai-codex-auth"],
|
||||
"mcp": {
|
||||
"weather": {
|
||||
"type": "local",
|
||||
"command": ["bun", "x", "@h1deya/mcp-server-weather"]
|
||||
},
|
||||
"context7": {
|
||||
"type": "remote",
|
||||
"url": "https://mcp.context7.com/mcp",
|
||||
"headers": {
|
||||
"CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}"
|
||||
}
|
||||
}
|
||||
}
|
||||
"plugin": ["opencode-openai-codex-auth"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/theme.json",
|
||||
"defs": {
|
||||
"nord0": "#2E3440",
|
||||
"nord1": "#3B4252",
|
||||
"nord2": "#434C5E",
|
||||
"nord3": "#4C566A",
|
||||
"nord4": "#D8DEE9",
|
||||
"nord5": "#E5E9F0",
|
||||
"nord6": "#ECEFF4",
|
||||
"nord7": "#8FBCBB",
|
||||
"nord8": "#88C0D0",
|
||||
"nord9": "#81A1C1",
|
||||
"nord10": "#5E81AC",
|
||||
"nord11": "#BF616A",
|
||||
"nord12": "#D08770",
|
||||
"nord13": "#EBCB8B",
|
||||
"nord14": "#A3BE8C",
|
||||
"nord15": "#B48EAD"
|
||||
},
|
||||
"theme": {
|
||||
"primary": {
|
||||
"dark": "nord8",
|
||||
"light": "nord10"
|
||||
},
|
||||
"secondary": {
|
||||
"dark": "nord9",
|
||||
"light": "nord9"
|
||||
},
|
||||
"accent": {
|
||||
"dark": "nord7",
|
||||
"light": "nord7"
|
||||
},
|
||||
"error": {
|
||||
"dark": "nord11",
|
||||
"light": "nord11"
|
||||
},
|
||||
"warning": {
|
||||
"dark": "nord12",
|
||||
"light": "nord12"
|
||||
},
|
||||
"success": {
|
||||
"dark": "nord14",
|
||||
"light": "nord14"
|
||||
},
|
||||
"info": {
|
||||
"dark": "nord8",
|
||||
"light": "nord10"
|
||||
},
|
||||
"text": {
|
||||
"dark": "nord4",
|
||||
"light": "nord0"
|
||||
},
|
||||
"textMuted": {
|
||||
"dark": "nord3",
|
||||
"light": "nord1"
|
||||
},
|
||||
"background": {
|
||||
"dark": "nord0",
|
||||
"light": "nord6"
|
||||
},
|
||||
"backgroundPanel": {
|
||||
"dark": "nord1",
|
||||
"light": "nord5"
|
||||
},
|
||||
"backgroundElement": {
|
||||
"dark": "nord1",
|
||||
"light": "nord4"
|
||||
},
|
||||
"border": {
|
||||
"dark": "nord2",
|
||||
"light": "nord3"
|
||||
},
|
||||
"borderActive": {
|
||||
"dark": "nord3",
|
||||
"light": "nord2"
|
||||
},
|
||||
"borderSubtle": {
|
||||
"dark": "nord2",
|
||||
"light": "nord3"
|
||||
},
|
||||
"diffAdded": {
|
||||
"dark": "nord14",
|
||||
"light": "nord14"
|
||||
},
|
||||
"diffRemoved": {
|
||||
"dark": "nord11",
|
||||
"light": "nord11"
|
||||
},
|
||||
"diffContext": {
|
||||
"dark": "nord3",
|
||||
"light": "nord3"
|
||||
},
|
||||
"diffHunkHeader": {
|
||||
"dark": "nord3",
|
||||
"light": "nord3"
|
||||
},
|
||||
"diffHighlightAdded": {
|
||||
"dark": "nord14",
|
||||
"light": "nord14"
|
||||
},
|
||||
"diffHighlightRemoved": {
|
||||
"dark": "nord11",
|
||||
"light": "nord11"
|
||||
},
|
||||
"diffAddedBg": {
|
||||
"dark": "#3B4252",
|
||||
"light": "#E5E9F0"
|
||||
},
|
||||
"diffRemovedBg": {
|
||||
"dark": "#3B4252",
|
||||
"light": "#E5E9F0"
|
||||
},
|
||||
"diffContextBg": {
|
||||
"dark": "nord1",
|
||||
"light": "nord5"
|
||||
},
|
||||
"diffLineNumber": {
|
||||
"dark": "nord2",
|
||||
"light": "nord4"
|
||||
},
|
||||
"diffAddedLineNumberBg": {
|
||||
"dark": "#3B4252",
|
||||
"light": "#E5E9F0"
|
||||
},
|
||||
"diffRemovedLineNumberBg": {
|
||||
"dark": "#3B4252",
|
||||
"light": "#E5E9F0"
|
||||
},
|
||||
"markdownText": {
|
||||
"dark": "nord4",
|
||||
"light": "nord0"
|
||||
},
|
||||
"markdownHeading": {
|
||||
"dark": "nord8",
|
||||
"light": "nord10"
|
||||
},
|
||||
"markdownLink": {
|
||||
"dark": "nord9",
|
||||
"light": "nord9"
|
||||
},
|
||||
"markdownLinkText": {
|
||||
"dark": "nord7",
|
||||
"light": "nord7"
|
||||
},
|
||||
"markdownCode": {
|
||||
"dark": "nord14",
|
||||
"light": "nord14"
|
||||
},
|
||||
"markdownBlockQuote": {
|
||||
"dark": "nord3",
|
||||
"light": "nord3"
|
||||
},
|
||||
"markdownEmph": {
|
||||
"dark": "nord12",
|
||||
"light": "nord12"
|
||||
},
|
||||
"markdownStrong": {
|
||||
"dark": "nord13",
|
||||
"light": "nord13"
|
||||
},
|
||||
"markdownHorizontalRule": {
|
||||
"dark": "nord3",
|
||||
"light": "nord3"
|
||||
},
|
||||
"markdownListItem": {
|
||||
"dark": "nord8",
|
||||
"light": "nord10"
|
||||
},
|
||||
"markdownListEnumeration": {
|
||||
"dark": "nord7",
|
||||
"light": "nord7"
|
||||
},
|
||||
"markdownImage": {
|
||||
"dark": "nord9",
|
||||
"light": "nord9"
|
||||
},
|
||||
"markdownImageText": {
|
||||
"dark": "nord7",
|
||||
"light": "nord7"
|
||||
},
|
||||
"markdownCodeBlock": {
|
||||
"dark": "nord4",
|
||||
"light": "nord0"
|
||||
},
|
||||
"syntaxComment": {
|
||||
"dark": "nord3",
|
||||
"light": "nord3"
|
||||
},
|
||||
"syntaxKeyword": {
|
||||
"dark": "nord9",
|
||||
"light": "nord9"
|
||||
},
|
||||
"syntaxFunction": {
|
||||
"dark": "nord8",
|
||||
"light": "nord8"
|
||||
},
|
||||
"syntaxVariable": {
|
||||
"dark": "nord7",
|
||||
"light": "nord7"
|
||||
},
|
||||
"syntaxString": {
|
||||
"dark": "nord14",
|
||||
"light": "nord14"
|
||||
},
|
||||
"syntaxNumber": {
|
||||
"dark": "nord15",
|
||||
"light": "nord15"
|
||||
},
|
||||
"syntaxType": {
|
||||
"dark": "nord7",
|
||||
"light": "nord7"
|
||||
},
|
||||
"syntaxOperator": {
|
||||
"dark": "nord9",
|
||||
"light": "nord9"
|
||||
},
|
||||
"syntaxPunctuation": {
|
||||
"dark": "nord4",
|
||||
"light": "nord0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,3 +133,8 @@
|
||||
| 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-07 | 696,646 (+10,394) | 642,146 (+11,261) | 1,338,792 (+21,655) |
|
||||
| 2025-11-08 | 706,035 (+9,389) | 653,489 (+11,343) | 1,359,524 (+20,732) |
|
||||
| 2025-11-09 | 713,462 (+7,427) | 660,459 (+6,970) | 1,373,921 (+14,397) |
|
||||
| 2025-11-10 | 722,288 (+8,826) | 668,225 (+7,766) | 1,390,513 (+16,592) |
|
||||
| 2025-11-11 | 729,769 (+7,481) | 677,501 (+9,276) | 1,407,270 (+16,757) |
|
||||
| 2025-11-12 | 740,180 (+10,411) | 686,454 (+8,953) | 1,426,634 (+19,364) |
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "opencode",
|
||||
@@ -11,7 +12,7 @@
|
||||
"@tsconfig/bun": "catalog:",
|
||||
"husky": "9.1.7",
|
||||
"prettier": "3.6.2",
|
||||
"sst": "3.17.22",
|
||||
"sst": "3.17.23",
|
||||
"turbo": "2.5.6",
|
||||
},
|
||||
},
|
||||
@@ -39,7 +40,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -66,7 +67,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -90,7 +91,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -106,12 +107,15 @@
|
||||
"@cloudflare/workers-types": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "catalog:",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@types/node": "catalog:",
|
||||
"cloudflare": "5.2.0",
|
||||
},
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -151,7 +155,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
@@ -167,7 +171,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -185,8 +189,8 @@
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opentui/core": "0.1.39",
|
||||
"@opentui/solid": "0.1.39",
|
||||
"@opentui/core": "0.1.42",
|
||||
"@opentui/solid": "0.1.42",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -245,7 +249,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -265,7 +269,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.81.0",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -276,7 +280,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -289,7 +293,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -319,7 +323,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -434,7 +438,7 @@
|
||||
|
||||
"@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.1", "", { "dependencies": { "@astrojs/internal-helpers": "0.6.1", "@astrojs/prism": "3.2.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", "remark-smartypants": "^3.0.2", "shiki": "^3.0.0", "smol-toml": "^1.3.1", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-c5F5gGrkczUaTVgmMW9g1YMJGzOtRvjjhw6IfGuxarM6ct09MpwysP10US729dy07gg8y+ofVifezvP3BNsWZg=="],
|
||||
|
||||
"@astrojs/mdx": ["@astrojs/mdx@4.3.9", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.8", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.15.0", "es-module-lexer": "^1.7.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "picocolors": "^1.1.1", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-80LHiM4z3FxAjATHNgFpa8nlTNSprAWB4UUKnr/QG56Pwk7uRnJWrXlok4wSCi/3fg8kTZ98A408Q91M+iqJdw=="],
|
||||
"@astrojs/mdx": ["@astrojs/mdx@4.3.10", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.8", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.15.0", "es-module-lexer": "^1.7.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "picocolors": "^1.1.1", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-2T5+XIr7PMqMeXhRofXY5NlY4lA0Km+wkfsqmr9lq5KXUHpGlKPQ9dlDZJP9E/CtljJyEBNS17zq66LrIJ1tiQ=="],
|
||||
|
||||
"@astrojs/prism": ["@astrojs/prism@3.2.0", "", { "dependencies": { "prismjs": "^1.29.0" } }, "sha512-GilTHKGCW6HMq7y3BUv9Ac7GMe/MO9gi9GW62GzKtth0SwukCu/qp2wLiGpEujhY+VVhaG9v7kv/5vFzvf4NYw=="],
|
||||
|
||||
@@ -574,15 +578,15 @@
|
||||
|
||||
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.9", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "^1.20250927.0" }, "optionalPeers": ["workerd"] }, "sha512-Drm7qlTKnvncEv+DANiQNEonq0H0LyIsoFZYJ6tJ8OhAoy5udIE8yp6BsVDYcIjcYLIybp4M7c/P7ly/56SoHg=="],
|
||||
|
||||
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20251011.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0DirVP+Z82RtZLlK2B+VhLOkk+ShBqDYO/jhcRw4oVlp0TOvk3cOVZChrt3+y3NV8Y/PYgTEywzLKFSziK4wCg=="],
|
||||
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20251105.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-nztUP35wTtUKM+681dBWtUNSySNWELTV+LY43oWy7ZhK19/iBJPQoFY7xpvF7zy4qOOShtise259B65DS4/71Q=="],
|
||||
|
||||
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20251011.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-1WuFBGwZd15p4xssGN/48OE2oqokIuc51YvHvyNivyV8IYnAs3G9bJNGWth1X7iMDPe4g44pZrKhRnISS2+5dA=="],
|
||||
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20251105.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-WS/dvPYTW/+gs8s0UvDqDY7wcuIAg/hUpjrMNGepr+Mo38vMU39FYhJQOly99oJCXxMluQqAnRKg09b/9Gr+Rg=="],
|
||||
|
||||
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20251011.0", "", { "os": "linux", "cpu": "x64" }, "sha512-BccMiBzFlWZyFghIw2szanmYJrJGBGHomw2y/GV6pYXChFzMGZkeCEMfmCyJj29xczZXxcZmUVJxNy4eJxO8QA=="],
|
||||
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20251105.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RdHRHo/hpjR6sNw529FkmslVSz/K3Pb1+i3fIoqUrHCrZOUYzFyz3nLeZh4EYaAhcztLWiSTwBv54bcl4sG3wA=="],
|
||||
|
||||
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20251011.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-79o/216lsbAbKEVDZYXR24ivEIE2ysDL9jvo0rDTkViLWju9dAp3CpyetglpJatbSi3uWBPKZBEOqN68zIjVsQ=="],
|
||||
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20251105.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-5zkxQCqLjwrqZVVJh92J2Drv6xifkP8kN2ltjHdwZQlVzfDW48d7tAtCm1ZooUv204ixvZFarusCfL+IRjExZg=="],
|
||||
|
||||
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20251011.0", "", { "os": "win32", "cpu": "x64" }, "sha512-RIXUQRchFdqEvaUqn1cXZXSKjpqMaSaVAkI5jNZ8XzAw/bw2bcdOVUtakrflgxDprltjFb0PTNtuss1FKtH9Jg=="],
|
||||
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20251105.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6BpkfjBIbGR+4FBOcZGcWDLM0XQuoI6R9Dublj/BKf4pv0/xJ4zHdnaYUb5NIlC75L55Ouqw0CEJasoKlMjgnw=="],
|
||||
|
||||
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251008.0", "", {}, "sha512-dZLkO4PbCL0qcCSKzuW7KE4GYe49lI12LCfQ5y9XeSwgYBoAUbwH4gmJ6A0qUIURiTJTkGkRkhVPqpq2XNgYRA=="],
|
||||
|
||||
@@ -962,21 +966,21 @@
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.1.39", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.39", "@opentui/core-darwin-x64": "0.1.39", "@opentui/core-linux-arm64": "0.1.39", "@opentui/core-linux-x64": "0.1.39", "@opentui/core-win32-arm64": "0.1.39", "@opentui/core-win32-x64": "0.1.39", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-5gPyg3X/8Nr80RfNEJFiMM8Tj01VFfvFwEMCMQrDiOhmSfFXSH2grF/KPl2bnd2Qa13maXWFEl6W3aATObnrnQ=="],
|
||||
"@opentui/core": ["@opentui/core@0.1.42", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.42", "@opentui/core-darwin-x64": "0.1.42", "@opentui/core-linux-arm64": "0.1.42", "@opentui/core-linux-x64": "0.1.42", "@opentui/core-win32-arm64": "0.1.42", "@opentui/core-win32-x64": "0.1.42", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-oV2xHBB2HaNiGvaV6R0C8GmniNJSsLKop4APq4FrLyCYberc6vZcATSHcA5YT9krdvHbBDOOn9RI2oaVJYRbUQ=="],
|
||||
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.39", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tDUdNdzGeylkDWTiDIy/CalM/9nIeDwMZGN0Q6FLqABnAplwBhdIH2w/gInAcMaTyagm7Qk88p398Wbnxa9uyg=="],
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.42", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sk5b/kh/y8HUJ7stGA5ydkajJX/z2OiGqSm+wn6XIoqdDavxQaFoQOt1PCuCqaxqZWJcXZ6OmISDVagZPUsPuw=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.39", "", { "os": "darwin", "cpu": "x64" }, "sha512-dWXXNUpdi3ndd+6WotQezsO7g54MLSc/6DmYcl0p7fZrQFct8fX0c9ny/S0xAusNHgBGVS5j5FWE75Mx79301Q=="],
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.42", "", { "os": "darwin", "cpu": "x64" }, "sha512-b0FKTw+t/wlJg4u+wTurWzbQe47gExkjguaGSUua0m0vybrkkvbUvmrADr+yivCjxcPAhSZ3lOOVU3uZuWsNqw=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.39", "", { "os": "linux", "cpu": "arm64" }, "sha512-ookQbxLjsg51iwGb6/KTxCfiVRtE9lSE2OVFLLYork8iVzxg81jX29Uoxe1knZ8FjOJ0+VqTzex2IqQH6mjJlw=="],
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.42", "", { "os": "linux", "cpu": "arm64" }, "sha512-Vy8BrjJpv2f56JAsYmv4PkC+2HsCv8Gh0ErrlIJQ8L4h29oWabS44m0uxFdvjuTDgKpCJzOScsxsy1VGzSd9rw=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.39", "", { "os": "linux", "cpu": "x64" }, "sha512-CeXVNa3hB7gTYKYoZAuMtxWMIXn2rPhmXLkHKpEvXvDRjODFDk8wN1AIVnT5tfncXbWNa5z35BhmqewpGkl4oQ=="],
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.42", "", { "os": "linux", "cpu": "x64" }, "sha512-cO+13E1HIAPUdV/DRdKotHFAxsLc+ipbbFKGAuu/msfvywCnnNs86w22yeMg0cEqx7aBocWWT1XfJEHDJLFOqw=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.39", "", { "os": "win32", "cpu": "arm64" }, "sha512-eeBrVOHz7B+JNZ+w7GH6QxXhXQVBxI6jHmw3B05czG905Je62P0skZNHxiol2BZRawDljo1J/nXQdO5XPeAk2A=="],
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.42", "", { "os": "win32", "cpu": "arm64" }, "sha512-xpLhODjOWh7gMOSrKIldb4v6hR0TGyz6kjckDKwcjUv3LGbLJuSly+3O/zuWWS60dt56G1X4A0OyjWwiGZjc0g=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.39", "", { "os": "win32", "cpu": "x64" }, "sha512-lLXeQUBg6Wlenauwd+xaBD+0HT4YIcONeZUTHA+Gyd/rqVhxId97rhhzFikp3bBTvNJlYAscJI3yIF2JvRiFNQ=="],
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.42", "", { "os": "win32", "cpu": "x64" }, "sha512-pao5XdAln93WWPdsTF+V+HccZ5d1ijSmv0OoBbkjkVbP+tiN41yxNqg/7jzW9IiAakYsvmpKV+3ixi/dlBEvOQ=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.1.39", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.39", "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-J34JpWh3HdiDbZajo06WUpd+9CLE/RotVjpVlBE4xtWs9tVMVSUrEZqjI7enoRS/IcCZaeNy3HEREuNA8ng7dw=="],
|
||||
"@opentui/solid": ["@opentui/solid@0.1.42", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.42", "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-4TNlEtatZ4n9TcKPWSF/EoaPaLmZuFVJ4hHh9wRggNaGrmDlmJ+9N/8oEKXETt+oRDX/1CdowAaTOVfaqb1t6g=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
@@ -1126,49 +1130,49 @@
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.5", "", { "os": "android", "cpu": "arm" }, "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ=="],
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.2", "", { "os": "android", "cpu": "arm" }, "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.5", "", { "os": "android", "cpu": "arm64" }, "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA=="],
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.2", "", { "os": "android", "cpu": "arm64" }, "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA=="],
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA=="],
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA=="],
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ=="],
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.5", "", { "os": "linux", "cpu": "arm" }, "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ=="],
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.5", "", { "os": "linux", "cpu": "arm" }, "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ=="],
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg=="],
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q=="],
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA=="],
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw=="],
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw=="],
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg=="],
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ=="],
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.5", "", { "os": "linux", "cpu": "x64" }, "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q=="],
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.5", "", { "os": "linux", "cpu": "x64" }, "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg=="],
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.5", "", { "os": "none", "cpu": "arm64" }, "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw=="],
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.2", "", { "os": "none", "cpu": "arm64" }, "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w=="],
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg=="],
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ=="],
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg=="],
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA=="],
|
||||
|
||||
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
|
||||
|
||||
@@ -1204,57 +1208,57 @@
|
||||
|
||||
"@slack/web-api": ["@slack/web-api@6.13.0", "", { "dependencies": { "@slack/logger": "^3.0.0", "@slack/types": "^2.11.0", "@types/is-stream": "^1.1.0", "@types/node": ">=12.0.0", "axios": "^1.7.4", "eventemitter3": "^3.1.0", "form-data": "^2.5.0", "is-electron": "2.2.2", "is-stream": "^1.1.0", "p-queue": "^6.6.1", "p-retry": "^4.0.0" } }, "sha512-dv65crIgdh9ZYHrevLU6XFHTQwTyDmNqEqzuIrV+Vqe/vgiG6w37oex5ePDU1RGm2IJ90H8iOvHFvzdEO/vB+g=="],
|
||||
|
||||
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ=="],
|
||||
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="],
|
||||
|
||||
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-4Jys0ni2tB2VZzgslbEgszZyMdTkPOFGA8g+So/NjR8oy6Qwaq4eSwsrRI+NMtb0Dq4kqCzGUu/nGUx7OM/xfw=="],
|
||||
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw=="],
|
||||
|
||||
"@smithy/core": ["@smithy/core@3.17.2", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-stream": "^4.5.5", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ=="],
|
||||
"@smithy/core": ["@smithy/core@3.18.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-vGSDXOJFZgOPTatSI1ly7Gwyy/d/R9zh2TO3y0JZ0uut5qQ88p9IaWaZYIWSSqtdekNM4CGok/JppxbAff4KcQ=="],
|
||||
|
||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/property-provider": "^4.2.4", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-YVNMjhdz2pVto5bRdux7GMs0x1m0Afz3OcQy/4Yf9DH4fWOtroGH7uLvs7ZmDyoBJzLdegtIPpXrpJOZWvUXdw=="],
|
||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="],
|
||||
|
||||
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-aV8blR9RBDKrOlZVgjOdmOibTC2sBXNiT7WA558b4MPdsLTV6sbyc1WIE9QiIuYMJjYtnPLciefoqSW8Gi+MZQ=="],
|
||||
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="],
|
||||
|
||||
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ=="],
|
||||
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg=="],
|
||||
|
||||
"@smithy/hash-node": ["@smithy/hash-node@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kKU0gVhx/ppVMntvUOZE7WRMFW86HuaxLwvqileBEjL7PoILI8/djoILw3gPQloGVE6O0oOzqafxeNi2KbnUJw=="],
|
||||
"@smithy/hash-node": ["@smithy/hash-node@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA=="],
|
||||
|
||||
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-z6aDLGiHzsMhbS2MjetlIWopWz//K+mCoPXjW6aLr0mypF+Y7qdEh5TyJ20Onf9FbWHiWl4eC+rITdizpnXqOw=="],
|
||||
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A=="],
|
||||
|
||||
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="],
|
||||
|
||||
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-hJRZuFS9UsElX4DJSJfoX4M1qXRH+VFiLMUnhsWvtOOUWRNvvOfDaUSdlNbjwv1IkpVjj/Rd/O59Jl3nhAcxow=="],
|
||||
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A=="],
|
||||
|
||||
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.6", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-serde": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w=="],
|
||||
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.7", "", { "dependencies": { "@smithy/core": "^3.18.0", "@smithy/middleware-serde": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-i8Mi8OuY6Yi82Foe3iu7/yhBj1HBRoOQwBSsUNYglJTNSFaWYTNM2NauBBs/7pq2sqkLRqeUXA3Ogi2utzpUlQ=="],
|
||||
|
||||
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/protocol-http": "^5.3.4", "@smithy/service-error-classification": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "@smithy/util-middleware": "^4.2.4", "@smithy/util-retry": "^4.2.4", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-OhLx131znrEDxZPAvH/OYufR9d1nB2CQADyYFN4C3V/NQS7Mg4V6uvxHC/Dr96ZQW8IlHJTJ+vAhKt6oxWRndA=="],
|
||||
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.7", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/service-error-classification": "^4.2.5", "@smithy/smithy-client": "^4.9.3", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-E7Vc6WHCHlzDRTx1W0jZ6J1L6ziEV0PIWcUdmfL4y+c8r7WYr6I+LkQudaD8Nfb7C5c4P3SQ972OmXHtv6m/OA=="],
|
||||
|
||||
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="],
|
||||
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-La1ldWTJTZ5NqQyPqnCNeH9B+zjFhrNoQIL1jTh4zuqXRlmXhxYHhMtI1/92OlnoAtp6JoN7kzuwhWoXrBwPqg=="],
|
||||
|
||||
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA=="],
|
||||
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ=="],
|
||||
|
||||
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.4", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw=="],
|
||||
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg=="],
|
||||
|
||||
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="],
|
||||
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw=="],
|
||||
|
||||
"@smithy/property-provider": ["@smithy/property-provider@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w=="],
|
||||
"@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="],
|
||||
|
||||
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="],
|
||||
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ=="],
|
||||
|
||||
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="],
|
||||
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="],
|
||||
|
||||
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-aHb5cqXZocdzEkZ/CvhVjdw5l4r1aU/9iMEyoKzH4eXMowT6M0YjBpp7W/+XjkBnY8Xh0kVd55GKjnPKlCwinQ=="],
|
||||
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ=="],
|
||||
|
||||
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1" } }, "sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng=="],
|
||||
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0" } }, "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ=="],
|
||||
|
||||
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-y5ozxeQ9omVjbnJo9dtTsdXj9BEvGx2X8xvRgKnV+/7wLBuYJQL6dOa/qMY6omyHi7yjt1OA97jZLoVRYi8lxA=="],
|
||||
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA=="],
|
||||
|
||||
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="],
|
||||
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.5", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w=="],
|
||||
|
||||
"@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="],
|
||||
"@smithy/smithy-client": ["@smithy/smithy-client@4.9.3", "", { "dependencies": { "@smithy/core": "^3.18.0", "@smithy/middleware-endpoint": "^4.3.7", "@smithy/middleware-stack": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-8tlueuTgV5n7inQCkhyptrB3jo2AO80uGrps/XTYZivv5MFQKKBj3CIWIGMI2fRY5LEduIiazOhAWdFknY1O9w=="],
|
||||
|
||||
"@smithy/types": ["@smithy/types@4.8.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA=="],
|
||||
"@smithy/types": ["@smithy/types@4.9.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA=="],
|
||||
|
||||
"@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="],
|
||||
"@smithy/url-parser": ["@smithy/url-parser@4.2.5", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ=="],
|
||||
|
||||
"@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="],
|
||||
|
||||
@@ -1266,19 +1270,19 @@
|
||||
|
||||
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
|
||||
|
||||
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-GwaGjv/QLuL/QHQaqhf/maM7+MnRFQQs7Bsl6FlaeK6lm6U7mV5AAnVabw68cIoMl5FQFyKK62u7RWRzWL25OQ=="],
|
||||
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.6", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.3", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-kbpuXbEf2YQ9zEE6eeVnUCQWO0e1BjMnKrXL8rfXgiWA0m8/E0leU4oSNzxP04WfCmW8vjEqaDeXWxwE4tpOjQ=="],
|
||||
|
||||
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.8", "", { "dependencies": { "@smithy/config-resolver": "^4.4.2", "@smithy/credential-provider-imds": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-gIoTf9V/nFSIZ0TtgDNLd+Ws59AJvijmMDYrOozoMHPJaG9cMRdqNO50jZTlbM6ydzQYY8L/mQ4tKSw/TB+s6g=="],
|
||||
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.9", "", { "dependencies": { "@smithy/config-resolver": "^4.4.3", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.3", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-dgyribrVWN5qE5usYJ0m5M93mVM3L3TyBPZWe1Xl6uZlH2gzfQx3dz+ZCdW93lWqdedJRkOecnvbnoEEXRZ5VQ=="],
|
||||
|
||||
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg=="],
|
||||
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A=="],
|
||||
|
||||
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="],
|
||||
|
||||
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="],
|
||||
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA=="],
|
||||
|
||||
"@smithy/util-retry": ["@smithy/util-retry@4.2.4", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA=="],
|
||||
"@smithy/util-retry": ["@smithy/util-retry@4.2.5", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg=="],
|
||||
|
||||
"@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="],
|
||||
"@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="],
|
||||
|
||||
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
|
||||
|
||||
@@ -1318,7 +1322,7 @@
|
||||
|
||||
"@solidjs/meta": ["@solidjs/meta@0.29.4", "", { "peerDependencies": { "solid-js": ">=1.8.4" } }, "sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g=="],
|
||||
|
||||
"@solidjs/router": ["@solidjs/router@0.15.3", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw=="],
|
||||
"@solidjs/router": ["@solidjs/router@0.15.4", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ=="],
|
||||
|
||||
"@solidjs/start": ["@solidjs/start@1.2.0", "", { "dependencies": { "@tanstack/server-functions-plugin": "1.121.21", "@vinxi/plugin-directives": "^0.5.0", "@vinxi/server-components": "^0.5.0", "cookie-es": "^2.0.0", "defu": "^6.1.2", "error-stack-parser": "^2.1.4", "html-to-image": "^1.11.11", "radix3": "^1.1.0", "seroval": "^1.0.2", "seroval-plugins": "^1.0.2", "shiki": "^1.26.1", "source-map-js": "^1.0.2", "terracotta": "^1.0.4", "tinyglobby": "^0.2.2", "vite-plugin-solid": "^2.11.1" }, "peerDependencies": { "vinxi": "^0.5.7" } }, "sha512-SRv1g3R+4sxZnxCBPK1IedtLKsPhPJ7W/Yv4xEHjM4jJGPWi3ed35/yd0D5zhRK0C7zJIkZKbhnR/S3g8JUD5w=="],
|
||||
|
||||
@@ -1432,6 +1436,8 @@
|
||||
|
||||
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
|
||||
|
||||
"@types/promise.allsettled": ["@types/promise.allsettled@1.0.6", "", {}, "sha512-wA0UT0HeT2fGHzIFV9kWpYz5mdoyLxKrTgMdZQM++5h6pYAFH73HXcQhefg24nD1yivUFEn5KU+EF4b+CXJ4Wg=="],
|
||||
|
||||
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
|
||||
@@ -1450,7 +1456,7 @@
|
||||
|
||||
"@types/scheduler": ["@types/scheduler@0.26.0", "", {}, "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA=="],
|
||||
|
||||
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
|
||||
"@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
|
||||
|
||||
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
|
||||
|
||||
@@ -1518,6 +1524,8 @@
|
||||
|
||||
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||
|
||||
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
|
||||
|
||||
"ai": ["ai@5.0.8", "", { "dependencies": { "@ai-sdk/gateway": "1.0.4", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.1", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-qbnhj046UvG30V1S5WhjBn+RBGEAmi8PSZWqMhRsE3EPxvO5BcePXTZFA23e9MYyWS9zr4Vm8Mv3wQXwLmtIBw=="],
|
||||
|
||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||
@@ -1580,7 +1588,7 @@
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
|
||||
"autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="],
|
||||
"autoprefixer": ["autoprefixer@10.4.22", "", { "dependencies": { "browserslist": "^4.27.0", "caniuse-lite": "^1.0.30001754", "fraction.js": "^5.3.4", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg=="],
|
||||
|
||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||
|
||||
@@ -1610,9 +1618,9 @@
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"bare-events": ["bare-events@2.8.1", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-oxSAxTS1hRfnyit2CL5QpAOS5ixfBjj6ex3yTNvXyY/kE719jQ/IjuESJBK2w5v4wwQRAHGseVJXx9QBYOtFGQ=="],
|
||||
"bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="],
|
||||
|
||||
"bare-fs": ["bare-fs@4.5.0", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-GljgCjeupKZJNetTqxKaQArLK10vpmK28or0+RwWjEl5Rk+/xG3wkpmkv+WrcBm3q1BwHKlnhXzR8O37kcvkXQ=="],
|
||||
"bare-fs": ["bare-fs@4.5.1", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-zGUCsm3yv/ePt2PHNbVxjjn0nNB1MkIaR4wOCxJ2ig5pCf5cCVAYJXVhQg/3OhhJV6DB1ts7Hv0oUaElc2TPQg=="],
|
||||
|
||||
"bare-os": ["bare-os@3.6.2", "", {}, "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A=="],
|
||||
|
||||
@@ -1662,7 +1670,7 @@
|
||||
|
||||
"brotli": ["brotli@1.3.3", "", { "dependencies": { "base64-js": "^1.1.2" } }, "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg=="],
|
||||
|
||||
"browserslist": ["browserslist@4.27.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", "electron-to-chromium": "^1.5.238", "node-releases": "^2.0.26", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw=="],
|
||||
"browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="],
|
||||
|
||||
"buffer": ["buffer@4.9.2", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="],
|
||||
|
||||
@@ -1672,7 +1680,7 @@
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"bun-ffi-structs": ["bun-ffi-structs@0.1.0", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-NoRfJ81pgLIHCzw624/2GS2FuxcU0G4SRJww/4PXvheNVUPSIUjkOC6v1/8rk66tJVCb9oR0D6rDNKK0qT5O2Q=="],
|
||||
"bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="],
|
||||
|
||||
@@ -1704,7 +1712,7 @@
|
||||
|
||||
"camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001753", "", {}, "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw=="],
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001754", "", {}, "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg=="],
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
|
||||
@@ -1742,6 +1750,8 @@
|
||||
|
||||
"clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="],
|
||||
|
||||
"cloudflare": ["cloudflare@5.2.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-dVzqDpPFYR9ApEC9e+JJshFJZXcw4HzM8W+3DHzO5oy9+8rLC53G7x6fEf9A7/gSuSCxuvndzui5qJKftfIM9A=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||
@@ -1922,7 +1932,7 @@
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.245", "", {}, "sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.250", "", {}, "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
@@ -2020,7 +2030,7 @@
|
||||
|
||||
"expressive-code": ["expressive-code@0.41.3", "", { "dependencies": { "@expressive-code/core": "^0.41.3", "@expressive-code/plugin-frames": "^0.41.3", "@expressive-code/plugin-shiki": "^0.41.3", "@expressive-code/plugin-text-markers": "^0.41.3" } }, "sha512-YLnD62jfgBZYrXIPQcJ0a51Afv9h8VlWqEGK9uU2T5nL/5rb8SnA86+7+mgCZe5D34Tff5RNEA5hjNVJYHzrFg=="],
|
||||
|
||||
"exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="],
|
||||
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
|
||||
@@ -2068,11 +2078,15 @@
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
|
||||
"form-data": ["form-data@2.5.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="],
|
||||
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
|
||||
|
||||
"form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
|
||||
|
||||
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
|
||||
|
||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||
|
||||
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
|
||||
"fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
|
||||
|
||||
"framer-motion": ["framer-motion@8.5.5", "", { "dependencies": { "@motionone/dom": "^10.15.3", "hey-listen": "^1.0.8", "tslib": "^2.4.0" }, "optionalDependencies": { "@emotion/is-prop-valid": "^0.8.2" }, "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" } }, "sha512-5IDx5bxkjWHWUF3CVJoSyUVOtrbAxtzYBBowRE2uYI/6VYhkEBD+rbTHEGuUmbGHRj6YqqSfoG7Aa1cLyWCrBA=="],
|
||||
|
||||
@@ -2258,6 +2272,8 @@
|
||||
|
||||
"human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],
|
||||
|
||||
"humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
|
||||
|
||||
"husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
|
||||
|
||||
"i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="],
|
||||
@@ -2668,7 +2684,7 @@
|
||||
|
||||
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
|
||||
|
||||
"miniflare": ["miniflare@4.20251011.2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251011.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-5oAaz6lqZus4QFwzEJiNtgpjZR2TBVwBeIhOW33V4gu+l23EukpKja831tFIX2o6sOD/hqZmKZHplOrWl3YGtQ=="],
|
||||
"miniflare": ["miniflare@4.20251105.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251105.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-n+lCQbGLPjHFm5EKMohxCl+hLIki9rIlJSU9FkYKdJ62cGacetmTH5IgWUZhUFFM+NqhqZLOuWXTAsoZTm0hog=="],
|
||||
|
||||
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
||||
|
||||
@@ -2716,6 +2732,8 @@
|
||||
|
||||
"node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="],
|
||||
|
||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||
|
||||
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||
@@ -2838,7 +2856,7 @@
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"path-scurry": ["path-scurry@2.0.0", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg=="],
|
||||
"path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
|
||||
|
||||
@@ -3056,7 +3074,7 @@
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"rollup": ["rollup@4.52.5", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.5", "@rollup/rollup-android-arm64": "4.52.5", "@rollup/rollup-darwin-arm64": "4.52.5", "@rollup/rollup-darwin-x64": "4.52.5", "@rollup/rollup-freebsd-arm64": "4.52.5", "@rollup/rollup-freebsd-x64": "4.52.5", "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", "@rollup/rollup-linux-arm-musleabihf": "4.52.5", "@rollup/rollup-linux-arm64-gnu": "4.52.5", "@rollup/rollup-linux-arm64-musl": "4.52.5", "@rollup/rollup-linux-loong64-gnu": "4.52.5", "@rollup/rollup-linux-ppc64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-musl": "4.52.5", "@rollup/rollup-linux-s390x-gnu": "4.52.5", "@rollup/rollup-linux-x64-gnu": "4.52.5", "@rollup/rollup-linux-x64-musl": "4.52.5", "@rollup/rollup-openharmony-arm64": "4.52.5", "@rollup/rollup-win32-arm64-msvc": "4.52.5", "@rollup/rollup-win32-ia32-msvc": "4.52.5", "@rollup/rollup-win32-x64-gnu": "4.52.5", "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw=="],
|
||||
"rollup": ["rollup@4.53.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.2", "@rollup/rollup-android-arm64": "4.53.2", "@rollup/rollup-darwin-arm64": "4.53.2", "@rollup/rollup-darwin-x64": "4.53.2", "@rollup/rollup-freebsd-arm64": "4.53.2", "@rollup/rollup-freebsd-x64": "4.53.2", "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", "@rollup/rollup-linux-arm-musleabihf": "4.53.2", "@rollup/rollup-linux-arm64-gnu": "4.53.2", "@rollup/rollup-linux-arm64-musl": "4.53.2", "@rollup/rollup-linux-loong64-gnu": "4.53.2", "@rollup/rollup-linux-ppc64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-musl": "4.53.2", "@rollup/rollup-linux-s390x-gnu": "4.53.2", "@rollup/rollup-linux-x64-gnu": "4.53.2", "@rollup/rollup-linux-x64-musl": "4.53.2", "@rollup/rollup-openharmony-arm64": "4.53.2", "@rollup/rollup-win32-arm64-msvc": "4.53.2", "@rollup/rollup-win32-ia32-msvc": "4.53.2", "@rollup/rollup-win32-x64-gnu": "4.53.2", "@rollup/rollup-win32-x64-msvc": "4.53.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g=="],
|
||||
|
||||
"rollup-plugin-visualizer": ["rollup-plugin-visualizer@6.0.5", "", { "dependencies": { "open": "^8.0.0", "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^17.5.1" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg=="],
|
||||
|
||||
@@ -3178,23 +3196,23 @@
|
||||
|
||||
"sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="],
|
||||
|
||||
"sst": ["sst@3.17.22", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.22", "sst-darwin-x64": "3.17.22", "sst-linux-arm64": "3.17.22", "sst-linux-x64": "3.17.22", "sst-linux-x86": "3.17.22", "sst-win32-arm64": "3.17.22", "sst-win32-x64": "3.17.22", "sst-win32-x86": "3.17.22" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-C+XMTbm6fx+7eT+ESAMATqG7qV7+pyVfxYQb6osdH3jd4u91QW1VU/xlEru+RU1rs1ZE58ixXdRP75UGPn+gog=="],
|
||||
"sst": ["sst@3.17.23", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.23", "sst-darwin-x64": "3.17.23", "sst-linux-arm64": "3.17.23", "sst-linux-x64": "3.17.23", "sst-linux-x86": "3.17.23", "sst-win32-arm64": "3.17.23", "sst-win32-x64": "3.17.23", "sst-win32-x86": "3.17.23" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-TwKgUgDnZdc1Swe+bvCNeyO4dQnYz5cTodMpYj3jlXZdK9/KNz0PVxT1f0u5E76i1pmilXrUBL/f7iiMPw4RDg=="],
|
||||
|
||||
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.22", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B2pKq1dWc60+7HfXQ6/9etskxxNv9axxlQKveCLQAuG2a3mmtv2/jcR0Ch3mvSTGtW+KfhzUXda2kj7nZ/phBA=="],
|
||||
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.23", "", { "os": "darwin", "cpu": "arm64" }, "sha512-R6kvmF+rUideOoU7KBs2SdvrIupoE+b+Dor/eq9Uo4Dojj7KvYDZI/EDm8sSCbbcx/opiWeyNqKtlnLEdCxE6g=="],
|
||||
|
||||
"sst-darwin-x64": ["sst-darwin-x64@3.17.22", "", { "os": "darwin", "cpu": "x64" }, "sha512-flikYqXvhwwrS6x2FDOde+MQODHaZCIbUkVHYO3/gYo99rbAMQ8VpC/3LXnmnPEQkLOwWCSzLp4S4F9nG/PW2g=="],
|
||||
"sst-darwin-x64": ["sst-darwin-x64@3.17.23", "", { "os": "darwin", "cpu": "x64" }, "sha512-WW4P1S35iYCifQXxD+sE3wuzcN+LHLpuKMaNoaBqEcWGZnH3IPaDJ7rpLF0arkDAo/z3jZmWWzOCkr0JuqJ8vQ=="],
|
||||
|
||||
"sst-linux-arm64": ["sst-linux-arm64@3.17.22", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pyD8Oej9js8XeCCebiEIde02vC5hc+bLl2/jR02K+9gYkGVJ6n5bkT8AlR8zWdS4FJKPyeJYUfjliT1T33j+g=="],
|
||||
"sst-linux-arm64": ["sst-linux-arm64@3.17.23", "", { "os": "linux", "cpu": "arm64" }, "sha512-TjtNqgIh7RlAWgPLFCAt0mXvIB+J7WjmRvIRrAdX0mXsndOiBJ/DMOgXSLVsIWHCfPj8MIEot/hWpnJgXgIeag=="],
|
||||
|
||||
"sst-linux-x64": ["sst-linux-x64@3.17.22", "", { "os": "linux", "cpu": "x64" }, "sha512-A5p941edP9wgfgsbLUMeEPvi9JExj0OSaxgtFAC6/6BYoW4zruGAPzq206Ln6dNYP3gRdo5TJbSjio3F0ot8qg=="],
|
||||
"sst-linux-x64": ["sst-linux-x64@3.17.23", "", { "os": "linux", "cpu": "x64" }, "sha512-qdqJiEbYfCjZlI3F/TA6eoIU7JXVkEEI/UMILNf2JWhky0KQdCW2Xyz+wb6c0msVJCWdUM/uj+1DaiP2eXvghw=="],
|
||||
|
||||
"sst-linux-x86": ["sst-linux-x86@3.17.22", "", { "os": "linux", "cpu": "none" }, "sha512-pFDIi+ZwH8GOvy5He9wsbAjRGf/sTGhGE/V480w0A6itb9BC4jQ9sblJkk3Jx/fP2g27pKN2RNz+ifOU+GrUYQ=="],
|
||||
"sst-linux-x86": ["sst-linux-x86@3.17.23", "", { "os": "linux", "cpu": "none" }, "sha512-aGmUujIvoNlmAABEGsOgfY1rxD9koC6hN8bnTLbDI+oI/u/zjHYh50jsbL0p3TlaHpwF/lxP3xFSuT6IKp+KgA=="],
|
||||
|
||||
"sst-win32-arm64": ["sst-win32-arm64@3.17.22", "", { "os": "win32", "cpu": "arm64" }, "sha512-9KaIrk+Z6hLDNi9GShf9NLrZi9jC/NNGpUAn6HvTXr8c6HUyQzg6takMH8nrISGCPn92y+IYWqdglaqbgnJTog=="],
|
||||
"sst-win32-arm64": ["sst-win32-arm64@3.17.23", "", { "os": "win32", "cpu": "arm64" }, "sha512-ZxdkGqYDrrZGz98rijDCN+m5yuCcwD6Bc9/6hubLsvdpNlVorUqzpg801Ec97xSK0nIC9g6pNiRyxAcsQQstUg=="],
|
||||
|
||||
"sst-win32-x64": ["sst-win32-x64@3.17.22", "", { "os": "win32", "cpu": "x64" }, "sha512-cvzyet4octGHK7w05jPUSPmUdlAWyh8IzjB8Pcs873K9AUGJEtQCftOKZjXaFdIG9DTvFWCCBi9zdzClxT9jJg=="],
|
||||
"sst-win32-x64": ["sst-win32-x64@3.17.23", "", { "os": "win32", "cpu": "x64" }, "sha512-yc9cor4MS49Ccy2tQCF1tf6M81yLeSGzGL+gjhUxpVKo2pN3bxl3w70eyU/mTXSEeyAmG9zEfbt6FNu4sy5cUA=="],
|
||||
|
||||
"sst-win32-x86": ["sst-win32-x86@3.17.22", "", { "os": "win32", "cpu": "none" }, "sha512-ol5icDJuHzG+AjbGbCIQoF8z3oiikTF9CtccdK/udqEF861DnngWzM99IY5TJvmJlN+38yOV0MY4XI5hM6SEQA=="],
|
||||
"sst-win32-x86": ["sst-win32-x86@3.17.23", "", { "os": "win32", "cpu": "none" }, "sha512-DIp3s54IpNAfdYjSRt6McvkbEPQDMxUu6RUeRAd2C+FcTJgTloon/ghAPQBaDgu2VoVgymjcJARO/XyfKcCLOQ=="],
|
||||
|
||||
"stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
|
||||
|
||||
@@ -3480,6 +3498,8 @@
|
||||
|
||||
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
|
||||
|
||||
"web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
@@ -3506,9 +3526,9 @@
|
||||
|
||||
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
|
||||
|
||||
"workerd": ["workerd@1.20251011.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251011.0", "@cloudflare/workerd-darwin-arm64": "1.20251011.0", "@cloudflare/workerd-linux-64": "1.20251011.0", "@cloudflare/workerd-linux-arm64": "1.20251011.0", "@cloudflare/workerd-windows-64": "1.20251011.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Dq35TLPEJAw7BuYQMkN3p9rge34zWMU2Gnd4DSJFeVqld4+DAO2aPG7+We2dNIAyM97S8Y9BmHulbQ00E0HC7Q=="],
|
||||
"workerd": ["workerd@1.20251105.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251105.0", "@cloudflare/workerd-darwin-arm64": "1.20251105.0", "@cloudflare/workerd-linux-64": "1.20251105.0", "@cloudflare/workerd-linux-arm64": "1.20251105.0", "@cloudflare/workerd-windows-64": "1.20251105.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-8D1UmsxrRr3Go7enbYCsYoiWeGn66u1WFNojPSgtjp7z8pV2cXskjr05vQ1OOzl7+rg1hDDofnCJqVwChMym8g=="],
|
||||
|
||||
"wrangler": ["wrangler@4.45.4", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.9", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251011.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20251011.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251011.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-niXT7B463wQi7WXIHjYK8txgWhuKQLrGmhjoR58SnPhlkq4wGjd3rFrkVyRc/O58clGTfs672BSGOph4XMoQKw=="],
|
||||
"wrangler": ["wrangler@4.46.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.9", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251105.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20251105.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251014.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-WRROO7CL+MW/E44RMT4X7w32qPjufiPpGdey5D6H7iKzzVqfUkTRULxYBfWANiU1yGnsiCXQtu3Ap0G2TmohtA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
@@ -3538,7 +3558,7 @@
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@1.2.1", "", {}, "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="],
|
||||
"yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="],
|
||||
|
||||
"yocto-spinner": ["yocto-spinner@0.2.3", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ=="],
|
||||
|
||||
@@ -3708,6 +3728,8 @@
|
||||
|
||||
"@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
|
||||
|
||||
"@opencode-ai/desktop/@solidjs/router": ["@solidjs/router@0.15.3", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw=="],
|
||||
|
||||
"@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.4.2", "", { "dependencies": { "@shikijs/core": "3.4.2", "@shikijs/types": "3.4.2" } }, "sha512-I5baLVi/ynLEOZoWSAMlACHNnG+yw5HDmse0oe+GW6U1u+ULdEB3UHiVWaHoJSSONV7tlcVxuaMy74sREDkSvg=="],
|
||||
|
||||
"@opencode-ai/web/@types/luxon": ["@types/luxon@3.6.2", "", {}, "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw=="],
|
||||
@@ -3754,6 +3776,8 @@
|
||||
|
||||
"@slack/web-api/eventemitter3": ["eventemitter3@3.1.2", "", {}, "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="],
|
||||
|
||||
"@slack/web-api/form-data": ["form-data@2.5.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@solidjs/start/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="],
|
||||
@@ -3778,8 +3802,6 @@
|
||||
|
||||
"@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
|
||||
|
||||
"@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
|
||||
|
||||
"@vercel/nft/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@vercel/nft/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
|
||||
@@ -3814,8 +3836,6 @@
|
||||
|
||||
"astro/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"axios/form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
|
||||
|
||||
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
|
||||
|
||||
"babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||
@@ -3890,6 +3910,8 @@
|
||||
|
||||
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
"gel/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"giget/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"giget/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
|
||||
@@ -3942,7 +3964,7 @@
|
||||
|
||||
"named-placeholders/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
|
||||
|
||||
"nitropack/c12": ["c12@3.3.1", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-LcWQ01LT9tkoUINHgpIOv3mMs+Abv7oVCrtpMRi1PaapVEpWoMga5WuT7/DqFTu7URP9ftbOmimNw1KNIGh9DQ=="],
|
||||
"nitropack/c12": ["c12@3.3.2", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A=="],
|
||||
|
||||
"nitropack/globby": ["globby@15.0.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "fast-glob": "^3.3.3", "ignore": "^7.0.5", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" } }, "sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw=="],
|
||||
|
||||
@@ -4350,6 +4372,8 @@
|
||||
|
||||
"@pierre/precision-diffs/shiki/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
|
||||
|
||||
"@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
|
||||
|
||||
"@slack/web-api/p-queue/p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="],
|
||||
@@ -4402,8 +4426,6 @@
|
||||
|
||||
"astro/shiki/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
|
||||
|
||||
"axios/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"babel-plugin-module-resolver/glob/minimatch": ["minimatch@8.0.4", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA=="],
|
||||
|
||||
"babel-plugin-module-resolver/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
|
||||
@@ -4700,6 +4722,8 @@
|
||||
|
||||
"@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
|
||||
|
||||
"@vercel/nft/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
@@ -4708,8 +4732,6 @@
|
||||
|
||||
"archiver-utils/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"axios/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"babel-plugin-module-resolver/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"babel-plugin-module-resolver/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
[install]
|
||||
exact = true
|
||||
exact = true
|
||||
|
||||
@@ -106,6 +106,7 @@ const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
||||
const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
|
||||
properties: { value: stripeWebhook.secret },
|
||||
})
|
||||
const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
|
||||
|
||||
////////////////
|
||||
// CONSOLE
|
||||
@@ -136,6 +137,13 @@ new sst.cloudflare.x.SolidStart("Console", {
|
||||
EMAILOCTOPUS_API_KEY,
|
||||
AWS_SES_ACCESS_KEY_ID,
|
||||
AWS_SES_SECRET_ACCESS_KEY,
|
||||
...($dev
|
||||
? [
|
||||
new sst.Secret("CLOUDFLARE_DEFAULT_ACCOUNT_ID", process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID!),
|
||||
new sst.Secret("CLOUDFLARE_API_TOKEN", process.env.CLOUDFLARE_API_TOKEN!),
|
||||
]
|
||||
: []),
|
||||
gatewayKv,
|
||||
],
|
||||
environment: {
|
||||
//VITE_DOCS_URL: web.url.apply((url) => url!),
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
"description": "AI-powered development tool",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.3.1",
|
||||
"packageManager": "bun@1.3.2",
|
||||
"scripts": {
|
||||
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
||||
"typecheck": "bun turbo typecheck",
|
||||
@@ -52,7 +52,7 @@
|
||||
"@tsconfig/bun": "catalog:",
|
||||
"husky": "9.1.7",
|
||||
"prettier": "3.6.2",
|
||||
"sst": "3.17.22",
|
||||
"sst": "3.17.23",
|
||||
"turbo": "2.5.6"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
||||
"build": "./script/generate-sitemap.ts && vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
||||
"start": "vinxi start",
|
||||
"version": "1.0.45"
|
||||
"version": "1.0.62"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
|
||||
@@ -19,7 +19,7 @@ const getUserEmail = query(async (workspaceID: string) => {
|
||||
|
||||
export default function WorkspaceLayout(props: RouteSectionProps) {
|
||||
const params = useParams()
|
||||
const userEmail = createAsync(() => getUserEmail(params.id))
|
||||
const userEmail = createAsync(() => getUserEmail(params.id!))
|
||||
return (
|
||||
<main data-page="workspace">
|
||||
<Link rel="icon" type="image/svg+xml" href="/favicon-zen.svg" />
|
||||
|
||||
@@ -5,7 +5,7 @@ import "./[id].css"
|
||||
|
||||
export default function WorkspaceLayout(props: RouteSectionProps) {
|
||||
const params = useParams()
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id))
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id!))
|
||||
|
||||
return (
|
||||
<main data-page="workspace">
|
||||
|
||||
@@ -27,24 +27,31 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
|
||||
export function BillingSection() {
|
||||
const params = useParams()
|
||||
// ORIGINAL CODE - COMMENTED OUT FOR TESTING
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
|
||||
const checkoutAction = useAction(createCheckoutUrl)
|
||||
const checkoutSubmission = useSubmission(createCheckoutUrl)
|
||||
const sessionAction = useAction(createSessionUrl)
|
||||
const sessionSubmission = useSubmission(createSessionUrl)
|
||||
const [store, setStore] = createStore({
|
||||
showAddBalanceForm: false,
|
||||
addBalanceAmount: "",
|
||||
addBalanceAmount: billingInfo()?.reloadAmount.toString() ?? "",
|
||||
checkoutRedirecting: false,
|
||||
sessionRedirecting: false,
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const info = billingInfo()
|
||||
if (info) {
|
||||
setStore("addBalanceAmount", info.reloadAmount.toString())
|
||||
}
|
||||
})
|
||||
const balance = createMemo(() => formatBalance(billingInfo()?.balance ?? 0))
|
||||
|
||||
async function onClickCheckout() {
|
||||
const amount = parseInt(store.addBalanceAmount)
|
||||
const baseUrl = window.location.href
|
||||
|
||||
const checkout = await checkoutAction(params.id, amount, baseUrl, baseUrl)
|
||||
const checkout = await checkoutAction(params.id!, amount, baseUrl, baseUrl)
|
||||
if (checkout && checkout.data) {
|
||||
setStore("checkoutRedirecting", true)
|
||||
window.location.href = checkout.data
|
||||
@@ -53,7 +60,7 @@ export function BillingSection() {
|
||||
|
||||
async function onClickSession() {
|
||||
const baseUrl = window.location.href
|
||||
const sessionUrl = await sessionAction(params.id, baseUrl)
|
||||
const sessionUrl = await sessionAction(params.id!, baseUrl)
|
||||
if (sessionUrl && sessionUrl.data) {
|
||||
setStore("sessionRedirecting", true)
|
||||
window.location.href = sessionUrl.data
|
||||
@@ -67,7 +74,6 @@ export function BillingSection() {
|
||||
}
|
||||
setStore({
|
||||
showAddBalanceForm: true,
|
||||
addBalanceAmount: billingInfo()!.reloadAmount.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import { queryBillingInfo, querySessionInfo } from "../../common"
|
||||
|
||||
export default function () {
|
||||
const params = useParams()
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id))
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id!))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
|
||||
|
||||
return (
|
||||
<div data-page="workspace-[id]">
|
||||
|
||||
@@ -30,7 +30,7 @@ export function MonthlyLimitSection() {
|
||||
const params = useParams()
|
||||
const submission = useSubmission(setMonthlyLimit)
|
||||
const [store, setStore] = createStore({ show: false })
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
|
||||
|
||||
let input: HTMLInputElement
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ const downloadReceipt = action(async (workspaceID: string, paymentID: string) =>
|
||||
|
||||
export function PaymentSection() {
|
||||
const params = useParams()
|
||||
const payments = createAsync(() => getPaymentsInfo(params.id))
|
||||
const payments = createAsync(() => getPaymentsInfo(params.id!))
|
||||
const downloadReceiptAction = useAction(downloadReceipt)
|
||||
|
||||
// DUMMY DATA FOR TESTING
|
||||
@@ -89,7 +89,7 @@ export function PaymentSection() {
|
||||
<td data-slot="payment-receipt">
|
||||
<button
|
||||
onClick={async () => {
|
||||
const receiptUrl = await downloadReceiptAction(params.id, payment.paymentID!)
|
||||
const receiptUrl = await downloadReceiptAction(params.id!, payment.paymentID!)
|
||||
if (receiptUrl) {
|
||||
window.open(receiptUrl, "_blank")
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ const setReload = action(async (form: FormData) => {
|
||||
|
||||
export function ReloadSection() {
|
||||
const params = useParams()
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
|
||||
const setReloadSubmission = useSubmission(setReload)
|
||||
const reloadSubmission = useSubmission(reload)
|
||||
const [store, setStore] = createStore({
|
||||
|
||||
@@ -10,8 +10,8 @@ import { querySessionInfo, queryBillingInfo, createCheckoutUrl, formatBalance }
|
||||
|
||||
export default function () {
|
||||
const params = useParams()
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id))
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id!))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
|
||||
const checkoutAction = useAction(createCheckoutUrl)
|
||||
const checkoutSubmission = useSubmission(createCheckoutUrl)
|
||||
const [store, setStore] = createStore({
|
||||
@@ -21,7 +21,7 @@ export default function () {
|
||||
|
||||
async function onClickCheckout() {
|
||||
const baseUrl = window.location.href
|
||||
const checkout = await checkoutAction(params.id, billingInfo()!.reloadAmount, baseUrl, baseUrl)
|
||||
const checkout = await checkoutAction(params.id!, billingInfo()!.reloadAmount, baseUrl, baseUrl)
|
||||
if (checkout && checkout.data) {
|
||||
setStore("checkoutRedirecting", true)
|
||||
window.location.href = checkout.data
|
||||
|
||||
@@ -45,7 +45,7 @@ const listKeys = query(async (workspaceID: string) => {
|
||||
|
||||
export function KeySection() {
|
||||
const params = useParams()
|
||||
const keys = createAsync(() => listKeys(params.id))
|
||||
const keys = createAsync(() => listKeys(params.id!))
|
||||
const submission = useSubmission(createKey)
|
||||
const [store, setStore] = createStore({ show: false })
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ const roleOptions = [
|
||||
|
||||
export function MemberSection() {
|
||||
const params = useParams()
|
||||
const data = createAsync(() => listMembers(params.id))
|
||||
const data = createAsync(() => listMembers(params.id!))
|
||||
const submission = useSubmission(inviteMember)
|
||||
const [store, setStore] = createStore({
|
||||
show: false,
|
||||
@@ -328,7 +328,7 @@ export function MemberSection() {
|
||||
{(member) => (
|
||||
<MemberRow
|
||||
member={member}
|
||||
workspaceID={params.id}
|
||||
workspaceID={params.id!}
|
||||
actorID={data()!.actorID}
|
||||
actorRole={data()!.actorRole}
|
||||
/>
|
||||
|
||||
@@ -52,8 +52,8 @@ const updateModel = action(async (form: FormData) => {
|
||||
|
||||
export function ModelSection() {
|
||||
const params = useParams()
|
||||
const modelsInfo = createAsync(() => getModelsInfo(params.id))
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id))
|
||||
const modelsInfo = createAsync(() => getModelsInfo(params.id!))
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id!))
|
||||
|
||||
const modelsWithLab = createMemo(() => {
|
||||
const info = modelsInfo()
|
||||
|
||||
@@ -21,8 +21,8 @@ const listKeys = query(async (workspaceID: string) => {
|
||||
export function NewUserSection() {
|
||||
const params = useParams()
|
||||
const [copiedKey, setCopiedKey] = createSignal(false)
|
||||
const keys = createAsync(() => listKeys(params.id))
|
||||
const usage = createAsync(() => getUsageInfo(params.id))
|
||||
const keys = createAsync(() => listKeys(params.id!))
|
||||
const usage = createAsync(() => getUsageInfo(params.id!))
|
||||
const isNew = createMemo(() => {
|
||||
const keysList = keys()
|
||||
const usageList = usage()
|
||||
|
||||
@@ -54,7 +54,7 @@ const listProviders = query(async (workspaceID: string) => {
|
||||
|
||||
function ProviderRow(props: { provider: Provider }) {
|
||||
const params = useParams()
|
||||
const providers = createAsync(() => listProviders(params.id))
|
||||
const providers = createAsync(() => listProviders(params.id!))
|
||||
const saveSubmission = useSubmission(saveProvider, ([fd]) => fd.get("provider")?.toString() === props.provider.key)
|
||||
const removeSubmission = useSubmission(
|
||||
removeProvider,
|
||||
|
||||
@@ -46,7 +46,7 @@ const updateWorkspace = action(async (form: FormData) => {
|
||||
|
||||
export function SettingsSection() {
|
||||
const params = useParams()
|
||||
const workspaceInfo = createAsync(() => getWorkspaceInfo(params.id))
|
||||
const workspaceInfo = createAsync(() => getWorkspaceInfo(params.id!))
|
||||
const submission = useSubmission(updateWorkspace)
|
||||
const [store, setStore] = createStore({ show: false })
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const getUsageInfo = query(async (workspaceID: string) => {
|
||||
export function UsageSection() {
|
||||
const params = useParams()
|
||||
// ORIGINAL CODE - COMMENTED OUT FOR TESTING
|
||||
const usage = createAsync(() => getUsageInfo(params.id))
|
||||
const usage = createAsync(() => getUsageInfo(params.id!))
|
||||
|
||||
// DUMMY DATA FOR TESTING
|
||||
// const usage = () => [
|
||||
|
||||
@@ -3,3 +3,4 @@ export class CreditsError extends Error {}
|
||||
export class MonthlyLimitError extends Error {}
|
||||
export class UserLimitError extends Error {}
|
||||
export class ModelError extends Error {}
|
||||
export class RateLimitError extends Error {}
|
||||
|
||||
@@ -12,14 +12,18 @@ import { UserTable } from "@opencode-ai/console-core/schema/user.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 { logger } from "./logger"
|
||||
import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError } from "./error"
|
||||
import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError, RateLimitError } from "./error"
|
||||
import { createBodyConverter, createStreamPartConverter, createResponseConverter } from "./provider/provider"
|
||||
import { anthropicHelper } from "./provider/anthropic"
|
||||
import { openaiHelper } from "./provider/openai"
|
||||
import { oaCompatHelper } from "./provider/openai-compatible"
|
||||
import { createRateLimiter } from "./rateLimiter"
|
||||
|
||||
type ZenData = Awaited<ReturnType<typeof ZenData.list>>
|
||||
type Model = ZenData["models"][string]
|
||||
type RetryOptions = {
|
||||
excludeProviders: string[]
|
||||
retryCount: number
|
||||
}
|
||||
|
||||
export async function handler(
|
||||
input: APIEvent,
|
||||
@@ -28,6 +32,11 @@ export async function handler(
|
||||
parseApiKey: (headers: Headers) => string | undefined
|
||||
},
|
||||
) {
|
||||
type AuthInfo = Awaited<ReturnType<typeof authenticate>>
|
||||
type ModelInfo = Awaited<ReturnType<typeof validateModel>>
|
||||
type ProviderInfo = Awaited<ReturnType<typeof selectProvider>>
|
||||
|
||||
const MAX_RETRIES = 3
|
||||
const FREE_WORKSPACES = [
|
||||
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
|
||||
"wrk_01K6W1A3VE0KMNVSCQT43BG2SX", // opencode bench
|
||||
@@ -35,6 +44,7 @@ export async function handler(
|
||||
|
||||
try {
|
||||
const body = await input.request.json()
|
||||
const ip = input.request.headers.get("x-real-ip") ?? ""
|
||||
logger.metric({
|
||||
is_tream: !!body.stream,
|
||||
session: input.request.headers.get("x-opencode-session"),
|
||||
@@ -42,38 +52,56 @@ export async function handler(
|
||||
})
|
||||
const zenData = ZenData.list()
|
||||
const modelInfo = validateModel(zenData, body.model)
|
||||
const providerInfo = selectProvider(zenData, modelInfo, input.request.headers.get("x-real-ip") ?? "")
|
||||
const authInfo = await authenticate(modelInfo, providerInfo)
|
||||
validateBilling(modelInfo, authInfo)
|
||||
validateModelSettings(authInfo)
|
||||
updateProviderKey(authInfo, providerInfo)
|
||||
logger.metric({ provider: providerInfo.id })
|
||||
const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip)
|
||||
await rateLimiter?.check()
|
||||
|
||||
// Request to model provider
|
||||
const startTimestamp = Date.now()
|
||||
const reqUrl = providerInfo.modifyUrl(providerInfo.api)
|
||||
const reqBody = JSON.stringify(
|
||||
providerInfo.modifyBody({
|
||||
...createBodyConverter(opts.format, providerInfo.format)(body),
|
||||
model: providerInfo.model,
|
||||
}),
|
||||
)
|
||||
logger.debug("REQUEST URL: " + reqUrl)
|
||||
logger.debug("REQUEST: " + reqBody)
|
||||
const res = await fetch(reqUrl, {
|
||||
method: "POST",
|
||||
headers: (() => {
|
||||
const headers = input.request.headers
|
||||
headers.delete("host")
|
||||
headers.delete("content-length")
|
||||
providerInfo.modifyHeaders(headers, body, providerInfo.apiKey)
|
||||
Object.entries(providerInfo.headerMappings ?? {}).forEach(([k, v]) => {
|
||||
headers.set(k, headers.get(v)!)
|
||||
const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
|
||||
const providerInfo = selectProvider(zenData, modelInfo, ip, retry)
|
||||
const authInfo = await authenticate(modelInfo, providerInfo)
|
||||
validateBilling(authInfo, modelInfo)
|
||||
validateModelSettings(authInfo)
|
||||
updateProviderKey(authInfo, providerInfo)
|
||||
logger.metric({ provider: providerInfo.id })
|
||||
|
||||
const startTimestamp = Date.now()
|
||||
const reqUrl = providerInfo.modifyUrl(providerInfo.api)
|
||||
const reqBody = JSON.stringify(
|
||||
providerInfo.modifyBody({
|
||||
...createBodyConverter(opts.format, providerInfo.format)(body),
|
||||
model: providerInfo.model,
|
||||
}),
|
||||
)
|
||||
logger.debug("REQUEST URL: " + reqUrl)
|
||||
logger.debug("REQUEST: " + reqBody.substring(0, 300) + "...")
|
||||
const res = await fetch(reqUrl, {
|
||||
method: "POST",
|
||||
headers: (() => {
|
||||
const headers = new Headers(input.request.headers)
|
||||
providerInfo.modifyHeaders(headers, body, providerInfo.apiKey)
|
||||
Object.entries(providerInfo.headerMappings ?? {}).forEach(([k, v]) => {
|
||||
headers.set(k, headers.get(v)!)
|
||||
})
|
||||
headers.delete("host")
|
||||
headers.delete("content-length")
|
||||
headers.delete("x-opencode-request")
|
||||
headers.delete("x-opencode-session")
|
||||
return headers
|
||||
})(),
|
||||
body: reqBody,
|
||||
})
|
||||
|
||||
// Try another provider => stop retrying if using fallback provider
|
||||
if (res.status !== 200 && modelInfo.fallbackProvider && providerInfo.id !== modelInfo.fallbackProvider) {
|
||||
return retriableRequest({
|
||||
excludeProviders: [...retry.excludeProviders, providerInfo.id],
|
||||
retryCount: retry.retryCount + 1,
|
||||
})
|
||||
return headers
|
||||
})(),
|
||||
body: reqBody,
|
||||
})
|
||||
}
|
||||
|
||||
return { providerInfo, authInfo, res, startTimestamp }
|
||||
}
|
||||
|
||||
const { providerInfo, authInfo, res, startTimestamp } = await retriableRequest()
|
||||
|
||||
// Scrub response headers
|
||||
const resHeaders = new Headers()
|
||||
@@ -84,9 +112,6 @@ export async function handler(
|
||||
}
|
||||
}
|
||||
logger.debug("STATUS: " + res.status + " " + res.statusText)
|
||||
if (res.status === 400 || res.status === 503) {
|
||||
logger.debug("RESPONSE: " + (await res.text()))
|
||||
}
|
||||
|
||||
// Handle non-streaming response
|
||||
if (!body.stream) {
|
||||
@@ -95,6 +120,7 @@ export async function handler(
|
||||
const body = JSON.stringify(responseConverter(json))
|
||||
logger.metric({ response_length: body.length })
|
||||
logger.debug("RESPONSE: " + body)
|
||||
await rateLimiter?.track()
|
||||
await trackUsage(authInfo, modelInfo, providerInfo, json.usage)
|
||||
await reload(authInfo)
|
||||
return new Response(body, {
|
||||
@@ -123,6 +149,7 @@ export async function handler(
|
||||
response_length: responseLength,
|
||||
"timestamp.last_byte": Date.now(),
|
||||
})
|
||||
await rateLimiter?.track()
|
||||
const usage = usageParser.retrieve()
|
||||
if (usage) {
|
||||
await trackUsage(authInfo, modelInfo, providerInfo, usage)
|
||||
@@ -197,6 +224,15 @@ export async function handler(
|
||||
{ status: 401 },
|
||||
)
|
||||
|
||||
if (error instanceof RateLimitError)
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
type: "error",
|
||||
error: { type: error.constructor.name, message: error.message },
|
||||
}),
|
||||
{ status: 429 },
|
||||
)
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
type: "error",
|
||||
@@ -221,36 +257,42 @@ export async function handler(
|
||||
return { id: modelId, ...modelData }
|
||||
}
|
||||
|
||||
function selectProvider(zenData: ZenData, model: Awaited<ReturnType<typeof validateModel>>, ip: string) {
|
||||
const providers = model.providers
|
||||
.filter((provider) => !provider.disabled)
|
||||
.flatMap((provider) => Array<typeof provider>(provider.weight ?? 1).fill(provider))
|
||||
function selectProvider(zenData: ZenData, modelInfo: ModelInfo, ip: string, retry: RetryOptions) {
|
||||
const provider = (() => {
|
||||
if (retry.retryCount === MAX_RETRIES) {
|
||||
return modelInfo.providers.find((provider) => provider.id === modelInfo.fallbackProvider)
|
||||
}
|
||||
|
||||
// Use the last 2 characters of IP address to select a provider
|
||||
const lastChars = ip.slice(-2)
|
||||
const index = parseInt(lastChars, 16) % providers.length
|
||||
const provider = providers[index || 0]
|
||||
const providers = modelInfo.providers
|
||||
.filter((provider) => !provider.disabled)
|
||||
.filter((provider) => !retry.excludeProviders.includes(provider.id))
|
||||
.flatMap((provider) => Array<typeof provider>(provider.weight ?? 1).fill(provider))
|
||||
|
||||
if (!(provider.id in zenData.providers)) {
|
||||
throw new ModelError(`Provider ${provider.id} not supported`)
|
||||
}
|
||||
// Use the last 2 characters of IP address to select a provider
|
||||
const lastChars = ip.slice(-2)
|
||||
const index = parseInt(lastChars, 16) % providers.length
|
||||
return providers[index || 0]
|
||||
})()
|
||||
|
||||
const format = zenData.providers[provider.id].format
|
||||
if (!provider) throw new ModelError("No provider available")
|
||||
if (!(provider.id in zenData.providers)) throw new ModelError(`Provider ${provider.id} not supported`)
|
||||
|
||||
return {
|
||||
...provider,
|
||||
...zenData.providers[provider.id],
|
||||
...(format === "anthropic" ? anthropicHelper : format === "openai" ? openaiHelper : oaCompatHelper),
|
||||
...(() => {
|
||||
const format = zenData.providers[provider.id].format
|
||||
if (format === "anthropic") return anthropicHelper
|
||||
if (format === "openai") return openaiHelper
|
||||
return oaCompatHelper
|
||||
})(),
|
||||
}
|
||||
}
|
||||
|
||||
async function authenticate(
|
||||
model: Awaited<ReturnType<typeof validateModel>>,
|
||||
providerInfo: Awaited<ReturnType<typeof selectProvider>>,
|
||||
) {
|
||||
async function authenticate(modelInfo: ModelInfo, providerInfo: ProviderInfo) {
|
||||
const apiKey = opts.parseApiKey(input.request.headers)
|
||||
if (!apiKey) {
|
||||
if (model.allowAnonymous) return
|
||||
if (modelInfo.allowAnonymous) return
|
||||
throw new AuthError("Missing API key.")
|
||||
}
|
||||
|
||||
@@ -282,7 +324,7 @@ export async function handler(
|
||||
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID))
|
||||
.innerJoin(BillingTable, eq(BillingTable.workspaceID, KeyTable.workspaceID))
|
||||
.innerJoin(UserTable, and(eq(UserTable.workspaceID, KeyTable.workspaceID), eq(UserTable.id, KeyTable.userID)))
|
||||
.leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, model.id)))
|
||||
.leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, modelInfo.id)))
|
||||
.leftJoin(
|
||||
ProviderTable,
|
||||
and(eq(ProviderTable.workspaceID, KeyTable.workspaceID), eq(ProviderTable.provider, providerInfo.id)),
|
||||
@@ -308,11 +350,11 @@ export async function handler(
|
||||
}
|
||||
}
|
||||
|
||||
function validateBilling(model: Model, authInfo: Awaited<ReturnType<typeof authenticate>>) {
|
||||
function validateBilling(authInfo: AuthInfo, modelInfo: ModelInfo) {
|
||||
if (!authInfo) return
|
||||
if (authInfo.provider?.credentials) return
|
||||
if (authInfo.isFree) return
|
||||
if (model.allowAnonymous) return
|
||||
if (modelInfo.allowAnonymous) return
|
||||
|
||||
const billing = authInfo.billing
|
||||
if (!billing.paymentMethodID)
|
||||
@@ -356,26 +398,18 @@ export async function handler(
|
||||
}
|
||||
}
|
||||
|
||||
function validateModelSettings(authInfo: Awaited<ReturnType<typeof authenticate>>) {
|
||||
function validateModelSettings(authInfo: AuthInfo) {
|
||||
if (!authInfo) return
|
||||
if (authInfo.isDisabled) throw new ModelError("Model is disabled")
|
||||
}
|
||||
|
||||
function updateProviderKey(
|
||||
authInfo: Awaited<ReturnType<typeof authenticate>>,
|
||||
providerInfo: Awaited<ReturnType<typeof selectProvider>>,
|
||||
) {
|
||||
function updateProviderKey(authInfo: AuthInfo, providerInfo: ProviderInfo) {
|
||||
if (!authInfo) return
|
||||
if (!authInfo.provider?.credentials) return
|
||||
providerInfo.apiKey = authInfo.provider.credentials
|
||||
}
|
||||
|
||||
async function trackUsage(
|
||||
authInfo: Awaited<ReturnType<typeof authenticate>>,
|
||||
modelInfo: ReturnType<typeof validateModel>,
|
||||
providerInfo: Awaited<ReturnType<typeof selectProvider>>,
|
||||
usage: any,
|
||||
) {
|
||||
async function trackUsage(authInfo: AuthInfo, modelInfo: ModelInfo, providerInfo: ProviderInfo, usage: any) {
|
||||
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
|
||||
providerInfo.normalizeUsage(usage)
|
||||
|
||||
@@ -483,7 +517,7 @@ export async function handler(
|
||||
)
|
||||
}
|
||||
|
||||
async function reload(authInfo: Awaited<ReturnType<typeof authenticate>>) {
|
||||
async function reload(authInfo: AuthInfo) {
|
||||
if (!authInfo) return
|
||||
if (authInfo.isFree) return
|
||||
if (authInfo.provider?.credentials) return
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Resource } from "@opencode-ai/console-resource"
|
||||
import { RateLimitError } from "./error"
|
||||
import { logger } from "./logger"
|
||||
|
||||
export function createRateLimiter(model: string, limit: number | undefined, ip: string) {
|
||||
if (!limit) return
|
||||
|
||||
const now = Date.now()
|
||||
const currKey = `usage:${ip}:${model}:${buildYYYYMMDDHH(now)}`
|
||||
const prevKey = `usage:${ip}:${model}:${buildYYYYMMDDHH(now - 3_600_000)}`
|
||||
let currRate: number
|
||||
let prevRate: number
|
||||
|
||||
return {
|
||||
track: async () => {
|
||||
await Resource.GatewayKv.put(currKey, currRate + 1, { expirationTtl: 3600 })
|
||||
},
|
||||
check: async () => {
|
||||
const values = await Resource.GatewayKv.get([currKey, prevKey])
|
||||
const prevValue = values?.get(prevKey)
|
||||
const currValue = values?.get(currKey)
|
||||
prevRate = prevValue ? parseInt(prevValue) : 0
|
||||
currRate = currValue ? parseInt(currValue) : 0
|
||||
logger.debug(`rate limit ${model} prev/curr: ${prevRate}/${currRate}`)
|
||||
if (prevRate + currRate >= limit) throw new RateLimitError(`Rate limit exceeded. Please try again later.`)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function buildYYYYMMDDHH(timestamp: number) {
|
||||
return new Date(timestamp)
|
||||
.toISOString()
|
||||
.replace(/[^0-9]/g, "")
|
||||
.substring(0, 10)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -7,7 +7,6 @@ import { ZenData } from "../src/model"
|
||||
|
||||
const root = path.resolve(process.cwd(), "..", "..", "..")
|
||||
const models = await $`bun sst secret list`.cwd(root).text()
|
||||
console.log("models", models)
|
||||
|
||||
// read the line starting with "ZEN_MODELS"
|
||||
const oldValue1 = models
|
||||
|
||||
@@ -24,6 +24,8 @@ export namespace ZenData {
|
||||
cost: ModelCostSchema,
|
||||
cost200K: ModelCostSchema.optional(),
|
||||
allowAnonymous: z.boolean().optional(),
|
||||
rateLimit: z.number().optional(),
|
||||
fallbackProvider: z.string().optional(),
|
||||
providers: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
|
||||
Vendored
+9
@@ -22,6 +22,14 @@ declare module "sst" {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
CLOUDFLARE_API_TOKEN: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
Console: {
|
||||
type: "sst.cloudflare.SolidStart"
|
||||
url: string
|
||||
@@ -96,6 +104,7 @@ declare module "sst" {
|
||||
AuthApi: cloudflare.Service
|
||||
AuthStorage: cloudflare.KVNamespace
|
||||
Bucket: cloudflare.R2Bucket
|
||||
GatewayKv: cloudflare.KVNamespace
|
||||
LogProcessor: cloudflare.Service
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
+9
@@ -22,6 +22,14 @@ declare module "sst" {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
CLOUDFLARE_API_TOKEN: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
Console: {
|
||||
type: "sst.cloudflare.SolidStart"
|
||||
url: string
|
||||
@@ -96,6 +104,7 @@ declare module "sst" {
|
||||
AuthApi: cloudflare.Service
|
||||
AuthStorage: cloudflare.KVNamespace
|
||||
Bucket: cloudflare.R2Bucket
|
||||
GatewayKv: cloudflare.KVNamespace
|
||||
LogProcessor: cloudflare.Service
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "22.0.2"
|
||||
"@cloudflare/workers-types": "catalog:",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@types/node": "catalog:",
|
||||
"cloudflare": "5.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,58 @@
|
||||
export { Resource } from "sst"
|
||||
import type { KVNamespaceListOptions, KVNamespaceListResult, KVNamespacePutOptions } from "@cloudflare/workers-types"
|
||||
import { Resource as ResourceBase } from "sst"
|
||||
import Cloudflare from "cloudflare"
|
||||
|
||||
export const Resource = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_target, prop: keyof typeof ResourceBase) {
|
||||
const value = ResourceBase[prop]
|
||||
// @ts-ignore
|
||||
if ("type" in value && value.type === "sst.cloudflare.Kv") {
|
||||
const client = new Cloudflare({
|
||||
apiToken: ResourceBase.CLOUDFLARE_API_TOKEN.value,
|
||||
})
|
||||
// @ts-ignore
|
||||
const namespaceId = value.namespaceId
|
||||
const accountId = ResourceBase.CLOUDFLARE_DEFAULT_ACCOUNT_ID.value
|
||||
return {
|
||||
get: (k: string | string[]) => {
|
||||
const isMulti = Array.isArray(k)
|
||||
return client.kv.namespaces
|
||||
.bulkGet(namespaceId, {
|
||||
keys: Array.isArray(k) ? k : [k],
|
||||
account_id: accountId,
|
||||
})
|
||||
.then((result) => (isMulti ? new Map(Object.entries(result?.values ?? {})) : result?.values?.[k]))
|
||||
},
|
||||
put: (k: string, v: string, opts?: KVNamespacePutOptions) =>
|
||||
client.kv.namespaces.values.update(namespaceId, k, {
|
||||
account_id: accountId,
|
||||
value: v,
|
||||
expiration: opts?.expiration,
|
||||
expiration_ttl: opts?.expirationTtl,
|
||||
metadata: opts?.metadata,
|
||||
}),
|
||||
delete: (k: string) =>
|
||||
client.kv.namespaces.values.delete(namespaceId, k, {
|
||||
account_id: accountId,
|
||||
}),
|
||||
list: (opts?: KVNamespaceListOptions): Promise<KVNamespaceListResult<unknown, string>> =>
|
||||
client.kv.namespaces.keys
|
||||
.list(namespaceId, {
|
||||
account_id: accountId,
|
||||
prefix: opts?.prefix ?? undefined,
|
||||
})
|
||||
.then((result) => {
|
||||
return {
|
||||
keys: result.result,
|
||||
list_complete: true,
|
||||
cacheStatus: null,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
return value
|
||||
},
|
||||
},
|
||||
) as Record<string, any>
|
||||
|
||||
+9
@@ -22,6 +22,14 @@ declare module "sst" {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
CLOUDFLARE_API_TOKEN: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
Console: {
|
||||
type: "sst.cloudflare.SolidStart"
|
||||
url: string
|
||||
@@ -96,6 +104,7 @@ declare module "sst" {
|
||||
AuthApi: cloudflare.Service
|
||||
AuthStorage: cloudflare.KVNamespace
|
||||
Bucket: cloudflare.R2Bucket
|
||||
GatewayKv: cloudflare.KVNamespace
|
||||
LogProcessor: cloudflare.Service
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -64,7 +64,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const handleFileSelect = (path: string | undefined) => {
|
||||
if (!path) return
|
||||
addPart({ type: "file", path, content: "@" + getFilename(path), start: 0, end: 0 })
|
||||
setStore("popoverIsOpen", false)
|
||||
}
|
||||
|
||||
const { flat, active, onInput, onKeyDown, refetch } = useFilteredList<string>({
|
||||
@@ -114,16 +113,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
),
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => session.prompt.cursor(),
|
||||
(cursor) => {
|
||||
if (cursor === undefined) return
|
||||
queueMicrotask(() => setCursorPosition(editorRef, cursor))
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
const parseFromDOM = (): Prompt => {
|
||||
const newParts: Prompt = []
|
||||
let position = 0
|
||||
@@ -173,118 +162,70 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
|
||||
const addPart = (part: ContentPart) => {
|
||||
const selection = window.getSelection()
|
||||
if (!selection || selection.rangeCount === 0) return
|
||||
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
const prompt = session.prompt.current()
|
||||
const rawText = prompt.map((p) => p.content).join("")
|
||||
const textBeforeCursor = rawText.substring(0, cursorPosition)
|
||||
const atMatch = textBeforeCursor.match(/@(\S*)$/)
|
||||
|
||||
const startIndex = atMatch ? atMatch.index! : cursorPosition
|
||||
const endIndex = atMatch ? cursorPosition : cursorPosition
|
||||
if (part.type === "file") {
|
||||
const pill = document.createElement("span")
|
||||
pill.textContent = part.content
|
||||
pill.setAttribute("data-type", "file")
|
||||
pill.setAttribute("data-path", part.path)
|
||||
pill.setAttribute("contenteditable", "false")
|
||||
pill.style.userSelect = "text"
|
||||
pill.style.cursor = "default"
|
||||
|
||||
const pushText = (acc: { parts: ContentPart[]; runningIndex: number }, value: string) => {
|
||||
if (!value) return
|
||||
const last = acc.parts[acc.parts.length - 1]
|
||||
if (last && last.type === "text") {
|
||||
acc.parts[acc.parts.length - 1] = {
|
||||
type: "text",
|
||||
content: last.content + value,
|
||||
start: last.start,
|
||||
end: last.end + value.length,
|
||||
const gap = document.createTextNode(" ")
|
||||
const range = selection.getRangeAt(0)
|
||||
|
||||
if (atMatch) {
|
||||
let node: Node | null = range.startContainer
|
||||
let offset = range.startOffset
|
||||
let runningLength = 0
|
||||
|
||||
const walker = document.createTreeWalker(editorRef, NodeFilter.SHOW_TEXT, null)
|
||||
let currentNode = walker.nextNode()
|
||||
while (currentNode) {
|
||||
const textContent = currentNode.textContent || ""
|
||||
if (runningLength + textContent.length >= atMatch.index!) {
|
||||
const localStart = atMatch.index! - runningLength
|
||||
const localEnd = cursorPosition - runningLength
|
||||
if (currentNode === range.startContainer || runningLength + textContent.length >= cursorPosition) {
|
||||
range.setStart(currentNode, localStart)
|
||||
range.setEnd(currentNode, Math.min(localEnd, textContent.length))
|
||||
break
|
||||
}
|
||||
}
|
||||
runningLength += textContent.length
|
||||
currentNode = walker.nextNode()
|
||||
}
|
||||
return
|
||||
}
|
||||
acc.parts.push({ type: "text", content: value, start: acc.runningIndex, end: acc.runningIndex + value.length })
|
||||
|
||||
range.deleteContents()
|
||||
range.insertNode(gap)
|
||||
range.insertNode(pill)
|
||||
range.setStartAfter(gap)
|
||||
range.collapse(true)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
} else if (part.type === "text") {
|
||||
const textNode = document.createTextNode(part.content)
|
||||
const range = selection.getRangeAt(0)
|
||||
range.deleteContents()
|
||||
range.insertNode(textNode)
|
||||
range.setStartAfter(textNode)
|
||||
range.collapse(true)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
|
||||
const {
|
||||
parts: nextParts,
|
||||
inserted,
|
||||
cursorPositionAfter,
|
||||
} = prompt.reduce(
|
||||
(acc, item) => {
|
||||
if (acc.inserted) {
|
||||
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
|
||||
acc.runningIndex += item.content.length
|
||||
return acc
|
||||
}
|
||||
|
||||
const nextIndex = acc.runningIndex + item.content.length
|
||||
if (nextIndex <= startIndex) {
|
||||
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
|
||||
acc.runningIndex = nextIndex
|
||||
return acc
|
||||
}
|
||||
|
||||
if (item.type !== "text") {
|
||||
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
|
||||
acc.runningIndex = nextIndex
|
||||
return acc
|
||||
}
|
||||
|
||||
const headLength = Math.max(0, startIndex - acc.runningIndex)
|
||||
const tailLength = Math.max(0, endIndex - acc.runningIndex)
|
||||
const head = item.content.slice(0, headLength)
|
||||
const tail = item.content.slice(tailLength)
|
||||
|
||||
pushText(acc, head)
|
||||
acc.runningIndex += head.length
|
||||
|
||||
if (part.type === "text") {
|
||||
pushText(acc, part.content)
|
||||
acc.runningIndex += part.content.length
|
||||
}
|
||||
if (part.type !== "text") {
|
||||
acc.parts.push({ ...part, start: acc.runningIndex, end: acc.runningIndex + part.content.length })
|
||||
acc.runningIndex += part.content.length
|
||||
}
|
||||
|
||||
const needsGap = Boolean(atMatch)
|
||||
const rest = needsGap ? (tail ? (/^\s/.test(tail) ? tail : ` ${tail}`) : " ") : tail
|
||||
pushText(acc, rest)
|
||||
acc.runningIndex += rest.length
|
||||
|
||||
const baseCursor = startIndex + part.content.length
|
||||
const cursorAddition = needsGap && rest.length > 0 ? 1 : 0
|
||||
acc.cursorPositionAfter = baseCursor + cursorAddition
|
||||
|
||||
acc.inserted = true
|
||||
return acc
|
||||
},
|
||||
{
|
||||
parts: [] as ContentPart[],
|
||||
runningIndex: 0,
|
||||
inserted: false,
|
||||
cursorPositionAfter: cursorPosition + part.content.length,
|
||||
},
|
||||
)
|
||||
|
||||
if (!inserted) {
|
||||
const baseParts = prompt.filter((item) => !(item.type === "text" && item.content === ""))
|
||||
const runningIndex = baseParts.reduce((sum, p) => sum + p.content.length, 0)
|
||||
const appendedAcc = { parts: [...baseParts] as ContentPart[], runningIndex }
|
||||
if (part.type === "text") {
|
||||
pushText(appendedAcc, part.content)
|
||||
}
|
||||
if (part.type !== "text") {
|
||||
appendedAcc.parts.push({
|
||||
...part,
|
||||
start: appendedAcc.runningIndex,
|
||||
end: appendedAcc.runningIndex + part.content.length,
|
||||
})
|
||||
}
|
||||
const next = appendedAcc.parts.length > 0 ? appendedAcc.parts : DEFAULT_PROMPT
|
||||
const nextCursor = rawText.length + part.content.length
|
||||
session.prompt.set(next, nextCursor)
|
||||
setStore("popoverIsOpen", false)
|
||||
queueMicrotask(() => setCursorPosition(editorRef, nextCursor))
|
||||
return
|
||||
}
|
||||
|
||||
session.prompt.set(nextParts, cursorPositionAfter)
|
||||
handleInput()
|
||||
setStore("popoverIsOpen", false)
|
||||
|
||||
queueMicrotask(() => setCursorPosition(editorRef, cursorPositionAfter))
|
||||
}
|
||||
|
||||
const abort = () =>
|
||||
@@ -378,7 +319,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
session.layout.setActiveTab(undefined)
|
||||
session.messages.setActive(undefined)
|
||||
session.prompt.set(DEFAULT_PROMPT, 0)
|
||||
// Clear the editor DOM directly to ensure it's empty
|
||||
editorRef.innerHTML = ""
|
||||
session.prompt.set([{ type: "text", content: "", start: 0, end: 0 }], 0)
|
||||
|
||||
sdk.client.session.prompt({
|
||||
path: { id: existing.id },
|
||||
@@ -402,7 +345,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
return (
|
||||
<div class="relative size-full _max-h-[320px] flex flex-col gap-3">
|
||||
<Show when={store.popoverIsOpen}>
|
||||
<div class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-[252px] min-h-10 overflow-y-auto flex flex-col p-2 pb-0 rounded-2xl border border-border-base bg-surface-raised-stronger-non-alpha shadow-md">
|
||||
<div
|
||||
class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-[252px] min-h-10
|
||||
overflow-auto no-scrollbar flex flex-col p-2 pb-0 rounded-2xl
|
||||
border border-border-base bg-surface-raised-stronger-non-alpha shadow-md"
|
||||
>
|
||||
<Show when={flat().length > 0} fallback={<div class="text-text-weak px-2">No matching files</div>}>
|
||||
<For each={flat()}>
|
||||
{(i) => (
|
||||
@@ -419,7 +366,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
|
||||
{getDirectory(i)}
|
||||
</span>
|
||||
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
|
||||
<Show when={!i.endsWith("/")}>
|
||||
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-1 text-text-muted/40 shrink-0"></div>
|
||||
|
||||
@@ -15,18 +15,19 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
||||
|
||||
const [store, setStore] = makePersisted(
|
||||
createStore<{
|
||||
prompt: Prompt
|
||||
cursorPosition?: number
|
||||
messageId?: string
|
||||
tabs: {
|
||||
active?: string
|
||||
opened: string[]
|
||||
}
|
||||
prompt: Prompt
|
||||
cursor?: number
|
||||
}>({
|
||||
prompt: [{ type: "text", content: "", start: 0, end: 0 }],
|
||||
tabs: {
|
||||
opened: [],
|
||||
},
|
||||
prompt: clonePrompt(DEFAULT_PROMPT),
|
||||
cursor: undefined,
|
||||
}),
|
||||
{
|
||||
name: props.sessionId ?? "new-session",
|
||||
@@ -102,12 +103,13 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
||||
diffs,
|
||||
prompt: {
|
||||
current: createMemo(() => store.prompt),
|
||||
cursor: createMemo(() => store.cursorPosition),
|
||||
cursor: createMemo(() => store.cursor),
|
||||
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
|
||||
set(prompt: Prompt, cursorPosition?: number) {
|
||||
const next = clonePrompt(prompt)
|
||||
batch(() => {
|
||||
setStore("prompt", prompt)
|
||||
if (cursorPosition !== undefined) setStore("cursorPosition", cursorPosition)
|
||||
setStore("prompt", next)
|
||||
if (cursorPosition !== undefined) setStore("cursor", cursorPosition)
|
||||
})
|
||||
},
|
||||
},
|
||||
@@ -172,7 +174,6 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
||||
opened.splice(to, 0, opened.splice(index, 1)[0])
|
||||
}),
|
||||
)
|
||||
// setStore("node", path, "pinned", true)
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -215,3 +216,20 @@ export function isPromptEqual(promptA: Prompt, promptB: Prompt): boolean {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function cloneSelection(selection?: TextSelection) {
|
||||
if (!selection) return undefined
|
||||
return { ...selection }
|
||||
}
|
||||
|
||||
function clonePart(part: ContentPart): ContentPart {
|
||||
if (part.type === "text") return { ...part }
|
||||
return {
|
||||
...part,
|
||||
selection: cloneSelection(part.selection),
|
||||
}
|
||||
}
|
||||
|
||||
function clonePrompt(prompt: Prompt): Prompt {
|
||||
return prompt.map(clonePart)
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
setStore("limit", (x) => x + count)
|
||||
await load.session()
|
||||
},
|
||||
more: createMemo(() => store.session.length === store.limit),
|
||||
more: createMemo(() => store.session.length >= store.limit),
|
||||
},
|
||||
load,
|
||||
absolute,
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
useDragDropContext,
|
||||
} from "@thisbeyond/solid-dnd"
|
||||
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
|
||||
import type { JSX } from "solid-js"
|
||||
import type { JSX, ParentProps } from "solid-js"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
|
||||
import { Markdown } from "@opencode-ai/ui"
|
||||
@@ -332,11 +332,12 @@ export default function Page() {
|
||||
<Tabs.Content value="chat" class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden">
|
||||
<div
|
||||
classList={{
|
||||
"w-full grid flex-1 min-h-0": true,
|
||||
"grid-cols-2": local.layout.review.state() === "open",
|
||||
"w-full flex-1 min-h-0": true,
|
||||
grid: local.layout.review.state() !== "open",
|
||||
flex: local.layout.review.state() === "open",
|
||||
}}
|
||||
>
|
||||
<div class="relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0 max-w-2xl mx-auto">
|
||||
<div class="relative shrink-0 px-6 py-2 flex flex-col gap-6 flex-1 min-h-0 w-full max-w-xl mx-auto">
|
||||
<Switch>
|
||||
<Match when={session.id}>
|
||||
<div class="h-8 flex shrink-0 self-stretch items-center justify-end">
|
||||
@@ -357,8 +358,8 @@ export default function Page() {
|
||||
role="list"
|
||||
classList={{
|
||||
"mr-8 shrink-0 flex flex-col items-start": true,
|
||||
"absolute right-full w-60 @7xl:gap-2": true, // local.layout.review.state() !== "open",
|
||||
"": local.layout.review.state() === "open",
|
||||
"absolute right-full w-60 @7xl:gap-2": local.layout.review.state() !== "open",
|
||||
"mt-1": local.layout.review.state() === "open",
|
||||
}}
|
||||
>
|
||||
<For each={session.messages.user()}>
|
||||
@@ -436,7 +437,7 @@ export default function Page() {
|
||||
</For>
|
||||
</ul>
|
||||
</Show>
|
||||
<div ref={messageScrollElement} class="grow w-full min-w-0 h-full overflow-y-auto no-scrollbar">
|
||||
<div ref={messageScrollElement} class="grow size-full min-w-0 overflow-y-auto no-scrollbar">
|
||||
<For each={session.messages.user()}>
|
||||
{(message) => {
|
||||
const isActive = createMemo(() => session.messages.active()?.id === message.id)
|
||||
@@ -476,7 +477,7 @@ export default function Page() {
|
||||
class="flex flex-col items-start self-stretch gap-8 pb-20"
|
||||
>
|
||||
{/* Title */}
|
||||
<div class="flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-10 pb-1">
|
||||
<div class="flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-20 pb-1">
|
||||
<div class="w-full text-14-medium text-text-strong">
|
||||
<Show
|
||||
when={titled()}
|
||||
@@ -523,7 +524,7 @@ export default function Page() {
|
||||
<For each={message.summary?.diffs ?? []}>
|
||||
{(diff) => (
|
||||
<Accordion.Item value={diff.file}>
|
||||
<Accordion.Header>
|
||||
<StickyAccordionHeader class="top-10 data-expanded:before:-top-10 ">
|
||||
<Accordion.Trigger>
|
||||
<div class="flex items-center justify-between w-full gap-5">
|
||||
<div class="grow flex items-center gap-5 min-w-0">
|
||||
@@ -548,8 +549,8 @@ export default function Page() {
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Trigger>
|
||||
</Accordion.Header>
|
||||
<Accordion.Content class="max-h-[360px] overflow-y-auto no-scrollbar">
|
||||
</StickyAccordionHeader>
|
||||
<Accordion.Content class="max-h-60 overflow-y-auto no-scrollbar">
|
||||
<Diff
|
||||
before={{
|
||||
name: diff.file!,
|
||||
@@ -655,7 +656,7 @@ export default function Page() {
|
||||
<Show when={local.layout.review.state() === "open"}>
|
||||
<div
|
||||
classList={{
|
||||
"relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0 border-l border-border-weak-base": true,
|
||||
"relative grow px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0 border-l border-border-weak-base": true,
|
||||
}}
|
||||
>
|
||||
<div class="h-8 w-full flex items-center justify-between shrink-0 self-stretch">
|
||||
@@ -681,7 +682,7 @@ export default function Page() {
|
||||
<For each={session.diffs()}>
|
||||
{(diff) => (
|
||||
<Accordion.Item value={diff.file} defaultOpen>
|
||||
<Accordion.Header>
|
||||
<StickyAccordionHeader>
|
||||
<Accordion.Trigger>
|
||||
<div class="flex items-center justify-between w-full gap-5">
|
||||
<div class="grow flex items-center gap-5 min-w-0">
|
||||
@@ -701,7 +702,7 @@ export default function Page() {
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Trigger>
|
||||
</Accordion.Header>
|
||||
</StickyAccordionHeader>
|
||||
<Accordion.Content>
|
||||
<Diff
|
||||
before={{
|
||||
@@ -724,22 +725,19 @@ export default function Page() {
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Show when={local.layout.review.state() === "tab" && session.diffs().length}>
|
||||
<Tabs.Content value="review" class="select-text">
|
||||
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden mt-8">
|
||||
<div
|
||||
classList={{
|
||||
"relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0": true,
|
||||
"relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0 overflow-hidden": true,
|
||||
}}
|
||||
>
|
||||
<div class="h-8 w-full flex items-center justify-between shrink-0 self-stretch sticky top-0 bg-background-stronger z-100">
|
||||
<div class="flex items-center gap-x-3"></div>
|
||||
</div>
|
||||
<div class="text-14-medium text-text-strong">All changes</div>
|
||||
<div class="h-full pb-40 overflow-y-auto no-scrollbar">
|
||||
<div class="text-14-medium text-text-strong shrink-0">All changes</div>
|
||||
<div class="flex-1 min-h-0 pb-40 overflow-y-auto no-scrollbar">
|
||||
<Accordion class="w-full" multiple>
|
||||
<For each={session.diffs()}>
|
||||
{(diff) => (
|
||||
<Accordion.Item value={diff.file} defaultOpen>
|
||||
<Accordion.Header>
|
||||
<StickyAccordionHeader>
|
||||
<Accordion.Trigger>
|
||||
<div class="flex items-center justify-between w-full gap-5">
|
||||
<div class="grow flex items-center gap-5 min-w-0">
|
||||
@@ -757,7 +755,7 @@ export default function Page() {
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Trigger>
|
||||
</Accordion.Header>
|
||||
</StickyAccordionHeader>
|
||||
<Accordion.Content>
|
||||
<Diff
|
||||
diffStyle="split"
|
||||
@@ -897,3 +895,18 @@ export default function Page() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function StickyAccordionHeader(props: ParentProps<{ class?: string }>) {
|
||||
return (
|
||||
<Accordion.Header
|
||||
classList={{
|
||||
"sticky top-0 data-expanded:z-10": true,
|
||||
"data-expanded:before:content-[''] data-expanded:before:z-[-10]": true,
|
||||
"data-expanded:before:absolute data-expanded:before:inset-0 data-expanded:before:bg-background-stronger": true,
|
||||
[props.class ?? ""]: !!props.class,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</Accordion.Header>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The AI coding agent built for the terminal"
|
||||
version = "1.0.45"
|
||||
version = "1.0.62"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/sst/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.45/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.62/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.45/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.62/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.45/opencode-linux-arm64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.62/opencode-linux-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.45/opencode-linux-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.62/opencode-linux-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.45/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.62/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
Vendored
+9
@@ -22,6 +22,14 @@ declare module "sst" {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
CLOUDFLARE_API_TOKEN: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
Console: {
|
||||
type: "sst.cloudflare.SolidStart"
|
||||
url: string
|
||||
@@ -96,6 +104,7 @@ declare module "sst" {
|
||||
AuthApi: cloudflare.Service
|
||||
AuthStorage: cloudflare.KVNamespace
|
||||
Bucket: cloudflare.R2Bucket
|
||||
GatewayKv: cloudflare.KVNamespace
|
||||
LogProcessor: cloudflare.Service
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.62",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
@@ -54,8 +54,8 @@
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opentui/core": "0.1.39",
|
||||
"@opentui/solid": "0.1.39",
|
||||
"@opentui/core": "0.1.42",
|
||||
"@opentui/solid": "0.1.42",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
|
||||
@@ -17,38 +17,87 @@ import { Script } from "@opencode-ai/script"
|
||||
|
||||
const singleFlag = process.argv.includes("--single")
|
||||
|
||||
const allTargets = [
|
||||
["windows", "x64"],
|
||||
["linux", "arm64"],
|
||||
["linux", "x64"],
|
||||
["linux", "x64-baseline"],
|
||||
["darwin", "x64"],
|
||||
["darwin", "x64-baseline"],
|
||||
["darwin", "arm64"],
|
||||
const allTargets: {
|
||||
os: string
|
||||
arch: "arm64" | "x64"
|
||||
abi?: "musl"
|
||||
avx2?: false
|
||||
}[] = [
|
||||
{
|
||||
os: "linux",
|
||||
arch: "arm64",
|
||||
},
|
||||
{
|
||||
os: "linux",
|
||||
arch: "x64",
|
||||
},
|
||||
{
|
||||
os: "linux",
|
||||
arch: "x64",
|
||||
avx2: false,
|
||||
},
|
||||
{
|
||||
os: "linux",
|
||||
arch: "arm64",
|
||||
abi: "musl",
|
||||
},
|
||||
{
|
||||
os: "linux",
|
||||
arch: "x64",
|
||||
abi: "musl",
|
||||
},
|
||||
{
|
||||
os: "linux",
|
||||
arch: "x64",
|
||||
abi: "musl",
|
||||
avx2: false,
|
||||
},
|
||||
{
|
||||
os: "darwin",
|
||||
arch: "arm64",
|
||||
},
|
||||
{
|
||||
os: "darwin",
|
||||
arch: "x64",
|
||||
},
|
||||
{
|
||||
os: "darwin",
|
||||
arch: "x64",
|
||||
avx2: false,
|
||||
},
|
||||
{
|
||||
os: "windows",
|
||||
arch: "x64",
|
||||
},
|
||||
{
|
||||
os: "windows",
|
||||
arch: "x64",
|
||||
avx2: false,
|
||||
},
|
||||
]
|
||||
|
||||
const targets = singleFlag
|
||||
? allTargets.filter(([os, arch]) => os === process.platform && arch === process.arch)
|
||||
? allTargets.filter((item) => item.os === process.platform && item.arch === process.arch)
|
||||
: allTargets
|
||||
|
||||
await $`rm -rf dist`
|
||||
|
||||
const binaries: Record<string, string> = {}
|
||||
for (const [os, arch] of targets) {
|
||||
console.log(`building ${os}-${arch}`)
|
||||
const name = `${pkg.name}-${os}-${arch}`
|
||||
await $`bun install --os="*" --cpu="*" @opentui/core@${pkg.dependencies["@opentui/core"]}`
|
||||
await $`bun install --os="*" --cpu="*" @parcel/watcher@${pkg.dependencies["@parcel/watcher"]}`
|
||||
for (const item of targets) {
|
||||
const name = [
|
||||
pkg.name,
|
||||
item.os,
|
||||
item.arch,
|
||||
item.avx2 === false ? "baseline" : undefined,
|
||||
item.abi === undefined ? undefined : item.abi,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("-")
|
||||
console.log(`building ${name}`)
|
||||
await $`mkdir -p dist/${name}/bin`
|
||||
|
||||
const opentui = `@opentui/core-${os === "windows" ? "win32" : os}-${arch.replace("-baseline", "")}`
|
||||
await $`mkdir -p ../../node_modules/${opentui}`
|
||||
await $`npm pack ${opentui}@${pkg.dependencies["@opentui/core"]}`.cwd(path.join(dir, "../../node_modules"))
|
||||
await $`tar -xf ../../node_modules/${opentui.replace("@opentui/", "opentui-")}-*.tgz -C ../../node_modules/${opentui} --strip-components=1`
|
||||
|
||||
const watcher = `@parcel/watcher-${os === "windows" ? "win32" : os}-${arch.replace("-baseline", "")}${os === "linux" ? "-glibc" : ""}`
|
||||
await $`mkdir -p ../../node_modules/${watcher}`
|
||||
await $`npm pack ${watcher}`.cwd(path.join(dir, "../../node_modules")).quiet()
|
||||
await $`tar -xf ../../node_modules/${watcher.replace("@parcel/", "parcel-")}-*.tgz -C ../../node_modules/${watcher} --strip-components=1`
|
||||
|
||||
const parserWorker = fs.realpathSync(path.resolve(dir, "./node_modules/@opentui/core/parser.worker.js"))
|
||||
const workerPath = "./src/cli/cmd/tui/worker.ts"
|
||||
|
||||
@@ -58,7 +107,7 @@ for (const [os, arch] of targets) {
|
||||
plugins: [solidPlugin],
|
||||
sourcemap: "external",
|
||||
compile: {
|
||||
target: `bun-${os}-${arch}` as any,
|
||||
target: name.replace(pkg.name, "bun") as any,
|
||||
outfile: `dist/${name}/bin/opencode`,
|
||||
execArgv: [`--user-agent=opencode/${Script.version}`, `--env-file=""`, `--`],
|
||||
windows: {},
|
||||
@@ -78,8 +127,8 @@ for (const [os, arch] of targets) {
|
||||
{
|
||||
name,
|
||||
version: Script.version,
|
||||
os: [os === "windows" ? "win32" : os],
|
||||
cpu: [arch],
|
||||
os: [item.os === "windows" ? "win32" : item.os],
|
||||
cpu: [item.arch],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
|
||||
@@ -16,10 +16,13 @@ export namespace Agent {
|
||||
builtIn: z.boolean(),
|
||||
topP: z.number().optional(),
|
||||
temperature: z.number().optional(),
|
||||
color: z.string().optional(),
|
||||
permission: z.object({
|
||||
edit: Config.Permission,
|
||||
bash: z.record(z.string(), Config.Permission),
|
||||
webfetch: Config.Permission.optional(),
|
||||
doom_loop: Config.Permission.optional(),
|
||||
external_directory: Config.Permission.optional(),
|
||||
}),
|
||||
model: z
|
||||
.object({
|
||||
@@ -45,6 +48,8 @@ export namespace Agent {
|
||||
"*": "allow",
|
||||
},
|
||||
webfetch: "allow",
|
||||
doom_loop: "ask",
|
||||
external_directory: "ask",
|
||||
}
|
||||
const agentPermission = mergeAgentPermissions(defaultPermission, cfg.permission ?? {})
|
||||
|
||||
@@ -143,7 +148,7 @@ export namespace Agent {
|
||||
tools: {},
|
||||
builtIn: false,
|
||||
}
|
||||
const { name, model, prompt, tools, description, temperature, top_p, mode, permission, ...extra } = value
|
||||
const { name, model, prompt, tools, description, temperature, top_p, mode, permission, color, ...extra } = value
|
||||
item.options = {
|
||||
...item.options,
|
||||
...extra,
|
||||
@@ -163,6 +168,7 @@ export namespace Agent {
|
||||
if (temperature != undefined) item.temperature = temperature
|
||||
if (top_p != undefined) item.topP = top_p
|
||||
if (mode) item.mode = mode
|
||||
if (color) item.color = color
|
||||
// just here for consistency & to prevent it from being added as an option
|
||||
if (name) item.name = name
|
||||
|
||||
@@ -244,6 +250,8 @@ function mergeAgentPermissions(basePermission: any, overridePermission: any): Ag
|
||||
edit: merged.edit ?? "allow",
|
||||
webfetch: merged.webfetch ?? "allow",
|
||||
bash: mergedBash ?? { "*": "allow" },
|
||||
doom_loop: merged.doom_loop,
|
||||
external_directory: merged.external_directory,
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -439,9 +439,10 @@ export const GithubRunCommand = cmd({
|
||||
// Local PR
|
||||
if (prData.headRepository.nameWithOwner === prData.baseRepository.nameWithOwner) {
|
||||
await checkoutLocalBranch(prData)
|
||||
const head = (await $`git rev-parse HEAD`).stdout.toString().trim()
|
||||
const dataPrompt = buildPromptDataForPR(prData)
|
||||
const response = await chat(`${userPrompt}\n\n${dataPrompt}`, promptFiles)
|
||||
if (await branchIsDirty()) {
|
||||
if (await branchIsDirty(head)) {
|
||||
const summary = await summarize(response)
|
||||
await pushToLocalBranch(summary)
|
||||
}
|
||||
@@ -451,9 +452,10 @@ export const GithubRunCommand = cmd({
|
||||
// Fork PR
|
||||
else {
|
||||
await checkoutForkBranch(prData)
|
||||
const head = (await $`git rev-parse HEAD`).stdout.toString().trim()
|
||||
const dataPrompt = buildPromptDataForPR(prData)
|
||||
const response = await chat(`${userPrompt}\n\n${dataPrompt}`, promptFiles)
|
||||
if (await branchIsDirty()) {
|
||||
if (await branchIsDirty(head)) {
|
||||
const summary = await summarize(response)
|
||||
await pushToForkBranch(summary, prData)
|
||||
}
|
||||
@@ -464,10 +466,11 @@ export const GithubRunCommand = cmd({
|
||||
// Issue
|
||||
else {
|
||||
const branch = await checkoutNewBranch()
|
||||
const head = (await $`git rev-parse HEAD`).stdout.toString().trim()
|
||||
const issueData = await fetchIssue()
|
||||
const dataPrompt = buildPromptDataForIssue(issueData)
|
||||
const response = await chat(`${userPrompt}\n\n${dataPrompt}`, promptFiles)
|
||||
if (await branchIsDirty()) {
|
||||
if (await branchIsDirty(head)) {
|
||||
const summary = await summarize(response)
|
||||
await pushToNewBranch(summary, branch)
|
||||
const pr = await createPR(
|
||||
@@ -832,10 +835,13 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
await $`git push fork HEAD:${remoteBranch}`
|
||||
}
|
||||
|
||||
async function branchIsDirty() {
|
||||
async function branchIsDirty(originalHead: string) {
|
||||
console.log("Checking if branch is dirty...")
|
||||
const ret = await $`git status --porcelain`
|
||||
return ret.stdout.toString().trim().length > 0
|
||||
const status = ret.stdout.toString().trim()
|
||||
if (status.length > 0) return true
|
||||
const head = await $`git rev-parse HEAD`
|
||||
return head.stdout.toString().trim() !== originalHead
|
||||
}
|
||||
|
||||
async function assertPermissions() {
|
||||
|
||||
@@ -1,21 +1,57 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { Provider } from "../../provider/provider"
|
||||
import { cmd } from "./cmd"
|
||||
import { UI } from "../ui"
|
||||
import { EOL } from "os"
|
||||
|
||||
export const ModelsCommand = cmd({
|
||||
command: "models",
|
||||
command: "models [provider]",
|
||||
describe: "list all available models",
|
||||
handler: async () => {
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs
|
||||
.positional("provider", {
|
||||
describe: "provider ID to filter models by",
|
||||
type: "string",
|
||||
array: false,
|
||||
})
|
||||
.option("verbose", {
|
||||
describe: "use more verbose model output (includes metadata like costs)",
|
||||
type: "boolean",
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
await Instance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
const providers = await Provider.list()
|
||||
|
||||
for (const [providerID, provider] of Object.entries(providers)) {
|
||||
for (const modelID of Object.keys(provider.info.models)) {
|
||||
console.log(`${providerID}/${modelID}`)
|
||||
function printModels(providerID: string, verbose?: boolean) {
|
||||
const provider = providers[providerID]
|
||||
for (const [modelID, model] of Object.entries(provider.info.models)) {
|
||||
process.stdout.write(`${providerID}/${modelID}`)
|
||||
process.stdout.write(EOL)
|
||||
if (verbose) {
|
||||
process.stdout.write(JSON.stringify(model, null, 2))
|
||||
process.stdout.write(EOL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (args.provider) {
|
||||
const provider = providers[args.provider]
|
||||
if (!provider) {
|
||||
UI.error(`Provider not found: ${args.provider}`)
|
||||
return
|
||||
}
|
||||
|
||||
printModels(args.provider, args.verbose)
|
||||
return
|
||||
}
|
||||
|
||||
for (const providerID of Object.keys(providers)) {
|
||||
printModels(providerID, args.verbose)
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { UI } from "../ui"
|
||||
import { cmd } from "./cmd"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { $ } from "bun"
|
||||
|
||||
export const PrCommand = cmd({
|
||||
command: "pr <number>",
|
||||
describe: "fetch and checkout a GitHub PR branch, then run opencode",
|
||||
builder: (yargs) =>
|
||||
yargs.positional("number", {
|
||||
type: "number",
|
||||
describe: "PR number to checkout",
|
||||
demandOption: true,
|
||||
}),
|
||||
async handler(args) {
|
||||
await Instance.provide({
|
||||
directory: process.cwd(),
|
||||
async fn() {
|
||||
const project = Instance.project
|
||||
if (project.vcs !== "git") {
|
||||
UI.error("Could not find git repository. Please run this command from a git repository.")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const prNumber = args.number
|
||||
const localBranchName = `pr/${prNumber}`
|
||||
UI.println(`Fetching and checking out PR #${prNumber}...`)
|
||||
|
||||
// Use gh pr checkout with custom branch name
|
||||
const result = await $`gh pr checkout ${prNumber} --branch ${localBranchName} --force`.nothrow()
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
UI.error(`Failed to checkout PR #${prNumber}. Make sure you have gh CLI installed and authenticated.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Fetch PR info for fork handling and session link detection
|
||||
const prInfoResult =
|
||||
await $`gh pr view ${prNumber} --json headRepository,headRepositoryOwner,isCrossRepository,headRefName,body`.nothrow()
|
||||
|
||||
let sessionId: string | undefined
|
||||
|
||||
if (prInfoResult.exitCode === 0) {
|
||||
const prInfoText = prInfoResult.text()
|
||||
if (prInfoText.trim()) {
|
||||
const prInfo = JSON.parse(prInfoText)
|
||||
|
||||
// Handle fork PRs
|
||||
if (prInfo && prInfo.isCrossRepository && prInfo.headRepository && prInfo.headRepositoryOwner) {
|
||||
const forkOwner = prInfo.headRepositoryOwner.login
|
||||
const forkName = prInfo.headRepository.name
|
||||
const remoteName = forkOwner
|
||||
|
||||
// Check if remote already exists
|
||||
const remotes = (await $`git remote`.nothrow().text()).trim()
|
||||
if (!remotes.split("\n").includes(remoteName)) {
|
||||
await $`git remote add ${remoteName} https://github.com/${forkOwner}/${forkName}.git`.nothrow()
|
||||
UI.println(`Added fork remote: ${remoteName}`)
|
||||
}
|
||||
|
||||
// Set upstream to the fork so pushes go there
|
||||
const headRefName = prInfo.headRefName
|
||||
await $`git branch --set-upstream-to=${remoteName}/${headRefName} ${localBranchName}`.nothrow()
|
||||
}
|
||||
|
||||
// Check for opencode session link in PR body
|
||||
if (prInfo && prInfo.body) {
|
||||
const sessionMatch = prInfo.body.match(/https:\/\/opencode\.ai\/s\/([a-zA-Z0-9_-]+)/)
|
||||
if (sessionMatch) {
|
||||
const sessionUrl = sessionMatch[0]
|
||||
UI.println(`Found opencode session: ${sessionUrl}`)
|
||||
UI.println(`Importing session...`)
|
||||
|
||||
const importResult = await $`opencode import ${sessionUrl}`.nothrow()
|
||||
if (importResult.exitCode === 0) {
|
||||
const importOutput = importResult.text().trim()
|
||||
// Extract session ID from the output (format: "Imported session: <session-id>")
|
||||
const sessionIdMatch = importOutput.match(/Imported session: ([a-zA-Z0-9_-]+)/)
|
||||
if (sessionIdMatch) {
|
||||
sessionId = sessionIdMatch[1]
|
||||
UI.println(`Session imported: ${sessionId}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UI.println(`Successfully checked out PR #${prNumber} as branch '${localBranchName}'`)
|
||||
UI.println()
|
||||
UI.println("Starting opencode...")
|
||||
UI.println()
|
||||
|
||||
// Launch opencode TUI with session ID if available
|
||||
const { spawn } = await import("child_process")
|
||||
const opencodeArgs = sessionId ? ["-s", sessionId] : []
|
||||
const opencodeProcess = spawn("opencode", opencodeArgs, {
|
||||
stdio: "inherit",
|
||||
cwd: process.cwd(),
|
||||
})
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
opencodeProcess.on("exit", (code) => {
|
||||
if (code === 0) resolve()
|
||||
else reject(new Error(`opencode exited with code ${code}`))
|
||||
})
|
||||
opencodeProcess.on("error", reject)
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -1,13 +1,13 @@
|
||||
import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
|
||||
import { Clipboard } from "@tui/util/clipboard"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { RouteProvider, useRoute, type Route } from "@tui/context/route"
|
||||
import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal } from "solid-js"
|
||||
import { RouteProvider, useRoute } from "@tui/context/route"
|
||||
import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch } from "solid-js"
|
||||
import { Installation } from "@/installation"
|
||||
import { Global } from "@/global"
|
||||
import { DialogProvider, useDialog } from "@tui/ui/dialog"
|
||||
import { SDKProvider, useSDK } from "@tui/context/sdk"
|
||||
import { SyncProvider } from "@tui/context/sync"
|
||||
import { SyncProvider, useSync } from "@tui/context/sync"
|
||||
import { LocalProvider, useLocal } from "@tui/context/local"
|
||||
import { DialogModel } from "@tui/component/dialog-model"
|
||||
import { DialogStatus } from "@tui/component/dialog-status"
|
||||
@@ -27,6 +27,8 @@ import { ExitProvider, useExit } from "./context/exit"
|
||||
import { Session as SessionApi } from "@/session"
|
||||
import { TuiEvent } from "./event"
|
||||
import { KVProvider, useKV } from "./context/kv"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { ArgsProvider, useArgs, type Args } from "./context/args"
|
||||
|
||||
async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
|
||||
// can't set raw mode if not a TTY
|
||||
@@ -88,25 +90,10 @@ async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
|
||||
})
|
||||
}
|
||||
|
||||
export function tui(input: {
|
||||
url: string
|
||||
sessionID?: string
|
||||
model?: string
|
||||
agent?: string
|
||||
prompt?: string
|
||||
onExit?: () => Promise<void>
|
||||
}) {
|
||||
export function tui(input: { url: string; args: Args; onExit?: () => Promise<void> }) {
|
||||
// promise to prevent immediate exit
|
||||
return new Promise<void>(async (resolve) => {
|
||||
const mode = await getTerminalBackgroundColor()
|
||||
|
||||
const routeData: Route | undefined = input.sessionID
|
||||
? {
|
||||
type: "session",
|
||||
sessionID: input.sessionID,
|
||||
}
|
||||
: undefined
|
||||
|
||||
const onExit = async () => {
|
||||
await input.onExit?.()
|
||||
resolve()
|
||||
@@ -116,35 +103,33 @@ export function tui(input: {
|
||||
() => {
|
||||
return (
|
||||
<ErrorBoundary fallback={(error, reset) => <ErrorComponent error={error} reset={reset} onExit={onExit} />}>
|
||||
<ExitProvider onExit={onExit}>
|
||||
<KVProvider>
|
||||
<ToastProvider>
|
||||
<RouteProvider data={routeData}>
|
||||
<SDKProvider url={input.url}>
|
||||
<SyncProvider>
|
||||
<ThemeProvider mode={mode}>
|
||||
<LocalProvider
|
||||
initialModel={input.model}
|
||||
initialAgent={input.agent}
|
||||
initialPrompt={input.prompt}
|
||||
>
|
||||
<KeybindProvider>
|
||||
<DialogProvider>
|
||||
<CommandProvider>
|
||||
<PromptHistoryProvider>
|
||||
<App />
|
||||
</PromptHistoryProvider>
|
||||
</CommandProvider>
|
||||
</DialogProvider>
|
||||
</KeybindProvider>
|
||||
</LocalProvider>
|
||||
</ThemeProvider>
|
||||
</SyncProvider>
|
||||
</SDKProvider>
|
||||
</RouteProvider>
|
||||
</ToastProvider>
|
||||
</KVProvider>
|
||||
</ExitProvider>
|
||||
<ArgsProvider {...input.args}>
|
||||
<ExitProvider onExit={onExit}>
|
||||
<KVProvider>
|
||||
<ToastProvider>
|
||||
<RouteProvider>
|
||||
<SDKProvider url={input.url}>
|
||||
<SyncProvider>
|
||||
<ThemeProvider mode={mode}>
|
||||
<LocalProvider>
|
||||
<KeybindProvider>
|
||||
<DialogProvider>
|
||||
<CommandProvider>
|
||||
<PromptHistoryProvider>
|
||||
<App />
|
||||
</PromptHistoryProvider>
|
||||
</CommandProvider>
|
||||
</DialogProvider>
|
||||
</KeybindProvider>
|
||||
</LocalProvider>
|
||||
</ThemeProvider>
|
||||
</SyncProvider>
|
||||
</SDKProvider>
|
||||
</RouteProvider>
|
||||
</ToastProvider>
|
||||
</KVProvider>
|
||||
</ExitProvider>
|
||||
</ArgsProvider>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
},
|
||||
@@ -170,12 +155,49 @@ function App() {
|
||||
const { event } = useSDK()
|
||||
const toast = useToast()
|
||||
const { theme, mode, setMode } = useTheme()
|
||||
const sync = useSync()
|
||||
const exit = useExit()
|
||||
|
||||
createEffect(() => {
|
||||
console.log(JSON.stringify(route.data))
|
||||
})
|
||||
|
||||
const args = useArgs()
|
||||
onMount(() => {
|
||||
batch(() => {
|
||||
if (args.agent) local.agent.set(args.agent)
|
||||
if (args.model) {
|
||||
const { providerID, modelID } = Provider.parseModel(args.model)
|
||||
if (!providerID || !modelID)
|
||||
return toast.show({
|
||||
variant: "warning",
|
||||
message: `Invalid model format: ${args.model}`,
|
||||
duration: 3000,
|
||||
})
|
||||
local.model.set({ providerID, modelID }, { recent: true })
|
||||
}
|
||||
if (args.sessionID) {
|
||||
route.navigate({
|
||||
type: "session",
|
||||
sessionID: args.sessionID,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (sync.status !== "complete") return
|
||||
if (args.continue) {
|
||||
const match = sync.data.session.at(0)?.id
|
||||
if (match) {
|
||||
route.navigate({
|
||||
type: "session",
|
||||
sessionID: match,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
command.register(() => [
|
||||
{
|
||||
title: "Switch session",
|
||||
@@ -371,6 +393,15 @@ function App() {
|
||||
})
|
||||
})
|
||||
|
||||
event.on(Installation.Event.Updated.type, (evt) => {
|
||||
toast.show({
|
||||
variant: "success",
|
||||
title: "Update Complete",
|
||||
message: `OpenCode updated to v${evt.properties.version}`,
|
||||
duration: 5000,
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<box
|
||||
width={dimensions().width}
|
||||
@@ -457,6 +488,8 @@ function ErrorComponent(props: { error: Error; reset: () => void; onExit: () =>
|
||||
)
|
||||
}
|
||||
|
||||
issueURL.searchParams.set("opencode-version", Installation.VERSION)
|
||||
|
||||
const copyIssueURL = () => {
|
||||
Clipboard.copy(issueURL.toString()).then(() => {
|
||||
setCopied(true)
|
||||
|
||||
@@ -17,6 +17,9 @@ export const AttachCommand = cmd({
|
||||
}),
|
||||
handler: async (args) => {
|
||||
if (args.dir) process.chdir(args.dir)
|
||||
await tui(args)
|
||||
await tui({
|
||||
url: args.url,
|
||||
args: {},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4,12 +4,19 @@ import { useSync } from "@tui/context/sync"
|
||||
import { map, pipe, flatMap, entries, filter, isDeepEqual, sortBy } from "remeda"
|
||||
import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
import { useTheme } from "../context/theme"
|
||||
|
||||
function Free() {
|
||||
const { theme } = useTheme()
|
||||
return <span style={{ fg: theme.secondary }}>Free</span>
|
||||
}
|
||||
|
||||
export function DialogModel() {
|
||||
const local = useLocal()
|
||||
const sync = useSync()
|
||||
const dialog = useDialog()
|
||||
const [ref, setRef] = createSignal<DialogSelectRef<unknown>>()
|
||||
const { theme } = useTheme()
|
||||
|
||||
const options = createMemo(() => {
|
||||
return [
|
||||
@@ -29,6 +36,7 @@ export function DialogModel() {
|
||||
title: model.name ?? item.modelID,
|
||||
description: provider.name,
|
||||
category: "Recent",
|
||||
footer: model.cost?.input === 0 && provider.id === "opencode" ? <Free /> : undefined,
|
||||
},
|
||||
]
|
||||
})
|
||||
@@ -51,6 +59,7 @@ export function DialogModel() {
|
||||
title: info.name ?? model,
|
||||
description: provider.name,
|
||||
category: provider.name,
|
||||
footer: info.cost?.input === 0 && provider.id === "opencode" ? <Free /> : undefined,
|
||||
})),
|
||||
filter((x) => Boolean(ref()?.filter) || !local.model.recent().find((y) => isDeepEqual(y, x.value))),
|
||||
),
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useSync } from "@tui/context/sync"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { SplitBorder } from "@tui/component/border"
|
||||
import { useCommandDialog } from "@tui/component/dialog-command"
|
||||
import { Locale } from "@/util/locale"
|
||||
import type { PromptInfo } from "./history"
|
||||
|
||||
export type AutocompleteRef = {
|
||||
@@ -125,10 +126,11 @@ export function Autocomplete(props: {
|
||||
|
||||
// Add file options
|
||||
if (!result.error && result.data) {
|
||||
const width = store.position.width - 4
|
||||
options.push(
|
||||
...result.data.map(
|
||||
(item): AutocompleteOption => ({
|
||||
display: item,
|
||||
display: Locale.truncateMiddle(item, width),
|
||||
onSelect: () => {
|
||||
insertPart(item, {
|
||||
type: "file",
|
||||
@@ -217,12 +219,6 @@ export function Autocomplete(props: {
|
||||
description: "compact the session",
|
||||
onSelect: () => command.trigger("session.compact"),
|
||||
},
|
||||
{
|
||||
display: "/share",
|
||||
disabled: !!s.share?.url,
|
||||
description: "share a session",
|
||||
onSelect: () => command.trigger("session.share"),
|
||||
},
|
||||
{
|
||||
display: "/unshare",
|
||||
disabled: !s.share,
|
||||
@@ -250,7 +246,16 @@ export function Autocomplete(props: {
|
||||
onSelect: () => command.trigger("session.timeline"),
|
||||
},
|
||||
)
|
||||
if (sync.data.config.share !== "disabled") {
|
||||
results.push({
|
||||
display: "/share",
|
||||
disabled: !!s.share?.url,
|
||||
description: "share a session",
|
||||
onSelect: () => command.trigger("session.share"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
results.push(
|
||||
{
|
||||
display: "/new",
|
||||
|
||||
@@ -101,16 +101,81 @@ export function Prompt(props: PromptProps) {
|
||||
value: "prompt.editor",
|
||||
onSelect: async (dialog, trigger) => {
|
||||
dialog.clear()
|
||||
const value = trigger === "prompt" ? "" : input.plainText
|
||||
|
||||
// replace summarized text parts with the actual text
|
||||
const text = store.prompt.parts
|
||||
.filter((p) => p.type === "text")
|
||||
.reduce((acc, p) => {
|
||||
if (!p.source) return acc
|
||||
return acc.replace(p.source.text.value, p.text)
|
||||
}, store.prompt.input)
|
||||
|
||||
const nonTextParts = store.prompt.parts.filter((p) => p.type !== "text")
|
||||
|
||||
const value = trigger === "prompt" ? "" : text
|
||||
const content = await Editor.open({ value, renderer })
|
||||
if (content) {
|
||||
input.setText(content, { history: false })
|
||||
setStore("prompt", {
|
||||
input: content,
|
||||
parts: [],
|
||||
if (!content) return
|
||||
|
||||
input.setText(content, { history: false })
|
||||
|
||||
// Update positions for nonTextParts based on their location in new content
|
||||
// Filter out parts whose virtual text was deleted
|
||||
// this handles a case where the user edits the text in the editor
|
||||
// such that the virtual text moves around or is deleted
|
||||
const updatedNonTextParts = nonTextParts
|
||||
.map((part) => {
|
||||
let virtualText = ""
|
||||
if (part.type === "file" && part.source?.text) {
|
||||
virtualText = part.source.text.value
|
||||
} else if (part.type === "agent" && part.source) {
|
||||
virtualText = part.source.value
|
||||
}
|
||||
|
||||
if (!virtualText) return part
|
||||
|
||||
const newStart = content.indexOf(virtualText)
|
||||
// if the virtual text is deleted, remove the part
|
||||
if (newStart === -1) return null
|
||||
|
||||
const newEnd = newStart + virtualText.length
|
||||
|
||||
if (part.type === "file" && part.source?.text) {
|
||||
return {
|
||||
...part,
|
||||
source: {
|
||||
...part.source,
|
||||
text: {
|
||||
...part.source.text,
|
||||
start: newStart,
|
||||
end: newEnd,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (part.type === "agent" && part.source) {
|
||||
return {
|
||||
...part,
|
||||
source: {
|
||||
...part.source,
|
||||
start: newStart,
|
||||
end: newEnd,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return part
|
||||
})
|
||||
input.cursorOffset = Bun.stringWidth(content)
|
||||
}
|
||||
.filter((part) => part !== null)
|
||||
|
||||
setStore("prompt", {
|
||||
input: content,
|
||||
// keep only the non-text parts because the text parts were
|
||||
// already expanded inline
|
||||
parts: updatedNonTextParts,
|
||||
})
|
||||
restoreExtmarksFromParts(updatedNonTextParts)
|
||||
input.cursorOffset = Bun.stringWidth(content)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -163,11 +228,21 @@ export function Prompt(props: PromptProps) {
|
||||
if (!props.sessionID) return
|
||||
if (autocomplete.visible) return
|
||||
if (!input.focused) return
|
||||
sdk.client.session.abort({
|
||||
path: {
|
||||
id: props.sessionID,
|
||||
},
|
||||
})
|
||||
|
||||
setStore("interrupt", store.interrupt + 1)
|
||||
|
||||
setTimeout(() => {
|
||||
setStore("interrupt", 0)
|
||||
}, 5000)
|
||||
|
||||
if (store.interrupt >= 2) {
|
||||
sdk.client.session.abort({
|
||||
path: {
|
||||
id: props.sessionID,
|
||||
},
|
||||
})
|
||||
setStore("interrupt", 0)
|
||||
}
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
@@ -187,6 +262,7 @@ export function Prompt(props: PromptProps) {
|
||||
prompt: PromptInfo
|
||||
mode: "normal" | "shell"
|
||||
extmarkToPartIndex: Map<number, number>
|
||||
interrupt: number
|
||||
}>({
|
||||
prompt: {
|
||||
input: "",
|
||||
@@ -194,22 +270,13 @@ export function Prompt(props: PromptProps) {
|
||||
},
|
||||
mode: "normal",
|
||||
extmarkToPartIndex: new Map(),
|
||||
interrupt: 0,
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
input.focus()
|
||||
})
|
||||
|
||||
local.setInitialPrompt.listen((initialPrompt) => {
|
||||
batch(() => {
|
||||
setStore("prompt", {
|
||||
input: initialPrompt,
|
||||
parts: [],
|
||||
})
|
||||
input.insertText(initialPrompt)
|
||||
})
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
promptPartTypeId = input.extmarks.registerType("prompt-part")
|
||||
})
|
||||
@@ -691,8 +758,11 @@ export function Prompt(props: PromptProps) {
|
||||
</Match>
|
||||
<Match when={status() === "working"}>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={theme.text}>
|
||||
esc <span style={{ fg: theme.textMuted }}>interrupt</span>
|
||||
<text fg={store.interrupt > 0 ? theme.primary : theme.text}>
|
||||
esc{" "}
|
||||
<span style={{ fg: store.interrupt > 0 ? theme.primary : theme.textMuted }}>
|
||||
{store.interrupt > 0 ? "again to interrupt" : "interrupt"}
|
||||
</span>
|
||||
</text>
|
||||
</box>
|
||||
</Match>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { createSimpleContext } from "./helper"
|
||||
|
||||
export interface Args {
|
||||
model?: string
|
||||
agent?: string
|
||||
prompt?: string
|
||||
continue?: boolean
|
||||
sessionID?: string
|
||||
}
|
||||
|
||||
export const { use: useArgs, provider: ArgsProvider } = createSimpleContext({
|
||||
name: "Args",
|
||||
init: (props: Args) => props,
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createStore } from "solid-js/store"
|
||||
import { batch, createEffect, createMemo, createSignal, onMount } from "solid-js"
|
||||
import { batch, createEffect, createMemo } from "solid-js"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { uniqueBy } from "remeda"
|
||||
@@ -8,12 +8,12 @@ import { Global } from "@/global"
|
||||
import { iife } from "@/util/iife"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { useToast } from "../ui/toast"
|
||||
import { createEventBus } from "@solid-primitives/event-bus"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { useArgs } from "./args"
|
||||
|
||||
export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
name: "Local",
|
||||
init: (props: { initialModel?: string; initialAgent?: string; initialPrompt?: string }) => {
|
||||
init: () => {
|
||||
const sync = useSync()
|
||||
const toast = useToast()
|
||||
|
||||
@@ -30,25 +30,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
}
|
||||
}
|
||||
|
||||
// Set initial model if provided
|
||||
onMount(() => {
|
||||
batch(() => {
|
||||
if (props.initialAgent) {
|
||||
agent.set(props.initialAgent)
|
||||
}
|
||||
if (props.initialModel) {
|
||||
const { providerID, modelID } = Provider.parseModel(props.initialModel)
|
||||
if (!providerID || !modelID)
|
||||
return toast.show({
|
||||
variant: "warning",
|
||||
message: `Invalid model format: ${props.initialModel}`,
|
||||
duration: 3000,
|
||||
})
|
||||
model.set({ providerID, modelID }, { recent: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Automatically update model when agent changes
|
||||
createEffect(() => {
|
||||
const value = agent.current()
|
||||
@@ -109,6 +90,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
})
|
||||
},
|
||||
color(name: string) {
|
||||
const agent = agents().find((x) => x.name === name)
|
||||
if (agent?.color) return agent.color
|
||||
const index = agents().findIndex((x) => x.name === name)
|
||||
return colors()[index % colors().length]
|
||||
},
|
||||
@@ -147,9 +130,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
setModelStore("ready", true)
|
||||
})
|
||||
|
||||
const args = useArgs()
|
||||
const fallbackModel = createMemo(() => {
|
||||
if (props.initialModel) {
|
||||
const { providerID, modelID } = Provider.parseModel(props.initialModel)
|
||||
if (args.model) {
|
||||
const { providerID, modelID } = Provider.parseModel(args.model)
|
||||
if (isModelValid({ providerID, modelID })) {
|
||||
return {
|
||||
providerID,
|
||||
@@ -247,18 +231,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
}
|
||||
})
|
||||
|
||||
const setInitialPrompt = createEventBus<string>()
|
||||
|
||||
onMount(() => {
|
||||
if (props.initialPrompt) setInitialPrompt.emit(props.initialPrompt)
|
||||
})
|
||||
|
||||
const result = {
|
||||
model,
|
||||
agent,
|
||||
get setInitialPrompt() {
|
||||
return setInitialPrompt
|
||||
},
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
@@ -14,14 +14,13 @@ export type Route = HomeRoute | SessionRoute
|
||||
|
||||
export const { use: useRoute, provider: RouteProvider } = createSimpleContext({
|
||||
name: "Route",
|
||||
init: (props: { data?: Route }) => {
|
||||
init: () => {
|
||||
const [store, setStore] = createStore<Route>(
|
||||
props.data ??
|
||||
(process.env["OPENCODE_ROUTE"]
|
||||
? JSON.parse(process.env["OPENCODE_ROUTE"])
|
||||
: {
|
||||
type: "home",
|
||||
}),
|
||||
process.env["OPENCODE_ROUTE"]
|
||||
? JSON.parse(process.env["OPENCODE_ROUTE"])
|
||||
: {
|
||||
type: "home",
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@@ -22,7 +22,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
name: "Sync",
|
||||
init: () => {
|
||||
const [store, setStore] = createStore<{
|
||||
ready: boolean
|
||||
status: "loading" | "partial" | "complete"
|
||||
provider: Provider[]
|
||||
agent: Agent[]
|
||||
command: Command[]
|
||||
@@ -50,7 +50,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
formatter: FormatterStatus[]
|
||||
}>({
|
||||
config: {},
|
||||
ready: false,
|
||||
status: "loading",
|
||||
agent: [],
|
||||
permission: {},
|
||||
command: [],
|
||||
@@ -217,30 +217,36 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
|
||||
// blocking
|
||||
Promise.all([
|
||||
sdk.client.config.providers().then((x) => setStore("provider", x.data!.providers)),
|
||||
sdk.client.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
||||
sdk.client.config.get().then((x) => setStore("config", x.data!)),
|
||||
]).then(() => setStore("ready", true))
|
||||
|
||||
// non-blocking
|
||||
Promise.all([
|
||||
sdk.client.session.list().then((x) =>
|
||||
setStore(
|
||||
"session",
|
||||
(x.data ?? []).toSorted((a, b) => a.id.localeCompare(b.id)),
|
||||
sdk.client.config.providers({ throwOnError: true }).then((x) => setStore("provider", x.data!.providers)),
|
||||
sdk.client.app.agents({ throwOnError: true }).then((x) => setStore("agent", x.data ?? [])),
|
||||
sdk.client.config.get({ throwOnError: true }).then((x) => setStore("config", x.data!)),
|
||||
]).then(() => {
|
||||
setStore("status", "partial")
|
||||
// non-blocking
|
||||
Promise.all([
|
||||
sdk.client.session.list().then((x) =>
|
||||
setStore(
|
||||
"session",
|
||||
(x.data ?? []).toSorted((a, b) => a.id.localeCompare(b.id)),
|
||||
),
|
||||
),
|
||||
),
|
||||
sdk.client.command.list().then((x) => setStore("command", x.data ?? [])),
|
||||
sdk.client.lsp.status().then((x) => setStore("lsp", x.data!)),
|
||||
sdk.client.mcp.status().then((x) => setStore("mcp", x.data!)),
|
||||
sdk.client.formatter.status().then((x) => setStore("formatter", x.data!)),
|
||||
])
|
||||
sdk.client.command.list().then((x) => setStore("command", x.data ?? [])),
|
||||
sdk.client.lsp.status().then((x) => setStore("lsp", x.data!)),
|
||||
sdk.client.mcp.status().then((x) => setStore("mcp", x.data!)),
|
||||
sdk.client.formatter.status().then((x) => setStore("formatter", x.data!)),
|
||||
]).then(() => {
|
||||
setStore("status", "complete")
|
||||
})
|
||||
})
|
||||
|
||||
const result = {
|
||||
data: store,
|
||||
set: setStore,
|
||||
get status() {
|
||||
return store.status
|
||||
},
|
||||
get ready() {
|
||||
return store.ready
|
||||
return store.status !== "loading"
|
||||
},
|
||||
session: {
|
||||
get(sessionID: string) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { SyntaxStyle, RGBA, type TerminalColors } from "@opentui/core"
|
||||
import { createMemo } from "solid-js"
|
||||
import path from "path"
|
||||
import { createEffect, createMemo, onMount } from "solid-js"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import aura from "./theme/aura.json" with { type: "json" }
|
||||
@@ -27,7 +28,9 @@ import vesper from "./theme/vesper.json" with { type: "json" }
|
||||
import zenburn from "./theme/zenburn.json" with { type: "json" }
|
||||
import { useKV } from "./kv"
|
||||
import { useRenderer } from "@opentui/solid"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import { Global } from "@/global"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
|
||||
type Theme = {
|
||||
primary: RGBA
|
||||
@@ -144,6 +147,17 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
||||
themes: DEFAULT_THEMES,
|
||||
mode: props.mode,
|
||||
active: (sync.data.config.theme ?? kv.get("theme", "opencode")) as string,
|
||||
ready: false,
|
||||
})
|
||||
|
||||
createEffect(async () => {
|
||||
const custom = await getCustomThemes()
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
Object.assign(draft.themes, custom)
|
||||
draft.ready = true
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
const renderer = useRenderer()
|
||||
@@ -187,16 +201,43 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
||||
kv.set("theme", theme)
|
||||
},
|
||||
get ready() {
|
||||
return sync.ready
|
||||
return store.ready
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const CUSTOM_THEME_GLOB = new Bun.Glob("themes/*.json")
|
||||
async function getCustomThemes() {
|
||||
const directories = [
|
||||
Global.Path.config,
|
||||
...(await Array.fromAsync(
|
||||
Filesystem.up({
|
||||
targets: [".opencode"],
|
||||
start: process.cwd(),
|
||||
}),
|
||||
)),
|
||||
]
|
||||
|
||||
const result: Record<string, ThemeJson> = {}
|
||||
for (const dir of directories) {
|
||||
for await (const item of CUSTOM_THEME_GLOB.scan({
|
||||
absolute: true,
|
||||
followSymlinks: true,
|
||||
dot: true,
|
||||
cwd: dir,
|
||||
})) {
|
||||
const name = path.basename(item, ".json")
|
||||
result[name] = await Bun.file(item).json()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function generateSystem(colors: TerminalColors, mode: "dark" | "light"): ThemeJson {
|
||||
const bg = RGBA.fromHex(colors.defaultBackground ?? colors.palette[0]!)
|
||||
const fg = RGBA.fromHex(colors.defaultForeground ?? colors.palette[7]!)
|
||||
const palette = colors.palette.map((x) => RGBA.fromHex(x!))
|
||||
const palette = colors.palette.filter((x) => x !== null).map((x) => RGBA.fromHex(x))
|
||||
const isDark = mode == "dark"
|
||||
|
||||
// Generate gray scale based on terminal background
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Prompt } from "@tui/component/prompt"
|
||||
import { createMemo, Match, Show, Switch, type ParentProps } from "solid-js"
|
||||
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
||||
import { createMemo, Match, onMount, Show, Switch, type ParentProps } from "solid-js"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { useKeybind } from "../context/keybind"
|
||||
import type { KeybindsConfig } from "@opencode-ai/sdk"
|
||||
@@ -7,6 +7,10 @@ import { Logo } from "../component/logo"
|
||||
import { Locale } from "@/util/locale"
|
||||
import { useSync } from "../context/sync"
|
||||
import { Toast } from "../ui/toast"
|
||||
import { useArgs } from "../context/args"
|
||||
|
||||
// TODO: what is the best way to do this?
|
||||
let once = false
|
||||
|
||||
export function Home() {
|
||||
const sync = useSync()
|
||||
@@ -34,6 +38,16 @@ export function Home() {
|
||||
</Show>
|
||||
)
|
||||
|
||||
let prompt: PromptRef
|
||||
const args = useArgs()
|
||||
onMount(() => {
|
||||
if (once) return
|
||||
if (args.prompt) {
|
||||
prompt.set({ input: args.prompt, parts: [] })
|
||||
once = true
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<box flexGrow={1} justifyContent="center" alignItems="center" paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<Logo />
|
||||
@@ -44,7 +58,7 @@ export function Home() {
|
||||
<HelpRow keybind="agent_cycle">Switch agent</HelpRow>
|
||||
</box>
|
||||
<box width="100%" maxWidth={75} zIndex={1000} paddingTop={1}>
|
||||
<Prompt hint={Hint} />
|
||||
<Prompt ref={(r) => (prompt = r)} hint={Hint} />
|
||||
</box>
|
||||
<Toast />
|
||||
</box>
|
||||
|
||||
@@ -3,8 +3,13 @@ import { useSync } from "@tui/context/sync"
|
||||
import { DialogSelect } from "@tui/ui/dialog-select"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useRoute } from "@tui/context/route"
|
||||
import type { PromptInfo } from "@tui/component/prompt/history"
|
||||
|
||||
export function DialogMessage(props: { messageID: string; sessionID: string }) {
|
||||
export function DialogMessage(props: {
|
||||
messageID: string
|
||||
sessionID: string
|
||||
setPrompt?: (prompt: PromptInfo) => void
|
||||
}) {
|
||||
const sync = useSync()
|
||||
const sdk = useSDK()
|
||||
const message = createMemo(() => sync.data.message[props.sessionID]?.find((x) => x.id === props.messageID))
|
||||
@@ -19,14 +24,33 @@ export function DialogMessage(props: { messageID: string; sessionID: string }) {
|
||||
value: "session.revert",
|
||||
description: "undo messages and file changes",
|
||||
onSelect: (dialog) => {
|
||||
const msg = message()
|
||||
if (!msg) return
|
||||
|
||||
sdk.client.session.revert({
|
||||
path: {
|
||||
id: props.sessionID,
|
||||
},
|
||||
body: {
|
||||
messageID: message()!.id,
|
||||
messageID: msg.id,
|
||||
},
|
||||
})
|
||||
|
||||
if (props.setPrompt) {
|
||||
const parts = sync.data.part[msg.id]
|
||||
const promptInfo = parts.reduce(
|
||||
(agg, part) => {
|
||||
if (part.type === "text") {
|
||||
if (!part.synthetic) agg.input += part.text
|
||||
}
|
||||
if (part.type === "file") agg.parts.push(part)
|
||||
return agg
|
||||
},
|
||||
{ input: "", parts: [] as PromptInfo["parts"] },
|
||||
)
|
||||
props.setPrompt(promptInfo)
|
||||
}
|
||||
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ import { useRoute, useRouteData } from "@tui/context/route"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { SplitBorder } from "@tui/component/border"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers } from "@opentui/core"
|
||||
import { BoxRenderable, ScrollBoxRenderable, TextAttributes, addDefaultParsers } from "@opentui/core"
|
||||
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
||||
import type { AssistantMessage, Part, ToolPart, UserMessage, TextPart, ReasoningPart } from "@opencode-ai/sdk"
|
||||
import { useLocal } from "@tui/context/local"
|
||||
@@ -58,6 +58,7 @@ import { Editor } from "../../util/editor"
|
||||
import { Global } from "@/global"
|
||||
import fs from "fs/promises"
|
||||
import stripAnsi from "strip-ansi"
|
||||
import { LSP } from "@/lsp/index.ts"
|
||||
|
||||
addDefaultParsers(parsers.parsers)
|
||||
|
||||
@@ -98,7 +99,7 @@ export function Session() {
|
||||
await sync.session
|
||||
.sync(route.sessionID)
|
||||
.then(() => {
|
||||
scroll.scrollBy(100_000)
|
||||
if (scroll) scroll.scrollBy(100_000)
|
||||
})
|
||||
.catch(() => {
|
||||
toast.show({
|
||||
@@ -145,19 +146,10 @@ export function Session() {
|
||||
|
||||
function toBottom() {
|
||||
setTimeout(() => {
|
||||
scroll.scrollTo(scroll.scrollHeight)
|
||||
if (scroll) scroll.scrollTo(scroll.scrollHeight)
|
||||
}, 50)
|
||||
}
|
||||
|
||||
// snap to bottom when revert position changes
|
||||
createEffect((old) => {
|
||||
if (old !== session()?.revert?.messageID) toBottom()
|
||||
return session()?.revert?.messageID
|
||||
})
|
||||
|
||||
// snap to bottom when session changes
|
||||
createEffect(on(() => route.sessionID, toBottom))
|
||||
|
||||
const local = useLocal()
|
||||
|
||||
function moveChild(direction: number) {
|
||||
@@ -225,29 +217,33 @@ export function Session() {
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Share session",
|
||||
value: "session.share",
|
||||
keybind: "session_share",
|
||||
disabled: !!session()?.share?.url,
|
||||
category: "Session",
|
||||
onSelect: async (dialog) => {
|
||||
await sdk.client.session
|
||||
.share({
|
||||
path: {
|
||||
id: route.sessionID,
|
||||
...(sync.data.config.share !== "disabled"
|
||||
? [
|
||||
{
|
||||
title: "Share session",
|
||||
value: "session.share",
|
||||
keybind: "session_share" as const,
|
||||
disabled: !!session()?.share?.url,
|
||||
category: "Session",
|
||||
onSelect: async (dialog: any) => {
|
||||
await sdk.client.session
|
||||
.share({
|
||||
path: {
|
||||
id: route.sessionID,
|
||||
},
|
||||
})
|
||||
.then((res) =>
|
||||
Clipboard.copy(res.data!.share!.url).catch(() =>
|
||||
toast.show({ message: "Failed to copy URL to clipboard", variant: "error" }),
|
||||
),
|
||||
)
|
||||
.then(() => toast.show({ message: "Share URL copied to clipboard!", variant: "success" }))
|
||||
.catch(() => toast.show({ message: "Failed to share session", variant: "error" }))
|
||||
dialog.clear()
|
||||
},
|
||||
})
|
||||
.then((res) =>
|
||||
Clipboard.copy(res.data!.share!.url).catch(() =>
|
||||
toast.show({ message: "Failed to copy URL to clipboard", variant: "error" }),
|
||||
),
|
||||
)
|
||||
.then(() => toast.show({ message: "Share URL copied to clipboard!", variant: "success" }))
|
||||
.catch(() => toast.show({ message: "Failed to share session", variant: "error" }))
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: "Unshare session",
|
||||
value: "session.unshare",
|
||||
@@ -272,14 +268,18 @@ export function Session() {
|
||||
const revert = session().revert?.messageID
|
||||
const message = messages().findLast((x) => (!revert || x.id < revert) && x.role === "user")
|
||||
if (!message) return
|
||||
sdk.client.session.revert({
|
||||
path: {
|
||||
id: route.sessionID,
|
||||
},
|
||||
body: {
|
||||
messageID: message.id,
|
||||
},
|
||||
})
|
||||
sdk.client.session
|
||||
.revert({
|
||||
path: {
|
||||
id: route.sessionID,
|
||||
},
|
||||
body: {
|
||||
messageID: message.id,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
toBottom()
|
||||
})
|
||||
const parts = sync.data.part[message.id]
|
||||
prompt.set(
|
||||
parts.reduce(
|
||||
@@ -327,7 +327,7 @@ export function Session() {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Toggle sidebar",
|
||||
title: sidebarVisible() ? "Hide sidebar" : "Show sidebar",
|
||||
value: "session.sidebar.toggle",
|
||||
keybind: "sidebar_toggle",
|
||||
category: "Session",
|
||||
@@ -631,6 +631,9 @@ export function Session() {
|
||||
const dialog = useDialog()
|
||||
const renderer = useRenderer()
|
||||
|
||||
// snap to bottom when session changes
|
||||
createEffect(on(() => route.sessionID, toBottom))
|
||||
|
||||
return (
|
||||
<context.Provider
|
||||
value={{
|
||||
@@ -754,7 +757,13 @@ export function Session() {
|
||||
index={index()}
|
||||
onMouseUp={() => {
|
||||
if (renderer.getSelection()?.getSelectedText()) return
|
||||
dialog.replace(() => <DialogMessage messageID={message.id} sessionID={route.sessionID} />)
|
||||
dialog.replace(() => (
|
||||
<DialogMessage
|
||||
messageID={message.id}
|
||||
sessionID={route.sessionID}
|
||||
setPrompt={(promptInfo) => prompt.set(promptInfo)}
|
||||
/>
|
||||
))
|
||||
}}
|
||||
message={message as UserMessage}
|
||||
parts={sync.data.part[message.id] ?? []}
|
||||
@@ -879,11 +888,16 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
|
||||
return (
|
||||
<>
|
||||
<For each={props.parts}>
|
||||
{(part) => {
|
||||
{(part, index) => {
|
||||
const component = createMemo(() => PART_MAPPING[part.type as keyof typeof PART_MAPPING])
|
||||
return (
|
||||
<Show when={component()}>
|
||||
<Dynamic component={component()} part={part as any} message={props.message} />
|
||||
<Dynamic
|
||||
last={index() === props.parts.length - 1}
|
||||
component={component()}
|
||||
part={part as any}
|
||||
message={props.message}
|
||||
/>
|
||||
</Show>
|
||||
)
|
||||
}}
|
||||
@@ -944,27 +958,36 @@ const PART_MAPPING = {
|
||||
reasoning: ReasoningPart,
|
||||
}
|
||||
|
||||
function ReasoningPart(props: { part: ReasoningPart; message: AssistantMessage }) {
|
||||
const { theme } = useTheme()
|
||||
function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: AssistantMessage }) {
|
||||
const { theme, syntax } = useTheme()
|
||||
const ctx = use()
|
||||
const content = createMemo(() => props.part.text.trim())
|
||||
return (
|
||||
<Show when={props.part.text.trim()}>
|
||||
<Show when={content()}>
|
||||
<box
|
||||
id={"text-" + props.part.id}
|
||||
paddingLeft={2}
|
||||
marginTop={1}
|
||||
flexShrink={0}
|
||||
flexDirection="column"
|
||||
border={["left"]}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
borderColor={theme.backgroundPanel}
|
||||
borderColor={theme.backgroundElement}
|
||||
>
|
||||
<box paddingTop={1} paddingBottom={1} paddingLeft={2} backgroundColor={theme.backgroundPanel}>
|
||||
<text fg={theme.text}>{props.part.text.trim()}</text>
|
||||
</box>
|
||||
<code
|
||||
filetype="markdown"
|
||||
drawUnstyledText={false}
|
||||
streaming={true}
|
||||
syntaxStyle={syntax()}
|
||||
content={"_Thinking:_ " + content()}
|
||||
conceal={ctx.conceal()}
|
||||
fg={theme.text}
|
||||
/>
|
||||
</box>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function TextPart(props: { part: TextPart; message: AssistantMessage }) {
|
||||
function TextPart(props: { last: boolean; part: TextPart; message: AssistantMessage }) {
|
||||
const ctx = use()
|
||||
const { syntax } = useTheme()
|
||||
return (
|
||||
@@ -985,7 +1008,7 @@ function TextPart(props: { part: TextPart; message: AssistantMessage }) {
|
||||
|
||||
// Pending messages moved to individual tool pending functions
|
||||
|
||||
function ToolPart(props: { part: ToolPart; message: AssistantMessage }) {
|
||||
function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMessage }) {
|
||||
const { theme } = useTheme()
|
||||
const sync = useSync()
|
||||
const [margin, setMargin] = createSignal(0)
|
||||
@@ -1189,6 +1212,8 @@ ToolRegistry.register<typeof WriteTool>({
|
||||
.map((x) => x.toString().padStart(pad, " "))
|
||||
})
|
||||
|
||||
const diagnostics = createMemo(() => props.metadata.diagnostics?.[props.input.filePath ?? ""] ?? [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToolTitle icon="←" fallback="Preparing write..." when={props.input.filePath}>
|
||||
@@ -1202,6 +1227,15 @@ ToolRegistry.register<typeof WriteTool>({
|
||||
<code filetype={filetype(props.input.filePath!)} syntaxStyle={syntax()} content={code()} />
|
||||
</box>
|
||||
</box>
|
||||
<Show when={diagnostics().length}>
|
||||
<For each={diagnostics()}>
|
||||
{(diagnostic) => (
|
||||
<text fg={theme.error}>
|
||||
Error [{diagnostic.range.start.line}:{diagnostic.range.start.character}]: {diagnostic.message}
|
||||
</text>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
},
|
||||
@@ -1379,6 +1413,12 @@ ToolRegistry.register<typeof EditTool>({
|
||||
|
||||
const ft = createMemo(() => filetype(props.input.filePath))
|
||||
|
||||
createEffect(() => console.log(props.metadata.diagnostics))
|
||||
const diagnostics = createMemo(() => {
|
||||
const arr = props.metadata.diagnostics?.[props.input.filePath ?? ""] ?? []
|
||||
return arr.filter((x) => x.severity === 1).slice(0, 3)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToolTitle icon="←" fallback="Preparing edit..." when={props.input.filePath}>
|
||||
@@ -1407,6 +1447,17 @@ ToolRegistry.register<typeof EditTool>({
|
||||
</box>
|
||||
</Match>
|
||||
</Switch>
|
||||
<Show when={diagnostics().length}>
|
||||
<box>
|
||||
<For each={diagnostics()}>
|
||||
{(diagnostic) => (
|
||||
<text fg={theme.error}>
|
||||
Error [{diagnostic.range.start.line + 1}:{diagnostic.range.start.character + 1}] {diagnostic.message}
|
||||
</text>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
},
|
||||
@@ -1438,15 +1489,24 @@ ToolRegistry.register<typeof TodoWriteTool>({
|
||||
render(props) {
|
||||
const { theme } = useTheme()
|
||||
return (
|
||||
<box>
|
||||
<For each={props.input.todos ?? []}>
|
||||
{(todo) => (
|
||||
<text style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}>
|
||||
[{todo.status === "completed" ? "✓" : " "}] {todo.content}
|
||||
</text>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
<>
|
||||
<Show when={!props.input.todos?.length}>
|
||||
<ToolTitle icon="⚙" fallback="Updating todos..." when={true}>
|
||||
Updating todos...
|
||||
</ToolTitle>
|
||||
</Show>
|
||||
<Show when={props.metadata.todos?.length}>
|
||||
<box>
|
||||
<For each={props.input.todos ?? []}>
|
||||
{(todo) => (
|
||||
<text style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}>
|
||||
[{todo.status === "completed" ? "✓" : " "}] {todo.content}
|
||||
</text>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { createMemo, For, Show, Switch, Match } from "solid-js"
|
||||
import { createMemo, For, Show, Switch, Match, createSignal } from "solid-js"
|
||||
import { useTheme } from "../../context/theme"
|
||||
import { Locale } from "@/util/locale"
|
||||
import path from "path"
|
||||
@@ -13,6 +13,11 @@ export function Sidebar(props: { sessionID: string }) {
|
||||
const todo = createMemo(() => sync.data.todo[props.sessionID] ?? [])
|
||||
const messages = createMemo(() => sync.data.message[props.sessionID] ?? [])
|
||||
|
||||
const [mcpExpanded, setMcpExpanded] = createSignal(true)
|
||||
const [diffExpanded, setDiffExpanded] = createSignal(true)
|
||||
const [todoExpanded, setTodoExpanded] = createSignal(true)
|
||||
const [lspExpanded, setLspExpanded] = createSignal(true)
|
||||
|
||||
const cost = createMemo(() => {
|
||||
const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
@@ -55,110 +60,130 @@ export function Sidebar(props: { sessionID: string }) {
|
||||
</box>
|
||||
<Show when={Object.keys(sync.data.mcp).length > 0}>
|
||||
<box>
|
||||
<text fg={theme.text}>
|
||||
<b>MCP</b>
|
||||
</text>
|
||||
<For each={Object.entries(sync.data.mcp)}>
|
||||
{([key, item]) => (
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text
|
||||
flexShrink={0}
|
||||
style={{
|
||||
fg: {
|
||||
connected: theme.success,
|
||||
failed: theme.error,
|
||||
disabled: theme.textMuted,
|
||||
}[item.status],
|
||||
}}
|
||||
>
|
||||
•
|
||||
</text>
|
||||
<text fg={theme.text} wrapMode="word">
|
||||
{key}{" "}
|
||||
<span style={{ fg: theme.textMuted }}>
|
||||
<Switch>
|
||||
<Match when={item.status === "connected"}>Connected</Match>
|
||||
<Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match>
|
||||
<Match when={item.status === "disabled"}>Disabled in configuration</Match>
|
||||
</Switch>
|
||||
</span>
|
||||
</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
<box flexDirection="row" gap={1} onMouseDown={() => setMcpExpanded(!mcpExpanded())}>
|
||||
<text fg={theme.text}>{mcpExpanded() ? "▼" : "▶"}</text>
|
||||
<text fg={theme.text}>
|
||||
<b>MCP</b>
|
||||
</text>
|
||||
</box>
|
||||
<Show when={mcpExpanded()}>
|
||||
<For each={Object.entries(sync.data.mcp)}>
|
||||
{([key, item]) => (
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text
|
||||
flexShrink={0}
|
||||
style={{
|
||||
fg: {
|
||||
connected: theme.success,
|
||||
failed: theme.error,
|
||||
disabled: theme.textMuted,
|
||||
}[item.status],
|
||||
}}
|
||||
>
|
||||
•
|
||||
</text>
|
||||
<text fg={theme.text} wrapMode="word">
|
||||
{key}{" "}
|
||||
<span style={{ fg: theme.textMuted }}>
|
||||
<Switch>
|
||||
<Match when={item.status === "connected"}>Connected</Match>
|
||||
<Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match>
|
||||
<Match when={item.status === "disabled"}>Disabled in configuration</Match>
|
||||
</Switch>
|
||||
</span>
|
||||
</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={sync.data.lsp.length > 0}>
|
||||
<box>
|
||||
<text fg={theme.text}>
|
||||
<b>LSP</b>
|
||||
</text>
|
||||
<For each={sync.data.lsp}>
|
||||
{(item) => (
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text
|
||||
flexShrink={0}
|
||||
style={{
|
||||
fg: {
|
||||
connected: theme.success,
|
||||
error: theme.error,
|
||||
}[item.status],
|
||||
}}
|
||||
>
|
||||
•
|
||||
</text>
|
||||
<text fg={theme.textMuted}>
|
||||
{item.id} {item.root}
|
||||
</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={diff().length > 0}>
|
||||
<box>
|
||||
<text fg={theme.text}>
|
||||
<b>Modified Files</b>
|
||||
</text>
|
||||
<For each={diff() || []}>
|
||||
{(item) => {
|
||||
const file = createMemo(() => {
|
||||
const splits = item.file.split(path.sep).filter(Boolean)
|
||||
const last = splits.at(-1)!
|
||||
const rest = splits.slice(0, -1).join(path.sep)
|
||||
return Locale.truncateMiddle(rest, 30 - last.length) + "/" + last
|
||||
})
|
||||
return (
|
||||
<box flexDirection="row" gap={1} justifyContent="space-between">
|
||||
<text fg={theme.textMuted} wrapMode="char">
|
||||
{file()}
|
||||
<box flexDirection="row" gap={1} onMouseDown={() => setLspExpanded(!lspExpanded())}>
|
||||
<text fg={theme.text}>{lspExpanded() ? "▼" : "▶"}</text>
|
||||
<text fg={theme.text}>
|
||||
<b>LSP</b>
|
||||
</text>
|
||||
</box>
|
||||
<Show when={lspExpanded()}>
|
||||
<For each={sync.data.lsp}>
|
||||
{(item) => (
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text
|
||||
flexShrink={0}
|
||||
style={{
|
||||
fg: {
|
||||
connected: theme.success,
|
||||
error: theme.error,
|
||||
}[item.status],
|
||||
}}
|
||||
>
|
||||
•
|
||||
</text>
|
||||
<text fg={theme.textMuted}>
|
||||
{item.id} {item.root}
|
||||
</text>
|
||||
<box flexDirection="row" gap={1} flexShrink={0}>
|
||||
<Show when={item.additions}>
|
||||
<text fg={theme.diffAdded}>+{item.additions}</text>
|
||||
</Show>
|
||||
<Show when={item.deletions}>
|
||||
<text fg={theme.diffRemoved}>-{item.deletions}</text>
|
||||
</Show>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={todo().length > 0}>
|
||||
<box>
|
||||
<text fg={theme.text}>
|
||||
<b>Todo</b>
|
||||
</text>
|
||||
<For each={todo()}>
|
||||
{(todo) => (
|
||||
<text style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}>
|
||||
[{todo.status === "completed" ? "✓" : " "}] {todo.content}
|
||||
</text>
|
||||
)}
|
||||
</For>
|
||||
<box flexDirection="row" gap={1} onMouseDown={() => setTodoExpanded(!todoExpanded())}>
|
||||
<text fg={theme.text}>{todoExpanded() ? "▼" : "▶"}</text>
|
||||
<text fg={theme.text}>
|
||||
<b>Todo</b>
|
||||
</text>
|
||||
</box>
|
||||
<Show when={todoExpanded()}>
|
||||
<For each={todo()}>
|
||||
{(todo) => (
|
||||
<text style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}>
|
||||
[{todo.status === "completed" ? "✓" : " "}] {todo.content}
|
||||
</text>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={diff().length > 0}>
|
||||
<box>
|
||||
<box flexDirection="row" gap={1} onMouseDown={() => setDiffExpanded(!diffExpanded())}>
|
||||
<text fg={theme.text}>{diffExpanded() ? "▼" : "▶"}</text>
|
||||
<text fg={theme.text}>
|
||||
<b>Modified Files</b>
|
||||
</text>
|
||||
</box>
|
||||
<Show when={diffExpanded()}>
|
||||
<For each={diff() || []}>
|
||||
{(item) => {
|
||||
const file = createMemo(() => {
|
||||
const splits = item.file.split(path.sep).filter(Boolean)
|
||||
const last = splits.at(-1)!
|
||||
const rest = splits.slice(0, -1).join(path.sep)
|
||||
return Locale.truncateMiddle(rest, 30 - last.length) + "/" + last
|
||||
})
|
||||
return (
|
||||
<box flexDirection="row" gap={1} justifyContent="space-between">
|
||||
<text fg={theme.textMuted} wrapMode="char">
|
||||
{file()}
|
||||
</text>
|
||||
<box flexDirection="row" gap={1} flexShrink={0}>
|
||||
<Show when={item.additions}>
|
||||
<text fg={theme.diffAdded}>+{item.additions}</text>
|
||||
</Show>
|
||||
<Show when={item.deletions}>
|
||||
<text fg={theme.diffRemoved}>-{item.deletions}</text>
|
||||
</Show>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</Show>
|
||||
</box>
|
||||
</Show>
|
||||
</box>
|
||||
|
||||
@@ -48,6 +48,10 @@ export const TuiSpawnCommand = cmd({
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
stdin: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_OPTIONS: "",
|
||||
},
|
||||
})
|
||||
await proc.exited
|
||||
await Instance.disposeAll()
|
||||
|
||||
@@ -2,10 +2,9 @@ import { cmd } from "@/cli/cmd/cmd"
|
||||
import { tui } from "./app"
|
||||
import { Rpc } from "@/util/rpc"
|
||||
import { type rpc } from "./worker"
|
||||
import { Session } from "@/session"
|
||||
import { bootstrap } from "@/cli/bootstrap"
|
||||
import path from "path"
|
||||
import { UI } from "@/cli/ui"
|
||||
import { iife } from "@/util/iife"
|
||||
|
||||
declare global {
|
||||
const OPENCODE_WORKER_PATH: string
|
||||
@@ -32,8 +31,8 @@ export const TuiThreadCommand = cmd({
|
||||
})
|
||||
.option("session", {
|
||||
alias: ["s"],
|
||||
describe: "session id to continue",
|
||||
type: "string",
|
||||
describe: "session id to continue",
|
||||
})
|
||||
.option("prompt", {
|
||||
alias: ["p"],
|
||||
@@ -55,12 +54,6 @@ export const TuiThreadCommand = cmd({
|
||||
default: "127.0.0.1",
|
||||
}),
|
||||
handler: async (args) => {
|
||||
const prompt = await (async () => {
|
||||
const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined
|
||||
if (!args.prompt) return piped
|
||||
return piped ? piped + "\n" + args.prompt : args.prompt
|
||||
})()
|
||||
|
||||
// Resolve relative paths against PWD to preserve behavior when using --cwd flag
|
||||
const baseCwd = process.env.PWD ?? process.cwd()
|
||||
const cwd = args.project ? path.resolve(baseCwd, args.project) : process.cwd()
|
||||
@@ -76,54 +69,40 @@ export const TuiThreadCommand = cmd({
|
||||
return
|
||||
}
|
||||
|
||||
await bootstrap(cwd, async () => {
|
||||
const sessionID = await (async () => {
|
||||
if (args.continue) {
|
||||
const it = Session.list()
|
||||
try {
|
||||
for await (const s of it) {
|
||||
if (s.parentID === undefined) {
|
||||
return s.id
|
||||
}
|
||||
}
|
||||
return
|
||||
} finally {
|
||||
await it.return()
|
||||
}
|
||||
}
|
||||
if (args.session) {
|
||||
return args.session
|
||||
}
|
||||
return undefined
|
||||
})()
|
||||
|
||||
const worker = new Worker(workerPath, {
|
||||
env: Object.fromEntries(
|
||||
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
|
||||
),
|
||||
})
|
||||
worker.onerror = console.error
|
||||
const client = Rpc.client<typeof rpc>(worker)
|
||||
process.on("uncaughtException", (e) => {
|
||||
console.error(e)
|
||||
})
|
||||
process.on("unhandledRejection", (e) => {
|
||||
console.error(e)
|
||||
})
|
||||
const server = await client.call("server", {
|
||||
port: args.port,
|
||||
hostname: args.hostname,
|
||||
})
|
||||
await tui({
|
||||
url: server.url,
|
||||
sessionID,
|
||||
model: args.model,
|
||||
const worker = new Worker(workerPath, {
|
||||
env: Object.fromEntries(
|
||||
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
|
||||
),
|
||||
})
|
||||
worker.onerror = console.error
|
||||
const client = Rpc.client<typeof rpc>(worker)
|
||||
process.on("uncaughtException", (e) => {
|
||||
console.error(e)
|
||||
})
|
||||
process.on("unhandledRejection", (e) => {
|
||||
console.error(e)
|
||||
})
|
||||
const server = await client.call("server", {
|
||||
port: args.port,
|
||||
hostname: args.hostname,
|
||||
})
|
||||
const prompt = await iife(async () => {
|
||||
const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined
|
||||
if (!args.prompt) return piped
|
||||
return piped ? piped + "\n" + args.prompt : args.prompt
|
||||
})
|
||||
await tui({
|
||||
url: server.url,
|
||||
args: {
|
||||
continue: args.continue,
|
||||
sessionID: args.session,
|
||||
agent: args.agent,
|
||||
model: args.model,
|
||||
prompt,
|
||||
onExit: async () => {
|
||||
await client.call("shutdown", undefined)
|
||||
},
|
||||
})
|
||||
},
|
||||
onExit: async () => {
|
||||
await client.call("shutdown", undefined)
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -30,7 +30,7 @@ export interface DialogSelectOption<T = any> {
|
||||
title: string
|
||||
value: T
|
||||
description?: string
|
||||
footer?: string
|
||||
footer?: JSX.Element | string
|
||||
category?: string
|
||||
disabled?: boolean
|
||||
bg?: RGBA
|
||||
@@ -172,7 +172,6 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||
props.onFilter?.(e)
|
||||
})
|
||||
}}
|
||||
onKeyDown={(e) => {}}
|
||||
focusedBackgroundColor={theme.backgroundPanel}
|
||||
cursorColor={theme.primary}
|
||||
focusedTextColor={theme.textMuted}
|
||||
@@ -256,14 +255,19 @@ function Option(props: {
|
||||
description?: string
|
||||
active?: boolean
|
||||
current?: boolean
|
||||
footer?: string
|
||||
footer?: JSX.Element | string
|
||||
onMouseOver?: () => void
|
||||
}) {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Show when={props.current && !props.active}>
|
||||
<text flexShrink={0} fg={theme.primary} marginRight={0.5}>
|
||||
<Show when={props.current}>
|
||||
<text
|
||||
flexShrink={0}
|
||||
fg={props.active ? theme.background : props.current ? theme.primary : theme.text}
|
||||
marginRight={0.5}
|
||||
>
|
||||
●
|
||||
</text>
|
||||
</Show>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { $ } from "bun"
|
||||
import { platform } from "os"
|
||||
import { platform, release } from "os"
|
||||
import clipboardy from "clipboardy"
|
||||
import { lazy } from "../../../../util/lazy.js"
|
||||
import { tmpdir } from "os"
|
||||
@@ -29,6 +29,18 @@ export namespace Clipboard {
|
||||
}
|
||||
}
|
||||
|
||||
if (os === "win32" || release().includes("WSL")) {
|
||||
const script =
|
||||
"Add-Type -AssemblyName System.Windows.Forms; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); [System.Convert]::ToBase64String($ms.ToArray()) }"
|
||||
const base64 = await $`powershell.exe -command "${script}"`.nothrow().text()
|
||||
if (base64) {
|
||||
const imageBuffer = Buffer.from(base64.trim(), "base64")
|
||||
if (imageBuffer.length > 0) {
|
||||
return { data: imageBuffer.toString("base64"), mime: "image/png" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (os === "linux") {
|
||||
const wayland = await $`wl-paste -t image/png`.nothrow().arrayBuffer()
|
||||
if (wayland && wayland.byteLength > 0) {
|
||||
@@ -40,18 +52,6 @@ export namespace Clipboard {
|
||||
}
|
||||
}
|
||||
|
||||
if (os === "win32") {
|
||||
const script =
|
||||
"Add-Type -AssemblyName System.Windows.Forms; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); [System.Convert]::ToBase64String($ms.ToArray()) }"
|
||||
const base64 = await $`powershell -command "${script}"`.nothrow().text()
|
||||
if (base64) {
|
||||
const imageBuffer = Buffer.from(base64.trim(), "base64")
|
||||
if (imageBuffer.length > 0) {
|
||||
return { data: imageBuffer.toString("base64"), mime: "image/png" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const text = await clipboardy.read().catch(() => {})
|
||||
if (text) {
|
||||
return { data: text, mime: "text/plain" }
|
||||
|
||||
@@ -24,12 +24,6 @@ export namespace Config {
|
||||
export const state = Instance.state(async () => {
|
||||
const auth = await Auth.all()
|
||||
let result = await global()
|
||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||
const found = await Filesystem.findUp(file, Instance.directory, Instance.worktree)
|
||||
for (const resolved of found.toReversed()) {
|
||||
result = mergeDeep(result, await loadFile(resolved))
|
||||
}
|
||||
}
|
||||
|
||||
// Override with custom config if provided
|
||||
if (Flag.OPENCODE_CONFIG) {
|
||||
@@ -37,6 +31,13 @@ export namespace Config {
|
||||
log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
|
||||
}
|
||||
|
||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||
const found = await Filesystem.findUp(file, Instance.directory, Instance.worktree)
|
||||
for (const resolved of found.toReversed()) {
|
||||
result = mergeDeep(result, await loadFile(resolved))
|
||||
}
|
||||
}
|
||||
|
||||
if (Flag.OPENCODE_CONFIG_CONTENT) {
|
||||
result = mergeDeep(result, JSON.parse(Flag.OPENCODE_CONFIG_CONTENT))
|
||||
log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT")
|
||||
@@ -74,12 +75,15 @@ export namespace Config {
|
||||
for (const dir of directories) {
|
||||
await assertValid(dir)
|
||||
|
||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||
result = mergeDeep(result, await loadFile(path.join(dir, file)))
|
||||
// to satisy the type checker
|
||||
result.agent ??= {}
|
||||
result.mode ??= {}
|
||||
result.plugin ??= []
|
||||
if (dir.endsWith(".opencode")) {
|
||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||
log.debug(`loading config from ${path.join(dir, file)}`)
|
||||
result = mergeDeep(result, await loadFile(path.join(dir, file)))
|
||||
// to satisy the type checker
|
||||
result.agent ??= {}
|
||||
result.mode ??= {}
|
||||
result.plugin ??= []
|
||||
}
|
||||
}
|
||||
|
||||
promises.push(installDependencies(dir))
|
||||
@@ -355,11 +359,18 @@ export namespace Config {
|
||||
disable: z.boolean().optional(),
|
||||
description: z.string().optional().describe("Description of when to use the agent"),
|
||||
mode: z.union([z.literal("subagent"), z.literal("primary"), z.literal("all")]).optional(),
|
||||
color: z
|
||||
.string()
|
||||
.regex(/^#[0-9a-fA-F]{6}$/, "Invalid hex color format")
|
||||
.optional()
|
||||
.describe("Hex color code for the agent (e.g., #FF5733)"),
|
||||
permission: z
|
||||
.object({
|
||||
edit: Permission.optional(),
|
||||
bash: z.union([Permission, z.record(z.string(), Permission)]).optional(),
|
||||
webfetch: Permission.optional(),
|
||||
doom_loop: Permission.optional(),
|
||||
external_directory: Permission.optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
@@ -574,6 +585,8 @@ export namespace Config {
|
||||
edit: Permission.optional(),
|
||||
bash: z.union([Permission, z.record(z.string(), Permission)]).optional(),
|
||||
webfetch: Permission.optional(),
|
||||
doom_loop: Permission.optional(),
|
||||
external_directory: Permission.optional(),
|
||||
})
|
||||
.optional(),
|
||||
tools: z.record(z.string(), z.boolean()).optional(),
|
||||
|
||||
@@ -13,9 +13,9 @@ export namespace Flag {
|
||||
export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"]
|
||||
|
||||
// Experimental
|
||||
export const OPENCODE_EXPERIMENTAL_WATCHER = truthy("OPENCODE_EXPERIMENTAL_WATCHER")
|
||||
export const OPENCODE_EXPERIMENTAL_TURN_SUMMARY = truthy("OPENCODE_EXPERIMENTAL_TURN_SUMMARY")
|
||||
export const OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP = truthy("OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP")
|
||||
export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL")
|
||||
export const OPENCODE_EXPERIMENTAL_WATCHER = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WATCHER")
|
||||
export const OPENCODE_EXPERIMENTAL_EXA = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA")
|
||||
|
||||
function truthy(key: string) {
|
||||
const value = process.env[key]?.toLowerCase()
|
||||
|
||||
@@ -24,6 +24,7 @@ import { TuiSpawnCommand } from "./cli/cmd/tui/spawn"
|
||||
import { AcpCommand } from "./cli/cmd/acp"
|
||||
import { EOL } from "os"
|
||||
import { WebCommand } from "./cli/cmd/web"
|
||||
import { PrCommand } from "./cli/cmd/pr"
|
||||
|
||||
process.on("unhandledRejection", (e) => {
|
||||
Log.Default.error("rejection", {
|
||||
@@ -90,6 +91,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
.command(ExportCommand)
|
||||
.command(ImportCommand)
|
||||
.command(GithubCommand)
|
||||
.command(PrCommand)
|
||||
.fail((msg) => {
|
||||
if (
|
||||
msg.startsWith("Unknown argument") ||
|
||||
|
||||
@@ -62,6 +62,14 @@ export namespace LSPClient {
|
||||
// Return server initialization options
|
||||
return [input.server.initialization ?? {}]
|
||||
})
|
||||
connection.onRequest("client/registerCapability", async () => {})
|
||||
connection.onRequest("client/unregisterCapability", async () => {})
|
||||
connection.onRequest("workspace/workspaceFolders", async () => [
|
||||
{
|
||||
name: "workspace",
|
||||
uri: "file://" + input.root,
|
||||
},
|
||||
])
|
||||
connection.listen()
|
||||
|
||||
l.info("sending initialize")
|
||||
|
||||
@@ -103,6 +103,7 @@ export namespace LSP {
|
||||
broken: new Set<string>(),
|
||||
servers,
|
||||
clients,
|
||||
spawning: new Map<string, Promise<LSPClient.Info | undefined>>(),
|
||||
}
|
||||
},
|
||||
async (state) => {
|
||||
@@ -145,6 +146,49 @@ export namespace LSP {
|
||||
const s = await state()
|
||||
const extension = path.parse(file).ext || file
|
||||
const result: LSPClient.Info[] = []
|
||||
|
||||
async function schedule(server: LSPServer.Info, root: string, key: string) {
|
||||
const handle = await server
|
||||
.spawn(root)
|
||||
.then((value) => {
|
||||
if (!value) s.broken.add(key)
|
||||
return value
|
||||
})
|
||||
.catch((err) => {
|
||||
s.broken.add(key)
|
||||
log.error(`Failed to spawn LSP server ${server.id}`, { error: err })
|
||||
return undefined
|
||||
})
|
||||
|
||||
if (!handle) return undefined
|
||||
log.info("spawned lsp server", { serverID: server.id })
|
||||
|
||||
const client = await LSPClient.create({
|
||||
serverID: server.id,
|
||||
server: handle,
|
||||
root,
|
||||
}).catch((err) => {
|
||||
s.broken.add(key)
|
||||
handle.process.kill()
|
||||
log.error(`Failed to initialize LSP client ${server.id}`, { error: err })
|
||||
return undefined
|
||||
})
|
||||
|
||||
if (!client) {
|
||||
handle.process.kill()
|
||||
return undefined
|
||||
}
|
||||
|
||||
const existing = s.clients.find((x) => x.root === root && x.serverID === server.id)
|
||||
if (existing) {
|
||||
handle.process.kill()
|
||||
return existing
|
||||
}
|
||||
|
||||
s.clients.push(client)
|
||||
return client
|
||||
}
|
||||
|
||||
for (const server of Object.values(s.servers)) {
|
||||
if (server.extensions.length && !server.extensions.includes(extension)) continue
|
||||
const root = await server.root(file)
|
||||
@@ -156,49 +200,42 @@ export namespace LSP {
|
||||
result.push(match)
|
||||
continue
|
||||
}
|
||||
const handle = await server
|
||||
.spawn(root)
|
||||
.then((h) => {
|
||||
if (h === undefined) {
|
||||
s.broken.add(root + server.id)
|
||||
}
|
||||
return h
|
||||
})
|
||||
.catch((err) => {
|
||||
s.broken.add(root + server.id)
|
||||
log.error(`Failed to spawn LSP server ${server.id}`, { error: err })
|
||||
return undefined
|
||||
})
|
||||
if (!handle) continue
|
||||
log.info("spawned lsp server", { serverID: server.id })
|
||||
|
||||
const client = await LSPClient.create({
|
||||
serverID: server.id,
|
||||
server: handle,
|
||||
root,
|
||||
}).catch((err) => {
|
||||
s.broken.add(root + server.id)
|
||||
handle.process.kill()
|
||||
log.error(`Failed to initialize LSP client ${server.id}`, {
|
||||
error: err,
|
||||
})
|
||||
return undefined
|
||||
const inflight = s.spawning.get(root + server.id)
|
||||
if (inflight) {
|
||||
const client = await inflight
|
||||
if (!client) continue
|
||||
result.push(client)
|
||||
continue
|
||||
}
|
||||
|
||||
const task = schedule(server, root, root + server.id)
|
||||
s.spawning.set(root + server.id, task)
|
||||
|
||||
task.finally(() => {
|
||||
if (s.spawning.get(root + server.id) === task) {
|
||||
s.spawning.delete(root + server.id)
|
||||
}
|
||||
})
|
||||
|
||||
const client = await task
|
||||
if (!client) continue
|
||||
s.clients.push(client)
|
||||
|
||||
result.push(client)
|
||||
Bus.publish(Event.Updated, {})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export async function touchFile(input: string, waitForDiagnostics?: boolean) {
|
||||
log.info("touching file", { file: input })
|
||||
const clients = await getClients(input)
|
||||
await run(async (client) => {
|
||||
if (!clients.includes(client)) return
|
||||
|
||||
const wait = waitForDiagnostics ? client.waitForDiagnostics({ path: input }) : Promise.resolve()
|
||||
await client.notify.open({ path: input })
|
||||
|
||||
return wait
|
||||
}).catch((err) => {
|
||||
log.error("failed to touch file", { err, file: input })
|
||||
|
||||
@@ -547,6 +547,40 @@ export namespace LSPServer {
|
||||
},
|
||||
}
|
||||
|
||||
export const SourceKit: Info = {
|
||||
id: "sourcekit-lsp",
|
||||
extensions: [".swift", ".objc", "objcpp"],
|
||||
root: NearestRoot(["Package.swift", "*.xcodeproj", "*.xcworkspace"]),
|
||||
async spawn(root) {
|
||||
// Check if sourcekit-lsp is available in the PATH
|
||||
// This is installed with the Swift toolchain
|
||||
const sourcekit = Bun.which("sourcekit-lsp")
|
||||
if (sourcekit) {
|
||||
return {
|
||||
process: spawn(sourcekit, {
|
||||
cwd: root,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// If sourcekit-lsp not found, check if xcrun is available
|
||||
// This is specific to macOS where sourcekit-lsp is typically installed with Xcode
|
||||
if (!Bun.which("xcrun")) return
|
||||
|
||||
const lspLoc = await $`xcrun --find sourcekit-lsp`.quiet().nothrow()
|
||||
|
||||
if (lspLoc.exitCode !== 0) return
|
||||
|
||||
const bin = lspLoc.text().trim()
|
||||
|
||||
return {
|
||||
process: spawn(bin, {
|
||||
cwd: root,
|
||||
}),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const RustAnalyzer: Info = {
|
||||
id: "rust",
|
||||
root: async (root) => {
|
||||
@@ -598,73 +632,135 @@ export namespace LSPServer {
|
||||
root: NearestRoot(["compile_commands.json", "compile_flags.txt", ".clangd", "CMakeLists.txt", "Makefile"]),
|
||||
extensions: [".c", ".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp", ".hh", ".hxx", ".h++"],
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("clangd", {
|
||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
log.info("downloading clangd from GitHub releases")
|
||||
|
||||
const releaseResponse = await fetch("https://api.github.com/repos/clangd/clangd/releases/latest")
|
||||
if (!releaseResponse.ok) {
|
||||
log.error("Failed to fetch clangd release info")
|
||||
return
|
||||
const args = ["--background-index", "--clang-tidy"]
|
||||
const fromPath = Bun.which("clangd")
|
||||
if (fromPath) {
|
||||
return {
|
||||
process: spawn(fromPath, args, {
|
||||
cwd: root,
|
||||
}),
|
||||
}
|
||||
|
||||
const release = (await releaseResponse.json()) as any
|
||||
|
||||
const platform = process.platform
|
||||
let assetName = ""
|
||||
|
||||
if (platform === "darwin") {
|
||||
assetName = "clangd-mac-"
|
||||
} else if (platform === "linux") {
|
||||
assetName = "clangd-linux-"
|
||||
} else if (platform === "win32") {
|
||||
assetName = "clangd-windows-"
|
||||
} else {
|
||||
log.error(`Platform ${platform} is not supported by clangd auto-download`)
|
||||
return
|
||||
}
|
||||
|
||||
assetName += release.tag_name + ".zip"
|
||||
|
||||
const asset = release.assets.find((a: any) => a.name === assetName)
|
||||
if (!asset) {
|
||||
log.error(`Could not find asset ${assetName} in latest clangd release`)
|
||||
return
|
||||
}
|
||||
|
||||
const downloadUrl = asset.browser_download_url
|
||||
const downloadResponse = await fetch(downloadUrl)
|
||||
if (!downloadResponse.ok) {
|
||||
log.error("Failed to download clangd")
|
||||
return
|
||||
}
|
||||
|
||||
const zipPath = path.join(Global.Path.bin, "clangd.zip")
|
||||
await Bun.file(zipPath).write(downloadResponse)
|
||||
|
||||
await $`unzip -o -q ${zipPath}`.quiet().cwd(Global.Path.bin).nothrow()
|
||||
await fs.rm(zipPath, { force: true })
|
||||
|
||||
const extractedDir = path.join(Global.Path.bin, assetName.replace(".zip", ""))
|
||||
bin = path.join(extractedDir, "bin", "clangd" + (platform === "win32" ? ".exe" : ""))
|
||||
|
||||
if (!(await Bun.file(bin).exists())) {
|
||||
log.error("Failed to extract clangd binary")
|
||||
return
|
||||
}
|
||||
|
||||
if (platform !== "win32") {
|
||||
await $`chmod +x ${bin}`.nothrow()
|
||||
}
|
||||
|
||||
log.info(`installed clangd`, { bin })
|
||||
}
|
||||
|
||||
const ext = process.platform === "win32" ? ".exe" : ""
|
||||
const direct = path.join(Global.Path.bin, "clangd" + ext)
|
||||
if (await Bun.file(direct).exists()) {
|
||||
return {
|
||||
process: spawn(direct, args, {
|
||||
cwd: root,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(Global.Path.bin, { withFileTypes: true }).catch(() => [])
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue
|
||||
if (!entry.name.startsWith("clangd_")) continue
|
||||
const candidate = path.join(Global.Path.bin, entry.name, "bin", "clangd" + ext)
|
||||
if (await Bun.file(candidate).exists()) {
|
||||
return {
|
||||
process: spawn(candidate, args, {
|
||||
cwd: root,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
log.info("downloading clangd from GitHub releases")
|
||||
|
||||
const releaseResponse = await fetch("https://api.github.com/repos/clangd/clangd/releases/latest")
|
||||
if (!releaseResponse.ok) {
|
||||
log.error("Failed to fetch clangd release info")
|
||||
return
|
||||
}
|
||||
|
||||
const release: {
|
||||
tag_name?: string
|
||||
assets?: { name?: string; browser_download_url?: string }[]
|
||||
} = await releaseResponse.json()
|
||||
|
||||
const tag = release.tag_name
|
||||
if (!tag) {
|
||||
log.error("clangd release did not include a tag name")
|
||||
return
|
||||
}
|
||||
const platform = process.platform
|
||||
const tokens: Record<string, string> = {
|
||||
darwin: "mac",
|
||||
linux: "linux",
|
||||
win32: "windows",
|
||||
}
|
||||
const token = tokens[platform]
|
||||
if (!token) {
|
||||
log.error(`Platform ${platform} is not supported by clangd auto-download`)
|
||||
return
|
||||
}
|
||||
|
||||
const assets = release.assets ?? []
|
||||
const valid = (item: { name?: string; browser_download_url?: string }) => {
|
||||
if (!item.name) return false
|
||||
if (!item.browser_download_url) return false
|
||||
if (!item.name.includes(token)) return false
|
||||
return item.name.includes(tag)
|
||||
}
|
||||
|
||||
const asset =
|
||||
assets.find((item) => valid(item) && item.name?.endsWith(".zip")) ??
|
||||
assets.find((item) => valid(item) && item.name?.endsWith(".tar.xz")) ??
|
||||
assets.find((item) => valid(item))
|
||||
if (!asset?.name || !asset.browser_download_url) {
|
||||
log.error("clangd could not match release asset", { tag, platform })
|
||||
return
|
||||
}
|
||||
|
||||
const name = asset.name
|
||||
const downloadResponse = await fetch(asset.browser_download_url)
|
||||
if (!downloadResponse.ok) {
|
||||
log.error("Failed to download clangd")
|
||||
return
|
||||
}
|
||||
|
||||
const archive = path.join(Global.Path.bin, name)
|
||||
const buf = await downloadResponse.arrayBuffer()
|
||||
if (buf.byteLength === 0) {
|
||||
log.error("Failed to write clangd archive")
|
||||
return
|
||||
}
|
||||
await Bun.write(archive, buf)
|
||||
|
||||
const zip = name.endsWith(".zip")
|
||||
const tar = name.endsWith(".tar.xz")
|
||||
if (!zip && !tar) {
|
||||
log.error("clangd encountered unsupported asset", { asset: name })
|
||||
return
|
||||
}
|
||||
|
||||
if (zip) {
|
||||
await $`unzip -o -q ${archive}`.quiet().cwd(Global.Path.bin).nothrow()
|
||||
}
|
||||
if (tar) {
|
||||
await $`tar -xf ${archive}`.cwd(Global.Path.bin).nothrow()
|
||||
}
|
||||
await fs.rm(archive, { force: true })
|
||||
|
||||
const bin = path.join(Global.Path.bin, "clangd_" + tag, "bin", "clangd" + ext)
|
||||
if (!(await Bun.file(bin).exists())) {
|
||||
log.error("Failed to extract clangd binary")
|
||||
return
|
||||
}
|
||||
|
||||
if (platform !== "win32") {
|
||||
await $`chmod +x ${bin}`.nothrow()
|
||||
}
|
||||
|
||||
await fs.unlink(path.join(Global.Path.bin, "clangd")).catch(() => {})
|
||||
await fs.symlink(bin, path.join(Global.Path.bin, "clangd")).catch(() => {})
|
||||
|
||||
log.info(`installed clangd`, { bin })
|
||||
|
||||
return {
|
||||
process: spawn(bin, ["--background-index", "--clang-tidy"], {
|
||||
process: spawn(bin, args, {
|
||||
cwd: root,
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export namespace Plugin {
|
||||
}
|
||||
const plugins = [...(config.plugin ?? [])]
|
||||
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
|
||||
plugins.push("opencode-copilot-auth@0.0.4")
|
||||
plugins.push("opencode-copilot-auth@0.0.5")
|
||||
plugins.push("opencode-anthropic-auth@0.0.2")
|
||||
}
|
||||
for (let plugin of plugins) {
|
||||
|
||||
@@ -9,9 +9,10 @@ import { Project } from "./project"
|
||||
import { Bus } from "../bus"
|
||||
import { Command } from "../command"
|
||||
import { Instance } from "./instance"
|
||||
import { Log } from "@/util/log"
|
||||
|
||||
export async function InstanceBootstrap() {
|
||||
if (Flag.OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP) return
|
||||
Log.Default.info("bootstrapping", { directory: Instance.directory })
|
||||
await Plugin.init()
|
||||
Share.init()
|
||||
Format.init()
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Log } from "@/util/log"
|
||||
import { Context } from "../util/context"
|
||||
import { Project } from "./project"
|
||||
import { State } from "./state"
|
||||
import { iife } from "@/util/iife"
|
||||
|
||||
interface Context {
|
||||
directory: string
|
||||
@@ -9,24 +10,29 @@ interface Context {
|
||||
project: Project.Info
|
||||
}
|
||||
const context = Context.create<Context>("instance")
|
||||
const cache = new Map<string, Context>()
|
||||
const cache = new Map<string, Promise<Context>>()
|
||||
|
||||
export const Instance = {
|
||||
async provide<R>(input: { directory: string; init?: () => Promise<any>; fn: () => R }): Promise<R> {
|
||||
let existing = cache.get(input.directory)
|
||||
if (!existing) {
|
||||
const project = await Project.fromDirectory(input.directory)
|
||||
existing = {
|
||||
directory: input.directory,
|
||||
worktree: project.worktree,
|
||||
project,
|
||||
}
|
||||
Log.Default.info("creating instance", { directory: input.directory })
|
||||
existing = iife(async () => {
|
||||
const project = await Project.fromDirectory(input.directory)
|
||||
const ctx = {
|
||||
directory: input.directory,
|
||||
worktree: project.worktree,
|
||||
project,
|
||||
}
|
||||
await context.provide(ctx, async () => {
|
||||
await input.init?.()
|
||||
})
|
||||
return ctx
|
||||
})
|
||||
cache.set(input.directory, existing)
|
||||
}
|
||||
return context.provide(existing, async () => {
|
||||
if (!cache.has(input.directory)) {
|
||||
cache.set(input.directory, existing)
|
||||
await input.init?.()
|
||||
}
|
||||
const ctx = await existing
|
||||
return context.provide(ctx, async () => {
|
||||
return input.fn()
|
||||
})
|
||||
},
|
||||
@@ -48,7 +54,7 @@ export const Instance = {
|
||||
},
|
||||
async disposeAll() {
|
||||
for (const [_key, value] of cache) {
|
||||
await context.provide(value, async () => {
|
||||
await context.provide(await value, async () => {
|
||||
await Instance.dispose()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -41,18 +41,28 @@ export namespace Project {
|
||||
return project
|
||||
}
|
||||
let worktree = path.dirname(git)
|
||||
const [id] = await $`git rev-list --max-parents=0 --all`
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.cwd(worktree)
|
||||
const timer = log.time("git.rev-parse")
|
||||
let id = await Bun.file(path.join(git, "opencode"))
|
||||
.text()
|
||||
.then((x) =>
|
||||
x
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.map((x) => x.trim())
|
||||
.toSorted(),
|
||||
)
|
||||
.then((x) => x.trim())
|
||||
.catch(() => {})
|
||||
if (!id) {
|
||||
const roots = await $`git rev-list --max-parents=0 --all`
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.cwd(worktree)
|
||||
.text()
|
||||
.then((x) =>
|
||||
x
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.map((x) => x.trim())
|
||||
.toSorted(),
|
||||
)
|
||||
id = roots[0]
|
||||
if (id) Bun.file(path.join(git, "opencode")).write(id)
|
||||
}
|
||||
timer.stop()
|
||||
if (!id) {
|
||||
const project: Info = {
|
||||
id: "global",
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Auth } from "../auth"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Global } from "../global"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { iife } from "@/util/iife"
|
||||
|
||||
export namespace Provider {
|
||||
const log = Log.create({ service: "provider" })
|
||||
@@ -193,7 +194,7 @@ export namespace Provider {
|
||||
},
|
||||
"google-vertex-anthropic": async () => {
|
||||
const project = process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCP_PROJECT"] ?? process.env["GCLOUD_PROJECT"]
|
||||
const location = process.env["GOOGLE_CLOUD_LOCATION"] ?? process.env["VERTEX_LOCATION"] ?? "us-east5"
|
||||
const location = process.env["GOOGLE_CLOUD_LOCATION"] ?? process.env["VERTEX_LOCATION"] ?? "global"
|
||||
const autoload = Boolean(project)
|
||||
if (!autoload) return { autoload: false }
|
||||
return {
|
||||
@@ -211,6 +212,7 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
const state = Instance.state(async () => {
|
||||
using _ = log.time("state")
|
||||
const config = await Config.get()
|
||||
const database = await ModelsDev.get()
|
||||
|
||||
@@ -288,10 +290,15 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
for (const [modelID, model] of Object.entries(provider.models ?? {})) {
|
||||
const existing = parsed.models[modelID]
|
||||
const existing = parsed.models[model.id ?? modelID]
|
||||
const name = iife(() => {
|
||||
if (model.name) return model.name
|
||||
if (model.id && model.id !== modelID) return modelID
|
||||
return existing?.name ?? modelID
|
||||
})
|
||||
const parsedModel: ModelsDev.Model = {
|
||||
id: modelID,
|
||||
name: model.name ?? existing?.name ?? modelID,
|
||||
name,
|
||||
release_date: model.release_date ?? existing?.release_date,
|
||||
attachment: model.attachment ?? existing?.attachment ?? false,
|
||||
reasoning: model.reasoning ?? existing?.reasoning ?? false,
|
||||
|
||||
@@ -108,12 +108,13 @@ export namespace Server {
|
||||
path: c.req.path,
|
||||
})
|
||||
}
|
||||
const start = Date.now()
|
||||
const timer = log.time("request", {
|
||||
method: c.req.method,
|
||||
path: c.req.path,
|
||||
})
|
||||
await next()
|
||||
if (!skipLogging) {
|
||||
log.info("response", {
|
||||
duration: Date.now() - start,
|
||||
})
|
||||
timer.stop()
|
||||
}
|
||||
})
|
||||
.use(async (c, next) => {
|
||||
@@ -162,6 +163,7 @@ export namespace Server {
|
||||
return c.json(await Config.get())
|
||||
},
|
||||
)
|
||||
|
||||
.patch(
|
||||
"/config",
|
||||
describeRoute({
|
||||
@@ -1075,6 +1077,7 @@ export namespace Server {
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
using _ = log.time("providers")
|
||||
const providers = await Provider.list().then((x) => mapValues(x, (item) => item.info))
|
||||
return c.json({
|
||||
providers: Object.values(providers),
|
||||
|
||||
@@ -267,15 +267,24 @@ export namespace SessionCompaction {
|
||||
max: maxRetries,
|
||||
})
|
||||
if (result.shouldRetry) {
|
||||
const start = Date.now()
|
||||
for (let retry = 1; retry < maxRetries; retry++) {
|
||||
const lastRetryPart = result.parts.findLast((p): p is MessageV2.RetryPart => p.type === "retry")
|
||||
|
||||
if (lastRetryPart) {
|
||||
const delayMs = SessionRetry.getRetryDelayInMs(lastRetryPart.error, retry)
|
||||
const delayMs = SessionRetry.getBoundedDelay({
|
||||
error: lastRetryPart.error,
|
||||
attempt: retry,
|
||||
startTime: start,
|
||||
})
|
||||
if (!delayMs) {
|
||||
break
|
||||
}
|
||||
|
||||
log.info("retrying with backoff", {
|
||||
attempt: retry,
|
||||
delayMs,
|
||||
elapsed: Date.now() - start,
|
||||
})
|
||||
|
||||
const stop = await SessionRetry.sleep(delayMs, signal)
|
||||
|
||||
@@ -378,8 +378,14 @@ export namespace Session {
|
||||
metadata: z.custom<ProviderMetadata>().optional(),
|
||||
}),
|
||||
(input) => {
|
||||
const cachedInputTokens = input.usage.cachedInputTokens ?? 0
|
||||
const excludesCachedTokens = !!(input.metadata?.["anthropic"] || input.metadata?.["bedrock"])
|
||||
const adjustedInputTokens = excludesCachedTokens
|
||||
? (input.usage.inputTokens ?? 0)
|
||||
: (input.usage.inputTokens ?? 0) - cachedInputTokens
|
||||
|
||||
const tokens = {
|
||||
input: input.usage.inputTokens ?? 0,
|
||||
input: adjustedInputTokens,
|
||||
output: input.usage.outputTokens ?? 0,
|
||||
reasoning: input.usage?.reasoningTokens ?? 0,
|
||||
cache: {
|
||||
@@ -387,7 +393,7 @@ export namespace Session {
|
||||
// @ts-expect-error
|
||||
input.metadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
|
||||
0) as number,
|
||||
read: input.usage.cachedInputTokens ?? 0,
|
||||
read: cachedInputTokens,
|
||||
},
|
||||
}
|
||||
return {
|
||||
@@ -396,6 +402,9 @@ export namespace Session {
|
||||
.add(new Decimal(tokens.output).mul(input.model.cost?.output ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.cache.read).mul(input.model.cost?.cache_read ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.cache.write).mul(input.model.cost?.cache_write ?? 0).div(1_000_000))
|
||||
// TODO: update models.dev to have better pricing model, for now:
|
||||
// charge reasoning tokens at the same rate as output tokens
|
||||
.add(new Decimal(tokens.reasoning).mul(input.model.cost?.output ?? 0).div(1_000_000))
|
||||
.toNumber(),
|
||||
tokens,
|
||||
}
|
||||
|
||||
@@ -145,6 +145,54 @@ export namespace SessionPrompt {
|
||||
),
|
||||
})
|
||||
export type PromptInput = z.infer<typeof PromptInput>
|
||||
|
||||
export async function resolvePromptParts(template: string): Promise<PromptInput["parts"]> {
|
||||
const parts: PromptInput["parts"] = [
|
||||
{
|
||||
type: "text",
|
||||
text: template,
|
||||
},
|
||||
]
|
||||
const files = ConfigMarkdown.files(template)
|
||||
await Promise.all(
|
||||
files.map(async (match) => {
|
||||
const name = match[1]
|
||||
const filepath = name.startsWith("~/")
|
||||
? path.join(os.homedir(), name.slice(2))
|
||||
: path.resolve(Instance.worktree, name)
|
||||
|
||||
const stats = await fs.stat(filepath).catch(() => undefined)
|
||||
if (!stats) {
|
||||
const agent = await Agent.get(name)
|
||||
if (agent) {
|
||||
parts.push({
|
||||
type: "agent",
|
||||
name: agent.name,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
parts.push({
|
||||
type: "file",
|
||||
url: `file://${filepath}`,
|
||||
filename: name,
|
||||
mime: "application/x-directory",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
parts.push({
|
||||
type: "file",
|
||||
url: `file://${filepath}`,
|
||||
filename: name,
|
||||
mime: "text/plain",
|
||||
})
|
||||
}),
|
||||
)
|
||||
return parts
|
||||
}
|
||||
export async function prompt(input: PromptInput): Promise<MessageV2.WithParts> {
|
||||
const l = log.clone().tag("session", input.sessionID)
|
||||
l.info("prompt")
|
||||
@@ -206,6 +254,8 @@ export namespace SessionPrompt {
|
||||
const params = await Plugin.trigger(
|
||||
"chat.params",
|
||||
{
|
||||
sessionID: input.sessionID,
|
||||
agent: agent.name,
|
||||
model: model.info,
|
||||
provider: await Provider.getProvider(model.providerID),
|
||||
message: userMsg,
|
||||
@@ -353,15 +403,24 @@ export namespace SessionPrompt {
|
||||
max: maxRetries,
|
||||
})
|
||||
if (result.shouldRetry) {
|
||||
const start = Date.now()
|
||||
for (let retry = 1; retry < maxRetries; retry++) {
|
||||
const lastRetryPart = result.parts.findLast((p): p is MessageV2.RetryPart => p.type === "retry")
|
||||
|
||||
if (lastRetryPart) {
|
||||
const delayMs = SessionRetry.getRetryDelayInMs(lastRetryPart.error, retry)
|
||||
const delayMs = SessionRetry.getBoundedDelay({
|
||||
error: lastRetryPart.error,
|
||||
attempt: retry,
|
||||
startTime: start,
|
||||
})
|
||||
if (!delayMs) {
|
||||
break
|
||||
}
|
||||
|
||||
log.info("retrying with backoff", {
|
||||
attempt: retry,
|
||||
delayMs,
|
||||
elapsed: Date.now() - start,
|
||||
})
|
||||
|
||||
const stop = await SessionRetry.sleep(delayMs, abort.signal)
|
||||
@@ -882,7 +941,12 @@ export namespace SessionPrompt {
|
||||
|
||||
await Plugin.trigger(
|
||||
"chat.message",
|
||||
{},
|
||||
{
|
||||
sessionID: input.sessionID,
|
||||
agent: input.agent,
|
||||
model: input.model,
|
||||
messageID: input.messageID,
|
||||
},
|
||||
{
|
||||
message: info,
|
||||
parts,
|
||||
@@ -1099,18 +1163,21 @@ export namespace SessionPrompt {
|
||||
JSON.stringify(p.state.input) === JSON.stringify(value.input),
|
||||
)
|
||||
) {
|
||||
await Permission.ask({
|
||||
type: "doom-loop",
|
||||
pattern: value.toolName,
|
||||
sessionID: assistantMsg.sessionID,
|
||||
messageID: assistantMsg.id,
|
||||
callID: value.toolCallId,
|
||||
title: `Possible doom loop: "${value.toolName}" called ${DOOM_LOOP_THRESHOLD} times with identical arguments`,
|
||||
metadata: {
|
||||
tool: value.toolName,
|
||||
input: value.input,
|
||||
},
|
||||
})
|
||||
const permission = await Agent.get(input.agent).then((x) => x.permission)
|
||||
if (permission.doom_loop === "ask") {
|
||||
await Permission.ask({
|
||||
type: "doom_loop",
|
||||
pattern: value.toolName,
|
||||
sessionID: assistantMsg.sessionID,
|
||||
messageID: assistantMsg.id,
|
||||
callID: value.toolCallId,
|
||||
title: `Possible doom loop: "${value.toolName}" called ${DOOM_LOOP_THRESHOLD} times with identical arguments`,
|
||||
metadata: {
|
||||
tool: value.toolName,
|
||||
input: value.input,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
@@ -1586,51 +1653,7 @@ export namespace SessionPrompt {
|
||||
}
|
||||
template = template.trim()
|
||||
|
||||
const parts = [
|
||||
{
|
||||
type: "text",
|
||||
text: template,
|
||||
},
|
||||
] as PromptInput["parts"]
|
||||
|
||||
const files = ConfigMarkdown.files(template)
|
||||
await Promise.all(
|
||||
files.map(async (match) => {
|
||||
const name = match[1]
|
||||
const filepath = name.startsWith("~/")
|
||||
? path.join(os.homedir(), name.slice(2))
|
||||
: path.resolve(Instance.worktree, name)
|
||||
|
||||
const stats = await fs.stat(filepath).catch(() => undefined)
|
||||
if (!stats) {
|
||||
const agent = await Agent.get(name)
|
||||
if (agent) {
|
||||
parts.push({
|
||||
type: "agent",
|
||||
name: agent.name,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
parts.push({
|
||||
type: "file",
|
||||
url: `file://${filepath}`,
|
||||
filename: name,
|
||||
mime: "application/x-directory",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
parts.push({
|
||||
type: "file",
|
||||
url: `file://${filepath}`,
|
||||
filename: name,
|
||||
mime: "text/plain",
|
||||
})
|
||||
}),
|
||||
)
|
||||
const parts = await resolvePromptParts(template)
|
||||
|
||||
const model = await (async () => {
|
||||
if (command.model) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { iife } from "@/util/iife"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
|
||||
export namespace SessionRetry {
|
||||
export const RETRY_INITIAL_DELAY = 2000
|
||||
export const RETRY_BACKOFF_FACTOR = 2
|
||||
export const RETRY_MAX_DELAY = 600_000 // 10 minutes
|
||||
|
||||
export async function sleep(ms: number, signal: AbortSignal): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -18,40 +20,57 @@ export namespace SessionRetry {
|
||||
})
|
||||
}
|
||||
|
||||
export function getRetryDelayInMs(error: MessageV2.APIError, attempt: number): number {
|
||||
const base = RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1)
|
||||
const headers = error.data.responseHeaders
|
||||
if (!headers) return base
|
||||
export function getRetryDelayInMs(error: MessageV2.APIError, attempt: number) {
|
||||
const delay = iife(() => {
|
||||
const headers = error.data.responseHeaders
|
||||
if (headers) {
|
||||
const retryAfterMs = headers["retry-after-ms"]
|
||||
if (retryAfterMs) {
|
||||
const parsedMs = Number.parseFloat(retryAfterMs)
|
||||
if (!Number.isNaN(parsedMs)) {
|
||||
return parsedMs
|
||||
}
|
||||
}
|
||||
|
||||
const retryAfterMs = headers["retry-after-ms"]
|
||||
if (retryAfterMs) {
|
||||
const parsed = Number.parseFloat(retryAfterMs)
|
||||
const normalized = normalizeDelay({ base, candidate: parsed })
|
||||
if (normalized != null) return normalized
|
||||
}
|
||||
const retryAfter = headers["retry-after"]
|
||||
if (retryAfter) {
|
||||
const parsedSeconds = Number.parseFloat(retryAfter)
|
||||
if (!Number.isNaN(parsedSeconds)) {
|
||||
// convert seconds to milliseconds
|
||||
return Math.ceil(parsedSeconds * 1000)
|
||||
}
|
||||
// Try parsing as HTTP date format
|
||||
const parsed = Date.parse(retryAfter) - Date.now()
|
||||
if (!Number.isNaN(parsed) && parsed > 0) {
|
||||
return Math.ceil(parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const retryAfter = headers["retry-after"]
|
||||
if (!retryAfter) return base
|
||||
return RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1)
|
||||
})
|
||||
|
||||
const seconds = Number.parseFloat(retryAfter)
|
||||
if (!Number.isNaN(seconds)) {
|
||||
const normalized = normalizeDelay({ base, candidate: seconds * 1000 })
|
||||
if (normalized != null) return normalized
|
||||
return base
|
||||
}
|
||||
// dont retry if wait is too far from now
|
||||
if (delay > RETRY_MAX_DELAY) return undefined
|
||||
|
||||
const dateMs = Date.parse(retryAfter) - Date.now()
|
||||
const normalized = normalizeDelay({ base, candidate: dateMs })
|
||||
if (normalized != null) return normalized
|
||||
|
||||
return base
|
||||
return delay
|
||||
}
|
||||
|
||||
function normalizeDelay(input: { base: number; candidate: number }): number | undefined {
|
||||
if (Number.isNaN(input.candidate)) return undefined
|
||||
if (input.candidate < 0) return undefined
|
||||
if (input.candidate < 60_000) return input.candidate
|
||||
if (input.candidate < input.base) return input.candidate
|
||||
return undefined
|
||||
export function getBoundedDelay(input: {
|
||||
error: MessageV2.APIError
|
||||
attempt: number
|
||||
startTime: number
|
||||
maxDuration?: number
|
||||
}) {
|
||||
const elapsed = Date.now() - input.startTime
|
||||
const maxDuration = input.maxDuration ?? RETRY_MAX_DELAY
|
||||
const remaining = maxDuration - elapsed
|
||||
|
||||
if (remaining <= 0) return undefined
|
||||
|
||||
const delay = getRetryDelayInMs(input.error, input.attempt)
|
||||
if (!delay) return undefined
|
||||
|
||||
return Math.min(delay, remaining)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,15 +143,16 @@ export namespace Snapshot {
|
||||
export async function diffFull(from: string, to: string): Promise<FileDiff[]> {
|
||||
const git = gitdir()
|
||||
const result: FileDiff[] = []
|
||||
for await (const line of $`git --git-dir=${git} diff --numstat ${from} ${to} -- .`
|
||||
for await (const line of $`git --git-dir=${git} diff --no-renames --numstat ${from} ${to} -- .`
|
||||
.quiet()
|
||||
.cwd(Instance.directory)
|
||||
.nothrow()
|
||||
.lines()) {
|
||||
if (!line) continue
|
||||
const [additions, deletions, file] = line.split("\t")
|
||||
const before = await $`git --git-dir=${git} show ${from}:${file}`.quiet().nothrow().text()
|
||||
const after = await $`git --git-dir=${git} show ${to}:${file}`.quiet().nothrow().text()
|
||||
const isBinaryFile = additions === "-" && deletions === "-"
|
||||
const before = isBinaryFile ? "" : await $`git --git-dir=${git} show ${from}:${file}`.quiet().nothrow().text()
|
||||
const after = isBinaryFile ? "" : await $`git --git-dir=${git} show ${to}:${file}`.quiet().nothrow().text()
|
||||
result.push({
|
||||
file,
|
||||
before,
|
||||
|
||||
@@ -170,7 +170,8 @@ export namespace Storage {
|
||||
const target = path.join(dir, ...key) + ".json"
|
||||
return withErrorHandling(async () => {
|
||||
using _ = await Lock.read(target)
|
||||
return Bun.file(target).json() as Promise<T>
|
||||
const result = await Bun.file(target).json()
|
||||
return result as T
|
||||
})
|
||||
}
|
||||
|
||||
@@ -178,7 +179,7 @@ export namespace Storage {
|
||||
const dir = await state().then((x) => x.dir)
|
||||
const target = path.join(dir, ...key) + ".json"
|
||||
return withErrorHandling(async () => {
|
||||
using _ = await Lock.write("storage")
|
||||
using _ = await Lock.write(target)
|
||||
const content = await Bun.file(target).json()
|
||||
fn(content)
|
||||
await Bun.write(target, JSON.stringify(content, null, 2))
|
||||
@@ -190,7 +191,7 @@ export namespace Storage {
|
||||
const dir = await state().then((x) => x.dir)
|
||||
const target = path.join(dir, ...key) + ".json"
|
||||
return withErrorHandling(async () => {
|
||||
using _ = await Lock.write("storage")
|
||||
using _ = await Lock.write(target)
|
||||
await Bun.write(target, JSON.stringify(content, null, 2))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import z from "zod"
|
||||
import { Tool } from "./tool"
|
||||
import DESCRIPTION from "./codesearch.txt"
|
||||
import { Config } from "../config/config"
|
||||
import { Permission } from "../permission"
|
||||
|
||||
const API_CONFIG = {
|
||||
BASE_URL: "https://mcp.exa.ai",
|
||||
ENDPOINTS: {
|
||||
CONTEXT: "/mcp",
|
||||
},
|
||||
} as const
|
||||
|
||||
interface McpCodeRequest {
|
||||
jsonrpc: string
|
||||
id: number
|
||||
method: string
|
||||
params: {
|
||||
name: string
|
||||
arguments: {
|
||||
query: string
|
||||
tokensNum: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface McpCodeResponse {
|
||||
jsonrpc: string
|
||||
result: {
|
||||
content: Array<{
|
||||
type: string
|
||||
text: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export const CodeSearchTool = Tool.define("codesearch", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
query: z
|
||||
.string()
|
||||
.describe(
|
||||
"Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'",
|
||||
),
|
||||
tokensNum: z
|
||||
.number()
|
||||
.min(1000)
|
||||
.max(50000)
|
||||
.default(5000)
|
||||
.describe(
|
||||
"Number of tokens to return (1000-50000). Default is 5000 tokens. Adjust this value based on how much context you need - use lower values for focused queries and higher values for comprehensive documentation.",
|
||||
),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const cfg = await Config.get()
|
||||
if (cfg.permission?.webfetch === "ask")
|
||||
await Permission.ask({
|
||||
type: "codesearch",
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: "Search code for: " + params.query,
|
||||
metadata: {
|
||||
query: params.query,
|
||||
tokensNum: params.tokensNum,
|
||||
},
|
||||
})
|
||||
|
||||
const codeRequest: McpCodeRequest = {
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
method: "tools/call",
|
||||
params: {
|
||||
name: "get_code_context_exa",
|
||||
arguments: {
|
||||
query: params.query,
|
||||
tokensNum: params.tokensNum || 5000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 30000)
|
||||
|
||||
try {
|
||||
const headers: Record<string, string> = {
|
||||
accept: "application/json, text/event-stream",
|
||||
"content-type": "application/json",
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.CONTEXT}`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(codeRequest),
|
||||
signal: AbortSignal.any([controller.signal, ctx.abort]),
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Code search error (${response.status}): ${errorText}`)
|
||||
}
|
||||
|
||||
const responseText = await response.text()
|
||||
|
||||
// Parse SSE response
|
||||
const lines = responseText.split("\n")
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("data: ")) {
|
||||
const data: McpCodeResponse = JSON.parse(line.substring(6))
|
||||
if (data.result && data.result.content && data.result.content.length > 0) {
|
||||
return {
|
||||
output: data.result.content[0].text,
|
||||
title: `Code search: ${params.query}`,
|
||||
metadata: {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
output:
|
||||
"No code snippets or documentation found. Please try a different query, be more specific about the library or programming concept, or check the spelling of framework names.",
|
||||
title: `Code search: ${params.query}`,
|
||||
metadata: {},
|
||||
}
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (error instanceof Error && error.name === "AbortError") {
|
||||
throw new Error("Code search request timed out")
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
- Search and get relevant context for any programming task using Exa Code API
|
||||
- Provides the highest quality and freshest context for libraries, SDKs, and APIs
|
||||
- Use this tool for ANY question or task related to programming
|
||||
- Returns comprehensive code examples, documentation, and API references
|
||||
- Optimized for finding specific programming patterns and solutions
|
||||
|
||||
Usage notes:
|
||||
- Adjustable token count (1000-50000) for focused or comprehensive results
|
||||
- Default 5000 tokens provides balanced context for most queries
|
||||
- Use lower values for specific questions, higher values for comprehensive documentation
|
||||
- Supports queries about frameworks, libraries, APIs, and programming concepts
|
||||
- Examples: 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware'
|
||||
@@ -35,24 +35,27 @@ export const EditTool = Tool.define("edit", {
|
||||
throw new Error("oldString and newString must be different")
|
||||
}
|
||||
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
|
||||
const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
|
||||
if (!Filesystem.contains(Instance.directory, filePath)) {
|
||||
const parentDir = path.dirname(filePath)
|
||||
await Permission.ask({
|
||||
type: "external-directory",
|
||||
pattern: parentDir,
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: `Edit file outside working directory: ${filePath}`,
|
||||
metadata: {
|
||||
filepath: filePath,
|
||||
parentDir,
|
||||
},
|
||||
})
|
||||
if (agent.permission.external_directory === "ask") {
|
||||
await Permission.ask({
|
||||
type: "external_directory",
|
||||
pattern: parentDir,
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: `Edit file outside working directory: ${filePath}`,
|
||||
metadata: {
|
||||
filepath: filePath,
|
||||
parentDir,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
let diff = ""
|
||||
let contentOld = ""
|
||||
let contentNew = ""
|
||||
|
||||
@@ -55,18 +55,20 @@ export const PatchTool = Tool.define("patch", {
|
||||
|
||||
if (!Filesystem.contains(Instance.directory, filePath)) {
|
||||
const parentDir = path.dirname(filePath)
|
||||
await Permission.ask({
|
||||
type: "external-directory",
|
||||
pattern: parentDir,
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: `Patch file outside working directory: ${filePath}`,
|
||||
metadata: {
|
||||
filepath: filePath,
|
||||
parentDir,
|
||||
},
|
||||
})
|
||||
if (agent.permission.external_directory === "ask") {
|
||||
await Permission.ask({
|
||||
type: "external_directory",
|
||||
pattern: parentDir,
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: `Patch file outside working directory: ${filePath}`,
|
||||
metadata: {
|
||||
filepath: filePath,
|
||||
parentDir,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
switch (hunk.type) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Instance } from "../project/instance"
|
||||
import { Provider } from "../provider/provider"
|
||||
import { Identifier } from "../id/id"
|
||||
import { Permission } from "../permission"
|
||||
import { Agent } from "@/agent/agent"
|
||||
|
||||
const DEFAULT_READ_LIMIT = 2000
|
||||
const MAX_LINE_LENGTH = 2000
|
||||
@@ -27,21 +28,24 @@ export const ReadTool = Tool.define("read", {
|
||||
filepath = path.join(process.cwd(), filepath)
|
||||
}
|
||||
const title = path.relative(Instance.worktree, filepath)
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
|
||||
if (!ctx.extra?.["bypassCwdCheck"] && !Filesystem.contains(Instance.directory, filepath)) {
|
||||
const parentDir = path.dirname(filepath)
|
||||
await Permission.ask({
|
||||
type: "external-directory",
|
||||
pattern: parentDir,
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: `Access file outside working directory: ${filepath}`,
|
||||
metadata: {
|
||||
filepath,
|
||||
parentDir,
|
||||
},
|
||||
})
|
||||
if (agent.permission.external_directory === "ask") {
|
||||
await Permission.ask({
|
||||
type: "external_directory",
|
||||
pattern: parentDir,
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: `Access file outside working directory: ${filepath}`,
|
||||
metadata: {
|
||||
filepath,
|
||||
parentDir,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const file = Bun.file(filepath)
|
||||
|
||||
@@ -17,6 +17,9 @@ import path from "path"
|
||||
import { type ToolDefinition } from "@opencode-ai/plugin"
|
||||
import z from "zod"
|
||||
import { Plugin } from "../plugin"
|
||||
import { WebSearchTool } from "./websearch"
|
||||
import { CodeSearchTool } from "./codesearch"
|
||||
import { Flag } from "@/flag/flag"
|
||||
|
||||
export namespace ToolRegistry {
|
||||
export const state = Instance.state(async () => {
|
||||
@@ -91,6 +94,7 @@ export namespace ToolRegistry {
|
||||
TodoWriteTool,
|
||||
TodoReadTool,
|
||||
TaskTool,
|
||||
...(Flag.OPENCODE_EXPERIMENTAL_EXA ? [WebSearchTool, CodeSearchTool] : []),
|
||||
...custom,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ export const TaskTool = Tool.define("task", async () => {
|
||||
ctx.abort.addEventListener("abort", () => {
|
||||
SessionLock.abort(session.id)
|
||||
})
|
||||
const promptParts = await SessionPrompt.resolvePromptParts(params.prompt)
|
||||
const result = await SessionPrompt.prompt({
|
||||
messageID,
|
||||
sessionID: session.id,
|
||||
@@ -79,13 +80,7 @@ export const TaskTool = Tool.define("task", async () => {
|
||||
task: false,
|
||||
...agent.tools,
|
||||
},
|
||||
parts: [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
type: "text",
|
||||
text: params.prompt,
|
||||
},
|
||||
],
|
||||
parts: promptParts,
|
||||
})
|
||||
unsub()
|
||||
let all
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import z from "zod"
|
||||
import { Tool } from "./tool"
|
||||
import DESCRIPTION from "./websearch.txt"
|
||||
import { Config } from "../config/config"
|
||||
import { Permission } from "../permission"
|
||||
|
||||
const API_CONFIG = {
|
||||
BASE_URL: "https://mcp.exa.ai",
|
||||
ENDPOINTS: {
|
||||
SEARCH: "/mcp",
|
||||
},
|
||||
DEFAULT_NUM_RESULTS: 8,
|
||||
} as const
|
||||
|
||||
interface McpSearchRequest {
|
||||
jsonrpc: string
|
||||
id: number
|
||||
method: string
|
||||
params: {
|
||||
name: string
|
||||
arguments: {
|
||||
query: string
|
||||
numResults?: number
|
||||
livecrawl?: "fallback" | "preferred"
|
||||
type?: "auto" | "fast" | "deep"
|
||||
contextMaxCharacters?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface McpSearchResponse {
|
||||
jsonrpc: string
|
||||
result: {
|
||||
content: Array<{
|
||||
type: string
|
||||
text: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export const WebSearchTool = Tool.define("websearch", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
query: z.string().describe("Websearch query"),
|
||||
numResults: z.number().optional().describe("Number of search results to return (default: 8)"),
|
||||
livecrawl: z
|
||||
.enum(["fallback", "preferred"])
|
||||
.optional()
|
||||
.describe(
|
||||
"Live crawl mode - 'fallback': use live crawling as backup if cached content unavailable, 'preferred': prioritize live crawling (default: 'fallback')",
|
||||
),
|
||||
type: z
|
||||
.enum(["auto", "fast", "deep"])
|
||||
.optional()
|
||||
.describe("Search type - 'auto': balanced search (default), 'fast': quick results, 'deep': comprehensive search"),
|
||||
contextMaxCharacters: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Maximum characters for context string optimized for LLMs (default: 10000)"),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const cfg = await Config.get()
|
||||
if (cfg.permission?.webfetch === "ask")
|
||||
await Permission.ask({
|
||||
type: "websearch",
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: "Search web for: " + params.query,
|
||||
metadata: {
|
||||
query: params.query,
|
||||
numResults: params.numResults,
|
||||
livecrawl: params.livecrawl,
|
||||
type: params.type,
|
||||
contextMaxCharacters: params.contextMaxCharacters,
|
||||
},
|
||||
})
|
||||
|
||||
const searchRequest: McpSearchRequest = {
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
method: "tools/call",
|
||||
params: {
|
||||
name: "web_search_exa",
|
||||
arguments: {
|
||||
query: params.query,
|
||||
type: params.type || "auto",
|
||||
numResults: params.numResults || API_CONFIG.DEFAULT_NUM_RESULTS,
|
||||
livecrawl: params.livecrawl || "fallback",
|
||||
contextMaxCharacters: params.contextMaxCharacters,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 25000)
|
||||
|
||||
try {
|
||||
const headers: Record<string, string> = {
|
||||
accept: "application/json, text/event-stream",
|
||||
"content-type": "application/json",
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SEARCH}`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(searchRequest),
|
||||
signal: AbortSignal.any([controller.signal, ctx.abort]),
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Search error (${response.status}): ${errorText}`)
|
||||
}
|
||||
|
||||
const responseText = await response.text()
|
||||
|
||||
// Parse SSE response
|
||||
const lines = responseText.split("\n")
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("data: ")) {
|
||||
const data: McpSearchResponse = JSON.parse(line.substring(6))
|
||||
if (data.result && data.result.content && data.result.content.length > 0) {
|
||||
return {
|
||||
output: data.result.content[0].text,
|
||||
title: `Web search: ${params.query}`,
|
||||
metadata: {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
output: "No search results found. Please try a different query.",
|
||||
title: `Web search: ${params.query}`,
|
||||
metadata: {},
|
||||
}
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (error instanceof Error && error.name === "AbortError") {
|
||||
throw new Error("Search request timed out")
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -1,11 +1,11 @@
|
||||
|
||||
- Allows opencode to search the web and use the results to inform responses
|
||||
- Search the web using Exa AI - performs real-time web searches and can scrape content from specific URLs
|
||||
- Provides up-to-date information for current events and recent data
|
||||
- Returns search result information formatted as search result blocks
|
||||
- Use this tool for accessing information beyond Claude's knowledge cutoff
|
||||
- Supports configurable result counts and returns the content from the most relevant websites
|
||||
- Use this tool for accessing information beyond knowledge cutoff
|
||||
- Searches are performed automatically within a single API call
|
||||
|
||||
Usage notes:
|
||||
- Domain filtering is supported to include or block specific websites
|
||||
- Web search is only available in the US
|
||||
|
||||
- Supports live crawling modes: 'fallback' (backup if cached unavailable) or 'preferred' (prioritize live crawling)
|
||||
- Search types: 'auto' (balanced), 'fast' (quick results), 'deep' (comprehensive search)
|
||||
- Configurable context length for optimal LLM integration
|
||||
- Domain filtering and advanced search options available
|
||||
|
||||
@@ -18,28 +18,31 @@ export const WriteTool = Tool.define("write", {
|
||||
filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
|
||||
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
|
||||
if (!Filesystem.contains(Instance.directory, filepath)) {
|
||||
const parentDir = path.dirname(filepath)
|
||||
await Permission.ask({
|
||||
type: "external-directory",
|
||||
pattern: parentDir,
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: `Write file outside working directory: ${filepath}`,
|
||||
metadata: {
|
||||
filepath,
|
||||
parentDir,
|
||||
},
|
||||
})
|
||||
if (agent.permission.external_directory === "ask") {
|
||||
await Permission.ask({
|
||||
type: "external_directory",
|
||||
pattern: parentDir,
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: `Write file outside working directory: ${filepath}`,
|
||||
metadata: {
|
||||
filepath,
|
||||
parentDir,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const file = Bun.file(filepath)
|
||||
const exists = await file.exists()
|
||||
if (exists) await FileTime.assert(ctx.sessionID, filepath)
|
||||
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
if (agent.permission.edit === "ask")
|
||||
await Permission.ask({
|
||||
type: "write",
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
export namespace Color {
|
||||
export function isValidHex(hex?: string): hex is string {
|
||||
if (!hex) return false
|
||||
return /^#[0-9a-fA-F]{6}$/.test(hex)
|
||||
}
|
||||
|
||||
export function hexToRgb(hex: string): { r: number; g: number; b: number } {
|
||||
const r = parseInt(hex.slice(1, 3), 16)
|
||||
const g = parseInt(hex.slice(3, 5), 16)
|
||||
const b = parseInt(hex.slice(5, 7), 16)
|
||||
return { r, g, b }
|
||||
}
|
||||
|
||||
export function hexToAnsiBold(hex?: string): string | undefined {
|
||||
if (!isValidHex(hex)) return undefined
|
||||
const { r, g, b } = hexToRgb(hex)
|
||||
return `\x1b[38;2;${r};${g};${b}m\x1b[1m`
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { test, expect } from "bun:test"
|
||||
import path from "path"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { Config } from "../../src/config/config"
|
||||
import { Agent as AgentSvc } from "../../src/agent/agent"
|
||||
import { Color } from "../../src/util/color"
|
||||
|
||||
test("agent color parsed from project config", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
agent: {
|
||||
build: { color: "#FFA500" },
|
||||
},
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const cfg = await Config.get()
|
||||
expect(cfg.agent?.["build"]?.color).toBe("#FFA500")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("Agent.get includes color from config", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
agent: {
|
||||
plan: { color: "#A855F7" },
|
||||
},
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const plan = await AgentSvc.get("plan")
|
||||
expect(plan?.color).toBe("#A855F7")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("Color.hexToAnsiBold converts valid hex to ANSI", () => {
|
||||
const result = Color.hexToAnsiBold("#FFA500")
|
||||
expect(result).toBe("\x1b[38;2;255;165;0m\x1b[1m")
|
||||
})
|
||||
|
||||
test("Color.hexToAnsiBold returns undefined for invalid hex", () => {
|
||||
expect(Color.hexToAnsiBold(undefined)).toBeUndefined()
|
||||
expect(Color.hexToAnsiBold("")).toBeUndefined()
|
||||
expect(Color.hexToAnsiBold("#FFF")).toBeUndefined()
|
||||
expect(Color.hexToAnsiBold("FFA500")).toBeUndefined()
|
||||
expect(Color.hexToAnsiBold("#GGGGGG")).toBeUndefined()
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user