mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-15 17:08:21 -04:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0fd15e9e4 | |||
| 4f2514d9f5 | |||
| 5d9801f206 | |||
| a300cd6465 | |||
| 399699469b | |||
| cacf9df24c | |||
| 2844086752 |
@@ -92,7 +92,7 @@ jobs:
|
|||||||
|
|
||||||
publish-tauri:
|
publish-tauri:
|
||||||
needs: publish
|
needs: publish
|
||||||
continue-on-error: false
|
continue-on-error: true
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ on:
|
|||||||
- "packages/*/package.json"
|
- "packages/*/package.json"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-linux:
|
update:
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
env:
|
env:
|
||||||
@@ -47,14 +47,14 @@ jobs:
|
|||||||
nix flake update
|
nix flake update
|
||||||
echo "✅ flake.lock updated successfully"
|
echo "✅ flake.lock updated successfully"
|
||||||
|
|
||||||
- name: Update node_modules hash for x86_64-linux
|
- name: Update node_modules hash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
echo "🔄 Updating node_modules hash for x86_64-linux..."
|
echo "🔄 Updating node_modules hash..."
|
||||||
nix/scripts/update-hashes.sh
|
nix/scripts/update-hashes.sh
|
||||||
echo "✅ node_modules hash for x86_64-linux updated successfully"
|
echo "✅ node_modules hash updated successfully"
|
||||||
|
|
||||||
- name: Commit Linux hash changes
|
- name: Commit hash changes
|
||||||
env:
|
env:
|
||||||
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
summarize() {
|
summarize() {
|
||||||
local status="$1"
|
local status="$1"
|
||||||
{
|
{
|
||||||
echo "### Nix Hash Update (x86_64-linux)"
|
echo "### Nix Hash Update"
|
||||||
echo ""
|
echo ""
|
||||||
echo "- ref: ${GITHUB_REF_NAME}"
|
echo "- ref: ${GITHUB_REF_NAME}"
|
||||||
echo "- status: ${status}"
|
echo "- status: ${status}"
|
||||||
@@ -89,92 +89,7 @@ jobs:
|
|||||||
echo "🔗 Staging files..."
|
echo "🔗 Staging files..."
|
||||||
git add "${FILES[@]}"
|
git add "${FILES[@]}"
|
||||||
echo "💾 Committing changes..."
|
echo "💾 Committing changes..."
|
||||||
git commit -m "Update Nix flake.lock and x86_64-linux hash"
|
git commit -m "Update Nix flake.lock and hashes"
|
||||||
echo "✅ Changes committed"
|
|
||||||
|
|
||||||
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
|
||||||
echo "🌳 Pulling latest from branch: $BRANCH"
|
|
||||||
git pull --rebase origin "$BRANCH"
|
|
||||||
echo "🚀 Pushing changes to branch: $BRANCH"
|
|
||||||
git push origin HEAD:"$BRANCH"
|
|
||||||
echo "✅ Changes pushed successfully"
|
|
||||||
|
|
||||||
summarize "committed $(git rev-parse --short HEAD)"
|
|
||||||
|
|
||||||
update-macos:
|
|
||||||
needs: update-linux
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
|
||||||
runs-on: macos-latest
|
|
||||||
env:
|
|
||||||
SYSTEM: aarch64-darwin
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ github.head_ref || github.ref_name }}
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
|
||||||
|
|
||||||
- name: Setup Nix
|
|
||||||
uses: DeterminateSystems/nix-installer-action@v20
|
|
||||||
|
|
||||||
- name: Configure git
|
|
||||||
run: |
|
|
||||||
git config --global user.email "action@github.com"
|
|
||||||
git config --global user.name "Github Action"
|
|
||||||
|
|
||||||
- name: Pull latest changes
|
|
||||||
env:
|
|
||||||
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
|
||||||
run: |
|
|
||||||
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
|
||||||
git pull origin "$BRANCH"
|
|
||||||
|
|
||||||
- name: Update node_modules hash for aarch64-darwin
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
echo "🔄 Updating node_modules hash for aarch64-darwin..."
|
|
||||||
nix/scripts/update-hashes.sh
|
|
||||||
echo "✅ node_modules hash for aarch64-darwin updated successfully"
|
|
||||||
|
|
||||||
- name: Commit macOS hash changes
|
|
||||||
env:
|
|
||||||
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
echo "🔍 Checking for changes in tracked Nix files..."
|
|
||||||
|
|
||||||
summarize() {
|
|
||||||
local status="$1"
|
|
||||||
{
|
|
||||||
echo "### Nix Hash Update (aarch64-darwin)"
|
|
||||||
echo ""
|
|
||||||
echo "- ref: ${GITHUB_REF_NAME}"
|
|
||||||
echo "- status: ${status}"
|
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
if [ -n "${GITHUB_SERVER_URL:-}" ] && [ -n "${GITHUB_REPOSITORY:-}" ] && [ -n "${GITHUB_RUN_ID:-}" ]; then
|
|
||||||
echo "- run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
fi
|
|
||||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
}
|
|
||||||
|
|
||||||
FILES=(nix/hashes.json)
|
|
||||||
STATUS="$(git status --short -- "${FILES[@]}" || true)"
|
|
||||||
if [ -z "$STATUS" ]; then
|
|
||||||
echo "✅ No changes detected. Hash is already up to date."
|
|
||||||
summarize "no changes"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "📝 Changes detected:"
|
|
||||||
echo "$STATUS"
|
|
||||||
echo "🔗 Staging files..."
|
|
||||||
git add "${FILES[@]}"
|
|
||||||
echo "💾 Committing changes..."
|
|
||||||
git commit -m "Update aarch64-darwin hash"
|
|
||||||
echo "✅ Changes committed"
|
echo "✅ Changes committed"
|
||||||
|
|
||||||
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
||||||
|
|||||||
@@ -24,4 +24,3 @@ target
|
|||||||
# Local dev files
|
# Local dev files
|
||||||
opencode-dev
|
opencode-dev
|
||||||
logs/
|
logs/
|
||||||
*.bun-build
|
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ Desktop app issues:
|
|||||||
|
|
||||||
#### zen
|
#### zen
|
||||||
|
|
||||||
**Only** add if the issue mentions "zen" or "opencode zen" or "opencode black".
|
**Only** add if the issue mentions "zen" or "opencode zen". Zen is our gateway for coding models. **Do not** add for other gateways or inference providers.
|
||||||
|
|
||||||
If the issue doesn't have "zen" or "opencode black" in it then don't add zen label
|
If the issue doesn't have "zen" in it then don't add zen label
|
||||||
|
|
||||||
#### docs
|
#### docs
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
- To test opencode in `packages/opencode`, run `bun dev`.
|
- To test opencode in the `packages/opencode` directory you can run `bun dev`
|
||||||
- To regenerate the JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
|
- To regenerate the javascript SDK, run ./packages/sdk/js/script/build.ts
|
||||||
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
|
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
|
||||||
- The default branch in this repo is `dev`.
|
- the default branch in this repo is `dev`
|
||||||
|
|||||||
-11
@@ -1,11 +0,0 @@
|
|||||||
# Reporting Security Issues
|
|
||||||
|
|
||||||
We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
|
|
||||||
|
|
||||||
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/anomalyco/opencode/security/advisories/new) tab.
|
|
||||||
|
|
||||||
The team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
|
|
||||||
|
|
||||||
## Escalation
|
|
||||||
|
|
||||||
If you do not receive an acknowledgement of your report within 6 business days, you may send an email to security@anoma.ly
|
|
||||||
@@ -196,7 +196,3 @@
|
|||||||
| 2026-01-07 | 2,123,239 (+162,251) | 1,398,648 (+21,271) | 3,521,887 (+183,522) |
|
| 2026-01-07 | 2,123,239 (+162,251) | 1,398,648 (+21,271) | 3,521,887 (+183,522) |
|
||||||
| 2026-01-08 | 2,272,630 (+149,391) | 1,432,480 (+33,832) | 3,705,110 (+183,223) |
|
| 2026-01-08 | 2,272,630 (+149,391) | 1,432,480 (+33,832) | 3,705,110 (+183,223) |
|
||||||
| 2026-01-09 | 2,443,565 (+170,935) | 1,469,451 (+36,971) | 3,913,016 (+207,906) |
|
| 2026-01-09 | 2,443,565 (+170,935) | 1,469,451 (+36,971) | 3,913,016 (+207,906) |
|
||||||
| 2026-01-10 | 2,632,023 (+188,458) | 1,503,670 (+34,219) | 4,135,693 (+222,677) |
|
|
||||||
| 2026-01-11 | 2,836,394 (+204,371) | 1,530,479 (+26,809) | 4,366,873 (+231,180) |
|
|
||||||
| 2026-01-12 | 3,053,594 (+217,200) | 1,553,671 (+23,192) | 4,607,265 (+240,392) |
|
|
||||||
| 2026-01-13 | 3,297,078 (+243,484) | 1,595,062 (+41,391) | 4,892,140 (+284,875) |
|
|
||||||
|
|||||||
+9
-69
@@ -1,71 +1,11 @@
|
|||||||
## Style Guide
|
## Style Guide
|
||||||
|
|
||||||
- Keep things in one function unless composable or reusable
|
- Try to keep things in one function unless composable or reusable
|
||||||
- Avoid unnecessary destructuring. Instead of `const { a, b } = obj`, use `obj.a` and `obj.b` to preserve context
|
- AVOID unnecessary destructuring of variables. instead of doing `const { a, b }
|
||||||
- Avoid `try`/`catch` where possible
|
= obj` just reference it as obj.a and obj.b. this preserves context
|
||||||
- Avoid using the `any` type
|
- AVOID `try`/`catch` where possible
|
||||||
- Prefer single word variable names where possible
|
- AVOID `else` statements
|
||||||
- Use Bun APIs when possible, like `Bun.file()`
|
- AVOID using `any` type
|
||||||
|
- AVOID `let` statements
|
||||||
# Avoid let statements
|
- PREFER single word variable names where possible
|
||||||
|
- Use as many bun apis as possible like Bun.file()
|
||||||
We don't like `let` statements, especially combined with if/else statements.
|
|
||||||
Prefer `const`.
|
|
||||||
|
|
||||||
Good:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const foo = condition ? 1 : 2
|
|
||||||
```
|
|
||||||
|
|
||||||
Bad:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
let foo
|
|
||||||
|
|
||||||
if (condition) foo = 1
|
|
||||||
else foo = 2
|
|
||||||
```
|
|
||||||
|
|
||||||
# Avoid else statements
|
|
||||||
|
|
||||||
Prefer early returns or using an `iife` to avoid else statements.
|
|
||||||
|
|
||||||
Good:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
function foo() {
|
|
||||||
if (condition) return 1
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Bad:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
function foo() {
|
|
||||||
if (condition) return 1
|
|
||||||
else return 2
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Prefer single word naming
|
|
||||||
|
|
||||||
Try your best to find a single word name for your variables, functions, etc.
|
|
||||||
Only use multiple words if you cannot.
|
|
||||||
|
|
||||||
Good:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const foo = 1
|
|
||||||
const bar = 2
|
|
||||||
const baz = 3
|
|
||||||
```
|
|
||||||
|
|
||||||
Bad:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const fooBar = 1
|
|
||||||
const barBaz = 2
|
|
||||||
const bazFoo = 3
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -87,7 +87,6 @@
|
|||||||
"chart.js": "4.5.1",
|
"chart.js": "4.5.1",
|
||||||
"nitro": "3.0.1-alpha.1",
|
"nitro": "3.0.1-alpha.1",
|
||||||
"solid-js": "catalog:",
|
"solid-js": "catalog:",
|
||||||
"solid-list": "0.3.0",
|
|
||||||
"vite": "catalog:",
|
"vite": "catalog:",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -99,7 +98,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -126,7 +125,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "2.0.0",
|
"@ai-sdk/anthropic": "2.0.0",
|
||||||
"@ai-sdk/openai": "2.0.2",
|
"@ai-sdk/openai": "2.0.2",
|
||||||
@@ -150,7 +149,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -174,7 +173,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -203,7 +202,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
@@ -232,7 +231,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -248,7 +247,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -256,25 +255,25 @@
|
|||||||
"@actions/core": "1.11.1",
|
"@actions/core": "1.11.1",
|
||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@agentclientprotocol/sdk": "0.5.1",
|
"@agentclientprotocol/sdk": "0.5.1",
|
||||||
"@ai-sdk/amazon-bedrock": "3.0.73",
|
"@ai-sdk/amazon-bedrock": "3.0.57",
|
||||||
"@ai-sdk/anthropic": "2.0.57",
|
"@ai-sdk/anthropic": "2.0.56",
|
||||||
"@ai-sdk/azure": "2.0.91",
|
"@ai-sdk/azure": "2.0.82",
|
||||||
"@ai-sdk/cerebras": "1.0.34",
|
"@ai-sdk/cerebras": "1.0.33",
|
||||||
"@ai-sdk/cohere": "2.0.22",
|
"@ai-sdk/cohere": "2.0.21",
|
||||||
"@ai-sdk/deepinfra": "1.0.31",
|
"@ai-sdk/deepinfra": "1.0.30",
|
||||||
"@ai-sdk/gateway": "2.0.25",
|
"@ai-sdk/gateway": "2.0.23",
|
||||||
"@ai-sdk/google": "2.0.52",
|
"@ai-sdk/google": "2.0.49",
|
||||||
"@ai-sdk/google-vertex": "3.0.97",
|
"@ai-sdk/google-vertex": "3.0.81",
|
||||||
"@ai-sdk/groq": "2.0.34",
|
"@ai-sdk/groq": "2.0.33",
|
||||||
"@ai-sdk/mistral": "2.0.27",
|
"@ai-sdk/mistral": "2.0.26",
|
||||||
"@ai-sdk/openai": "2.0.89",
|
"@ai-sdk/openai": "2.0.71",
|
||||||
"@ai-sdk/openai-compatible": "1.0.30",
|
"@ai-sdk/openai-compatible": "1.0.29",
|
||||||
"@ai-sdk/perplexity": "2.0.23",
|
"@ai-sdk/perplexity": "2.0.22",
|
||||||
"@ai-sdk/provider": "2.0.1",
|
"@ai-sdk/provider": "2.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.20",
|
"@ai-sdk/provider-utils": "3.0.19",
|
||||||
"@ai-sdk/togetherai": "1.0.31",
|
"@ai-sdk/togetherai": "1.0.30",
|
||||||
"@ai-sdk/vercel": "1.0.31",
|
"@ai-sdk/vercel": "1.0.31",
|
||||||
"@ai-sdk/xai": "2.0.51",
|
"@ai-sdk/xai": "2.0.42",
|
||||||
"@clack/prompts": "1.0.0-alpha.1",
|
"@clack/prompts": "1.0.0-alpha.1",
|
||||||
"@hono/standard-validator": "0.1.5",
|
"@hono/standard-validator": "0.1.5",
|
||||||
"@hono/zod-validator": "catalog:",
|
"@hono/zod-validator": "catalog:",
|
||||||
@@ -351,7 +350,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
@@ -371,7 +370,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.88.1",
|
"@hey-api/openapi-ts": "0.88.1",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
@@ -382,7 +381,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -395,7 +394,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -407,7 +406,6 @@
|
|||||||
"@solid-primitives/resize-observer": "2.1.3",
|
"@solid-primitives/resize-observer": "2.1.3",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
"dompurify": "catalog:",
|
|
||||||
"fuzzysort": "catalog:",
|
"fuzzysort": "catalog:",
|
||||||
"katex": "0.16.27",
|
"katex": "0.16.27",
|
||||||
"luxon": "catalog:",
|
"luxon": "catalog:",
|
||||||
@@ -435,7 +433,7 @@
|
|||||||
},
|
},
|
||||||
"packages/util": {
|
"packages/util": {
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -446,7 +444,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -506,9 +504,8 @@
|
|||||||
"@types/luxon": "3.7.1",
|
"@types/luxon": "3.7.1",
|
||||||
"@types/node": "22.13.9",
|
"@types/node": "22.13.9",
|
||||||
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
||||||
"ai": "5.0.119",
|
"ai": "5.0.97",
|
||||||
"diff": "8.0.2",
|
"diff": "8.0.2",
|
||||||
"dompurify": "3.3.1",
|
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
"hono": "4.10.7",
|
"hono": "4.10.7",
|
||||||
"hono-openapi": "1.1.2",
|
"hono-openapi": "1.1.2",
|
||||||
@@ -544,43 +541,43 @@
|
|||||||
|
|
||||||
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.5.1", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg=="],
|
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.5.1", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.73", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.57", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EAAGJ/dfbAZaqIhK3w52hq6cftSLZwXdC6uHKh8Cls1T0N4MxS6ykDf54UyFO3bZWkQxR+Mdw1B3qireGOxtJQ=="],
|
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.57", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.45", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-mOUSLe+RgZzx0rtL1p9QXmSd/08z1EkBR+vQ1ydpd1t5P0Nx2kB8afiukEgM8nuDvmO9eYQlp7VTy1n5ffPs2g=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
|
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
|
||||||
|
|
||||||
"@ai-sdk/azure": ["@ai-sdk/azure@2.0.91", "", { "dependencies": { "@ai-sdk/openai": "2.0.89", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9tznVSs6LGQNKKxb8pKd7CkBV9yk+a/ENpFicHCj2CmBUKefxzwJ9JbUqrlK3VF6dGZw3LXq0dWxt7/Yekaj1w=="],
|
"@ai-sdk/azure": ["@ai-sdk/azure@2.0.82", "", { "dependencies": { "@ai-sdk/openai": "2.0.80", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Bpab51ETBB4adZC1xGMYsryL/CB8j1sA+t5aDqhRv3t3WRLTxhaBDcFKtQTIuxiEQTFosz9Q2xQqdfBvQm5jHw=="],
|
||||||
|
|
||||||
"@ai-sdk/cerebras": ["@ai-sdk/cerebras@1.0.34", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XOK0dJsAGoPYi/lfR4KFBi8xhvJ46oCpAxUD6FmJAuJ4eh0qlj5zDt+myvzM8gvN7S6K7zHD+mdWlOPKGQT8Vg=="],
|
"@ai-sdk/cerebras": ["@ai-sdk/cerebras@1.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2gSSS/7kunIwMdC4td5oWsUAzoLw84ccGpz6wQbxVnrb1iWnrEnKa5tRBduaP6IXpzLWsu8wME3+dQhZy+gT7w=="],
|
||||||
|
|
||||||
"@ai-sdk/cohere": ["@ai-sdk/cohere@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yJ9kP5cEDJwo8qpITq5TQFD8YNfNtW+HbyvWwrKMbFzmiMvIZuk95HIaFXE7PCTuZsqMA05yYu+qX/vQ3rNKjA=="],
|
"@ai-sdk/cohere": ["@ai-sdk/cohere@2.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZjaZFvJlc5XOPi3QwTLEFZbHIgTJc6YGvxz+8zIMGVZi/hdynR8/f/C1A9x6mhzmBtAqi/dZ2h11oouAQH5z4g=="],
|
||||||
|
|
||||||
"@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.31", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-87qFcYNvDF/89hB//MQjYTb3tlsAfmgeZrZ34RESeBTZpSgs0EzYOMqPMwFTHUNp4wteoifikDJbaS/9Da8cfw=="],
|
"@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.30", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XK8oRZFApzo6xnS5C+FhWUUkB2itA5Nfon3pU9dJVM0goViq8GwdleZTBRqhu4DE4KJURo5DGWpJr2hfV54cEg=="],
|
||||||
|
|
||||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.25", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Rq+FX55ne7lMiqai7NcvvDZj4HLsr+hg77WayqmySqc6zhw3tIOLxd4Ty6OpwNj0C0bVMi3iCl2zvJIEirh9XA=="],
|
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-qmX7afPRszUqG5hryHF3UN8ITPIRSGmDW6VYCmByzjoUkgm3MekzSx2hMV1wr0P+llDeuXb378SjqUfpvWJulg=="],
|
||||||
|
|
||||||
"@ai-sdk/google": ["@ai-sdk/google@2.0.52", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2XUnGi3f7TV4ujoAhA+Fg3idUoG/+Y2xjCRg70a1/m0DH1KSQqYaCboJ1C19y6ZHGdf5KNT20eJdswP6TvrY2g=="],
|
"@ai-sdk/google": ["@ai-sdk/google@2.0.49", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-efwKk4mOV0SpumUaQskeYABk37FJPmEYwoDJQEjyLRmGSjtHRe9P5Cwof5ffLvaFav2IaJpBGEz98pyTs7oNWA=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.97", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.57", "@ai-sdk/google": "2.0.52", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-s4tI7Z15i6FlbtCvS4SBRal8wRfkOXJzKxlS6cU4mJW/QfUfoVy4b22836NVNJwDvkG/HkDSfzwm/X8mn46MhA=="],
|
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.81", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.50", "@ai-sdk/google": "2.0.44", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18", "google-auth-library": "^9.15.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yrl5Ug0Mqwo9ya45oxczgy2RWgpEA/XQQCSFYP+3NZMQ4yA3Iim1vkOjVCsGaZZ8rjVk395abi1ZMZV0/6rqVA=="],
|
||||||
|
|
||||||
"@ai-sdk/groq": ["@ai-sdk/groq@2.0.34", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wfCYkVgmVjxNA32T57KbLabVnv9aFUflJ4urJ7eWgTwbnmGQHElCTu+rJ3ydxkXSqxOkXPwMOttDm7XNrvPjmg=="],
|
"@ai-sdk/groq": ["@ai-sdk/groq@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FWGl7xNr88NBveao3y9EcVWYUt9ABPrwLFY7pIutSNgaTf32vgvyhREobaMrLU4Scr5G/2tlNqOPZ5wkYMaZig=="],
|
||||||
|
|
||||||
"@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.27", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-gaptHgaXjMw3+eA0Q4FABcsj5nQNP6EpFaGUR+Pj5WJy7Kn6mApl975/x57224MfeJIShNpt8wFKK3tvh5ewKg=="],
|
"@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.26", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jxDB++4WI1wEx5ONNBI+VbkmYJOYIuS8UQY13/83UGRaiW7oB/WHiH4ETe6KzbKpQPB3XruwTJQjUMsMfKyTXA=="],
|
||||||
|
|
||||||
"@ai-sdk/openai": ["@ai-sdk/openai@2.0.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="],
|
"@ai-sdk/openai": ["@ai-sdk/openai@2.0.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="],
|
||||||
|
|
||||||
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-luHVcU+yKzwv3ekKgbP3v+elUVxb2Rt+8c6w9qi7g2NYG2/pEL21oIrnaEnc6UtTZLLZX9EFBcpq2N1FQKDIMw=="],
|
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-luHVcU+yKzwv3ekKgbP3v+elUVxb2Rt+8c6w9qi7g2NYG2/pEL21oIrnaEnc6UtTZLLZX9EFBcpq2N1FQKDIMw=="],
|
||||||
|
|
||||||
"@ai-sdk/perplexity": ["@ai-sdk/perplexity@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-aiaRvnc6mhQZKhTTSXPCjPH8Iqr5D/PfCN1hgVP/3RGTBbJtsd9HemIBSABeSdAKbsMH/PwJxgnqH75HEamcBA=="],
|
"@ai-sdk/perplexity": ["@ai-sdk/perplexity@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zwzcnk08R2J3mZcQPn4Ifl4wYGrvANR7jsBB0hCTUSbb+Rx3ybpikSWiGuXQXxdiRc1I5MWXgj70m+bZaLPvHw=="],
|
||||||
|
|
||||||
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="],
|
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
||||||
|
|
||||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="],
|
||||||
|
|
||||||
"@ai-sdk/togetherai": ["@ai-sdk/togetherai@1.0.31", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RlYubjStoZQxna4Ng91Vvo8YskvL7lW9zj68IwZfCnaDBSAp1u6Nhc5BR4ZtKnY6PA3XEtu4bATIQl7yiiQ+Lw=="],
|
"@ai-sdk/togetherai": ["@ai-sdk/togetherai@1.0.30", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9bxQbIXnWSN4bNismrza3NvIo+ui/Y3pj3UN6e9vCszCWFCN45RgISi4oDe10RqmzaJ/X8cfO/Tem+K8MT3wGQ=="],
|
||||||
|
|
||||||
"@ai-sdk/vercel": ["@ai-sdk/vercel@1.0.31", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ggvwAMt/KsbqcdR6ILQrjwrRONLV/8aG6rOLbjcOGvV0Ai+WdZRRKQj5nOeQ06PvwVQtKdkp7S4IinpXIhCiHg=="],
|
"@ai-sdk/vercel": ["@ai-sdk/vercel@1.0.31", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ggvwAMt/KsbqcdR6ILQrjwrRONLV/8aG6rOLbjcOGvV0Ai+WdZRRKQj5nOeQ06PvwVQtKdkp7S4IinpXIhCiHg=="],
|
||||||
|
|
||||||
"@ai-sdk/xai": ["@ai-sdk/xai@2.0.51", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-AI3le03qiegkZvn9hpnpDwez49lOvQLj4QUBT8H41SMbrdTYOxn3ktTwrsSu90cNDdzKGMvoH0u2GHju1EdnCg=="],
|
"@ai-sdk/xai": ["@ai-sdk/xai@2.0.42", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wlwO4yRoZ/d+ca29vN8SDzxus7POdnL7GBTyRdSrt6icUF0hooLesauC8qRUC4aLxtqvMEc1YHtJOU7ZnLWbTQ=="],
|
||||||
|
|
||||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||||
|
|
||||||
@@ -1830,8 +1827,6 @@
|
|||||||
|
|
||||||
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
|
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
|
||||||
|
|
||||||
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
|
||||||
|
|
||||||
"@types/tsscmp": ["@types/tsscmp@1.0.2", "", {}, "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g=="],
|
"@types/tsscmp": ["@types/tsscmp@1.0.2", "", {}, "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g=="],
|
||||||
|
|
||||||
"@types/tunnel": ["@types/tunnel@0.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA=="],
|
"@types/tunnel": ["@types/tunnel@0.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA=="],
|
||||||
@@ -1908,7 +1903,7 @@
|
|||||||
|
|
||||||
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
|
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
|
||||||
|
|
||||||
"ai": ["ai@5.0.119", "", { "dependencies": { "@ai-sdk/gateway": "2.0.25", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HUOwhc17fl2SZTJGZyA/99aNu706qKfXaUBCy9vgZiXBwrxg2eTzn2BCz7kmYDsfx6Fg2ACBy2icm41bsDXCTw=="],
|
"ai": ["ai@5.0.97", "", { "dependencies": { "@ai-sdk/gateway": "2.0.12", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8zBx0b/owis4eJI2tAlV8a1Rv0BANmLxontcAelkLNwEHhgfgXeKpDkhNB6OgV+BJSwboIUDkgd9312DdJnCOQ=="],
|
||||||
|
|
||||||
"ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
|
"ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
|
||||||
|
|
||||||
@@ -2210,8 +2205,6 @@
|
|||||||
|
|
||||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
|
||||||
|
|
||||||
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
||||||
|
|
||||||
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
|
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
|
||||||
@@ -2284,8 +2277,6 @@
|
|||||||
|
|
||||||
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||||
|
|
||||||
"dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="],
|
|
||||||
|
|
||||||
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||||
|
|
||||||
"dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
|
"dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
|
||||||
@@ -2428,8 +2419,6 @@
|
|||||||
|
|
||||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
|
||||||
|
|
||||||
"file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
|
"file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
|
||||||
|
|
||||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||||
@@ -2460,8 +2449,6 @@
|
|||||||
|
|
||||||
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
|
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
|
||||||
|
|
||||||
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
|
||||||
|
|
||||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||||
|
|
||||||
"fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
|
"fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
|
||||||
@@ -2484,9 +2471,9 @@
|
|||||||
|
|
||||||
"fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="],
|
"fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="],
|
||||||
|
|
||||||
"gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="],
|
"gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="],
|
||||||
|
|
||||||
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
|
"gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="],
|
||||||
|
|
||||||
"gel": ["gel@2.2.0", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ=="],
|
"gel": ["gel@2.2.0", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ=="],
|
||||||
|
|
||||||
@@ -2532,9 +2519,9 @@
|
|||||||
|
|
||||||
"globby": ["globby@11.0.4", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.1.1", "ignore": "^5.1.4", "merge2": "^1.3.0", "slash": "^3.0.0" } }, "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg=="],
|
"globby": ["globby@11.0.4", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.1.1", "ignore": "^5.1.4", "merge2": "^1.3.0", "slash": "^3.0.0" } }, "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg=="],
|
||||||
|
|
||||||
"google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="],
|
"google-auth-library": ["google-auth-library@9.15.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", "jws": "^4.0.0" } }, "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng=="],
|
||||||
|
|
||||||
"google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
|
"google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
|
||||||
|
|
||||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||||
|
|
||||||
@@ -2542,7 +2529,7 @@
|
|||||||
|
|
||||||
"gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
|
"gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
|
||||||
|
|
||||||
"gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="],
|
"gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="],
|
||||||
|
|
||||||
"h3": ["h3@2.0.1-rc.4", "", { "dependencies": { "rou3": "^0.7.8", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-vZq8pEUp6THsXKXrUXX44eOqfChic2wVQ1GlSzQCBr7DeFBkfIZAo2WyNND4GSv54TAa0E4LYIK73WSPdgKUgw=="],
|
"h3": ["h3@2.0.1-rc.4", "", { "dependencies": { "rou3": "^0.7.8", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-vZq8pEUp6THsXKXrUXX44eOqfChic2wVQ1GlSzQCBr7DeFBkfIZAo2WyNND4GSv54TAa0E4LYIK73WSPdgKUgw=="],
|
||||||
|
|
||||||
@@ -3430,8 +3417,6 @@
|
|||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
|
|
||||||
|
|
||||||
"rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="],
|
"rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="],
|
||||||
|
|
||||||
"rou3": ["rou3@0.7.10", "", {}, "sha512-aoFj6f7MJZ5muJ+Of79nrhs9N3oLGqi2VEMe94Zbkjb6Wupha46EuoYgpWSOZlXww3bbd8ojgXTAA2mzimX5Ww=="],
|
"rou3": ["rou3@0.7.10", "", {}, "sha512-aoFj6f7MJZ5muJ+Of79nrhs9N3oLGqi2VEMe94Zbkjb6Wupha46EuoYgpWSOZlXww3bbd8ojgXTAA2mzimX5Ww=="],
|
||||||
@@ -3924,33 +3909,35 @@
|
|||||||
|
|
||||||
"@agentclientprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
"@agentclientprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.57", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.45", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Ipv62vavDCmrV/oE/lXehL9FzwQuZOnnlhPEftWizx464Wb6lvnBTJx8uhmEYruFSzOWTI95Z33ncZ4tA8E6RQ=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
||||||
|
|
||||||
"@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
|
"@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.80", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tNHuraF11db+8xJEDBoU9E3vMcpnHFKRhnLQ3DQX2LnEzfPB9DksZ8rE+yVuDN1WRW9cm2OWAhgHFgVKs7ICuw=="],
|
||||||
|
|
||||||
"@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
"@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
||||||
|
|
||||||
"@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
"@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.57", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
|
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="],
|
||||||
|
|
||||||
"@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
"@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="],
|
||||||
|
|
||||||
"@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
"@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
||||||
|
|
||||||
"@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
|
||||||
|
|
||||||
"@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
"@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
||||||
|
|
||||||
"@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
"@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
||||||
|
|
||||||
"@ai-sdk/vercel/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
"@ai-sdk/vercel/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
||||||
|
|
||||||
"@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
"@ai-sdk/vercel/@ai-sdk/provider": ["@ai-sdk/provider@2.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="],
|
||||||
|
|
||||||
|
"@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
||||||
|
|
||||||
"@astrojs/cloudflare/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
|
"@astrojs/cloudflare/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
|
||||||
|
|
||||||
@@ -4218,6 +4205,10 @@
|
|||||||
|
|
||||||
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
|
"ai/@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.12", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W+cB1sOWvPcz9qiIsNtD+HxUrBUva2vWv2K1EFukuImX+HA0uZx3EyyOjhYQ9gtf/teqEG80M6OvJ7xx/VLV2A=="],
|
||||||
|
|
||||||
|
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
|
||||||
|
|
||||||
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
@@ -4282,13 +4273,13 @@
|
|||||||
|
|
||||||
"express/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
|
"express/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
|
||||||
|
|
||||||
"fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
|
||||||
|
|
||||||
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||||
|
|
||||||
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
"gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
"gaxios/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||||
|
|
||||||
|
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||||
|
|
||||||
"glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
|
"glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
|
||||||
|
|
||||||
@@ -4330,11 +4321,11 @@
|
|||||||
|
|
||||||
"nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
"nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||||
|
|
||||||
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.57", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DREpYqW2pylgaj69gZ+K8u92bo9DaMgFdictYnY+IwYeY3bawQ4zI7l/o1VkDsBDljAx8iYz5lPURwVZNu+Xpg=="],
|
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ=="],
|
||||||
|
|
||||||
"opencode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
|
"opencode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.71", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tg+gj+R0z/On9P4V7hy7/7o04cQPjKGayMCL3gzWD/aNGjAKkhEnaocuNDidSnghizt8g2zJn16cAuAolnW+qQ=="],
|
||||||
|
|
||||||
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
|
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
|
||||||
|
|
||||||
@@ -4372,8 +4363,6 @@
|
|||||||
|
|
||||||
"readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
|
"readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
|
||||||
|
|
||||||
"rimraf/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
|
||||||
|
|
||||||
"router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
"router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
||||||
|
|
||||||
"safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
"safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||||
@@ -4936,6 +4925,8 @@
|
|||||||
|
|
||||||
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||||
|
|
||||||
|
"opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
||||||
@@ -4952,12 +4943,6 @@
|
|||||||
|
|
||||||
"readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
"readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||||
|
|
||||||
"rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
|
||||||
|
|
||||||
"rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
|
||||||
|
|
||||||
"rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
|
||||||
|
|
||||||
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||||
|
|
||||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
@@ -5140,8 +5125,6 @@
|
|||||||
|
|
||||||
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
|
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
|
||||||
|
|
||||||
"rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
|
||||||
|
|
||||||
"tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
"tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
"tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
"tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||||
|
|||||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1768178648,
|
"lastModified": 1767364772,
|
||||||
"narHash": "sha256-kz/F6mhESPvU1diB7tOM3nLcBfQe7GU7GQCymRlTi/s=",
|
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "3fbab70c6e69c87ea2b6e48aa6629da2aa6a23b0",
|
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -27,28 +27,11 @@
|
|||||||
"aarch64-darwin" = "bun-darwin-arm64";
|
"aarch64-darwin" = "bun-darwin-arm64";
|
||||||
"x86_64-darwin" = "bun-darwin-x64";
|
"x86_64-darwin" = "bun-darwin-x64";
|
||||||
};
|
};
|
||||||
|
defaultNodeModules = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||||
# Parse "bun-{os}-{cpu}" to {os, cpu}
|
|
||||||
parseBunTarget =
|
|
||||||
target:
|
|
||||||
let
|
|
||||||
parts = lib.splitString "-" target;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
os = builtins.elemAt parts 1;
|
|
||||||
cpu = builtins.elemAt parts 2;
|
|
||||||
};
|
|
||||||
|
|
||||||
hashesFile = "${./nix}/hashes.json";
|
hashesFile = "${./nix}/hashes.json";
|
||||||
hashesData =
|
hashesData =
|
||||||
if builtins.pathExists hashesFile then builtins.fromJSON (builtins.readFile hashesFile) else { };
|
if builtins.pathExists hashesFile then builtins.fromJSON (builtins.readFile hashesFile) else { };
|
||||||
# Lookup hash: supports per-system ({system: hash}) or legacy single hash
|
nodeModulesHash = hashesData.nodeModules or defaultNodeModules;
|
||||||
nodeModulesHashFor =
|
|
||||||
system:
|
|
||||||
if builtins.isAttrs hashesData.nodeModules then
|
|
||||||
hashesData.nodeModules.${system}
|
|
||||||
else
|
|
||||||
hashesData.nodeModules;
|
|
||||||
modelsDev = forEachSystem (
|
modelsDev = forEachSystem (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
@@ -80,11 +63,8 @@
|
|||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
pkgs = pkgsFor system;
|
pkgs = pkgsFor system;
|
||||||
bunPlatform = parseBunTarget bunTarget.${system};
|
|
||||||
mkNodeModules = pkgs.callPackage ./nix/node-modules.nix {
|
mkNodeModules = pkgs.callPackage ./nix/node-modules.nix {
|
||||||
hash = nodeModulesHashFor system;
|
hash = nodeModulesHash;
|
||||||
bunCpu = bunPlatform.cpu;
|
|
||||||
bunOs = bunPlatform.os;
|
|
||||||
};
|
};
|
||||||
mkOpencode = pkgs.callPackage ./nix/opencode.nix { };
|
mkOpencode = pkgs.callPackage ./nix/opencode.nix { };
|
||||||
mkDesktop = pkgs.callPackage ./nix/desktop.nix { };
|
mkDesktop = pkgs.callPackage ./nix/desktop.nix { };
|
||||||
|
|||||||
+5
-6
@@ -81,13 +81,12 @@ This will walk you through installing the GitHub app, creating the workflow, and
|
|||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Run opencode
|
- name: Run opencode
|
||||||
uses: anomalyco/opencode/github@latest
|
uses: anomalyco/opencode/github@latest
|
||||||
env:
|
env:
|
||||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
|||||||
@@ -163,7 +163,6 @@ new sst.cloudflare.x.SolidStart("Console", {
|
|||||||
AWS_SES_ACCESS_KEY_ID,
|
AWS_SES_ACCESS_KEY_ID,
|
||||||
AWS_SES_SECRET_ACCESS_KEY,
|
AWS_SES_SECRET_ACCESS_KEY,
|
||||||
ZEN_BLACK,
|
ZEN_BLACK,
|
||||||
new sst.Secret("ZEN_SESSION_SECRET"),
|
|
||||||
...ZEN_MODELS,
|
...ZEN_MODELS,
|
||||||
...($dev
|
...($dev
|
||||||
? [
|
? [
|
||||||
|
|||||||
+1
-4
@@ -1,6 +1,3 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": "sha256-+QM5BDFxzrm1HY5ealjCm7jIO1t/rpW1q4GGLViPMmA="
|
||||||
"x86_64-linux": "sha256-UCPTTk4b7d2bets7KgCeYBHWAUwUAPUyKm+xDYkSexE=",
|
|
||||||
"aarch64-darwin": "sha256-Y3o6lovahSWoG9un/l1qxu7hCmIlZXm2LxOLKNiPQfQ="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
bun,
|
bun,
|
||||||
cacert,
|
cacert,
|
||||||
curl,
|
curl,
|
||||||
bunCpu,
|
|
||||||
bunOs,
|
|
||||||
}:
|
}:
|
||||||
args:
|
args:
|
||||||
stdenvNoCC.mkDerivation {
|
stdenvNoCC.mkDerivation {
|
||||||
@@ -31,8 +29,8 @@ stdenvNoCC.mkDerivation {
|
|||||||
export HOME=$(mktemp -d)
|
export HOME=$(mktemp -d)
|
||||||
export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
|
export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
|
||||||
bun install \
|
bun install \
|
||||||
--cpu="${bunCpu}" \
|
--cpu="*" \
|
||||||
--os="${bunOs}" \
|
--os="*" \
|
||||||
--frozen-lockfile \
|
--frozen-lockfile \
|
||||||
--ignore-scripts \
|
--ignore-scripts \
|
||||||
--no-progress \
|
--no-progress \
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ HASH_FILE=${HASH_FILE:-$DEFAULT_HASH_FILE}
|
|||||||
if [ ! -f "$HASH_FILE" ]; then
|
if [ ! -f "$HASH_FILE" ]; then
|
||||||
cat >"$HASH_FILE" <<EOF
|
cat >"$HASH_FILE" <<EOF
|
||||||
{
|
{
|
||||||
"nodeModules": {}
|
"nodeModules": "$DUMMY"
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
@@ -33,16 +33,9 @@ trap cleanup EXIT
|
|||||||
|
|
||||||
write_node_modules_hash() {
|
write_node_modules_hash() {
|
||||||
local value="$1"
|
local value="$1"
|
||||||
local system="${2:-$SYSTEM}"
|
|
||||||
local temp
|
local temp
|
||||||
temp=$(mktemp)
|
temp=$(mktemp)
|
||||||
|
jq --arg value "$value" '.nodeModules = $value' "$HASH_FILE" >"$temp"
|
||||||
if jq -e '.nodeModules | type == "object"' "$HASH_FILE" >/dev/null 2>&1; then
|
|
||||||
jq --arg system "$system" --arg value "$value" '.nodeModules[$system] = $value' "$HASH_FILE" >"$temp"
|
|
||||||
else
|
|
||||||
jq --arg system "$system" --arg value "$value" '.nodeModules = {($system): $value}' "$HASH_FILE" >"$temp"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv "$temp" "$HASH_FILE"
|
mv "$temp" "$HASH_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +104,7 @@ fi
|
|||||||
|
|
||||||
write_node_modules_hash "$CORRECT_HASH"
|
write_node_modules_hash "$CORRECT_HASH"
|
||||||
|
|
||||||
jq -e --arg system "$SYSTEM" --arg hash "$CORRECT_HASH" '.nodeModules[$system] == $hash' "$HASH_FILE" >/dev/null
|
jq -e --arg hash "$CORRECT_HASH" '.nodeModules == $hash' "$HASH_FILE" >/dev/null
|
||||||
|
|
||||||
echo "node_modules hash updated for ${SYSTEM}: $CORRECT_HASH"
|
echo "node_modules hash updated for ${SYSTEM}: $CORRECT_HASH"
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -36,8 +36,7 @@
|
|||||||
"@solid-primitives/storage": "4.3.3",
|
"@solid-primitives/storage": "4.3.3",
|
||||||
"@tailwindcss/vite": "4.1.11",
|
"@tailwindcss/vite": "4.1.11",
|
||||||
"diff": "8.0.2",
|
"diff": "8.0.2",
|
||||||
"dompurify": "3.3.1",
|
"ai": "5.0.97",
|
||||||
"ai": "5.0.119",
|
|
||||||
"hono": "4.10.7",
|
"hono": "4.10.7",
|
||||||
"hono-openapi": "1.1.2",
|
"hono-openapi": "1.1.2",
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
+12
-12
@@ -33,10 +33,19 @@ const Loading = () => <div class="size-full flex items-center justify-center tex
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
__OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string }
|
__OPENCODE__?: { updaterEnabled?: boolean; port?: number; serverReady?: boolean }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultServerUrl = iife(() => {
|
||||||
|
if (location.hostname.includes("opencode.ai")) return "http://localhost:4096"
|
||||||
|
if (window.__OPENCODE__) return `http://127.0.0.1:${window.__OPENCODE__.port}`
|
||||||
|
if (import.meta.env.DEV)
|
||||||
|
return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}`
|
||||||
|
|
||||||
|
return window.location.origin
|
||||||
|
})
|
||||||
|
|
||||||
export function AppBaseProviders(props: ParentProps) {
|
export function AppBaseProviders(props: ParentProps) {
|
||||||
return (
|
return (
|
||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
@@ -65,18 +74,9 @@ function ServerKey(props: ParentProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AppInterface(props: { defaultUrl?: string }) {
|
export function AppInterface() {
|
||||||
const defaultServerUrl = () => {
|
|
||||||
if (props.defaultUrl) return props.defaultUrl
|
|
||||||
if (location.hostname.includes("opencode.ai")) return "http://localhost:4096"
|
|
||||||
if (import.meta.env.DEV)
|
|
||||||
return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}`
|
|
||||||
|
|
||||||
return window.location.origin
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServerProvider defaultUrl={defaultServerUrl()}>
|
<ServerProvider defaultUrl={defaultServerUrl}>
|
||||||
<ServerKey>
|
<ServerKey>
|
||||||
<GlobalSDKProvider>
|
<GlobalSDKProvider>
|
||||||
<GlobalSyncProvider>
|
<GlobalSyncProvider>
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
import { Component, createMemo } from "solid-js"
|
|
||||||
import { useNavigate, useParams } from "@solidjs/router"
|
|
||||||
import { useSync } from "@/context/sync"
|
|
||||||
import { useSDK } from "@/context/sdk"
|
|
||||||
import { usePrompt } from "@/context/prompt"
|
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
|
||||||
import { List } from "@opencode-ai/ui/list"
|
|
||||||
import { extractPromptFromParts } from "@/utils/prompt"
|
|
||||||
import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client"
|
|
||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
|
||||||
|
|
||||||
interface ForkableMessage {
|
|
||||||
id: string
|
|
||||||
text: string
|
|
||||||
time: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTime(date: Date): string {
|
|
||||||
return date.toLocaleTimeString(undefined, { timeStyle: "short" })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DialogFork: Component = () => {
|
|
||||||
const params = useParams()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const sync = useSync()
|
|
||||||
const sdk = useSDK()
|
|
||||||
const prompt = usePrompt()
|
|
||||||
const dialog = useDialog()
|
|
||||||
|
|
||||||
const messages = createMemo((): ForkableMessage[] => {
|
|
||||||
const sessionID = params.id
|
|
||||||
if (!sessionID) return []
|
|
||||||
|
|
||||||
const msgs = sync.data.message[sessionID] ?? []
|
|
||||||
const result: ForkableMessage[] = []
|
|
||||||
|
|
||||||
for (const message of msgs) {
|
|
||||||
if (message.role !== "user") continue
|
|
||||||
|
|
||||||
const parts = sync.data.part[message.id] ?? []
|
|
||||||
const textPart = parts.find((x): x is SDKTextPart => x.type === "text" && !x.synthetic && !x.ignored)
|
|
||||||
if (!textPart) continue
|
|
||||||
|
|
||||||
result.push({
|
|
||||||
id: message.id,
|
|
||||||
text: textPart.text.replace(/\n/g, " ").slice(0, 200),
|
|
||||||
time: formatTime(new Date(message.time.created)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.reverse()
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleSelect = (item: ForkableMessage | undefined) => {
|
|
||||||
if (!item) return
|
|
||||||
|
|
||||||
const sessionID = params.id
|
|
||||||
if (!sessionID) return
|
|
||||||
|
|
||||||
const parts = sync.data.part[item.id] ?? []
|
|
||||||
const restored = extractPromptFromParts(parts, { directory: sdk.directory })
|
|
||||||
|
|
||||||
dialog.close()
|
|
||||||
|
|
||||||
sdk.client.session.fork({ sessionID, messageID: item.id }).then((forked) => {
|
|
||||||
if (!forked.data) return
|
|
||||||
navigate(`/${base64Encode(sdk.directory)}/session/${forked.data.id}`)
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
prompt.set(restored)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog title="Fork from message">
|
|
||||||
<List
|
|
||||||
class="flex-1 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0"
|
|
||||||
search={{ placeholder: "Search", autofocus: true }}
|
|
||||||
emptyMessage="No messages to fork from"
|
|
||||||
key={(x) => x.id}
|
|
||||||
items={messages}
|
|
||||||
filterKeys={["text"]}
|
|
||||||
onSelect={handleSelect}
|
|
||||||
>
|
|
||||||
{(item) => (
|
|
||||||
<div class="w-full flex items-center gap-2">
|
|
||||||
<span class="truncate flex-1 min-w-0 text-left" style={{ "font-weight": "400" }}>
|
|
||||||
{item.text}
|
|
||||||
</span>
|
|
||||||
<span class="text-text-weak shrink-0" style={{ "font-weight": "400" }}>
|
|
||||||
{item.time}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,6 @@ import { Button } from "@opencode-ai/ui/button"
|
|||||||
import { Tag } from "@opencode-ai/ui/tag"
|
import { Tag } from "@opencode-ai/ui/tag"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
|
||||||
import type { IconName } from "@opencode-ai/ui/icons/provider"
|
|
||||||
import { DialogSelectProvider } from "./dialog-select-provider"
|
import { DialogSelectProvider } from "./dialog-select-provider"
|
||||||
import { DialogManageModels } from "./dialog-manage-models"
|
import { DialogManageModels } from "./dialog-manage-models"
|
||||||
|
|
||||||
@@ -37,12 +35,6 @@ const ModelList: Component<{
|
|||||||
filterKeys={["provider.name", "name", "id"]}
|
filterKeys={["provider.name", "name", "id"]}
|
||||||
sortBy={(a, b) => a.name.localeCompare(b.name)}
|
sortBy={(a, b) => a.name.localeCompare(b.name)}
|
||||||
groupBy={(x) => x.provider.name}
|
groupBy={(x) => x.provider.name}
|
||||||
groupHeader={(group) => (
|
|
||||||
<div class="flex items-center gap-x-3">
|
|
||||||
<ProviderIcon data-slot="list-item-extra-icon" id={group.items[0].provider.id as IconName} />
|
|
||||||
<span>{group.category}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
sortGroupsBy={(a, b) => {
|
sortGroupsBy={(a, b) => {
|
||||||
if (a.category === "Recent" && b.category !== "Recent") return -1
|
if (a.category === "Recent" && b.category !== "Recent") return -1
|
||||||
if (b.category === "Recent" && a.category !== "Recent") return 1
|
if (b.category === "Recent" && a.category !== "Recent") return 1
|
||||||
@@ -60,8 +52,7 @@ const ModelList: Component<{
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(i) => (
|
{(i) => (
|
||||||
<div class="w-full flex items-center gap-x-3 pl-1 text-13-regular">
|
<div class="w-full flex items-center gap-x-2 text-13-regular">
|
||||||
<ProviderIcon data-slot="list-item-extra-icon" id={i.provider.id as IconName} />
|
|
||||||
<span class="truncate">{i.name}</span>
|
<span class="truncate">{i.name}</span>
|
||||||
<Show when={i.provider.id === "opencode" && (!i.cost || i.cost?.input === 0)}>
|
<Show when={i.provider.id === "opencode" && (!i.cost || i.cost?.input === 0)}>
|
||||||
<Tag>Free</Tag>
|
<Tag>Free</Tag>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { createResource, createEffect, createMemo, onCleanup, Show } from "solid-js"
|
import { createEffect, createMemo, onCleanup } from "solid-js"
|
||||||
import { createStore, reconcile } from "solid-js/store"
|
import { createStore, reconcile } from "solid-js/store"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
|
||||||
import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server"
|
import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||||
@@ -36,8 +35,6 @@ export function DialogSelectServer() {
|
|||||||
error: "",
|
error: "",
|
||||||
status: {} as Record<string, ServerStatus | undefined>,
|
status: {} as Record<string, ServerStatus | undefined>,
|
||||||
})
|
})
|
||||||
const [defaultUrl, defaultUrlActions] = createResource(() => platform.getDefaultServerUrl?.())
|
|
||||||
const isDesktop = platform.platform === "desktop"
|
|
||||||
|
|
||||||
const items = createMemo(() => {
|
const items = createMemo(() => {
|
||||||
const current = server.url
|
const current = server.url
|
||||||
@@ -117,10 +114,6 @@ export function DialogSelectServer() {
|
|||||||
select(value, true)
|
select(value, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRemove(url: string) {
|
|
||||||
server.remove(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog title="Servers" description="Switch which OpenCode server this app connects to.">
|
<Dialog title="Servers" description="Switch which OpenCode server this app connects to.">
|
||||||
<div class="flex flex-col gap-4 pb-4">
|
<div class="flex flex-col gap-4 pb-4">
|
||||||
@@ -135,33 +128,20 @@ export function DialogSelectServer() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(i) => (
|
{(i) => (
|
||||||
<div class="flex items-center gap-2 min-w-0 flex-1 group/item">
|
<div
|
||||||
|
class="flex items-center gap-2 min-w-0 flex-1"
|
||||||
|
classList={{ "opacity-50": store.status[i]?.healthy === false }}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="flex items-center gap-2 min-w-0 flex-1"
|
classList={{
|
||||||
classList={{ "opacity-50": store.status[i]?.healthy === false }}
|
"size-1.5 rounded-full shrink-0": true,
|
||||||
>
|
"bg-icon-success-base": store.status[i]?.healthy === true,
|
||||||
<div
|
"bg-icon-critical-base": store.status[i]?.healthy === false,
|
||||||
classList={{
|
"bg-border-weak-base": store.status[i] === undefined,
|
||||||
"size-1.5 rounded-full shrink-0": true,
|
}}
|
||||||
"bg-icon-success-base": store.status[i]?.healthy === true,
|
/>
|
||||||
"bg-icon-critical-base": store.status[i]?.healthy === false,
|
<span class="truncate">{serverDisplayName(i)}</span>
|
||||||
"bg-border-weak-base": store.status[i] === undefined,
|
<span class="text-text-weak">{store.status[i]?.version}</span>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span class="truncate">{serverDisplayName(i)}</span>
|
|
||||||
<span class="text-text-weak">{store.status[i]?.version}</span>
|
|
||||||
</div>
|
|
||||||
<Show when={current() !== i && server.list.includes(i)}>
|
|
||||||
<IconButton
|
|
||||||
icon="circle-x"
|
|
||||||
variant="ghost"
|
|
||||||
class="bg-transparent transition-opacity shrink-0 hover:scale-110"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
handleRemove(i)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
@@ -193,53 +173,6 @@ export function DialogSelectServer() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={isDesktop}>
|
|
||||||
<div class="mt-6 px-3 flex flex-col gap-1.5">
|
|
||||||
<div class="px-3">
|
|
||||||
<h3 class="text-14-regular text-text-weak">Default server</h3>
|
|
||||||
<p class="text-12-regular text-text-weak mt-1">
|
|
||||||
Connect to this server on app launch instead of starting a local server. Requires restart.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2 px-3 py-2">
|
|
||||||
<Show
|
|
||||||
when={defaultUrl()}
|
|
||||||
fallback={
|
|
||||||
<Show
|
|
||||||
when={server.url}
|
|
||||||
fallback={<span class="text-14-regular text-text-weak">No server selected</span>}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="small"
|
|
||||||
onClick={async () => {
|
|
||||||
await platform.setDefaultServerUrl?.(server.url)
|
|
||||||
defaultUrlActions.refetch(server.url)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Set current server as default
|
|
||||||
</Button>
|
|
||||||
</Show>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-2 flex-1 min-w-0">
|
|
||||||
<span class="truncate text-14-regular">{serverDisplayName(defaultUrl()!)}</span>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="small"
|
|
||||||
onClick={async () => {
|
|
||||||
await platform.setDefaultServerUrl?.(null)
|
|
||||||
defaultUrlActions.refetch()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</Button>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,14 +33,11 @@ import { useSync } from "@/context/sync"
|
|||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
|
||||||
import type { IconName } from "@opencode-ai/ui/icons/provider"
|
|
||||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Select } from "@opencode-ai/ui/select"
|
import { Select } from "@opencode-ai/ui/select"
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
|
||||||
import { ModelSelectorPopover } from "@/components/dialog-select-model"
|
import { ModelSelectorPopover } from "@/components/dialog-select-model"
|
||||||
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
|
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
import { useProviders } from "@/hooks/use-providers"
|
||||||
@@ -389,7 +386,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const {
|
const {
|
||||||
flat: atFlat,
|
flat: atFlat,
|
||||||
active: atActive,
|
active: atActive,
|
||||||
setActive: setAtActive,
|
|
||||||
onInput: atOnInput,
|
onInput: atOnInput,
|
||||||
onKeyDown: atOnKeyDown,
|
onKeyDown: atOnKeyDown,
|
||||||
} = useFilteredList<AtOption>({
|
} = useFilteredList<AtOption>({
|
||||||
@@ -456,7 +452,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const {
|
const {
|
||||||
flat: slashFlat,
|
flat: slashFlat,
|
||||||
active: slashActive,
|
active: slashActive,
|
||||||
setActive: setSlashActive,
|
|
||||||
onInput: slashOnInput,
|
onInput: slashOnInput,
|
||||||
onKeyDown: slashOnKeyDown,
|
onKeyDown: slashOnKeyDown,
|
||||||
refetch: slashRefetch,
|
refetch: slashRefetch,
|
||||||
@@ -1304,7 +1299,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-80 min-h-10
|
class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-80 min-h-10
|
||||||
overflow-auto no-scrollbar flex flex-col p-2 rounded-md
|
overflow-auto no-scrollbar flex flex-col p-2 rounded-md
|
||||||
border border-border-base bg-surface-raised-stronger-non-alpha shadow-md"
|
border border-border-base bg-surface-raised-stronger-non-alpha shadow-md"
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
|
||||||
>
|
>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={store.popover === "at"}>
|
<Match when={store.popover === "at"}>
|
||||||
@@ -1320,7 +1314,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
"bg-surface-raised-base-hover": atActive() === atKey(item),
|
"bg-surface-raised-base-hover": atActive() === atKey(item),
|
||||||
}}
|
}}
|
||||||
onClick={() => handleAtSelect(item)}
|
onClick={() => handleAtSelect(item)}
|
||||||
onMouseEnter={() => setAtActive(atKey(item))}
|
|
||||||
>
|
>
|
||||||
<Show
|
<Show
|
||||||
when={item.type === "agent"}
|
when={item.type === "agent"}
|
||||||
@@ -1367,7 +1360,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
"bg-surface-raised-base-hover": slashActive() === cmd.id,
|
"bg-surface-raised-base-hover": slashActive() === cmd.id,
|
||||||
}}
|
}}
|
||||||
onClick={() => handleSlashSelect(cmd)}
|
onClick={() => handleSlashSelect(cmd)}
|
||||||
onMouseEnter={() => setSlashActive(cmd.id)}
|
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
<div class="flex items-center gap-2 min-w-0">
|
||||||
<span class="text-14-regular text-text-strong whitespace-nowrap">/{cmd.trigger}</span>
|
<span class="text-14-regular text-text-strong whitespace-nowrap">/{cmd.trigger}</span>
|
||||||
@@ -1487,10 +1479,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
<img
|
<img
|
||||||
src={attachment.dataUrl}
|
src={attachment.dataUrl}
|
||||||
alt={attachment.filename}
|
alt={attachment.filename}
|
||||||
class="size-16 rounded-md object-cover border border-border-base hover:border-border-strong-base transition-colors"
|
class="size-16 rounded-md object-cover border border-border-base"
|
||||||
onClick={() =>
|
|
||||||
dialog.show(() => <ImagePreview src={attachment.dataUrl} alt={attachment.filename} />)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<button
|
<button
|
||||||
@@ -1562,9 +1551,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
fallback={
|
fallback={
|
||||||
<TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
|
<TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
|
||||||
<Button as="div" variant="ghost" onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}>
|
<Button as="div" variant="ghost" onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}>
|
||||||
<Show when={local.model.current()?.provider?.id}>
|
|
||||||
<ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
|
|
||||||
</Show>
|
|
||||||
{local.model.current()?.name ?? "Select model"}
|
{local.model.current()?.name ?? "Select model"}
|
||||||
<Icon name="chevron-down" size="small" />
|
<Icon name="chevron-down" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1574,9 +1560,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
<ModelSelectorPopover>
|
<ModelSelectorPopover>
|
||||||
<TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
|
<TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
|
||||||
<Button as="div" variant="ghost">
|
<Button as="div" variant="ghost">
|
||||||
<Show when={local.model.current()?.provider?.id}>
|
|
||||||
<ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
|
|
||||||
</Show>
|
|
||||||
{local.model.current()?.name ?? "Select model"}
|
{local.model.current()?.name ?? "Select model"}
|
||||||
<Icon name="chevron-down" size="small" />
|
<Icon name="chevron-down" size="small" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1591,10 +1574,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="text-text-base _hidden group-hover/prompt-input:inline-block capitalize text-12-regular"
|
class="text-text-base _hidden group-hover/prompt-input:inline-block"
|
||||||
onClick={() => local.model.variant.cycle()}
|
onClick={() => local.model.variant.cycle()}
|
||||||
>
|
>
|
||||||
{local.model.variant.current() ?? "Default"}
|
<span class="capitalize text-12-regular">{local.model.variant.current() ?? "Default"}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -100,12 +100,9 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
const mod = await import("ghostty-web")
|
const mod = await import("ghostty-web")
|
||||||
ghostty = await mod.Ghostty.load()
|
ghostty = await mod.Ghostty.load()
|
||||||
|
|
||||||
const url = new URL(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
|
const socket = new WebSocket(
|
||||||
if (window.__OPENCODE__?.serverPassword) {
|
sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`,
|
||||||
url.username = "opencode"
|
)
|
||||||
url.password = window.__OPENCODE__?.serverPassword
|
|
||||||
}
|
|
||||||
const socket = new WebSocket(url)
|
|
||||||
ws = socket
|
ws = socket
|
||||||
|
|
||||||
const t = new mod.Terminal({
|
const t = new mod.Terminal({
|
||||||
|
|||||||
@@ -9,13 +9,11 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
name: "GlobalSDK",
|
name: "GlobalSDK",
|
||||||
init: () => {
|
init: () => {
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
const platform = usePlatform()
|
|
||||||
const abort = new AbortController()
|
const abort = new AbortController()
|
||||||
|
|
||||||
const eventSdk = createOpencodeClient({
|
const eventSdk = createOpencodeClient({
|
||||||
baseUrl: server.url,
|
baseUrl: server.url,
|
||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
fetch: platform.fetch,
|
|
||||||
})
|
})
|
||||||
const emitter = createGlobalEmitter<{
|
const emitter = createGlobalEmitter<{
|
||||||
[key: string]: Event
|
[key: string]: Event
|
||||||
@@ -95,6 +93,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
stop()
|
stop()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const platform = usePlatform()
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: server.url,
|
baseUrl: server.url,
|
||||||
fetch: platform.fetch,
|
fetch: platform.fetch,
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import { ErrorPage, type InitError } from "../pages/error"
|
|||||||
import { batch, createContext, useContext, onCleanup, onMount, type ParentProps, Switch, Match } from "solid-js"
|
import { batch, createContext, useContext, onCleanup, onMount, type ParentProps, Switch, Match } from "solid-js"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { usePlatform } from "./platform"
|
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
status: "loading" | "partial" | "complete"
|
status: "loading" | "partial" | "complete"
|
||||||
@@ -65,7 +64,6 @@ type State = {
|
|||||||
|
|
||||||
function createGlobalSync() {
|
function createGlobalSync() {
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
const platform = usePlatform()
|
|
||||||
const [globalStore, setGlobalStore] = createStore<{
|
const [globalStore, setGlobalStore] = createStore<{
|
||||||
ready: boolean
|
ready: boolean
|
||||||
error?: InitError
|
error?: InitError
|
||||||
@@ -141,7 +139,6 @@ function createGlobalSync() {
|
|||||||
const [store, setStore] = child(directory)
|
const [store, setStore] = child(directory)
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: globalSDK.url,
|
baseUrl: globalSDK.url,
|
||||||
fetch: platform.fetch,
|
|
||||||
directory,
|
directory,
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
})
|
})
|
||||||
@@ -399,7 +396,6 @@ function createGlobalSync() {
|
|||||||
case "lsp.updated": {
|
case "lsp.updated": {
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: globalSDK.url,
|
baseUrl: globalSDK.url,
|
||||||
fetch: platform.fetch,
|
|
||||||
directory,
|
directory,
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -37,12 +37,6 @@ export type Platform = {
|
|||||||
|
|
||||||
/** Fetch override */
|
/** Fetch override */
|
||||||
fetch?: typeof fetch
|
fetch?: typeof fetch
|
||||||
|
|
||||||
/** Get the configured default server URL (desktop only) */
|
|
||||||
getDefaultServerUrl?(): Promise<string | null>
|
|
||||||
|
|
||||||
/** Set the default server URL to use on app startup (desktop only) */
|
|
||||||
setDefaultServerUrl?(url: string | null): Promise<void>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({
|
export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ export function normalizeServerUrl(input: string) {
|
|||||||
|
|
||||||
export function serverDisplayName(url: string) {
|
export function serverDisplayName(url: string) {
|
||||||
if (!url) return ""
|
if (!url) return ""
|
||||||
return url.replace(/^https?:\/\//, "").replace(/\/+$/, "")
|
return url
|
||||||
|
.replace(/^https?:\/\//, "")
|
||||||
|
.replace(/\/+$/, "")
|
||||||
|
.split("/")[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
function projectsKey(url: string) {
|
function projectsKey(url: string) {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { Persist, persisted } from "@/utils/persist"
|
|||||||
export type LocalPTY = {
|
export type LocalPTY = {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
titleNumber: number
|
|
||||||
rows?: number
|
rows?: number
|
||||||
cols?: number
|
cols?: number
|
||||||
buffer?: string
|
buffer?: string
|
||||||
@@ -43,20 +42,8 @@ function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, id:
|
|||||||
all: createMemo(() => Object.values(store.all)),
|
all: createMemo(() => Object.values(store.all)),
|
||||||
active: createMemo(() => store.active),
|
active: createMemo(() => store.active),
|
||||||
new() {
|
new() {
|
||||||
const existingTitleNumbers = new Set(
|
|
||||||
store.all.map((pty) => {
|
|
||||||
const match = pty.titleNumber
|
|
||||||
return match
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
let nextNumber = 1
|
|
||||||
while (existingTitleNumbers.has(nextNumber)) {
|
|
||||||
nextNumber++
|
|
||||||
}
|
|
||||||
|
|
||||||
sdk.client.pty
|
sdk.client.pty
|
||||||
.create({ title: `Terminal ${nextNumber}` })
|
.create({ title: `Terminal ${store.all.length + 1}` })
|
||||||
.then((pty) => {
|
.then((pty) => {
|
||||||
const id = pty.data?.id
|
const id = pty.data?.id
|
||||||
if (!id) return
|
if (!id) return
|
||||||
@@ -65,7 +52,6 @@ function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, id:
|
|||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
title: pty.data?.title ?? "Terminal",
|
title: pty.data?.title ?? "Terminal",
|
||||||
titleNumber: nextNumber,
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
setStore("active", id)
|
setStore("active", id)
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|||||||
import { DialogSelectFile } from "@/components/dialog-select-file"
|
import { DialogSelectFile } from "@/components/dialog-select-file"
|
||||||
import { DialogSelectModel } from "@/components/dialog-select-model"
|
import { DialogSelectModel } from "@/components/dialog-select-model"
|
||||||
import { DialogSelectMcp } from "@/components/dialog-select-mcp"
|
import { DialogSelectMcp } from "@/components/dialog-select-mcp"
|
||||||
import { DialogFork } from "@/components/dialog-fork"
|
|
||||||
import { useCommand } from "@/context/command"
|
import { useCommand } from "@/context/command"
|
||||||
import { useNavigate, useParams } from "@solidjs/router"
|
import { useNavigate, useParams } from "@solidjs/router"
|
||||||
import { UserMessage } from "@opencode-ai/sdk/v2"
|
import { UserMessage } from "@opencode-ai/sdk/v2"
|
||||||
@@ -646,15 +645,6 @@ export default function Page() {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "session.fork",
|
|
||||||
title: "Fork from message",
|
|
||||||
description: "Create a new session from a previous message",
|
|
||||||
category: "Session",
|
|
||||||
slash: "fork",
|
|
||||||
disabled: !params.id || visibleUserMessages().length === 0,
|
|
||||||
onSelect: () => dialog.show(() => <DialogFork />),
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
"chart.js": "4.5.1",
|
"chart.js": "4.5.1",
|
||||||
"nitro": "3.0.1-alpha.1",
|
"nitro": "3.0.1-alpha.1",
|
||||||
"solid-js": "catalog:",
|
"solid-js": "catalog:",
|
||||||
"solid-list": "0.3.0",
|
|
||||||
"vite": "catalog:",
|
"vite": "catalog:",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
@@ -9,8 +9,8 @@ export const config = {
|
|||||||
github: {
|
github: {
|
||||||
repoUrl: "https://github.com/anomalyco/opencode",
|
repoUrl: "https://github.com/anomalyco/opencode",
|
||||||
starsFormatted: {
|
starsFormatted: {
|
||||||
compact: "60K",
|
compact: "50K",
|
||||||
full: "60,000",
|
full: "50,000",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { useSession } from "@solidjs/start/http"
|
||||||
|
|
||||||
|
export interface AuthSession {
|
||||||
|
account?: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
id: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
>
|
||||||
|
current?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuthSession() {
|
||||||
|
return useSession<AuthSession>({
|
||||||
|
password: "0".repeat(32),
|
||||||
|
name: "auth",
|
||||||
|
maxAge: 60 * 60 * 24 * 365,
|
||||||
|
cookie: {
|
||||||
|
secure: false,
|
||||||
|
httpOnly: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,38 +5,13 @@ import { redirect } from "@solidjs/router"
|
|||||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
|
|
||||||
import { createClient } from "@openauthjs/openauth/client"
|
import { createClient } from "@openauthjs/openauth/client"
|
||||||
|
import { useAuthSession } from "./auth.session"
|
||||||
|
|
||||||
export const AuthClient = createClient({
|
export const AuthClient = createClient({
|
||||||
clientID: "app",
|
clientID: "app",
|
||||||
issuer: import.meta.env.VITE_AUTH_URL,
|
issuer: import.meta.env.VITE_AUTH_URL,
|
||||||
})
|
})
|
||||||
|
|
||||||
import { useSession } from "@solidjs/start/http"
|
|
||||||
import { Resource } from "@opencode-ai/console-resource"
|
|
||||||
|
|
||||||
export interface AuthSession {
|
|
||||||
account?: Record<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
id: string
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
>
|
|
||||||
current?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAuthSession() {
|
|
||||||
return useSession<AuthSession>({
|
|
||||||
password: Resource.ZEN_SESSION_SECRET.value,
|
|
||||||
name: "auth",
|
|
||||||
maxAge: 60 * 60 * 24 * 365,
|
|
||||||
cookie: {
|
|
||||||
secure: false,
|
|
||||||
httpOnly: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getActor = async (workspace?: string): Promise<Actor.Info> => {
|
export const getActor = async (workspace?: string): Promise<Actor.Info> => {
|
||||||
"use server"
|
"use server"
|
||||||
const evt = getRequestEvent()
|
const evt = getRequestEvent()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { redirect } from "@solidjs/router"
|
import { redirect } from "@solidjs/router"
|
||||||
import type { APIEvent } from "@solidjs/start/server"
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
import { AuthClient } from "~/context/auth"
|
import { AuthClient } from "~/context/auth"
|
||||||
import { useAuthSession } from "~/context/auth"
|
import { useAuthSession } from "~/context/auth.session"
|
||||||
|
|
||||||
export async function GET(input: APIEvent) {
|
export async function GET(input: APIEvent) {
|
||||||
const url = new URL(input.request.url)
|
const url = new URL(input.request.url)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { redirect } from "@solidjs/router"
|
import { redirect } from "@solidjs/router"
|
||||||
import { APIEvent } from "@solidjs/start"
|
import { APIEvent } from "@solidjs/start"
|
||||||
import { useAuthSession } from "~/context/auth"
|
import { useAuthSession } from "~/context/auth.session"
|
||||||
|
|
||||||
export async function GET(event: APIEvent) {
|
export async function GET(event: APIEvent) {
|
||||||
const auth = await useAuthSession()
|
const auth = await useAuthSession()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { APIEvent } from "@solidjs/start"
|
import { APIEvent } from "@solidjs/start"
|
||||||
import { useAuthSession } from "~/context/auth"
|
import { useAuthSession } from "~/context/auth.session"
|
||||||
|
|
||||||
export async function GET(input: APIEvent) {
|
export async function GET(input: APIEvent) {
|
||||||
const session = await useAuthSession()
|
const session = await useAuthSession()
|
||||||
|
|||||||
@@ -1,409 +0,0 @@
|
|||||||
[data-page="black"] {
|
|
||||||
background: #000;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: stretch;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
[data-component="header-gradient"] {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 288px;
|
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(0, 0, 0, 0) 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="header"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding-top: 40px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
/* [data-component="header-logo"] { */
|
|
||||||
/* } */
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="content"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
[data-slot="hero-black"] {
|
|
||||||
margin-top: 110px;
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
margin-top: 150px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="cta"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 32px;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: -18px;
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
margin-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="heading"] {
|
|
||||||
color: rgba(255, 255, 255, 0.92);
|
|
||||||
text-align: center;
|
|
||||||
font-size: 18px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 160%; /* 28.8px */
|
|
||||||
|
|
||||||
span {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[data-slot="subheading"] {
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
font-size: 15px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 160%;
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 160%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[data-slot="button"] {
|
|
||||||
display: inline-flex;
|
|
||||||
height: 40px;
|
|
||||||
padding: 0 12px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: rgba(255, 255, 255, 0.92);
|
|
||||||
text-decoration: none;
|
|
||||||
color: #000;
|
|
||||||
font-family: "JetBrains Mono Nerd Font";
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: normal;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[data-slot="back-soon"] {
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
text-align: center;
|
|
||||||
font-size: 13px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 160%; /* 20.8px */
|
|
||||||
}
|
|
||||||
[data-slot="follow-us"] {
|
|
||||||
display: inline-flex;
|
|
||||||
height: 40px;
|
|
||||||
padding: 0 12px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.17);
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
font-family: "JetBrains Mono Nerd Font";
|
|
||||||
font-size: 14px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="pricing"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 540px;
|
|
||||||
padding: 0 20px;
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="pricing-card"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.17);
|
|
||||||
border-radius: 4px;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: border-color 0.15s ease;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: rgba(255, 255, 255, 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="icon"] {
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="price"] {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="amount"] {
|
|
||||||
color: rgba(255, 255, 255, 0.92);
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="period"] {
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="multiplier"] {
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "·";
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="selected-plan"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 32px;
|
|
||||||
width: fit-content;
|
|
||||||
max-width: calc(100% - 40px);
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="selected-card"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.17);
|
|
||||||
border-radius: 4px;
|
|
||||||
width: fit-content;
|
|
||||||
|
|
||||||
[data-slot="icon"] {
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="price"] {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="amount"] {
|
|
||||||
color: rgba(255, 255, 255, 0.92);
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="period"] {
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="multiplier"] {
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "·";
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="terms"] {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
li {
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.5;
|
|
||||||
padding-left: 16px;
|
|
||||||
position: relative;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "▪";
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="actions"] {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
margin-top: 8px;
|
|
||||||
|
|
||||||
button,
|
|
||||||
a {
|
|
||||||
flex: 1;
|
|
||||||
display: inline-flex;
|
|
||||||
height: 48px;
|
|
||||||
padding: 0 16px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 400;
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="cancel"] {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.17);
|
|
||||||
color: rgba(255, 255, 255, 0.92);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: rgba(255, 255, 255, 0.35);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="continue"] {
|
|
||||||
background: rgba(255, 255, 255, 0.17);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.17);
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.25);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="fine-print"] {
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
text-align: center;
|
|
||||||
font-size: 13px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 160%; /* 20.8px */
|
|
||||||
font-style: italic;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="footer"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 24px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
height: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="footer-content"] {
|
|
||||||
display: flex;
|
|
||||||
gap: 24px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
gap: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span,
|
|
||||||
a {
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
font-family: "JetBrains Mono Nerd Font";
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="github-stars"] {
|
|
||||||
color: rgba(255, 255, 255, 0.25);
|
|
||||||
font-family: "JetBrains Mono Nerd Font";
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="anomaly"] {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[data-slot="anomaly-alt"] {
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
font-family: "JetBrains Mono Nerd Font";
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
text-decoration: none;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
font-family: "JetBrains Mono Nerd Font";
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,214 +0,0 @@
|
|||||||
[data-page="black"] {
|
|
||||||
background: #000;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: stretch;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
[data-component="header-gradient"] {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 288px;
|
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(0, 0, 0, 0) 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="header"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding-top: 40px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
/* [data-component="header-logo"] { */
|
|
||||||
/* } */
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="content"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
[data-slot="hero-black"] {
|
|
||||||
margin-top: 110px;
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
margin-top: 150px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="select-workspace"] {
|
|
||||||
display: flex;
|
|
||||||
margin-top: -24px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 480px;
|
|
||||||
height: 305px;
|
|
||||||
padding: 32px 20px 0 20px;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 24px;
|
|
||||||
|
|
||||||
border: 1px solid #303030;
|
|
||||||
background: #0a0a0a;
|
|
||||||
box-shadow:
|
|
||||||
0 100px 80px 0 rgba(0, 0, 0, 0.04),
|
|
||||||
0 41.778px 33.422px 0 rgba(0, 0, 0, 0.05),
|
|
||||||
0 22.336px 17.869px 0 rgba(0, 0, 0, 0.06),
|
|
||||||
0 12.522px 10.017px 0 rgba(0, 0, 0, 0.08),
|
|
||||||
0 6.65px 5.32px 0 rgba(0, 0, 0, 0.09),
|
|
||||||
0 2.767px 2.214px 0 rgba(0, 0, 0, 0.13);
|
|
||||||
|
|
||||||
[data-slot="select-workspace-title"] {
|
|
||||||
flex-shrink: 0;
|
|
||||||
align-self: stretch;
|
|
||||||
color: rgba(255, 255, 255, 0.59);
|
|
||||||
text-align: center;
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 160%; /* 25.6px */
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="workspaces"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 8px;
|
|
||||||
align-self: stretch;
|
|
||||||
outline: none;
|
|
||||||
overflow-y: auto;
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
|
|
||||||
scrollbar-width: none;
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="workspace"] {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
padding: 8px 12px;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
align-self: stretch;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
[data-slot="selected-icon"] {
|
|
||||||
visibility: hidden;
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
font-family: "IBM Plex Mono";
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 160%; /* 25.6px */
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: rgba(255, 255, 255, 0.92);
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 160%; /* 25.6px */
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="workspace"]:hover,
|
|
||||||
[data-slot="workspace"][data-active="true"] {
|
|
||||||
background: #161616;
|
|
||||||
|
|
||||||
[data-slot="selected-icon"] {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="footer"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 24px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
height: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="footer-content"] {
|
|
||||||
display: flex;
|
|
||||||
gap: 24px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
gap: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span,
|
|
||||||
a {
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
font-family: "JetBrains Mono Nerd Font";
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="github-stars"] {
|
|
||||||
color: rgba(255, 255, 255, 0.25);
|
|
||||||
font-family: "JetBrains Mono Nerd Font";
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="anomaly"] {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[data-slot="anomaly-alt"] {
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
font-family: "JetBrains Mono Nerd Font";
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
text-decoration: none;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: rgba(255, 255, 255, 0.39);
|
|
||||||
font-family: "JetBrains Mono Nerd Font";
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -24,7 +24,7 @@ export async function GET({ params: { platform } }: APIEvent) {
|
|||||||
const resp = await fetch(`https://github.com/anomalyco/opencode/releases/latest/download/${assetName}`, {
|
const resp = await fetch(`https://github.com/anomalyco/opencode/releases/latest/download/${assetName}`, {
|
||||||
cf: {
|
cf: {
|
||||||
// in case gh releases has rate limits
|
// in case gh releases has rate limits
|
||||||
cacheTtl: 60 * 5,
|
cacheTtl: 60 * 60 * 24,
|
||||||
cacheEverything: true,
|
cacheEverything: true,
|
||||||
},
|
},
|
||||||
} as any)
|
} as any)
|
||||||
|
|||||||
@@ -244,8 +244,7 @@ export default function Download() {
|
|||||||
Download
|
Download
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{/* Disabled temporarily as it doesn't work */}
|
<div data-component="download-row">
|
||||||
{/*<div data-component="download-row">
|
|
||||||
<div data-component="download-info">
|
<div data-component="download-info">
|
||||||
<span data-slot="icon">
|
<span data-slot="icon">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -260,7 +259,7 @@ export default function Download() {
|
|||||||
<a href={getDownloadHref("linux-x64-appimage")} data-component="action-button">
|
<a href={getDownloadHref("linux-x64-appimage")} data-component="action-button">
|
||||||
Download
|
Download
|
||||||
</a>
|
</a>
|
||||||
</div>*/}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -522,7 +522,7 @@ body {
|
|||||||
[data-slot="content"] {
|
[data-slot="content"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1ch;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="text"] {
|
[data-slot="text"] {
|
||||||
|
|||||||
@@ -195,12 +195,6 @@ export default function Home() {
|
|||||||
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
|
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<span>[*]</span>
|
|
||||||
<div>
|
|
||||||
<strong>ChatGPT Plus/Pro</strong> Log in with OpenAI to use your ChatGPT Plus or Pro account
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<span>[*]</span>
|
<span>[*]</span>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -91,9 +91,6 @@ export default function Home() {
|
|||||||
<li>
|
<li>
|
||||||
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
|
<strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<strong>ChatGPT Plus/Pro</strong> Log in with OpenAI to use your ChatGPT Plus or Pro account
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<strong>Use any model</strong> Supports 75+ LLM providers through{" "}
|
<strong>Use any model</strong> Supports 75+ LLM providers through{" "}
|
||||||
<a href="https://models.dev">Models.dev</a>, including local models
|
<a href="https://models.dev">Models.dev</a>, including local models
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { action } from "@solidjs/router"
|
import { action } from "@solidjs/router"
|
||||||
import { getRequestEvent } from "solid-js/web"
|
import { getRequestEvent } from "solid-js/web"
|
||||||
import { useAuthSession } from "~/context/auth"
|
import { useAuthSession } from "~/context/auth.session"
|
||||||
import { Dropdown } from "~/component/dropdown"
|
import { Dropdown } from "~/component/dropdown"
|
||||||
import "./user-menu.css"
|
import "./user-menu.css"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -38,21 +38,10 @@ if (identifier.startsWith("wrk_")) {
|
|||||||
workspaceID: UserTable.workspaceID,
|
workspaceID: UserTable.workspaceID,
|
||||||
workspaceName: WorkspaceTable.name,
|
workspaceName: WorkspaceTable.name,
|
||||||
role: UserTable.role,
|
role: UserTable.role,
|
||||||
subscribed: SubscriptionTable.timeCreated,
|
|
||||||
})
|
})
|
||||||
.from(UserTable)
|
.from(UserTable)
|
||||||
.rightJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
|
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
|
||||||
.leftJoin(SubscriptionTable, eq(SubscriptionTable.userID, UserTable.id))
|
.where(eq(UserTable.accountID, accountID)),
|
||||||
.where(eq(UserTable.accountID, accountID))
|
|
||||||
.then((rows) =>
|
|
||||||
rows.map((row) => ({
|
|
||||||
userID: row.userID,
|
|
||||||
workspaceID: row.workspaceID,
|
|
||||||
workspaceName: row.workspaceName,
|
|
||||||
role: row.role,
|
|
||||||
subscribed: formatDate(row.subscribed),
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get all payments for these workspaces
|
// Get all payments for these workspaces
|
||||||
@@ -113,8 +102,6 @@ async function printWorkspace(workspaceID: string) {
|
|||||||
.select({
|
.select({
|
||||||
balance: BillingTable.balance,
|
balance: BillingTable.balance,
|
||||||
customerID: BillingTable.customerID,
|
customerID: BillingTable.customerID,
|
||||||
subscriptionID: BillingTable.subscriptionID,
|
|
||||||
subscriptionCouponID: BillingTable.subscriptionCouponID,
|
|
||||||
})
|
})
|
||||||
.from(BillingTable)
|
.from(BillingTable)
|
||||||
.where(eq(BillingTable.workspaceID, workspace.id))
|
.where(eq(BillingTable.workspaceID, workspace.id))
|
||||||
@@ -151,7 +138,6 @@ async function printWorkspace(workspaceID: string) {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
|
||||||
await printTable("Usage", (tx) =>
|
await printTable("Usage", (tx) =>
|
||||||
tx
|
tx
|
||||||
.select({
|
.select({
|
||||||
@@ -177,7 +163,6 @@ async function printWorkspace(workspaceID: string) {
|
|||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatMicroCents(value: number | null | undefined) {
|
function formatMicroCents(value: number | null | undefined) {
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import { Billing } from "../src/billing.js"
|
|
||||||
import { and, Database, eq } from "../src/drizzle/index.js"
|
|
||||||
import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js"
|
|
||||||
|
|
||||||
const workspaceID = process.argv[2]
|
|
||||||
|
|
||||||
if (!workspaceID) {
|
|
||||||
console.error("Usage: bun remove-black.ts <workspaceID>")
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Removing subscription from workspace ${workspaceID}`)
|
|
||||||
|
|
||||||
// Look up the workspace billing
|
|
||||||
const billing = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select({
|
|
||||||
customerID: BillingTable.customerID,
|
|
||||||
subscriptionID: BillingTable.subscriptionID,
|
|
||||||
})
|
|
||||||
.from(BillingTable)
|
|
||||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
|
||||||
.then((rows) => rows[0]),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!billing) {
|
|
||||||
console.error(`Error: No billing record found for workspace ${workspaceID}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!billing.subscriptionID) {
|
|
||||||
console.error(`Error: Workspace ${workspaceID} does not have a subscription`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(` Customer ID: ${billing.customerID}`)
|
|
||||||
console.log(` Subscription ID: ${billing.subscriptionID}`)
|
|
||||||
|
|
||||||
// Clear workspaceID from Stripe customer metadata
|
|
||||||
if (billing.customerID) {
|
|
||||||
//await Billing.stripe().customers.update(billing.customerID, {
|
|
||||||
// metadata: {
|
|
||||||
// workspaceID: "",
|
|
||||||
// },
|
|
||||||
//})
|
|
||||||
//console.log(`Cleared workspaceID from Stripe customer metadata`)
|
|
||||||
}
|
|
||||||
|
|
||||||
await Database.transaction(async (tx) => {
|
|
||||||
// Clear subscription-related fields from billing table
|
|
||||||
await tx
|
|
||||||
.update(BillingTable)
|
|
||||||
.set({
|
|
||||||
// customerID: null,
|
|
||||||
subscriptionID: null,
|
|
||||||
subscriptionCouponID: null,
|
|
||||||
// paymentMethodID: null,
|
|
||||||
// paymentMethodLast4: null,
|
|
||||||
// paymentMethodType: null,
|
|
||||||
})
|
|
||||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
|
||||||
|
|
||||||
// Delete from subscription table
|
|
||||||
await tx.delete(SubscriptionTable).where(eq(SubscriptionTable.workspaceID, workspaceID))
|
|
||||||
|
|
||||||
// Delete from payments table
|
|
||||||
await tx
|
|
||||||
.delete(PaymentTable)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(PaymentTable.workspaceID, workspaceID),
|
|
||||||
eq(PaymentTable.enrichment, { type: "subscription" }),
|
|
||||||
eq(PaymentTable.amount, 20000000000),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`Successfully removed subscription from workspace ${workspaceID}`)
|
|
||||||
Vendored
-4
@@ -130,10 +130,6 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
"ZEN_SESSION_SECRET": {
|
|
||||||
"type": "sst.sst.Secret"
|
|
||||||
"value": string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// cloudflare
|
// cloudflare
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
-4
@@ -130,10 +130,6 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
"ZEN_SESSION_SECRET": {
|
|
||||||
"type": "sst.sst.Secret"
|
|
||||||
"value": string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// cloudflare
|
// cloudflare
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
|
|||||||
-4
@@ -130,10 +130,6 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
"ZEN_SESSION_SECRET": {
|
|
||||||
"type": "sst.sst.Secret"
|
|
||||||
"value": string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// cloudflare
|
// cloudflare
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
|
|
||||||
import { copyBinaryToSidecarFolder, getCurrentSidecar, windowsify } from "./utils"
|
import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils"
|
||||||
|
|
||||||
const RUST_TARGET = Bun.env.TAURI_ENV_TARGET_TRIPLE
|
const RUST_TARGET = Bun.env.TAURI_ENV_TARGET_TRIPLE
|
||||||
|
|
||||||
const sidecarConfig = getCurrentSidecar(RUST_TARGET)
|
const sidecarConfig = getCurrentSidecar(RUST_TARGET)
|
||||||
|
|
||||||
const binaryPath = windowsify(`../opencode/dist/${sidecarConfig.ocBinary}/bin/opencode`)
|
const binaryPath = `../opencode/dist/${sidecarConfig.ocBinary}/bin/opencode${process.platform === "win32" ? ".exe" : ""}`
|
||||||
|
|
||||||
await $`cd ../opencode && bun run build --single`
|
await $`cd ../opencode && bun run build --single`
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
|
|
||||||
import { copyBinaryToSidecarFolder, getCurrentSidecar, windowsify } from "./utils"
|
import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils"
|
||||||
|
|
||||||
const sidecarConfig = getCurrentSidecar()
|
const sidecarConfig = getCurrentSidecar()
|
||||||
|
|
||||||
@@ -10,4 +10,6 @@ const dir = "src-tauri/target/opencode-binaries"
|
|||||||
await $`mkdir -p ${dir}`
|
await $`mkdir -p ${dir}`
|
||||||
await $`gh run download ${Bun.env.GITHUB_RUN_ID} -n opencode-cli`.cwd(dir)
|
await $`gh run download ${Bun.env.GITHUB_RUN_ID} -n opencode-cli`.cwd(dir)
|
||||||
|
|
||||||
await copyBinaryToSidecarFolder(windowsify(`${dir}/${sidecarConfig.ocBinary}/bin/opencode`))
|
await copyBinaryToSidecarFolder(
|
||||||
|
`${dir}/${sidecarConfig.ocBinary}/bin/opencode${process.platform === "win32" ? ".exe" : ""}`,
|
||||||
|
)
|
||||||
|
|||||||
@@ -41,13 +41,8 @@ export function getCurrentSidecar(target = RUST_TARGET) {
|
|||||||
|
|
||||||
export async function copyBinaryToSidecarFolder(source: string, target = RUST_TARGET) {
|
export async function copyBinaryToSidecarFolder(source: string, target = RUST_TARGET) {
|
||||||
await $`mkdir -p src-tauri/sidecars`
|
await $`mkdir -p src-tauri/sidecars`
|
||||||
const dest = windowsify(`src-tauri/sidecars/opencode-cli-${target}`)
|
const dest = `src-tauri/sidecars/opencode-cli-${target}${process.platform === "win32" ? ".exe" : ""}`
|
||||||
await $`cp ${source} ${dest}`
|
await $`cp ${source} ${dest}`
|
||||||
|
|
||||||
console.log(`Copied ${source} to ${dest}`)
|
console.log(`Copied ${source} to ${dest}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function windowsify(path: string) {
|
|
||||||
if (path.endsWith(".exe")) return path
|
|
||||||
return `${path}${process.platform === "win32" ? ".exe" : ""}`
|
|
||||||
}
|
|
||||||
|
|||||||
Generated
+3
-6
@@ -2795,7 +2795,6 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"gtk",
|
"gtk",
|
||||||
"listeners",
|
"listeners",
|
||||||
"reqwest",
|
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -2814,9 +2813,7 @@ dependencies = [
|
|||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"tauri-plugin-window-state",
|
"tauri-plugin-window-state",
|
||||||
"tokio",
|
"tokio",
|
||||||
"uuid",
|
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"windows",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5366,13 +5363,13 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.19.0"
|
version = "1.18.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
|
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde_core",
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name = "opencode-desktop"
|
|||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
description = "The open source AI coding agent"
|
description = "The open source AI coding agent"
|
||||||
authors = ["Anomaly Innovations"]
|
authors = ["Anomaly Innovations"]
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -38,17 +38,7 @@ listeners = "0.3"
|
|||||||
tauri-plugin-os = "2"
|
tauri-plugin-os = "2"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
semver = "1.0.27"
|
semver = "1.0.27"
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
|
||||||
uuid = { version = "1.19.0", features = ["v4"] }
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
gtk = "0.18.2"
|
gtk = "0.18.2"
|
||||||
webkit2gtk = "=2.0.1"
|
webkit2gtk = "=2.0.1"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
|
||||||
windows = { version = "0.61", features = [
|
|
||||||
"Win32_Foundation",
|
|
||||||
"Win32_System_JobObjects",
|
|
||||||
"Win32_System_Threading",
|
|
||||||
"Win32_Security"
|
|
||||||
] }
|
|
||||||
|
|||||||
@@ -1,30 +1,8 @@
|
|||||||
use tauri::{path::BaseDirectory, AppHandle, Manager};
|
use tauri::Manager;
|
||||||
use tauri_plugin_shell::{process::Command, ShellExt};
|
|
||||||
|
|
||||||
const CLI_INSTALL_DIR: &str = ".opencode/bin";
|
const CLI_INSTALL_DIR: &str = ".opencode/bin";
|
||||||
const CLI_BINARY_NAME: &str = "opencode";
|
const CLI_BINARY_NAME: &str = "opencode";
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
|
||||||
pub struct ServerConfig {
|
|
||||||
pub hostname: Option<String>,
|
|
||||||
pub port: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
|
||||||
pub struct Config {
|
|
||||||
pub server: Option<ServerConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_config(app: &AppHandle) -> Option<Config> {
|
|
||||||
create_command(app, "debug config")
|
|
||||||
.output()
|
|
||||||
.await
|
|
||||||
.inspect_err(|e| eprintln!("Failed to read OC config: {e}"))
|
|
||||||
.ok()
|
|
||||||
.and_then(|out| String::from_utf8(out.stdout.to_vec()).ok())
|
|
||||||
.and_then(|s| serde_json::from_str::<Config>(&s).ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cli_install_path() -> Option<std::path::PathBuf> {
|
fn get_cli_install_path() -> Option<std::path::PathBuf> {
|
||||||
std::env::var("HOME").ok().map(|home| {
|
std::env::var("HOME").ok().map(|home| {
|
||||||
std::path::PathBuf::from(home)
|
std::path::PathBuf::from(home)
|
||||||
@@ -139,36 +117,3 @@ pub fn sync_cli(app: tauri::AppHandle) -> Result<(), String> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_user_shell() -> String {
|
|
||||||
std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_command(app: &tauri::AppHandle, args: &str) -> Command {
|
|
||||||
let state_dir = app
|
|
||||||
.path()
|
|
||||||
.resolve("", BaseDirectory::AppLocalData)
|
|
||||||
.expect("Failed to resolve app local data dir");
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
return app
|
|
||||||
.shell()
|
|
||||||
.sidecar("opencode-cli")
|
|
||||||
.unwrap()
|
|
||||||
.args(args.split_whitespace())
|
|
||||||
.env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true")
|
|
||||||
.env("OPENCODE_CLIENT", "desktop")
|
|
||||||
.env("XDG_STATE_HOME", &state_dir);
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
return {
|
|
||||||
let sidecar = get_sidecar_path(app);
|
|
||||||
let shell = get_user_shell();
|
|
||||||
app.shell()
|
|
||||||
.command(&shell)
|
|
||||||
.env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true")
|
|
||||||
.env("OPENCODE_CLIENT", "desktop")
|
|
||||||
.env("XDG_STATE_HOME", &state_dir)
|
|
||||||
.args(["-il", "-c", &format!("\"{}\" {}", sidecar.display(), args)])
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
//! Windows Job Object for reliable child process cleanup.
|
|
||||||
//!
|
|
||||||
//! This module provides a wrapper around Windows Job Objects with the
|
|
||||||
//! `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` flag set. When the job object handle
|
|
||||||
//! is closed (including when the parent process exits or crashes), Windows
|
|
||||||
//! automatically terminates all processes assigned to the job.
|
|
||||||
//!
|
|
||||||
//! This is more reliable than manual cleanup because it works even if:
|
|
||||||
//! - The parent process crashes
|
|
||||||
//! - The parent is killed via Task Manager
|
|
||||||
//! - The RunEvent::Exit handler fails to run
|
|
||||||
|
|
||||||
use std::io::{Error, Result};
|
|
||||||
#[cfg(windows)]
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use windows::Win32::Foundation::{CloseHandle, HANDLE};
|
|
||||||
use windows::Win32::System::JobObjects::{
|
|
||||||
AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
|
|
||||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation,
|
|
||||||
SetInformationJobObject,
|
|
||||||
};
|
|
||||||
use windows::Win32::System::Threading::{OpenProcess, PROCESS_SET_QUOTA, PROCESS_TERMINATE};
|
|
||||||
|
|
||||||
/// A Windows Job Object configured to kill all assigned processes when closed.
|
|
||||||
///
|
|
||||||
/// When this struct is dropped or when the owning process exits (even abnormally),
|
|
||||||
/// Windows will automatically terminate all processes that have been assigned to it.
|
|
||||||
pub struct JobObject(HANDLE);
|
|
||||||
|
|
||||||
// SAFETY: HANDLE is just a pointer-sized value, and Windows job objects
|
|
||||||
// can be safely accessed from multiple threads.
|
|
||||||
unsafe impl Send for JobObject {}
|
|
||||||
unsafe impl Sync for JobObject {}
|
|
||||||
|
|
||||||
impl JobObject {
|
|
||||||
/// Creates a new anonymous job object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` set.
|
|
||||||
///
|
|
||||||
/// When the last handle to this job is closed (including on process exit),
|
|
||||||
/// Windows will terminate all processes assigned to the job.
|
|
||||||
pub fn new() -> Result<Self> {
|
|
||||||
unsafe {
|
|
||||||
// Create an anonymous job object
|
|
||||||
let job = CreateJobObjectW(None, None).map_err(|e| Error::other(e.message()))?;
|
|
||||||
|
|
||||||
// Configure the job to kill all processes when the handle is closed
|
|
||||||
let mut info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
|
|
||||||
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
||||||
|
|
||||||
SetInformationJobObject(
|
|
||||||
job,
|
|
||||||
JobObjectExtendedLimitInformation,
|
|
||||||
&info as *const _ as *const std::ffi::c_void,
|
|
||||||
std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
|
|
||||||
)
|
|
||||||
.map_err(|e| Error::other(e.message()))?;
|
|
||||||
|
|
||||||
Ok(Self(job))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assigns a process to this job object by its process ID.
|
|
||||||
///
|
|
||||||
/// Once assigned, the process will be terminated when this job object is dropped
|
|
||||||
/// or when the owning process exits.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
/// * `pid` - The process ID of the process to assign
|
|
||||||
pub fn assign_pid(&self, pid: u32) -> Result<()> {
|
|
||||||
unsafe {
|
|
||||||
// Open a handle to the process with the minimum required permissions
|
|
||||||
// PROCESS_SET_QUOTA and PROCESS_TERMINATE are required by AssignProcessToJobObject
|
|
||||||
let process = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, false, pid)
|
|
||||||
.map_err(|e| Error::other(e.message()))?;
|
|
||||||
|
|
||||||
// Assign the process to the job
|
|
||||||
let result = AssignProcessToJobObject(self.0, process);
|
|
||||||
|
|
||||||
// Close our handle to the process - the job object maintains its own reference
|
|
||||||
let _ = CloseHandle(process);
|
|
||||||
|
|
||||||
result.map_err(|e| Error::other(e.message()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for JobObject {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
// When this handle is closed and it's the last handle to the job,
|
|
||||||
// Windows will terminate all processes in the job due to KILL_ON_JOB_CLOSE
|
|
||||||
let _ = CloseHandle(self.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Holds the Windows Job Object that ensures child processes are killed when the app exits.
|
|
||||||
/// On Windows, when the job object handle is closed (including on crash), all assigned
|
|
||||||
/// processes are automatically terminated by the OS.
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub struct JobObjectState {
|
|
||||||
job: Mutex<Option<JobObject>>,
|
|
||||||
error: Mutex<Option<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
impl JobObjectState {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
match JobObject::new() {
|
|
||||||
Ok(job) => Self {
|
|
||||||
job: Mutex::new(Some(job)),
|
|
||||||
error: Mutex::new(None),
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Failed to create job object: {e}");
|
|
||||||
Self {
|
|
||||||
job: Mutex::new(None),
|
|
||||||
error: Mutex::new(Some(format!("Failed to create job object: {e}"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn assign_pid(&self, pid: u32) {
|
|
||||||
if let Some(job) = self.job.lock().unwrap().as_ref() {
|
|
||||||
if let Err(e) = job.assign_pid(pid) {
|
|
||||||
eprintln!("Failed to assign process {pid} to job object: {e}");
|
|
||||||
*self.error.lock().unwrap() =
|
|
||||||
Some(format!("Failed to assign process to job object: {e}"));
|
|
||||||
} else {
|
|
||||||
println!("Assigned process {pid} to job object for automatic cleanup");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_job_object_creation() {
|
|
||||||
let job = JobObject::new();
|
|
||||||
assert!(job.is_ok(), "Failed to create job object: {:?}", job.err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,34 @@
|
|||||||
mod cli;
|
mod cli;
|
||||||
#[cfg(windows)]
|
|
||||||
mod job_object;
|
|
||||||
mod window_customizer;
|
mod window_customizer;
|
||||||
|
|
||||||
use cli::{install_cli, sync_cli};
|
use cli::{get_sidecar_path, install_cli, sync_cli};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use futures::future;
|
|
||||||
#[cfg(windows)]
|
|
||||||
use job_object::*;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
net::TcpListener,
|
net::{SocketAddr, TcpListener},
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use tauri::{AppHandle, LogicalSize, Manager, RunEvent, State, WebviewUrl, WebviewWindow};
|
use tauri::{
|
||||||
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
|
path::BaseDirectory, AppHandle, LogicalSize, Manager, RunEvent, State, WebviewUrl,
|
||||||
|
WebviewWindow,
|
||||||
|
};
|
||||||
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
||||||
use tauri_plugin_store::StoreExt;
|
use tauri_plugin_shell::ShellExt;
|
||||||
use tokio::sync::oneshot;
|
use tokio::net::TcpSocket;
|
||||||
|
|
||||||
use crate::window_customizer::PinchZoomDisablePlugin;
|
use crate::window_customizer::PinchZoomDisablePlugin;
|
||||||
|
|
||||||
const SETTINGS_STORE: &str = "opencode.settings.dat";
|
|
||||||
const DEFAULT_SERVER_URL_KEY: &str = "defaultServerUrl";
|
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize)]
|
|
||||||
struct ServerReadyData {
|
|
||||||
url: String,
|
|
||||||
password: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ServerState {
|
struct ServerState {
|
||||||
child: Arc<Mutex<Option<CommandChild>>>,
|
child: Arc<Mutex<Option<CommandChild>>>,
|
||||||
status: future::Shared<oneshot::Receiver<Result<ServerReadyData, String>>>,
|
status: futures::future::Shared<tokio::sync::oneshot::Receiver<Result<(), String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerState {
|
impl ServerState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
child: Option<CommandChild>,
|
child: Option<CommandChild>,
|
||||||
status: oneshot::Receiver<Result<ServerReadyData, String>>,
|
status: tokio::sync::oneshot::Receiver<Result<(), String>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
child: Arc::new(Mutex::new(child)),
|
child: Arc::new(Mutex::new(child)),
|
||||||
@@ -92,7 +80,7 @@ async fn get_logs(app: AppHandle) -> Result<String, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn ensure_server_ready(state: State<'_, ServerState>) -> Result<ServerReadyData, String> {
|
async fn ensure_server_started(state: State<'_, ServerState>) -> Result<(), String> {
|
||||||
state
|
state
|
||||||
.status
|
.status
|
||||||
.clone()
|
.clone()
|
||||||
@@ -100,41 +88,6 @@ async fn ensure_server_ready(state: State<'_, ServerState>) -> Result<ServerRead
|
|||||||
.map_err(|_| "Failed to get server status".to_string())?
|
.map_err(|_| "Failed to get server status".to_string())?
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn get_default_server_url(app: AppHandle) -> Result<Option<String>, String> {
|
|
||||||
let store = app
|
|
||||||
.store(SETTINGS_STORE)
|
|
||||||
.map_err(|e| format!("Failed to open settings store: {}", e))?;
|
|
||||||
|
|
||||||
let value = store.get(DEFAULT_SERVER_URL_KEY);
|
|
||||||
match value {
|
|
||||||
Some(v) => Ok(v.as_str().map(String::from)),
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn set_default_server_url(app: AppHandle, url: Option<String>) -> Result<(), String> {
|
|
||||||
let store = app
|
|
||||||
.store(SETTINGS_STORE)
|
|
||||||
.map_err(|e| format!("Failed to open settings store: {}", e))?;
|
|
||||||
|
|
||||||
match url {
|
|
||||||
Some(u) => {
|
|
||||||
store.set(DEFAULT_SERVER_URL_KEY, serde_json::Value::String(u));
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
store.delete(DEFAULT_SERVER_URL_KEY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
store
|
|
||||||
.save()
|
|
||||||
.map_err(|e| format!("Failed to save settings: {}", e))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_sidecar_port() -> u32 {
|
fn get_sidecar_port() -> u32 {
|
||||||
option_env!("OPENCODE_PORT")
|
option_env!("OPENCODE_PORT")
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
@@ -149,17 +102,49 @@ fn get_sidecar_port() -> u32 {
|
|||||||
}) as u32
|
}) as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_sidecar(app: &AppHandle, port: u32, password: &str) -> CommandChild {
|
fn get_user_shell() -> String {
|
||||||
|
std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_sidecar(app: &AppHandle, port: u32) -> CommandChild {
|
||||||
let log_state = app.state::<LogState>();
|
let log_state = app.state::<LogState>();
|
||||||
let log_state_clone = log_state.inner().clone();
|
let log_state_clone = log_state.inner().clone();
|
||||||
|
|
||||||
println!("spawning sidecar on port {port}");
|
let state_dir = app
|
||||||
|
.path()
|
||||||
|
.resolve("", BaseDirectory::AppLocalData)
|
||||||
|
.expect("Failed to resolve app local data dir");
|
||||||
|
|
||||||
let (mut rx, child) = cli::create_command(app, format!("serve --port {port}").as_str())
|
#[cfg(target_os = "windows")]
|
||||||
.env("OPENCODE_SERVER_PASSWORD", password)
|
let (mut rx, child) = app
|
||||||
|
.shell()
|
||||||
|
.sidecar("opencode-cli")
|
||||||
|
.unwrap()
|
||||||
|
.env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true")
|
||||||
|
.env("OPENCODE_CLIENT", "desktop")
|
||||||
|
.env("XDG_STATE_HOME", &state_dir)
|
||||||
|
.args(["serve", &format!("--port={port}")])
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("Failed to spawn opencode");
|
.expect("Failed to spawn opencode");
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
let (mut rx, child) = {
|
||||||
|
let sidecar = get_sidecar_path(app);
|
||||||
|
let shell = get_user_shell();
|
||||||
|
app.shell()
|
||||||
|
.command(&shell)
|
||||||
|
.env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true")
|
||||||
|
.env("OPENCODE_CLIENT", "desktop")
|
||||||
|
.env("XDG_STATE_HOME", &state_dir)
|
||||||
|
.args([
|
||||||
|
"-il",
|
||||||
|
"-c",
|
||||||
|
&format!("\"{}\" serve --port={}", sidecar.display(), port),
|
||||||
|
])
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to spawn opencode")
|
||||||
|
};
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
while let Some(event) = rx.recv().await {
|
while let Some(event) = rx.recv().await {
|
||||||
match event {
|
match event {
|
||||||
@@ -197,37 +182,21 @@ fn spawn_sidecar(app: &AppHandle, port: u32, password: &str) -> CommandChild {
|
|||||||
child
|
child
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_server_health(url: &str, password: Option<&str>) -> bool {
|
async fn is_server_running(port: u32) -> bool {
|
||||||
let health_url = format!("{}/global/health", url.trim_end_matches('/'));
|
TcpSocket::new_v4()
|
||||||
let client = reqwest::Client::builder()
|
.unwrap()
|
||||||
.timeout(Duration::from_secs(3))
|
.connect(SocketAddr::new(
|
||||||
.build();
|
"127.0.0.1".parse().expect("Failed to parse IP"),
|
||||||
|
port as u16,
|
||||||
let Ok(client) = client else {
|
))
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut req = client.get(&health_url);
|
|
||||||
|
|
||||||
if let Some(password) = password {
|
|
||||||
req = req.basic_auth("opencode", Some(password));
|
|
||||||
}
|
|
||||||
|
|
||||||
req.send()
|
|
||||||
.await
|
.await
|
||||||
.map(|r| r.status().is_success())
|
.is_ok()
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
let updater_enabled = option_env!("TAURI_SIGNING_PRIVATE_KEY").is_some();
|
let updater_enabled = option_env!("TAURI_SIGNING_PRIVATE_KEY").is_some();
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let _ = std::process::Command::new("killall")
|
|
||||||
.arg("opencode-cli")
|
|
||||||
.output();
|
|
||||||
|
|
||||||
let mut builder = tauri::Builder::default()
|
let mut builder = tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
||||||
// Focus existing window when another instance is launched
|
// Focus existing window when another instance is launched
|
||||||
@@ -250,9 +219,7 @@ pub fn run() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
kill_sidecar,
|
kill_sidecar,
|
||||||
install_cli,
|
install_cli,
|
||||||
ensure_server_ready,
|
ensure_server_started
|
||||||
get_default_server_url,
|
|
||||||
set_default_server_url
|
|
||||||
])
|
])
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
let app = app.handle().clone();
|
let app = app.handle().clone();
|
||||||
@@ -260,15 +227,15 @@ pub fn run() {
|
|||||||
// Initialize log state
|
// Initialize log state
|
||||||
app.manage(LogState(Arc::new(Mutex::new(VecDeque::new()))));
|
app.manage(LogState(Arc::new(Mutex::new(VecDeque::new()))));
|
||||||
|
|
||||||
#[cfg(windows)]
|
// Get port and create window immediately for faster perceived startup
|
||||||
app.manage(JobObjectState::new());
|
let port = get_sidecar_port();
|
||||||
|
|
||||||
let primary_monitor = app.primary_monitor().ok().flatten();
|
let primary_monitor = app.primary_monitor().ok().flatten();
|
||||||
let size = primary_monitor
|
let size = primary_monitor
|
||||||
.map(|m| m.size().to_logical(m.scale_factor()))
|
.map(|m| m.size().to_logical(m.scale_factor()))
|
||||||
.unwrap_or(LogicalSize::new(1920, 1080));
|
.unwrap_or(LogicalSize::new(1920, 1080));
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
// Create window immediately with serverReady = false
|
||||||
let mut window_builder =
|
let mut window_builder =
|
||||||
WebviewWindow::builder(&app, "main", WebviewUrl::App("/".into()))
|
WebviewWindow::builder(&app, "main", WebviewUrl::App("/".into()))
|
||||||
.title("OpenCode")
|
.title("OpenCode")
|
||||||
@@ -280,6 +247,7 @@ pub fn run() {
|
|||||||
r#"
|
r#"
|
||||||
window.__OPENCODE__ ??= {{}};
|
window.__OPENCODE__ ??= {{}};
|
||||||
window.__OPENCODE__.updaterEnabled = {updater_enabled};
|
window.__OPENCODE__.updaterEnabled = {updater_enabled};
|
||||||
|
window.__OPENCODE__.port = {port};
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -290,44 +258,51 @@ pub fn run() {
|
|||||||
.hidden_title(true);
|
.hidden_title(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
window_builder.build().expect("Failed to create window");
|
let window = window_builder.build().expect("Failed to create window");
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||||
app.manage(ServerState::new(None, rx));
|
app.manage(ServerState::new(None, rx));
|
||||||
|
|
||||||
{
|
{
|
||||||
let app = app.clone();
|
let app = app.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let mut custom_url = None;
|
let should_spawn_sidecar = !is_server_running(port).await;
|
||||||
|
|
||||||
if let Some(url) = get_default_server_url(app.clone()).ok().flatten() {
|
let (child, res) = if should_spawn_sidecar {
|
||||||
println!("Using desktop-specific custom URL: {url}");
|
let child = spawn_sidecar(&app, port);
|
||||||
custom_url = Some(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
if custom_url.is_none()
|
let timestamp = Instant::now();
|
||||||
&& let Some(cli_config) = cli::get_config(&app).await
|
let res = loop {
|
||||||
&& let Some(url) = get_server_url_from_config(&cli_config)
|
if timestamp.elapsed() > Duration::from_secs(7) {
|
||||||
{
|
break Err(format!(
|
||||||
println!("Using custom server URL from config: {url}");
|
"Failed to spawn OpenCode Server. Logs:\n{}",
|
||||||
custom_url = Some(url);
|
get_logs(app.clone()).await.unwrap()
|
||||||
}
|
));
|
||||||
|
|
||||||
let res = match setup_server_connection(&app, custom_url).await {
|
|
||||||
Ok((child, url)) => {
|
|
||||||
#[cfg(windows)]
|
|
||||||
if let Some(child) = &child {
|
|
||||||
let job_state = app.state::<JobObjectState>();
|
|
||||||
job_state.assign_pid(child.pid());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.state::<ServerState>().set_child(child);
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||||
|
|
||||||
Ok(url)
|
if is_server_running(port).await {
|
||||||
}
|
// give the server a little bit more time to warm up
|
||||||
Err(e) => Err(e),
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||||
|
|
||||||
|
break Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Server ready after {:?}", timestamp.elapsed());
|
||||||
|
|
||||||
|
(Some(child), res)
|
||||||
|
} else {
|
||||||
|
(None, Ok(()))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
app.state::<ServerState>().set_child(child);
|
||||||
|
|
||||||
|
if res.is_ok() {
|
||||||
|
let _ = window.eval("window.__OPENCODE__.serverReady = true;");
|
||||||
|
}
|
||||||
|
|
||||||
let _ = tx.send(res);
|
let _ = tx.send(res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -359,105 +334,3 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_server_url_from_config(config: &cli::Config) -> Option<String> {
|
|
||||||
let server = config.server.as_ref()?;
|
|
||||||
let port = server.port?;
|
|
||||||
println!("server.port found in OC config: {port}");
|
|
||||||
let hostname = server.hostname.as_ref();
|
|
||||||
|
|
||||||
Some(format!(
|
|
||||||
"http://{}:{}",
|
|
||||||
hostname.map(|v| v.as_str()).unwrap_or("127.0.0.1"),
|
|
||||||
port
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn setup_server_connection(
|
|
||||||
app: &AppHandle,
|
|
||||||
custom_url: Option<String>,
|
|
||||||
) -> Result<(Option<CommandChild>, ServerReadyData), String> {
|
|
||||||
if let Some(url) = custom_url {
|
|
||||||
loop {
|
|
||||||
if check_server_health(&url, None).await {
|
|
||||||
println!("Connected to custom server: {}", url);
|
|
||||||
return Ok((
|
|
||||||
None,
|
|
||||||
ServerReadyData {
|
|
||||||
url: url.clone(),
|
|
||||||
password: None,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
const RETRY: &str = "Retry";
|
|
||||||
|
|
||||||
let res = app.dialog()
|
|
||||||
.message(format!("Could not connect to configured server:\n{}\n\nWould you like to retry or start a local server instead?", url))
|
|
||||||
.title("Connection Failed")
|
|
||||||
.buttons(MessageDialogButtons::OkCancelCustom(RETRY.to_string(), "Start Local".to_string()))
|
|
||||||
.blocking_show_with_result();
|
|
||||||
|
|
||||||
match res {
|
|
||||||
MessageDialogResult::Custom(name) if name == RETRY => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let local_port = get_sidecar_port();
|
|
||||||
let local_url = format!("http://127.0.0.1:{local_port}");
|
|
||||||
|
|
||||||
if !check_server_health(&local_url, None).await {
|
|
||||||
let password = uuid::Uuid::new_v4().to_string();
|
|
||||||
|
|
||||||
match spawn_local_server(app, local_port, &password).await {
|
|
||||||
Ok(child) => Ok((
|
|
||||||
Some(child),
|
|
||||||
ServerReadyData {
|
|
||||||
url: local_url,
|
|
||||||
password: Some(password),
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
Err(err) => Err(err),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok((
|
|
||||||
None,
|
|
||||||
ServerReadyData {
|
|
||||||
url: local_url,
|
|
||||||
password: None,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn spawn_local_server(
|
|
||||||
app: &AppHandle,
|
|
||||||
port: u32,
|
|
||||||
password: &str,
|
|
||||||
) -> Result<CommandChild, String> {
|
|
||||||
let child = spawn_sidecar(app, port, password);
|
|
||||||
let url = format!("http://127.0.0.1:{port}");
|
|
||||||
|
|
||||||
let timestamp = Instant::now();
|
|
||||||
loop {
|
|
||||||
if timestamp.elapsed() > Duration::from_secs(30) {
|
|
||||||
break Err(format!(
|
|
||||||
"Failed to spawn OpenCode Server. Logs:\n{}",
|
|
||||||
get_logs(app.clone()).await.unwrap()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
||||||
|
|
||||||
if check_server_health(&url, Some(password)).await {
|
|
||||||
println!("Server ready after {:?}", timestamp.elapsed());
|
|
||||||
break Ok(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"icons/dev/icon.ico"
|
"icons/dev/icon.ico"
|
||||||
],
|
],
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": ["deb", "rpm", "dmg", "nsis", "app"],
|
"targets": ["deb", "rpm", "dmg", "nsis", "app", "appimage"],
|
||||||
"externalBin": ["sidecars/opencode-cli"],
|
"externalBin": ["sidecars/opencode-cli"],
|
||||||
"macOS": {
|
"macOS": {
|
||||||
"entitlements": "./entitlements.plist"
|
"entitlements": "./entitlements.plist"
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ import { AsyncStorage } from "@solid-primitives/storage"
|
|||||||
import { fetch as tauriFetch } from "@tauri-apps/plugin-http"
|
import { fetch as tauriFetch } from "@tauri-apps/plugin-http"
|
||||||
import { Store } from "@tauri-apps/plugin-store"
|
import { Store } from "@tauri-apps/plugin-store"
|
||||||
import { Logo } from "@opencode-ai/ui/logo"
|
import { Logo } from "@opencode-ai/ui/logo"
|
||||||
import { createSignal, Show, Accessor, JSX, createResource } from "solid-js"
|
import { Suspense, createResource, ParentProps } from "solid-js"
|
||||||
|
|
||||||
import { UPDATER_ENABLED } from "./updater"
|
import { UPDATER_ENABLED } from "./updater"
|
||||||
import { createMenu } from "./menu"
|
import { createMenu } from "./menu"
|
||||||
import pkg from "../package.json"
|
import pkg from "../package.json"
|
||||||
|
import { Show } from "solid-js"
|
||||||
|
|
||||||
const root = document.getElementById("root")
|
const root = document.getElementById("root")
|
||||||
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||||
@@ -28,7 +29,7 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
|||||||
|
|
||||||
let update: Update | null = null
|
let update: Update | null = null
|
||||||
|
|
||||||
const createPlatform = (password: Accessor<string | null>): Platform => ({
|
const platform: Platform = {
|
||||||
platform: "desktop",
|
platform: "desktop",
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
|
|
||||||
@@ -255,35 +256,8 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
fetch: (input, init) => {
|
fetch: tauriFetch,
|
||||||
const pw = password()
|
}
|
||||||
|
|
||||||
const addHeader = (headers: Headers, password: string) => {
|
|
||||||
headers.append("Authorization", `Basic ${btoa(`opencode:${password}`)}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input instanceof Request) {
|
|
||||||
if (pw) addHeader(input.headers, pw)
|
|
||||||
return tauriFetch(input)
|
|
||||||
} else {
|
|
||||||
const headers = new Headers(init?.headers)
|
|
||||||
if (pw) addHeader(headers, pw)
|
|
||||||
return tauriFetch(input, {
|
|
||||||
...(init as any),
|
|
||||||
headers: headers,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getDefaultServerUrl: async () => {
|
|
||||||
const result = await invoke<string | null>("get_default_server_url").catch(() => null)
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
setDefaultServerUrl: async (url: string | null) => {
|
|
||||||
await invoke("set_default_server_url", { url })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
createMenu()
|
createMenu()
|
||||||
|
|
||||||
@@ -293,47 +267,41 @@ root?.addEventListener("mousewheel", (e) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
render(() => {
|
render(() => {
|
||||||
const [serverPassword, setServerPassword] = createSignal<string | null>(null)
|
|
||||||
const platform = createPlatform(() => serverPassword())
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlatformProvider value={platform}>
|
<PlatformProvider value={platform}>
|
||||||
|
{ostype() === "macos" && (
|
||||||
|
<div class="mx-px bg-background-base border-b border-border-weak-base h-8" data-tauri-drag-region />
|
||||||
|
)}
|
||||||
<AppBaseProviders>
|
<AppBaseProviders>
|
||||||
{ostype() === "macos" && (
|
|
||||||
<div class="mx-px bg-background-base border-b border-border-weak-base h-8" data-tauri-drag-region />
|
|
||||||
)}
|
|
||||||
<ServerGate>
|
<ServerGate>
|
||||||
{(data) => {
|
<AppInterface />
|
||||||
setServerPassword(data().password)
|
|
||||||
window.__OPENCODE__ ??= {}
|
|
||||||
window.__OPENCODE__.serverPassword = data().password ?? undefined
|
|
||||||
|
|
||||||
return <AppInterface defaultUrl={data().url} />
|
|
||||||
}}
|
|
||||||
</ServerGate>
|
</ServerGate>
|
||||||
</AppBaseProviders>
|
</AppBaseProviders>
|
||||||
</PlatformProvider>
|
</PlatformProvider>
|
||||||
)
|
)
|
||||||
}, root!)
|
}, root!)
|
||||||
|
|
||||||
type ServerReadyData = { url: string; password: string | null }
|
|
||||||
|
|
||||||
// Gate component that waits for the server to be ready
|
// Gate component that waits for the server to be ready
|
||||||
function ServerGate(props: { children: (data: Accessor<ServerReadyData>) => JSX.Element }) {
|
function ServerGate(props: ParentProps) {
|
||||||
const [serverData] = createResource<ServerReadyData>(() => invoke("ensure_server_ready"))
|
const [status] = createResource(async () => {
|
||||||
|
if (window.__OPENCODE__?.serverReady) return
|
||||||
|
return await invoke("ensure_server_started")
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// Not using suspense as not all components are compatible with it (undefined refs)
|
// Not using suspense as not all components are compatible with it (undefined refs)
|
||||||
<Show
|
<Show
|
||||||
when={serverData.state !== "pending" && serverData()}
|
when={status.state !== "pending"}
|
||||||
fallback={
|
fallback={
|
||||||
<div class="h-screen w-screen flex flex-col items-center justify-center bg-background-base">
|
<div class="h-screen w-screen flex flex-col items-center justify-center bg-background-base">
|
||||||
<Logo class="w-xl opacity-12 animate-pulse" />
|
<Logo class="w-xl opacity-12 animate-pulse" />
|
||||||
<div class="mt-8 text-14-regular text-text-weak">Initializing...</div>
|
<div class="mt-8 text-14-regular text-text-weak">Starting server...</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(data) => props.children(data)}
|
{/* Trigger error boundary without rendering the returned value */}
|
||||||
|
{(status(), null)}
|
||||||
|
{props.children}
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,10 +57,11 @@ Example: `mintlify`
|
|||||||
Hex color codes for your global theme
|
Hex color codes for your global theme
|
||||||
<Expandable title="Colors">
|
<Expandable title="Colors">
|
||||||
<ResponseField name="primary" type="string" required>
|
<ResponseField name="primary" type="string" required>
|
||||||
The primary color. Used most often for highlighted content, section headers, accents, in light mode
|
The primary color. Used for most often for highlighted content, section headers, accents, in light mode
|
||||||
</ResponseField>
|
</ResponseField>
|
||||||
<ResponseField name="light" type="string">
|
<ResponseField name="light" type="string">
|
||||||
The primary color for dark mode. Used most often for highlighted content, section headers, accents, in dark mode
|
The primary color for dark mode. Used for most often for highlighted content, section headers, accents, in dark
|
||||||
|
mode
|
||||||
</ResponseField>
|
</ResponseField>
|
||||||
<ResponseField name="dark" type="string">
|
<ResponseField name="dark" type="string">
|
||||||
The primary color for important buttons
|
The primary color for important buttons
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -9,8 +9,7 @@
|
|||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build:cloudflare": "OPENCODE_DEPLOYMENT_TARGET=cloudflare vite build",
|
"build:cloudflare": "OPENCODE_DEPLOYMENT_TARGET=cloudflare vite build",
|
||||||
"start": "vite start",
|
"start": "vite start"
|
||||||
"shell-prod": "sst shell --target Teams --stage production"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Storage } from "../src/core/storage"
|
|
||||||
|
|
||||||
// read share id from args
|
|
||||||
const args = process.argv.slice(2)
|
|
||||||
if (args.length !== 1) {
|
|
||||||
console.error("Usage: bun script/scrap.ts <shareID>")
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
const shareID = args[0]
|
|
||||||
|
|
||||||
await Storage.remove(["share", shareID])
|
|
||||||
const list = await Storage.list({ prefix: ["share_data", shareID] })
|
|
||||||
for (const item of list) {
|
|
||||||
await Storage.remove(item)
|
|
||||||
}
|
|
||||||
Vendored
-4
@@ -130,10 +130,6 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
"ZEN_SESSION_SECRET": {
|
|
||||||
"type": "sst.sst.Secret"
|
|
||||||
"value": string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// cloudflare
|
// cloudflare
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
id = "opencode"
|
id = "opencode"
|
||||||
name = "OpenCode"
|
name = "OpenCode"
|
||||||
description = "The open source coding agent."
|
description = "The open source coding agent."
|
||||||
version = "1.1.17"
|
version = "1.1.8"
|
||||||
schema_version = 1
|
schema_version = 1
|
||||||
authors = ["Anomaly"]
|
authors = ["Anomaly"]
|
||||||
repository = "https://github.com/anomalyco/opencode"
|
repository = "https://github.com/anomalyco/opencode"
|
||||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
|||||||
icon = "./icons/opencode.svg"
|
icon = "./icons/opencode.svg"
|
||||||
|
|
||||||
[agent_servers.opencode.targets.darwin-aarch64]
|
[agent_servers.opencode.targets.darwin-aarch64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.17/opencode-darwin-arm64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.8/opencode-darwin-arm64.zip"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.darwin-x86_64]
|
[agent_servers.opencode.targets.darwin-x86_64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.17/opencode-darwin-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.8/opencode-darwin-x64.zip"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.linux-aarch64]
|
[agent_servers.opencode.targets.linux-aarch64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.17/opencode-linux-arm64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.8/opencode-linux-arm64.tar.gz"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.linux-x86_64]
|
[agent_servers.opencode.targets.linux-x86_64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.17/opencode-linux-x64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.8/opencode-linux-x64.tar.gz"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.windows-x86_64]
|
[agent_servers.opencode.targets.windows-x86_64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.17/opencode-windows-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.8/opencode-windows-x64.zip"
|
||||||
cmd = "./opencode.exe"
|
cmd = "./opencode.exe"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
Vendored
-4
@@ -130,10 +130,6 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
"ZEN_SESSION_SECRET": {
|
|
||||||
"type": "sst.sst.Secret"
|
|
||||||
"value": string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// cloudflare
|
// cloudflare
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.1.17",
|
"version": "1.1.8",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -50,25 +50,25 @@
|
|||||||
"@actions/core": "1.11.1",
|
"@actions/core": "1.11.1",
|
||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@agentclientprotocol/sdk": "0.5.1",
|
"@agentclientprotocol/sdk": "0.5.1",
|
||||||
"@ai-sdk/amazon-bedrock": "3.0.73",
|
"@ai-sdk/amazon-bedrock": "3.0.57",
|
||||||
"@ai-sdk/anthropic": "2.0.57",
|
"@ai-sdk/anthropic": "2.0.56",
|
||||||
"@ai-sdk/azure": "2.0.91",
|
"@ai-sdk/azure": "2.0.82",
|
||||||
"@ai-sdk/cerebras": "1.0.34",
|
"@ai-sdk/cerebras": "1.0.33",
|
||||||
"@ai-sdk/cohere": "2.0.22",
|
"@ai-sdk/cohere": "2.0.21",
|
||||||
"@ai-sdk/deepinfra": "1.0.31",
|
"@ai-sdk/deepinfra": "1.0.30",
|
||||||
"@ai-sdk/gateway": "2.0.25",
|
"@ai-sdk/gateway": "2.0.23",
|
||||||
"@ai-sdk/google": "2.0.52",
|
"@ai-sdk/google": "2.0.49",
|
||||||
"@ai-sdk/google-vertex": "3.0.97",
|
"@ai-sdk/google-vertex": "3.0.81",
|
||||||
"@ai-sdk/groq": "2.0.34",
|
"@ai-sdk/groq": "2.0.33",
|
||||||
"@ai-sdk/mistral": "2.0.27",
|
"@ai-sdk/mistral": "2.0.26",
|
||||||
"@ai-sdk/openai": "2.0.89",
|
"@ai-sdk/openai": "2.0.71",
|
||||||
"@ai-sdk/openai-compatible": "1.0.30",
|
"@ai-sdk/openai-compatible": "1.0.29",
|
||||||
"@ai-sdk/perplexity": "2.0.23",
|
"@ai-sdk/perplexity": "2.0.22",
|
||||||
"@ai-sdk/provider": "2.0.1",
|
"@ai-sdk/provider": "2.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.20",
|
"@ai-sdk/provider-utils": "3.0.19",
|
||||||
"@ai-sdk/togetherai": "1.0.31",
|
"@ai-sdk/togetherai": "1.0.30",
|
||||||
"@ai-sdk/vercel": "1.0.31",
|
"@ai-sdk/vercel": "1.0.31",
|
||||||
"@ai-sdk/xai": "2.0.51",
|
"@ai-sdk/xai": "2.0.42",
|
||||||
"@clack/prompts": "1.0.0-alpha.1",
|
"@clack/prompts": "1.0.0-alpha.1",
|
||||||
"@hono/standard-validator": "0.1.5",
|
"@hono/standard-validator": "0.1.5",
|
||||||
"@hono/zod-validator": "catalog:",
|
"@hono/zod-validator": "catalog:",
|
||||||
|
|||||||
@@ -106,11 +106,8 @@ async function main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// On non-Windows platforms, just verify the binary package exists
|
const { binaryPath, binaryName } = findBinary()
|
||||||
// Don't replace the wrapper script - it handles binary execution
|
symlinkBinary(binaryPath, binaryName)
|
||||||
const { binaryPath } = findBinary()
|
|
||||||
console.log(`Platform binary verified at: ${binaryPath}`)
|
|
||||||
console.log("Wrapper script will handle binary execution")
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to setup opencode binary:", error.message)
|
console.error("Failed to setup opencode binary:", error.message)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import { Todo } from "@/session/todo"
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { LoadAPIKeyError } from "ai"
|
import { LoadAPIKeyError } from "ai"
|
||||||
import type { OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2"
|
import type { OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2"
|
||||||
import { applyPatch } from "diff"
|
|
||||||
|
|
||||||
export namespace ACP {
|
export namespace ACP {
|
||||||
const log = Log.create({ service: "acp-agent" })
|
const log = Log.create({ service: "acp-agent" })
|
||||||
@@ -106,22 +105,6 @@ export namespace ACP {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (res.outcome.optionId !== "reject" && permission.permission == "edit") {
|
|
||||||
const metadata = permission.metadata || {}
|
|
||||||
const filepath = typeof metadata["filepath"] === "string" ? metadata["filepath"] : ""
|
|
||||||
const diff = typeof metadata["diff"] === "string" ? metadata["diff"] : ""
|
|
||||||
|
|
||||||
const content = await Bun.file(filepath).text()
|
|
||||||
const newContent = getNewContent(content, diff)
|
|
||||||
|
|
||||||
if (newContent) {
|
|
||||||
this.connection.writeTextFile({
|
|
||||||
sessionId: sessionId,
|
|
||||||
path: filepath,
|
|
||||||
content: newContent,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await this.config.sdk.permission.reply({
|
await this.config.sdk.permission.reply({
|
||||||
requestID: permission.id,
|
requestID: permission.id,
|
||||||
reply: res.outcome.optionId as "once" | "always" | "reject",
|
reply: res.outcome.optionId as "once" | "always" | "reject",
|
||||||
@@ -1112,13 +1095,4 @@ export namespace ACP {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNewContent(fileOriginal: string, unifiedDiff: string): string | undefined {
|
|
||||||
const result = applyPatch(fileOriginal, unifiedDiff)
|
|
||||||
if (result === false) {
|
|
||||||
log.error("Failed to apply unified diff (context mismatch)")
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,14 +50,13 @@ export namespace Agent {
|
|||||||
external_directory: {
|
external_directory: {
|
||||||
"*": "ask",
|
"*": "ask",
|
||||||
[Truncate.DIR]: "allow",
|
[Truncate.DIR]: "allow",
|
||||||
[Truncate.GLOB]: "allow",
|
|
||||||
},
|
},
|
||||||
question: "deny",
|
question: "deny",
|
||||||
// mirrors github.com/github/gitignore Node.gitignore pattern for .env files
|
// mirrors github.com/github/gitignore Node.gitignore pattern for .env files
|
||||||
read: {
|
read: {
|
||||||
"*": "allow",
|
"*": "allow",
|
||||||
"*.env": "ask",
|
"*.env": "deny",
|
||||||
"*.env.*": "ask",
|
"*.env.*": "deny",
|
||||||
"*.env.example": "allow",
|
"*.env.example": "allow",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -125,7 +124,6 @@ export namespace Agent {
|
|||||||
read: "allow",
|
read: "allow",
|
||||||
external_directory: {
|
external_directory: {
|
||||||
[Truncate.DIR]: "allow",
|
[Truncate.DIR]: "allow",
|
||||||
[Truncate.GLOB]: "allow",
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
user,
|
user,
|
||||||
@@ -215,16 +213,14 @@ export namespace Agent {
|
|||||||
// Ensure Truncate.DIR is allowed unless explicitly configured
|
// Ensure Truncate.DIR is allowed unless explicitly configured
|
||||||
for (const name in result) {
|
for (const name in result) {
|
||||||
const agent = result[name]
|
const agent = result[name]
|
||||||
const explicit = agent.permission.some((r) => {
|
const explicit = agent.permission.some(
|
||||||
if (r.permission !== "external_directory") return false
|
(r) => r.permission === "external_directory" && r.pattern === Truncate.DIR && r.action === "deny",
|
||||||
if (r.action !== "deny") return false
|
)
|
||||||
return r.pattern === Truncate.DIR || r.pattern === Truncate.GLOB
|
|
||||||
})
|
|
||||||
if (explicit) continue
|
if (explicit) continue
|
||||||
|
|
||||||
result[name].permission = PermissionNext.merge(
|
result[name].permission = PermissionNext.merge(
|
||||||
result[name].permission,
|
result[name].permission,
|
||||||
PermissionNext.fromConfig({ external_directory: { [Truncate.DIR]: "allow", [Truncate.GLOB]: "allow" } }),
|
PermissionNext.fromConfig({ external_directory: { [Truncate.DIR]: "allow" } }),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export namespace Auth {
|
|||||||
refresh: z.string(),
|
refresh: z.string(),
|
||||||
access: z.string(),
|
access: z.string(),
|
||||||
expires: z.number(),
|
expires: z.number(),
|
||||||
accountId: z.string().optional(),
|
|
||||||
enterpriseUrl: z.string().optional(),
|
enterpriseUrl: z.string().optional(),
|
||||||
})
|
})
|
||||||
.meta({ ref: "OAuth" })
|
.meta({ ref: "OAuth" })
|
||||||
|
|||||||
@@ -294,7 +294,6 @@ export const AuthLoginCommand = cmd({
|
|||||||
hint: {
|
hint: {
|
||||||
opencode: "recommended",
|
opencode: "recommended",
|
||||||
anthropic: "Claude Max or API key",
|
anthropic: "Claude Max or API key",
|
||||||
openai: "ChatGPT Plus/Pro or API key",
|
|
||||||
}[x.id],
|
}[x.id],
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { basename } from "path"
|
import { basename } from "path"
|
||||||
import { Agent } from "../../../agent/agent"
|
import { Agent } from "../../../agent/agent"
|
||||||
import { Provider } from "../../../provider/provider"
|
|
||||||
import { Session } from "../../../session"
|
|
||||||
import type { MessageV2 } from "../../../session/message-v2"
|
|
||||||
import { Identifier } from "../../../id/id"
|
|
||||||
import { ToolRegistry } from "../../../tool/registry"
|
|
||||||
import { Instance } from "../../../project/instance"
|
|
||||||
import { PermissionNext } from "../../../permission/next"
|
|
||||||
import { iife } from "../../../util/iife"
|
|
||||||
import { bootstrap } from "../../bootstrap"
|
import { bootstrap } from "../../bootstrap"
|
||||||
import { cmd } from "../cmd"
|
import { cmd } from "../cmd"
|
||||||
|
|
||||||
@@ -16,20 +8,11 @@ export const AgentCommand = cmd({
|
|||||||
command: "agent <name>",
|
command: "agent <name>",
|
||||||
describe: "show agent configuration details",
|
describe: "show agent configuration details",
|
||||||
builder: (yargs) =>
|
builder: (yargs) =>
|
||||||
yargs
|
yargs.positional("name", {
|
||||||
.positional("name", {
|
type: "string",
|
||||||
type: "string",
|
demandOption: true,
|
||||||
demandOption: true,
|
description: "Agent name",
|
||||||
description: "Agent name",
|
}),
|
||||||
})
|
|
||||||
.option("tool", {
|
|
||||||
type: "string",
|
|
||||||
description: "Tool id to execute",
|
|
||||||
})
|
|
||||||
.option("params", {
|
|
||||||
type: "string",
|
|
||||||
description: "Tool params as JSON or a JS object literal",
|
|
||||||
}),
|
|
||||||
async handler(args) {
|
async handler(args) {
|
||||||
await bootstrap(process.cwd(), async () => {
|
await bootstrap(process.cwd(), async () => {
|
||||||
const agentName = args.name as string
|
const agentName = args.name as string
|
||||||
@@ -40,127 +23,7 @@ export const AgentCommand = cmd({
|
|||||||
)
|
)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
const availableTools = await getAvailableTools(agent)
|
process.stdout.write(JSON.stringify(agent, null, 2) + EOL)
|
||||||
const resolvedTools = await resolveTools(agent, availableTools)
|
|
||||||
const toolID = args.tool as string | undefined
|
|
||||||
if (toolID) {
|
|
||||||
const tool = availableTools.find((item) => item.id === toolID)
|
|
||||||
if (!tool) {
|
|
||||||
process.stderr.write(`Tool ${toolID} not found for agent ${agentName}` + EOL)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
if (resolvedTools[toolID] === false) {
|
|
||||||
process.stderr.write(`Tool ${toolID} is disabled for agent ${agentName}` + EOL)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
const params = parseToolParams(args.params as string | undefined)
|
|
||||||
const ctx = await createToolContext(agent)
|
|
||||||
const result = await tool.execute(params, ctx)
|
|
||||||
process.stdout.write(JSON.stringify({ tool: toolID, input: params, result }, null, 2) + EOL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = {
|
|
||||||
...agent,
|
|
||||||
tools: resolvedTools,
|
|
||||||
}
|
|
||||||
process.stdout.write(JSON.stringify(output, null, 2) + EOL)
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
async function getAvailableTools(agent: Agent.Info) {
|
|
||||||
const providerID = agent.model?.providerID ?? (await Provider.defaultModel()).providerID
|
|
||||||
return ToolRegistry.tools(providerID, agent)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolveTools(agent: Agent.Info, availableTools: Awaited<ReturnType<typeof getAvailableTools>>) {
|
|
||||||
const disabled = PermissionNext.disabled(
|
|
||||||
availableTools.map((tool) => tool.id),
|
|
||||||
agent.permission,
|
|
||||||
)
|
|
||||||
const resolved: Record<string, boolean> = {}
|
|
||||||
for (const tool of availableTools) {
|
|
||||||
resolved[tool.id] = !disabled.has(tool.id)
|
|
||||||
}
|
|
||||||
return resolved
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseToolParams(input?: string) {
|
|
||||||
if (!input) return {}
|
|
||||||
const trimmed = input.trim()
|
|
||||||
if (trimmed.length === 0) return {}
|
|
||||||
|
|
||||||
const parsed = iife(() => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(trimmed)
|
|
||||||
} catch (jsonError) {
|
|
||||||
try {
|
|
||||||
return new Function(`return (${trimmed})`)()
|
|
||||||
} catch (evalError) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to parse --params. Use JSON or a JS object literal. JSON error: ${jsonError}. Eval error: ${evalError}.`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
||||||
throw new Error("Tool params must be an object.")
|
|
||||||
}
|
|
||||||
return parsed as Record<string, unknown>
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createToolContext(agent: Agent.Info) {
|
|
||||||
const session = await Session.create({ title: `Debug tool run (${agent.name})` })
|
|
||||||
const messageID = Identifier.ascending("message")
|
|
||||||
const model = agent.model ?? (await Provider.defaultModel())
|
|
||||||
const now = Date.now()
|
|
||||||
const message: MessageV2.Assistant = {
|
|
||||||
id: messageID,
|
|
||||||
sessionID: session.id,
|
|
||||||
role: "assistant",
|
|
||||||
time: {
|
|
||||||
created: now,
|
|
||||||
},
|
|
||||||
parentID: messageID,
|
|
||||||
modelID: model.modelID,
|
|
||||||
providerID: model.providerID,
|
|
||||||
mode: "debug",
|
|
||||||
agent: agent.name,
|
|
||||||
path: {
|
|
||||||
cwd: Instance.directory,
|
|
||||||
root: Instance.worktree,
|
|
||||||
},
|
|
||||||
cost: 0,
|
|
||||||
tokens: {
|
|
||||||
input: 0,
|
|
||||||
output: 0,
|
|
||||||
reasoning: 0,
|
|
||||||
cache: {
|
|
||||||
read: 0,
|
|
||||||
write: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await Session.updateMessage(message)
|
|
||||||
|
|
||||||
const ruleset = PermissionNext.merge(agent.permission, session.permission ?? [])
|
|
||||||
|
|
||||||
return {
|
|
||||||
sessionID: session.id,
|
|
||||||
messageID,
|
|
||||||
callID: Identifier.ascending("part"),
|
|
||||||
agent: agent.name,
|
|
||||||
abort: new AbortController().signal,
|
|
||||||
metadata: () => {},
|
|
||||||
async ask(req: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">) {
|
|
||||||
for (const pattern of req.patterns) {
|
|
||||||
const rule = PermissionNext.evaluate(req.permission, pattern, ruleset)
|
|
||||||
if (rule.action === "deny") {
|
|
||||||
throw new PermissionNext.DeniedError(ruleset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ const FileTreeCommand = cmd({
|
|||||||
}),
|
}),
|
||||||
async handler(args) {
|
async handler(args) {
|
||||||
const files = await Ripgrep.tree({ cwd: args.dir, limit: 200 })
|
const files = await Ripgrep.tree({ cwd: args.dir, limit: 200 })
|
||||||
console.log(JSON.stringify(files, null, 2))
|
console.log(files)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -394,8 +394,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Run opencode
|
- name: Run opencode
|
||||||
uses: anomalyco/opencode/github@latest${envStr}
|
uses: anomalyco/opencode/github@latest${envStr}
|
||||||
@@ -626,7 +624,7 @@ export const GithubRunCommand = cmd({
|
|||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
exitCode = 1
|
exitCode = 1
|
||||||
console.error(e instanceof Error ? e.message : String(e))
|
console.error(e)
|
||||||
let msg = e
|
let msg = e
|
||||||
if (e instanceof $.ShellError) {
|
if (e instanceof $.ShellError) {
|
||||||
msg = e.stderr.toString()
|
msg = e.stderr.toString()
|
||||||
@@ -917,7 +915,7 @@ export const GithubRunCommand = cmd({
|
|||||||
|
|
||||||
// result should always be assistant just satisfying type checker
|
// result should always be assistant just satisfying type checker
|
||||||
if (result.info.role === "assistant" && result.info.error) {
|
if (result.info.role === "assistant" && result.info.error) {
|
||||||
console.error("Agent error:", result.info.error)
|
console.error(result.info)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${result.info.error.name}: ${"message" in result.info.error ? result.info.error.message : ""}`,
|
`${result.info.error.name}: ${"message" in result.info.error ? result.info.error.message : ""}`,
|
||||||
)
|
)
|
||||||
@@ -946,7 +944,7 @@ export const GithubRunCommand = cmd({
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (summary.info.role === "assistant" && summary.info.error) {
|
if (summary.info.role === "assistant" && summary.info.error) {
|
||||||
console.error("Summary agent error:", summary.info.error)
|
console.error(summary.info)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${summary.info.error.name}: ${"message" in summary.info.error ? summary.info.error.message : ""}`,
|
`${summary.info.error.name}: ${"message" in summary.info.error ? summary.info.error.message : ""}`,
|
||||||
)
|
)
|
||||||
@@ -964,7 +962,7 @@ export const GithubRunCommand = cmd({
|
|||||||
try {
|
try {
|
||||||
return await core.getIDToken("opencode-github-action")
|
return await core.getIDToken("opencode-github-action")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to get OIDC token:", error instanceof Error ? error.message : error)
|
console.error("Failed to get OIDC token:", error)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Could not fetch an OIDC token. Make sure to add `id-token: write` to your workflow permissions.",
|
"Could not fetch an OIDC token. Make sure to add `id-token: write` to your workflow permissions.",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { cmd } from "./cmd"
|
import { cmd } from "./cmd"
|
||||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
||||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
||||||
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
|
||||||
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
|
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
|
||||||
import * as prompts from "@clack/prompts"
|
import * as prompts from "@clack/prompts"
|
||||||
import { UI } from "../ui"
|
import { UI } from "../ui"
|
||||||
@@ -12,7 +13,6 @@ import { Instance } from "../../project/instance"
|
|||||||
import { Installation } from "../../installation"
|
import { Installation } from "../../installation"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Global } from "../../global"
|
import { Global } from "../../global"
|
||||||
import { modify, applyEdits } from "jsonc-parser"
|
|
||||||
|
|
||||||
function getAuthStatusIcon(status: MCP.AuthStatus): string {
|
function getAuthStatusIcon(status: MCP.AuthStatus): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@@ -366,204 +366,133 @@ export const McpLogoutCommand = cmd({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
async function resolveConfigPath(baseDir: string, global = false) {
|
|
||||||
// Check for existing config files (prefer .jsonc over .json, check .opencode/ subdirectory too)
|
|
||||||
const candidates = [path.join(baseDir, "opencode.json"), path.join(baseDir, "opencode.jsonc")]
|
|
||||||
|
|
||||||
if (!global) {
|
|
||||||
candidates.push(path.join(baseDir, ".opencode", "opencode.json"), path.join(baseDir, ".opencode", "opencode.jsonc"))
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const candidate of candidates) {
|
|
||||||
if (await Bun.file(candidate).exists()) {
|
|
||||||
return candidate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to opencode.json if none exist
|
|
||||||
return candidates[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addMcpToConfig(name: string, mcpConfig: Config.Mcp, configPath: string) {
|
|
||||||
const file = Bun.file(configPath)
|
|
||||||
|
|
||||||
let text = "{}"
|
|
||||||
if (await file.exists()) {
|
|
||||||
text = await file.text()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use jsonc-parser to modify while preserving comments
|
|
||||||
const edits = modify(text, ["mcp", name], mcpConfig, {
|
|
||||||
formattingOptions: { tabSize: 2, insertSpaces: true },
|
|
||||||
})
|
|
||||||
const result = applyEdits(text, edits)
|
|
||||||
|
|
||||||
await Bun.write(configPath, result)
|
|
||||||
|
|
||||||
return configPath
|
|
||||||
}
|
|
||||||
|
|
||||||
export const McpAddCommand = cmd({
|
export const McpAddCommand = cmd({
|
||||||
command: "add",
|
command: "add",
|
||||||
describe: "add an MCP server",
|
describe: "add an MCP server",
|
||||||
async handler() {
|
async handler() {
|
||||||
await Instance.provide({
|
UI.empty()
|
||||||
directory: process.cwd(),
|
prompts.intro("Add MCP server")
|
||||||
async fn() {
|
|
||||||
UI.empty()
|
|
||||||
prompts.intro("Add MCP server")
|
|
||||||
|
|
||||||
const project = Instance.project
|
const name = await prompts.text({
|
||||||
|
message: "Enter MCP server name",
|
||||||
|
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
||||||
|
})
|
||||||
|
if (prompts.isCancel(name)) throw new UI.CancelledError()
|
||||||
|
|
||||||
// Resolve config paths eagerly for hints
|
const type = await prompts.select({
|
||||||
const [projectConfigPath, globalConfigPath] = await Promise.all([
|
message: "Select MCP server type",
|
||||||
resolveConfigPath(Instance.worktree),
|
options: [
|
||||||
resolveConfigPath(Global.Path.config, true),
|
{
|
||||||
])
|
label: "Local",
|
||||||
|
value: "local",
|
||||||
|
hint: "Run a local command",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Remote",
|
||||||
|
value: "remote",
|
||||||
|
hint: "Connect to a remote URL",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
if (prompts.isCancel(type)) throw new UI.CancelledError()
|
||||||
|
|
||||||
// Determine scope
|
if (type === "local") {
|
||||||
let configPath = globalConfigPath
|
const command = await prompts.text({
|
||||||
if (project.vcs === "git") {
|
message: "Enter command to run",
|
||||||
const scopeResult = await prompts.select({
|
placeholder: "e.g., opencode x @modelcontextprotocol/server-filesystem",
|
||||||
message: "Location",
|
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
||||||
options: [
|
})
|
||||||
{
|
if (prompts.isCancel(command)) throw new UI.CancelledError()
|
||||||
label: "Current project",
|
|
||||||
value: projectConfigPath,
|
|
||||||
hint: projectConfigPath,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Global",
|
|
||||||
value: globalConfigPath,
|
|
||||||
hint: globalConfigPath,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
if (prompts.isCancel(scopeResult)) throw new UI.CancelledError()
|
|
||||||
configPath = scopeResult
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = await prompts.text({
|
prompts.log.info(`Local MCP server "${name}" configured with command: ${command}`)
|
||||||
message: "Enter MCP server name",
|
prompts.outro("MCP server added successfully")
|
||||||
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "remote") {
|
||||||
|
const url = await prompts.text({
|
||||||
|
message: "Enter MCP server URL",
|
||||||
|
placeholder: "e.g., https://example.com/mcp",
|
||||||
|
validate: (x) => {
|
||||||
|
if (!x) return "Required"
|
||||||
|
if (x.length === 0) return "Required"
|
||||||
|
const isValid = URL.canParse(x)
|
||||||
|
return isValid ? undefined : "Invalid URL"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (prompts.isCancel(url)) throw new UI.CancelledError()
|
||||||
|
|
||||||
|
const useOAuth = await prompts.confirm({
|
||||||
|
message: "Does this server require OAuth authentication?",
|
||||||
|
initialValue: false,
|
||||||
|
})
|
||||||
|
if (prompts.isCancel(useOAuth)) throw new UI.CancelledError()
|
||||||
|
|
||||||
|
if (useOAuth) {
|
||||||
|
const hasClientId = await prompts.confirm({
|
||||||
|
message: "Do you have a pre-registered client ID?",
|
||||||
|
initialValue: false,
|
||||||
})
|
})
|
||||||
if (prompts.isCancel(name)) throw new UI.CancelledError()
|
if (prompts.isCancel(hasClientId)) throw new UI.CancelledError()
|
||||||
|
|
||||||
const type = await prompts.select({
|
if (hasClientId) {
|
||||||
message: "Select MCP server type",
|
const clientId = await prompts.text({
|
||||||
options: [
|
message: "Enter client ID",
|
||||||
{
|
|
||||||
label: "Local",
|
|
||||||
value: "local",
|
|
||||||
hint: "Run a local command",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Remote",
|
|
||||||
value: "remote",
|
|
||||||
hint: "Connect to a remote URL",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
if (prompts.isCancel(type)) throw new UI.CancelledError()
|
|
||||||
|
|
||||||
if (type === "local") {
|
|
||||||
const command = await prompts.text({
|
|
||||||
message: "Enter command to run",
|
|
||||||
placeholder: "e.g., opencode x @modelcontextprotocol/server-filesystem",
|
|
||||||
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
||||||
})
|
})
|
||||||
if (prompts.isCancel(command)) throw new UI.CancelledError()
|
if (prompts.isCancel(clientId)) throw new UI.CancelledError()
|
||||||
|
|
||||||
const mcpConfig: Config.Mcp = {
|
const hasSecret = await prompts.confirm({
|
||||||
type: "local",
|
message: "Do you have a client secret?",
|
||||||
command: command.split(" "),
|
|
||||||
}
|
|
||||||
|
|
||||||
await addMcpToConfig(name, mcpConfig, configPath)
|
|
||||||
prompts.log.success(`MCP server "${name}" added to ${configPath}`)
|
|
||||||
prompts.outro("MCP server added successfully")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "remote") {
|
|
||||||
const url = await prompts.text({
|
|
||||||
message: "Enter MCP server URL",
|
|
||||||
placeholder: "e.g., https://example.com/mcp",
|
|
||||||
validate: (x) => {
|
|
||||||
if (!x) return "Required"
|
|
||||||
if (x.length === 0) return "Required"
|
|
||||||
const isValid = URL.canParse(x)
|
|
||||||
return isValid ? undefined : "Invalid URL"
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (prompts.isCancel(url)) throw new UI.CancelledError()
|
|
||||||
|
|
||||||
const useOAuth = await prompts.confirm({
|
|
||||||
message: "Does this server require OAuth authentication?",
|
|
||||||
initialValue: false,
|
initialValue: false,
|
||||||
})
|
})
|
||||||
if (prompts.isCancel(useOAuth)) throw new UI.CancelledError()
|
if (prompts.isCancel(hasSecret)) throw new UI.CancelledError()
|
||||||
|
|
||||||
let mcpConfig: Config.Mcp
|
let clientSecret: string | undefined
|
||||||
|
if (hasSecret) {
|
||||||
if (useOAuth) {
|
const secret = await prompts.password({
|
||||||
const hasClientId = await prompts.confirm({
|
message: "Enter client secret",
|
||||||
message: "Do you have a pre-registered client ID?",
|
|
||||||
initialValue: false,
|
|
||||||
})
|
})
|
||||||
if (prompts.isCancel(hasClientId)) throw new UI.CancelledError()
|
if (prompts.isCancel(secret)) throw new UI.CancelledError()
|
||||||
|
clientSecret = secret
|
||||||
if (hasClientId) {
|
|
||||||
const clientId = await prompts.text({
|
|
||||||
message: "Enter client ID",
|
|
||||||
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
|
||||||
})
|
|
||||||
if (prompts.isCancel(clientId)) throw new UI.CancelledError()
|
|
||||||
|
|
||||||
const hasSecret = await prompts.confirm({
|
|
||||||
message: "Do you have a client secret?",
|
|
||||||
initialValue: false,
|
|
||||||
})
|
|
||||||
if (prompts.isCancel(hasSecret)) throw new UI.CancelledError()
|
|
||||||
|
|
||||||
let clientSecret: string | undefined
|
|
||||||
if (hasSecret) {
|
|
||||||
const secret = await prompts.password({
|
|
||||||
message: "Enter client secret",
|
|
||||||
})
|
|
||||||
if (prompts.isCancel(secret)) throw new UI.CancelledError()
|
|
||||||
clientSecret = secret
|
|
||||||
}
|
|
||||||
|
|
||||||
mcpConfig = {
|
|
||||||
type: "remote",
|
|
||||||
url,
|
|
||||||
oauth: {
|
|
||||||
clientId,
|
|
||||||
...(clientSecret && { clientSecret }),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mcpConfig = {
|
|
||||||
type: "remote",
|
|
||||||
url,
|
|
||||||
oauth: {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mcpConfig = {
|
|
||||||
type: "remote",
|
|
||||||
url,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await addMcpToConfig(name, mcpConfig, configPath)
|
prompts.log.info(`Remote MCP server "${name}" configured with OAuth (client ID: ${clientId})`)
|
||||||
prompts.log.success(`MCP server "${name}" added to ${configPath}`)
|
prompts.log.info("Add this to your opencode.json:")
|
||||||
|
prompts.log.info(`
|
||||||
|
"mcp": {
|
||||||
|
"${name}": {
|
||||||
|
"type": "remote",
|
||||||
|
"url": "${url}",
|
||||||
|
"oauth": {
|
||||||
|
"clientId": "${clientId}"${clientSecret ? `,\n "clientSecret": "${clientSecret}"` : ""}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
} else {
|
||||||
|
prompts.log.info(`Remote MCP server "${name}" configured with OAuth (dynamic registration)`)
|
||||||
|
prompts.log.info("Add this to your opencode.json:")
|
||||||
|
prompts.log.info(`
|
||||||
|
"mcp": {
|
||||||
|
"${name}": {
|
||||||
|
"type": "remote",
|
||||||
|
"url": "${url}",
|
||||||
|
"oauth": {}
|
||||||
|
}
|
||||||
|
}`)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const client = new Client({
|
||||||
|
name: "opencode",
|
||||||
|
version: "1.0.0",
|
||||||
|
})
|
||||||
|
const transport = new StreamableHTTPClientTransport(new URL(url))
|
||||||
|
await client.connect(transport)
|
||||||
|
prompts.log.info(`Remote MCP server "${name}" configured with URL: ${url}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prompts.outro("MCP server added successfully")
|
prompts.outro("MCP server added successfully")
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -339,15 +339,13 @@ export const RunCommand = cmd({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await bootstrap(process.cwd(), async () => {
|
await bootstrap(process.cwd(), async () => {
|
||||||
const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
const server = Server.listen({ port: args.port ?? 0, hostname: "127.0.0.1" })
|
||||||
const request = new Request(input, init)
|
const sdk = createOpencodeClient({ baseUrl: `http://${server.hostname}:${server.port}` })
|
||||||
return Server.App().fetch(request)
|
|
||||||
}) as typeof globalThis.fetch
|
|
||||||
const sdk = createOpencodeClient({ baseUrl: "http://opencode.internal", fetch: fetchFn })
|
|
||||||
|
|
||||||
if (args.command) {
|
if (args.command) {
|
||||||
const exists = await Command.get(args.command)
|
const exists = await Command.get(args.command)
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
|
server.stop()
|
||||||
UI.error(`Command "${args.command}" not found`)
|
UI.error(`Command "${args.command}" not found`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
@@ -372,6 +370,7 @@ export const RunCommand = cmd({
|
|||||||
})()
|
})()
|
||||||
|
|
||||||
if (!sessionID) {
|
if (!sessionID) {
|
||||||
|
server.stop()
|
||||||
UI.error("Session not found")
|
UI.error("Session not found")
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
@@ -390,6 +389,7 @@ export const RunCommand = cmd({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await execute(sdk, sessionID)
|
await execute(sdk, sessionID)
|
||||||
|
server.stop()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import { Server } from "../../server/server"
|
import { Server } from "../../server/server"
|
||||||
import { cmd } from "./cmd"
|
import { cmd } from "./cmd"
|
||||||
import { withNetworkOptions, resolveNetworkOptions } from "../network"
|
import { withNetworkOptions, resolveNetworkOptions } from "../network"
|
||||||
import { Flag } from "../../flag/flag"
|
|
||||||
|
|
||||||
export const ServeCommand = cmd({
|
export const ServeCommand = cmd({
|
||||||
command: "serve",
|
command: "serve",
|
||||||
builder: (yargs) => withNetworkOptions(yargs),
|
builder: (yargs) => withNetworkOptions(yargs),
|
||||||
describe: "starts a headless opencode server",
|
describe: "starts a headless opencode server",
|
||||||
handler: async (args) => {
|
handler: async (args) => {
|
||||||
if (!Flag.OPENCODE_SERVER_PASSWORD) {
|
|
||||||
console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
|
|
||||||
}
|
|
||||||
const opts = await resolveNetworkOptions(args)
|
const opts = await resolveNetworkOptions(args)
|
||||||
const server = Server.listen(opts)
|
const server = Server.listen(opts)
|
||||||
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
|
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
|
||||||
|
|||||||
@@ -97,16 +97,7 @@ async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
import type { EventSource } from "./context/sdk"
|
export function tui(input: { url: string; args: Args; directory?: string; onExit?: () => Promise<void> }) {
|
||||||
|
|
||||||
export function tui(input: {
|
|
||||||
url: string
|
|
||||||
args: Args
|
|
||||||
directory?: string
|
|
||||||
fetch?: typeof fetch
|
|
||||||
events?: EventSource
|
|
||||||
onExit?: () => Promise<void>
|
|
||||||
}) {
|
|
||||||
// promise to prevent immediate exit
|
// promise to prevent immediate exit
|
||||||
return new Promise<void>(async (resolve) => {
|
return new Promise<void>(async (resolve) => {
|
||||||
const mode = await getTerminalBackgroundColor()
|
const mode = await getTerminalBackgroundColor()
|
||||||
@@ -126,12 +117,7 @@ export function tui(input: {
|
|||||||
<KVProvider>
|
<KVProvider>
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<RouteProvider>
|
<RouteProvider>
|
||||||
<SDKProvider
|
<SDKProvider url={input.url} directory={input.directory}>
|
||||||
url={input.url}
|
|
||||||
directory={input.directory}
|
|
||||||
fetch={input.fetch}
|
|
||||||
events={input.events}
|
|
||||||
>
|
|
||||||
<SyncProvider>
|
<SyncProvider>
|
||||||
<ThemeProvider mode={mode}>
|
<ThemeProvider mode={mode}>
|
||||||
<LocalProvider>
|
<LocalProvider>
|
||||||
@@ -606,6 +592,15 @@ function App() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
sdk.event.on(Installation.Event.Updated.type, (evt) => {
|
||||||
|
toast.show({
|
||||||
|
variant: "success",
|
||||||
|
title: "Update Complete",
|
||||||
|
message: `OpenCode updated to v${evt.properties.version}`,
|
||||||
|
duration: 5000,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
sdk.event.on(Installation.Event.UpdateAvailable.type, (evt) => {
|
sdk.event.on(Installation.Event.UpdateAvailable.type, (evt) => {
|
||||||
toast.show({
|
toast.show({
|
||||||
variant: "info",
|
variant: "info",
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ export function createDialogProviderOptions() {
|
|||||||
description: {
|
description: {
|
||||||
opencode: "(Recommended)",
|
opencode: "(Recommended)",
|
||||||
anthropic: "(Claude Max or API key)",
|
anthropic: "(Claude Max or API key)",
|
||||||
openai: "(ChatGPT Plus/Pro or API key)",
|
|
||||||
}[provider.id],
|
}[provider.id],
|
||||||
category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other",
|
category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other",
|
||||||
async onSelect() {
|
async onSelect() {
|
||||||
@@ -112,7 +111,8 @@ function AutoMethod(props: AutoMethodProps) {
|
|||||||
|
|
||||||
useKeyboard((evt) => {
|
useKeyboard((evt) => {
|
||||||
if (evt.name === "c" && !evt.ctrl && !evt.meta) {
|
if (evt.name === "c" && !evt.ctrl && !evt.meta) {
|
||||||
const code = props.authorization.instructions.match(/[A-Z0-9]{4}-[A-Z0-9]{4}/)?.[0] ?? props.authorization.url
|
const code =
|
||||||
|
props.authorization.instructions.match(/[A-Z0-9]{4}-[A-Z0-9]{4}/)?.[0] ?? props.authorization.instructions
|
||||||
Clipboard.copy(code)
|
Clipboard.copy(code)
|
||||||
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
|
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
|
||||||
.catch(toast.error)
|
.catch(toast.error)
|
||||||
@@ -225,7 +225,7 @@ function ApiMethod(props: ApiMethodProps) {
|
|||||||
}
|
}
|
||||||
onConfirm={async (value) => {
|
onConfirm={async (value) => {
|
||||||
if (!value) return
|
if (!value) return
|
||||||
await sdk.client.auth.set({
|
sdk.client.auth.set({
|
||||||
providerID: props.providerID,
|
providerID: props.providerID,
|
||||||
auth: {
|
auth: {
|
||||||
type: "api",
|
type: "api",
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { createMemo, createSignal, For } from "solid-js"
|
||||||
|
import { useTheme } from "@tui/context/theme"
|
||||||
|
import { useKeybind } from "@tui/context/keybind"
|
||||||
|
import { TIPS } from "./tips"
|
||||||
|
import { EmptyBorder } from "./border"
|
||||||
|
|
||||||
|
type TipPart = { text: string; highlight: boolean }
|
||||||
|
|
||||||
|
function parseTip(tip: string): TipPart[] {
|
||||||
|
const parts: TipPart[] = []
|
||||||
|
const regex = /\{highlight\}(.*?)\{\/highlight\}/g
|
||||||
|
let lastIndex = 0
|
||||||
|
let match
|
||||||
|
|
||||||
|
while ((match = regex.exec(tip)) !== null) {
|
||||||
|
if (match.index > lastIndex) {
|
||||||
|
parts.push({ text: tip.slice(lastIndex, match.index), highlight: false })
|
||||||
|
}
|
||||||
|
parts.push({ text: match[1], highlight: true })
|
||||||
|
lastIndex = regex.lastIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastIndex < tip.length) {
|
||||||
|
parts.push({ text: tip.slice(lastIndex), highlight: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
|
||||||
|
const [tipIndex, setTipIndex] = createSignal(Math.floor(Math.random() * TIPS.length))
|
||||||
|
|
||||||
|
export function randomizeTip() {
|
||||||
|
setTipIndex(Math.floor(Math.random() * TIPS.length))
|
||||||
|
}
|
||||||
|
|
||||||
|
const BOX_WIDTH = 42
|
||||||
|
const TITLE = " 🅘 Did you know? "
|
||||||
|
|
||||||
|
export function DidYouKnow() {
|
||||||
|
const { theme } = useTheme()
|
||||||
|
const keybind = useKeybind()
|
||||||
|
|
||||||
|
const tipParts = createMemo(() => parseTip(TIPS[tipIndex()]))
|
||||||
|
|
||||||
|
const dashes = createMemo(() => {
|
||||||
|
// ╭─ + title + ─...─ + ╮ = BOX_WIDTH
|
||||||
|
// 1 + 1 + title.length + dashes + 1 = BOX_WIDTH
|
||||||
|
return Math.max(0, BOX_WIDTH - 2 - TITLE.length - 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box position="absolute" bottom={3} right={2} width={BOX_WIDTH}>
|
||||||
|
<text>
|
||||||
|
<span style={{ fg: theme.border }}>╭─</span>
|
||||||
|
<span style={{ fg: theme.text }}>{TITLE}</span>
|
||||||
|
<span style={{ fg: theme.border }}>{"─".repeat(dashes())}╮</span>
|
||||||
|
</text>
|
||||||
|
<box
|
||||||
|
border={["left", "right", "bottom"]}
|
||||||
|
borderColor={theme.border}
|
||||||
|
customBorderChars={{
|
||||||
|
...EmptyBorder,
|
||||||
|
bottomLeft: "╰",
|
||||||
|
bottomRight: "╯",
|
||||||
|
horizontal: "─",
|
||||||
|
vertical: "│",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<box paddingLeft={2} paddingRight={2} paddingTop={1} paddingBottom={1}>
|
||||||
|
<text>
|
||||||
|
<For each={tipParts()}>
|
||||||
|
{(part) => <span style={{ fg: part.highlight ? theme.text : theme.textMuted }}>{part.text}</span>}
|
||||||
|
</For>
|
||||||
|
</text>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
<box flexDirection="row" justifyContent="flex-end">
|
||||||
|
<text>
|
||||||
|
<span style={{ fg: theme.text }}>{keybind.print("tips_toggle")}</span>
|
||||||
|
<span style={{ fg: theme.textMuted }}> hide tips</span>
|
||||||
|
</text>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { BoxRenderable, TextareaRenderable, KeyEvent, ScrollBoxRenderable } from "@opentui/core"
|
import type { BoxRenderable, TextareaRenderable, KeyEvent, ScrollBoxRenderable } from "@opentui/core"
|
||||||
import fuzzysort from "fuzzysort"
|
import fuzzysort from "fuzzysort"
|
||||||
import { firstBy } from "remeda"
|
import { firstBy } from "remeda"
|
||||||
import { createMemo, createResource, createEffect, onMount, onCleanup, Index, Show, createSignal } from "solid-js"
|
import { createMemo, createResource, createEffect, onMount, onCleanup, For, Show, createSignal } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useSDK } from "@tui/context/sdk"
|
import { useSDK } from "@tui/context/sdk"
|
||||||
import { useSync } from "@tui/context/sync"
|
import { useSync } from "@tui/context/sync"
|
||||||
@@ -601,31 +601,8 @@ export function Autocomplete(props: {
|
|||||||
(store.visible === "/" && value.match(/^\S+\s+\S+\s*$/))
|
(store.visible === "/" && value.match(/^\S+\s+\S+\s*$/))
|
||||||
) {
|
) {
|
||||||
hide()
|
hide()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if autocomplete should reopen (e.g., after backspace deleted a space)
|
|
||||||
const offset = props.input().cursorOffset
|
|
||||||
if (offset === 0) return
|
|
||||||
|
|
||||||
// Check for "/" at position 0 - reopen slash commands
|
|
||||||
if (value.startsWith("/") && !value.slice(0, offset).match(/\s/)) {
|
|
||||||
show("/")
|
|
||||||
setStore("index", 0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for "@" trigger - find the nearest "@" before cursor with no whitespace between
|
|
||||||
const text = value.slice(0, offset)
|
|
||||||
const idx = text.lastIndexOf("@")
|
|
||||||
if (idx === -1) return
|
|
||||||
|
|
||||||
const between = text.slice(idx)
|
|
||||||
const before = idx === 0 ? undefined : value[idx - 1]
|
|
||||||
if ((before === undefined || /\s/.test(before)) && !between.match(/\s/)) {
|
|
||||||
show("@")
|
|
||||||
setStore("index", idx)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onKeyDown(e: KeyEvent) {
|
onKeyDown(e: KeyEvent) {
|
||||||
@@ -709,7 +686,7 @@ export function Autocomplete(props: {
|
|||||||
height={height()}
|
height={height()}
|
||||||
scrollbarOptions={{ visible: false }}
|
scrollbarOptions={{ visible: false }}
|
||||||
>
|
>
|
||||||
<Index
|
<For
|
||||||
each={options()}
|
each={options()}
|
||||||
fallback={
|
fallback={
|
||||||
<box paddingLeft={1} paddingRight={1}>
|
<box paddingLeft={1} paddingRight={1}>
|
||||||
@@ -721,22 +698,20 @@ export function Autocomplete(props: {
|
|||||||
<box
|
<box
|
||||||
paddingLeft={1}
|
paddingLeft={1}
|
||||||
paddingRight={1}
|
paddingRight={1}
|
||||||
backgroundColor={index === store.selected ? theme.primary : undefined}
|
backgroundColor={index() === store.selected ? theme.primary : undefined}
|
||||||
flexDirection="row"
|
flexDirection="row"
|
||||||
onMouseOver={() => moveTo(index)}
|
|
||||||
onMouseUp={() => select()}
|
|
||||||
>
|
>
|
||||||
<text fg={index === store.selected ? selectedForeground(theme) : theme.text} flexShrink={0}>
|
<text fg={index() === store.selected ? selectedForeground(theme) : theme.text} flexShrink={0}>
|
||||||
{option().display}
|
{option.display}
|
||||||
</text>
|
</text>
|
||||||
<Show when={option().description}>
|
<Show when={option.description}>
|
||||||
<text fg={index === store.selected ? selectedForeground(theme) : theme.textMuted} wrapMode="none">
|
<text fg={index() === store.selected ? selectedForeground(theme) : theme.textMuted} wrapMode="none">
|
||||||
{option().description}
|
{option.description}
|
||||||
</text>
|
</text>
|
||||||
</Show>
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
)}
|
)}
|
||||||
</Index>
|
</For>
|
||||||
</scrollbox>
|
</scrollbox>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
const fileStyleId = syntax().getStyleId("extmark.file")!
|
const fileStyleId = syntax().getStyleId("extmark.file")!
|
||||||
const agentStyleId = syntax().getStyleId("extmark.agent")!
|
const agentStyleId = syntax().getStyleId("extmark.agent")!
|
||||||
const pasteStyleId = syntax().getStyleId("extmark.paste")!
|
const pasteStyleId = syntax().getStyleId("extmark.paste")!
|
||||||
let promptPartTypeId = 0
|
let promptPartTypeId: number
|
||||||
|
|
||||||
sdk.event.on(TuiEvent.PromptAppend.type, (evt) => {
|
sdk.event.on(TuiEvent.PromptAppend.type, (evt) => {
|
||||||
input.insertText(evt.properties.text)
|
input.insertText(evt.properties.text)
|
||||||
@@ -310,44 +310,15 @@ export function Prompt(props: PromptProps) {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const ref: PromptRef = {
|
|
||||||
get focused() {
|
|
||||||
return input.focused
|
|
||||||
},
|
|
||||||
get current() {
|
|
||||||
return store.prompt
|
|
||||||
},
|
|
||||||
focus() {
|
|
||||||
input.focus()
|
|
||||||
},
|
|
||||||
blur() {
|
|
||||||
input.blur()
|
|
||||||
},
|
|
||||||
set(prompt) {
|
|
||||||
input.setText(prompt.input)
|
|
||||||
setStore("prompt", prompt)
|
|
||||||
restoreExtmarksFromParts(prompt.parts)
|
|
||||||
input.gotoBufferEnd()
|
|
||||||
},
|
|
||||||
reset() {
|
|
||||||
input.clear()
|
|
||||||
input.extmarks.clear()
|
|
||||||
setStore("prompt", {
|
|
||||||
input: "",
|
|
||||||
parts: [],
|
|
||||||
})
|
|
||||||
setStore("extmarkToPartIndex", new Map())
|
|
||||||
},
|
|
||||||
submit() {
|
|
||||||
submit()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (props.visible !== false) input?.focus()
|
if (props.visible !== false) input?.focus()
|
||||||
if (props.visible === false) input?.blur()
|
if (props.visible === false) input?.blur()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
promptPartTypeId = input.extmarks.registerType("prompt-part")
|
||||||
|
})
|
||||||
|
|
||||||
function restoreExtmarksFromParts(parts: PromptInfo["parts"]) {
|
function restoreExtmarksFromParts(parts: PromptInfo["parts"]) {
|
||||||
input.extmarks.clear()
|
input.extmarks.clear()
|
||||||
setStore("extmarkToPartIndex", new Map())
|
setStore("extmarkToPartIndex", new Map())
|
||||||
@@ -481,6 +452,39 @@ export function Prompt(props: PromptProps) {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
props.ref?.({
|
||||||
|
get focused() {
|
||||||
|
return input.focused
|
||||||
|
},
|
||||||
|
get current() {
|
||||||
|
return store.prompt
|
||||||
|
},
|
||||||
|
focus() {
|
||||||
|
input.focus()
|
||||||
|
},
|
||||||
|
blur() {
|
||||||
|
input.blur()
|
||||||
|
},
|
||||||
|
set(prompt) {
|
||||||
|
input.setText(prompt.input)
|
||||||
|
setStore("prompt", prompt)
|
||||||
|
restoreExtmarksFromParts(prompt.parts)
|
||||||
|
input.gotoBufferEnd()
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
input.clear()
|
||||||
|
input.extmarks.clear()
|
||||||
|
setStore("prompt", {
|
||||||
|
input: "",
|
||||||
|
parts: [],
|
||||||
|
})
|
||||||
|
setStore("extmarkToPartIndex", new Map())
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
submit()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
if (props.disabled) return
|
if (props.disabled) return
|
||||||
if (autocomplete?.visible) return
|
if (autocomplete?.visible) return
|
||||||
@@ -563,27 +567,25 @@ export function Prompt(props: PromptProps) {
|
|||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
sdk.client.session
|
sdk.client.session.prompt({
|
||||||
.prompt({
|
sessionID,
|
||||||
sessionID,
|
...selectedModel,
|
||||||
...selectedModel,
|
messageID,
|
||||||
messageID,
|
agent: local.agent.current().name,
|
||||||
agent: local.agent.current().name,
|
model: selectedModel,
|
||||||
model: selectedModel,
|
variant,
|
||||||
variant,
|
parts: [
|
||||||
parts: [
|
{
|
||||||
{
|
id: Identifier.ascending("part"),
|
||||||
id: Identifier.ascending("part"),
|
type: "text",
|
||||||
type: "text",
|
text: inputText,
|
||||||
text: inputText,
|
},
|
||||||
},
|
...nonTextParts.map((x) => ({
|
||||||
...nonTextParts.map((x) => ({
|
id: Identifier.ascending("part"),
|
||||||
id: Identifier.ascending("part"),
|
...x,
|
||||||
...x,
|
})),
|
||||||
})),
|
],
|
||||||
],
|
})
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}
|
}
|
||||||
history.append({
|
history.append({
|
||||||
...store.prompt,
|
...store.prompt,
|
||||||
@@ -914,15 +916,12 @@ export function Prompt(props: PromptProps) {
|
|||||||
// Force layout update and render for the pasted content
|
// Force layout update and render for the pasted content
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
input.getLayoutNode().markDirty()
|
input.getLayoutNode().markDirty()
|
||||||
|
input.gotoBufferEnd()
|
||||||
renderer.requestRender()
|
renderer.requestRender()
|
||||||
}, 0)
|
}, 0)
|
||||||
}}
|
}}
|
||||||
ref={(r: TextareaRenderable) => {
|
ref={(r: TextareaRenderable) => {
|
||||||
input = r
|
input = r
|
||||||
if (promptPartTypeId === 0) {
|
|
||||||
promptPartTypeId = input.extmarks.registerType("prompt-part")
|
|
||||||
}
|
|
||||||
props.ref?.(ref)
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
input.cursorColor = theme.text
|
input.cursorColor = theme.text
|
||||||
}, 0)
|
}, 0)
|
||||||
@@ -1064,10 +1063,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
<Switch>
|
<Switch>
|
||||||
<Match when={store.mode === "normal"}>
|
<Match when={store.mode === "normal"}>
|
||||||
<text fg={theme.text}>
|
<text fg={theme.text}>
|
||||||
{keybind.print("variant_cycle")} <span style={{ fg: theme.textMuted }}>variants</span>
|
{keybind.print("agent_cycle")} <span style={{ fg: theme.textMuted }}>switch agent</span>
|
||||||
</text>
|
|
||||||
<text fg={theme.text}>
|
|
||||||
{keybind.print("agent_cycle")} <span style={{ fg: theme.textMuted }}>agents</span>
|
|
||||||
</text>
|
</text>
|
||||||
<text fg={theme.text}>
|
<text fg={theme.text}>
|
||||||
{keybind.print("command_list")} <span style={{ fg: theme.textMuted }}>commands</span>
|
{keybind.print("command_list")} <span style={{ fg: theme.textMuted }}>commands</span>
|
||||||
|
|||||||
+102
-149
@@ -1,150 +1,103 @@
|
|||||||
import { createMemo, createSignal, For } from "solid-js"
|
export const TIPS = [
|
||||||
import { useTheme } from "@tui/context/theme"
|
"Type {highlight}@{/highlight} followed by a filename to fuzzy search and attach files to your prompt.",
|
||||||
|
"Start a message with {highlight}!{/highlight} to run shell commands directly (e.g., {highlight}!ls -la{/highlight}).",
|
||||||
type TipPart = { text: string; highlight: boolean }
|
"Press {highlight}Tab{/highlight} to cycle between Build (full access) and Plan (read-only) agents.",
|
||||||
|
"Use {highlight}/undo{/highlight} to revert the last message and any file changes made by OpenCode.",
|
||||||
function parse(tip: string): TipPart[] {
|
"Use {highlight}/redo{/highlight} to restore previously undone messages and file changes.",
|
||||||
const parts: TipPart[] = []
|
"Run {highlight}/share{/highlight} to create a public link to your conversation at opencode.ai.",
|
||||||
const regex = /\{highlight\}(.*?)\{\/highlight\}/g
|
"Drag and drop images into the terminal to add them as context for your prompts.",
|
||||||
const found = Array.from(tip.matchAll(regex))
|
"Press {highlight}Ctrl+V{/highlight} to paste images from your clipboard directly into the prompt.",
|
||||||
const state = found.reduce(
|
"Press {highlight}Ctrl+X E{/highlight} or {highlight}/editor{/highlight} to compose messages in your external editor.",
|
||||||
(acc, match) => {
|
"Run {highlight}/init{/highlight} to auto-generate project rules based on your codebase structure.",
|
||||||
const start = match.index ?? 0
|
"Run {highlight}/models{/highlight} or {highlight}Ctrl+X M{/highlight} to see and switch between available AI models.",
|
||||||
if (start > acc.index) {
|
"Use {highlight}/theme{/highlight} or {highlight}Ctrl+X T{/highlight} to preview and switch between 50+ built-in themes.",
|
||||||
acc.parts.push({ text: tip.slice(acc.index, start), highlight: false })
|
"Press {highlight}Ctrl+X N{/highlight} or {highlight}/new{/highlight} to start a fresh conversation session.",
|
||||||
}
|
"Use {highlight}/sessions{/highlight} or {highlight}Ctrl+X L{/highlight} to list and continue previous conversations.",
|
||||||
acc.parts.push({ text: match[1], highlight: true })
|
"Run {highlight}/compact{/highlight} to summarize long sessions when approaching context limits.",
|
||||||
acc.index = start + match[0].length
|
"Press {highlight}Ctrl+X X{/highlight} or {highlight}/export{/highlight} to save the conversation as Markdown.",
|
||||||
return acc
|
"Press {highlight}Ctrl+X Y{/highlight} to copy the assistant's last message to clipboard.",
|
||||||
},
|
"Press {highlight}Ctrl+P{/highlight} to see all available actions and commands.",
|
||||||
{ parts, index: 0 },
|
"Run {highlight}/connect{/highlight} to add API keys for 75+ supported LLM providers.",
|
||||||
)
|
"The default leader key is {highlight}Ctrl+X{/highlight}; combine with other keys for quick actions.",
|
||||||
|
"Press {highlight}F2{/highlight} to quickly switch between recently used models.",
|
||||||
if (state.index < tip.length) {
|
"Press {highlight}Ctrl+X B{/highlight} to show/hide the sidebar panel.",
|
||||||
parts.push({ text: tip.slice(state.index), highlight: false })
|
"Use {highlight}PageUp{/highlight}/{highlight}PageDown{/highlight} to navigate through conversation history.",
|
||||||
}
|
"Press {highlight}Ctrl+G{/highlight} or {highlight}Home{/highlight} to jump to the beginning of the conversation.",
|
||||||
|
"Press {highlight}Ctrl+Alt+G{/highlight} or {highlight}End{/highlight} to jump to the most recent message.",
|
||||||
return parts
|
"Press {highlight}Shift+Enter{/highlight} or {highlight}Ctrl+J{/highlight} to add newlines in your prompt.",
|
||||||
}
|
"Press {highlight}Ctrl+C{/highlight} when typing to clear the input field.",
|
||||||
|
"Press {highlight}Escape{/highlight} to stop the AI mid-response.",
|
||||||
export function Tips() {
|
"Switch to {highlight}Plan{/highlight} agent to get suggestions without making actual changes.",
|
||||||
const theme = useTheme().theme
|
"Use {highlight}@<agent-name>{/highlight} in prompts to invoke specialized subagents.",
|
||||||
const parts = parse(TIPS[Math.floor(Math.random() * TIPS.length)])
|
"Press {highlight}Ctrl+X Right/Left{/highlight} to cycle through parent and child sessions.",
|
||||||
|
"Create {highlight}opencode.json{/highlight} in project root for project-specific settings.",
|
||||||
return (
|
"Place settings in {highlight}~/.config/opencode/opencode.json{/highlight} for global config.",
|
||||||
<box flexDirection="row" maxWidth="100%">
|
"Add {highlight}$schema{/highlight} to your config for autocomplete in your editor.",
|
||||||
<text flexShrink={0} style={{ fg: theme.warning }}>
|
"Configure {highlight}model{/highlight} in config to set your default model.",
|
||||||
● Tip{" "}
|
"Override any keybind in config via the {highlight}keybinds{/highlight} section.",
|
||||||
</text>
|
"Set any keybind to {highlight}none{/highlight} to disable it completely.",
|
||||||
<text flexShrink={1}>
|
"Configure local or remote MCP servers in the {highlight}mcp{/highlight} config section.",
|
||||||
<For each={parts}>
|
"OpenCode auto-handles OAuth for remote MCP servers requiring auth.",
|
||||||
{(part) => <span style={{ fg: part.highlight ? theme.text : theme.textMuted }}>{part.text}</span>}
|
"Add {highlight}.md{/highlight} files to {highlight}.opencode/command/{/highlight} to define reusable custom prompts.",
|
||||||
</For>
|
"Use {highlight}$ARGUMENTS{/highlight}, {highlight}$1{/highlight}, {highlight}$2{/highlight} in custom commands for dynamic input.",
|
||||||
</text>
|
"Use backticks in commands to inject shell output (e.g., {highlight}`git status`{/highlight}).",
|
||||||
</box>
|
"Add {highlight}.md{/highlight} files to {highlight}.opencode/agent/{/highlight} for specialized AI personas.",
|
||||||
)
|
"Configure per-agent permissions for {highlight}edit{/highlight}, {highlight}bash{/highlight}, and {highlight}webfetch{/highlight} tools.",
|
||||||
}
|
'Use patterns like {highlight}"git *": "allow"{/highlight} for granular bash permissions.',
|
||||||
|
'Set {highlight}"rm -rf *": "deny"{/highlight} to block destructive commands.',
|
||||||
const TIPS = [
|
'Configure {highlight}"git push": "ask"{/highlight} to require approval before pushing.',
|
||||||
"Type {highlight}@{/highlight} followed by a filename to fuzzy search and attach files",
|
"OpenCode auto-formats files using prettier, gofmt, ruff, and more.",
|
||||||
"Start a message with {highlight}!{/highlight} to run shell commands directly (e.g., {highlight}!ls -la{/highlight})",
|
'Set {highlight}"formatter": false{/highlight} in config to disable all auto-formatting.',
|
||||||
"Press {highlight}Tab{/highlight} to cycle between Build and Plan agents",
|
"Define custom formatter commands with file extensions in config.",
|
||||||
"Use {highlight}/undo{/highlight} to revert the last message and file changes",
|
"OpenCode uses LSP servers for intelligent code analysis.",
|
||||||
"Use {highlight}/redo{/highlight} to restore previously undone messages and file changes",
|
"Create {highlight}.ts{/highlight} files in {highlight}.opencode/tool/{/highlight} to define new LLM tools.",
|
||||||
"Run {highlight}/share{/highlight} to create a public link to your conversation at opencode.ai",
|
"Tool definitions can invoke scripts written in Python, Go, etc.",
|
||||||
"Drag and drop images into the terminal to add them as context",
|
"Add {highlight}.ts{/highlight} files to {highlight}.opencode/plugin/{/highlight} for event hooks.",
|
||||||
"Press {highlight}Ctrl+V{/highlight} to paste images from your clipboard into the prompt",
|
"Use plugins to send OS notifications when sessions complete.",
|
||||||
"Press {highlight}Ctrl+X E{/highlight} or {highlight}/editor{/highlight} to compose messages in your external editor",
|
"Create a plugin to prevent OpenCode from reading sensitive files.",
|
||||||
"Run {highlight}/init{/highlight} to auto-generate project rules based on your codebase",
|
"Use {highlight}opencode run{/highlight} for non-interactive scripting.",
|
||||||
"Run {highlight}/models{/highlight} or {highlight}Ctrl+X M{/highlight} to see and switch between available AI models",
|
"Use {highlight}opencode run --continue{/highlight} to resume the last session.",
|
||||||
"Use {highlight}/theme{/highlight} or {highlight}Ctrl+X T{/highlight} to switch between 50+ built-in themes",
|
"Use {highlight}opencode run -f file.ts{/highlight} to attach files via CLI.",
|
||||||
"Press {highlight}Ctrl+X N{/highlight} or {highlight}/new{/highlight} to start a fresh conversation session",
|
"Use {highlight}--format json{/highlight} for machine-readable output in scripts.",
|
||||||
"Use {highlight}/sessions{/highlight} or {highlight}Ctrl+X L{/highlight} to list and continue previous conversations",
|
"Run {highlight}opencode serve{/highlight} for headless API access to OpenCode.",
|
||||||
"Run {highlight}/compact{/highlight} to summarize long sessions near context limits",
|
"Use {highlight}opencode run --attach{/highlight} to connect to a running server for faster runs.",
|
||||||
"Press {highlight}Ctrl+X X{/highlight} or {highlight}/export{/highlight} to save the conversation as Markdown",
|
"Run {highlight}opencode upgrade{/highlight} to update to the latest version.",
|
||||||
"Press {highlight}Ctrl+X Y{/highlight} to copy the assistant's last message to clipboard",
|
"Run {highlight}opencode auth list{/highlight} to see all configured providers.",
|
||||||
"Press {highlight}Ctrl+P{/highlight} to see all available actions and commands",
|
"Run {highlight}opencode agent create{/highlight} for guided agent creation.",
|
||||||
"Run {highlight}/connect{/highlight} to add API keys for 75+ supported LLM providers",
|
"Use {highlight}/opencode{/highlight} in GitHub issues/PRs to trigger AI actions.",
|
||||||
"The leader key is {highlight}Ctrl+X{/highlight}; combine with other keys for quick actions",
|
"Run {highlight}opencode github install{/highlight} to set up the GitHub workflow.",
|
||||||
"Press {highlight}F2{/highlight} to quickly switch between recently used models",
|
"Comment {highlight}/opencode fix this{/highlight} on issues to auto-create PRs.",
|
||||||
"Press {highlight}Ctrl+X B{/highlight} to show/hide the sidebar panel",
|
"Comment {highlight}/oc{/highlight} on PR code lines for targeted code reviews.",
|
||||||
"Use {highlight}PageUp{/highlight}/{highlight}PageDown{/highlight} to navigate through conversation history",
|
'Use {highlight}"theme": "system"{/highlight} to match your terminal\'s colors.',
|
||||||
"Press {highlight}Ctrl+G{/highlight} or {highlight}Home{/highlight} to jump to the beginning of the conversation",
|
"Create JSON theme files in {highlight}.opencode/themes/{/highlight} directory.",
|
||||||
"Press {highlight}Ctrl+Alt+G{/highlight} or {highlight}End{/highlight} to jump to the most recent message",
|
"Themes support dark/light variants for both modes.",
|
||||||
"Press {highlight}Shift+Enter{/highlight} or {highlight}Ctrl+J{/highlight} to add newlines in your prompt",
|
"Reference ANSI colors 0-255 in custom themes.",
|
||||||
"Press {highlight}Ctrl+C{/highlight} when typing to clear the input field",
|
"Use {highlight}{env:VAR_NAME}{/highlight} syntax to reference environment variables in config.",
|
||||||
"Press {highlight}Escape{/highlight} to stop the AI mid-response",
|
"Use {highlight}{file:path}{/highlight} to include file contents in config values.",
|
||||||
"Switch to {highlight}Plan{/highlight} agent to get suggestions without making actual changes",
|
"Use {highlight}instructions{/highlight} in config to load additional rules files.",
|
||||||
"Use {highlight}@agent-name{/highlight} in prompts to invoke specialized subagents",
|
"Set agent {highlight}temperature{/highlight} from 0.0 (focused) to 1.0 (creative).",
|
||||||
"Press {highlight}Ctrl+X Right/Left{/highlight} to cycle through parent and child sessions",
|
"Configure {highlight}maxSteps{/highlight} to limit agentic iterations per request.",
|
||||||
"Create {highlight}opencode.json{/highlight} in project root for project-specific settings",
|
'Set {highlight}"tools": {"bash": false}{/highlight} to disable specific tools.',
|
||||||
"Place settings in {highlight}~/.config/opencode/opencode.json{/highlight} for global config",
|
'Use {highlight}"mcp_*": false{/highlight} to disable all tools from an MCP server.',
|
||||||
"Add {highlight}$schema{/highlight} to your config for autocomplete in your editor",
|
"Override global tool settings per agent configuration.",
|
||||||
"Configure {highlight}model{/highlight} in config to set your default model",
|
'Set {highlight}"share": "auto"{/highlight} to automatically share all sessions.',
|
||||||
"Override any keybind in config via the {highlight}keybinds{/highlight} section",
|
'Set {highlight}"share": "disabled"{/highlight} to prevent any session sharing.',
|
||||||
"Set any keybind to {highlight}none{/highlight} to disable it completely",
|
"Run {highlight}/unshare{/highlight} to remove a session from public access.",
|
||||||
"Configure local or remote MCP servers in the {highlight}mcp{/highlight} config section",
|
"Permission {highlight}doom_loop{/highlight} prevents infinite tool call loops.",
|
||||||
"OpenCode auto-handles OAuth for remote MCP servers requiring auth",
|
"Permission {highlight}external_directory{/highlight} protects files outside project.",
|
||||||
"Add {highlight}.md{/highlight} files to {highlight}.opencode/command/{/highlight} to define reusable custom prompts",
|
"Run {highlight}opencode debug config{/highlight} to troubleshoot configuration.",
|
||||||
"Use {highlight}$ARGUMENTS{/highlight}, {highlight}$1{/highlight}, {highlight}$2{/highlight} in custom commands for dynamic input",
|
"Use {highlight}--print-logs{/highlight} flag to see detailed logs in stderr.",
|
||||||
"Use backticks in commands to inject shell output (e.g., {highlight}`git status`{/highlight})",
|
"Press {highlight}Ctrl+X G{/highlight} or {highlight}/timeline{/highlight} to jump to specific messages.",
|
||||||
"Add {highlight}.md{/highlight} files to {highlight}.opencode/agent/{/highlight} for specialized AI personas",
|
"Press {highlight}Ctrl+X H{/highlight} to toggle code block visibility in messages.",
|
||||||
"Configure per-agent permissions for {highlight}edit{/highlight}, {highlight}bash{/highlight}, and {highlight}webfetch{/highlight} tools",
|
"Press {highlight}Ctrl+X S{/highlight} or {highlight}/status{/highlight} to see system status info.",
|
||||||
'Use patterns like {highlight}"git *": "allow"{/highlight} for granular bash permissions',
|
"Enable {highlight}tui.scroll_acceleration{/highlight} for smooth macOS-style scrolling.",
|
||||||
'Set {highlight}"rm -rf *": "deny"{/highlight} to block destructive commands',
|
"Toggle username display in chat via command palette ({highlight}Ctrl+P{/highlight}).",
|
||||||
'Configure {highlight}"git push": "ask"{/highlight} to require approval before pushing',
|
"Run {highlight}docker run -it --rm ghcr.io/anomalyco/opencode{/highlight} for containerized use.",
|
||||||
"OpenCode auto-formats files using prettier, gofmt, ruff, and more",
|
"Use {highlight}/connect{/highlight} with OpenCode Zen for curated, tested models.",
|
||||||
'Set {highlight}"formatter": false{/highlight} in config to disable all auto-formatting',
|
"Commit your project's {highlight}AGENTS.md{/highlight} file to Git for team sharing.",
|
||||||
"Define custom formatter commands with file extensions in config",
|
"Use {highlight}/review{/highlight} to review uncommitted changes, branches, or PRs.",
|
||||||
"OpenCode uses LSP servers for intelligent code analysis",
|
"Run {highlight}/help{/highlight} or {highlight}Ctrl+X H{/highlight} to show the help dialog.",
|
||||||
"Create {highlight}.ts{/highlight} files in {highlight}.opencode/tool/{/highlight} to define new LLM tools",
|
"Use {highlight}/details{/highlight} to toggle tool execution details visibility.",
|
||||||
"Tool definitions can invoke scripts written in Python, Go, etc",
|
"Use {highlight}/rename{/highlight} to rename the current session.",
|
||||||
"Add {highlight}.ts{/highlight} files to {highlight}.opencode/plugin/{/highlight} for event hooks",
|
"Press {highlight}Ctrl+Z{/highlight} to suspend the terminal and return to your shell.",
|
||||||
"Use plugins to send OS notifications when sessions complete",
|
|
||||||
"Create a plugin to prevent OpenCode from reading sensitive files",
|
|
||||||
"Use {highlight}opencode run{/highlight} for non-interactive scripting",
|
|
||||||
"Use {highlight}opencode run --continue{/highlight} to resume the last session",
|
|
||||||
"Use {highlight}opencode run -f file.ts{/highlight} to attach files via CLI",
|
|
||||||
"Use {highlight}--format json{/highlight} for machine-readable output in scripts",
|
|
||||||
"Run {highlight}opencode serve{/highlight} for headless API access to OpenCode",
|
|
||||||
"Use {highlight}opencode run --attach{/highlight} to connect to a running server",
|
|
||||||
"Run {highlight}opencode upgrade{/highlight} to update to the latest version",
|
|
||||||
"Run {highlight}opencode auth list{/highlight} to see all configured providers",
|
|
||||||
"Run {highlight}opencode agent create{/highlight} for guided agent creation",
|
|
||||||
"Use {highlight}/opencode{/highlight} in GitHub issues/PRs to trigger AI actions",
|
|
||||||
"Run {highlight}opencode github install{/highlight} to set up the GitHub workflow",
|
|
||||||
"Comment {highlight}/opencode fix this{/highlight} on issues to auto-create PRs",
|
|
||||||
"Comment {highlight}/oc{/highlight} on PR code lines for targeted code reviews",
|
|
||||||
'Use {highlight}"theme": "system"{/highlight} to match your terminal\'s colors',
|
|
||||||
"Create JSON theme files in {highlight}.opencode/themes/{/highlight} directory",
|
|
||||||
"Themes support dark/light variants for both modes",
|
|
||||||
"Reference ANSI colors 0-255 in custom themes",
|
|
||||||
"Use {highlight}{env:VAR_NAME}{/highlight} syntax to reference environment variables in config",
|
|
||||||
"Use {highlight}{file:path}{/highlight} to include file contents in config values",
|
|
||||||
"Use {highlight}instructions{/highlight} in config to load additional rules files",
|
|
||||||
"Set agent {highlight}temperature{/highlight} from 0.0 (focused) to 1.0 (creative)",
|
|
||||||
"Configure {highlight}maxSteps{/highlight} to limit agentic iterations per request",
|
|
||||||
'Set {highlight}"tools": {"bash": false}{/highlight} to disable specific tools',
|
|
||||||
'Set {highlight}"mcp_*": false{/highlight} to disable all tools from an MCP server',
|
|
||||||
"Override global tool settings per agent configuration",
|
|
||||||
'Set {highlight}"share": "auto"{/highlight} to automatically share all sessions',
|
|
||||||
'Set {highlight}"share": "disabled"{/highlight} to prevent any session sharing',
|
|
||||||
"Run {highlight}/unshare{/highlight} to remove a session from public access",
|
|
||||||
"Permission {highlight}doom_loop{/highlight} prevents infinite tool call loops",
|
|
||||||
"Permission {highlight}external_directory{/highlight} protects files outside project",
|
|
||||||
"Run {highlight}opencode debug config{/highlight} to troubleshoot configuration",
|
|
||||||
"Use {highlight}--print-logs{/highlight} flag to see detailed logs in stderr",
|
|
||||||
"Press {highlight}Ctrl+X G{/highlight} or {highlight}/timeline{/highlight} to jump to specific messages",
|
|
||||||
"Press {highlight}Ctrl+X H{/highlight} to toggle code block visibility in messages",
|
|
||||||
"Press {highlight}Ctrl+X S{/highlight} or {highlight}/status{/highlight} to see system status info",
|
|
||||||
"Enable {highlight}tui.scroll_acceleration{/highlight} for smooth macOS-style scrolling",
|
|
||||||
"Toggle username display in chat via command palette ({highlight}Ctrl+P{/highlight})",
|
|
||||||
"Run {highlight}docker run -it --rm ghcr.io/sst/opencode{/highlight} for containerized use",
|
|
||||||
"Use {highlight}/connect{/highlight} with OpenCode Zen for curated, tested models",
|
|
||||||
"Commit your project's {highlight}AGENTS.md{/highlight} file to Git for team sharing",
|
|
||||||
"Use {highlight}/review{/highlight} to review uncommitted changes, branches, or PRs",
|
|
||||||
"Run {highlight}/help{/highlight} or {highlight}Ctrl+X H{/highlight} to show the help dialog",
|
|
||||||
"Use {highlight}/details{/highlight} to toggle tool execution details visibility",
|
|
||||||
"Use {highlight}/rename{/highlight} to rename the current session",
|
|
||||||
"Press {highlight}Ctrl+Z{/highlight} to suspend the terminal and return to your shell",
|
|
||||||
]
|
]
|
||||||
@@ -8,13 +8,13 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({
|
|||||||
name: "KV",
|
name: "KV",
|
||||||
init: () => {
|
init: () => {
|
||||||
const [ready, setReady] = createSignal(false)
|
const [ready, setReady] = createSignal(false)
|
||||||
const [store, setStore] = createStore<Record<string, any>>()
|
const [kvStore, setKvStore] = createStore<Record<string, any>>()
|
||||||
const file = Bun.file(path.join(Global.Path.state, "kv.json"))
|
const file = Bun.file(path.join(Global.Path.state, "kv.json"))
|
||||||
|
|
||||||
file
|
file
|
||||||
.json()
|
.json()
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
setStore(x)
|
setKvStore(x)
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -25,11 +25,8 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({
|
|||||||
get ready() {
|
get ready() {
|
||||||
return ready()
|
return ready()
|
||||||
},
|
},
|
||||||
get store() {
|
|
||||||
return store
|
|
||||||
},
|
|
||||||
signal<T>(name: string, defaultValue: T) {
|
signal<T>(name: string, defaultValue: T) {
|
||||||
if (store[name] === undefined) setStore(name, defaultValue)
|
if (kvStore[name] === undefined) setKvStore(name, defaultValue)
|
||||||
return [
|
return [
|
||||||
function () {
|
function () {
|
||||||
return result.get(name)
|
return result.get(name)
|
||||||
@@ -40,11 +37,11 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({
|
|||||||
] as const
|
] as const
|
||||||
},
|
},
|
||||||
get(key: string, defaultValue?: any) {
|
get(key: string, defaultValue?: any) {
|
||||||
return store[key] ?? defaultValue
|
return kvStore[key] ?? defaultValue
|
||||||
},
|
},
|
||||||
set(key: string, value: any) {
|
set(key: string, value: any) {
|
||||||
setStore(key, value)
|
setKvStore(key, value)
|
||||||
Bun.write(file, JSON.stringify(store, null, 2))
|
Bun.write(file, JSON.stringify(kvStore, null, 2))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -3,66 +3,21 @@ import { createSimpleContext } from "./helper"
|
|||||||
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
||||||
import { batch, onCleanup, onMount } from "solid-js"
|
import { batch, onCleanup, onMount } from "solid-js"
|
||||||
|
|
||||||
export type EventSource = {
|
|
||||||
on: (handler: (event: Event) => void) => () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
||||||
name: "SDK",
|
name: "SDK",
|
||||||
init: (props: { url: string; directory?: string; fetch?: typeof fetch; events?: EventSource }) => {
|
init: (props: { url: string; directory?: string }) => {
|
||||||
const abort = new AbortController()
|
const abort = new AbortController()
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: props.url,
|
baseUrl: props.url,
|
||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
directory: props.directory,
|
directory: props.directory,
|
||||||
fetch: props.fetch,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const emitter = createGlobalEmitter<{
|
const emitter = createGlobalEmitter<{
|
||||||
[key in Event["type"]]: Extract<Event, { type: key }>
|
[key in Event["type"]]: Extract<Event, { type: key }>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
let queue: Event[] = []
|
|
||||||
let timer: Timer | undefined
|
|
||||||
let last = 0
|
|
||||||
|
|
||||||
const flush = () => {
|
|
||||||
if (queue.length === 0) return
|
|
||||||
const events = queue
|
|
||||||
queue = []
|
|
||||||
timer = undefined
|
|
||||||
last = Date.now()
|
|
||||||
// Batch all event emissions so all store updates result in a single render
|
|
||||||
batch(() => {
|
|
||||||
for (const event of events) {
|
|
||||||
emitter.emit(event.type, event)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleEvent = (event: Event) => {
|
|
||||||
queue.push(event)
|
|
||||||
const elapsed = Date.now() - last
|
|
||||||
|
|
||||||
if (timer) return
|
|
||||||
// If we just flushed recently (within 16ms), batch this with future events
|
|
||||||
// Otherwise, process immediately to avoid latency
|
|
||||||
if (elapsed < 16) {
|
|
||||||
timer = setTimeout(flush, 16)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
// If an event source is provided, use it instead of SSE
|
|
||||||
if (props.events) {
|
|
||||||
const unsub = props.events.on(handleEvent)
|
|
||||||
onCleanup(unsub)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to SSE
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (abort.signal.aborted) break
|
if (abort.signal.aborted) break
|
||||||
const events = await sdk.event.subscribe(
|
const events = await sdk.event.subscribe(
|
||||||
@@ -71,9 +26,36 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
let queue: Event[] = []
|
||||||
|
let timer: Timer | undefined
|
||||||
|
let last = 0
|
||||||
|
|
||||||
|
const flush = () => {
|
||||||
|
if (queue.length === 0) return
|
||||||
|
const events = queue
|
||||||
|
queue = []
|
||||||
|
timer = undefined
|
||||||
|
last = Date.now()
|
||||||
|
// Batch all event emissions so all store updates result in a single render
|
||||||
|
batch(() => {
|
||||||
|
for (const event of events) {
|
||||||
|
emitter.emit(event.type, event)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for await (const event of events.stream) {
|
for await (const event of events.stream) {
|
||||||
handleEvent(event)
|
queue.push(event)
|
||||||
|
const elapsed = Date.now() - last
|
||||||
|
|
||||||
|
if (timer) continue
|
||||||
|
// If we just flushed recently (within 16ms), batch this with future events
|
||||||
|
// Otherwise, process immediately to avoid latency
|
||||||
|
if (elapsed < 16) {
|
||||||
|
timer = setTimeout(flush, 16)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush any remaining events
|
// Flush any remaining events
|
||||||
@@ -86,7 +68,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
abort.abort()
|
abort.abort()
|
||||||
if (timer) clearTimeout(timer)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return { client: sdk, event: emitter, url: props.url }
|
return { client: sdk, event: emitter, url: props.url }
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
||||||
import { createMemo, Match, onMount, Show, Switch } from "solid-js"
|
import { createMemo, Match, onMount, Show, Switch } from "solid-js"
|
||||||
import { useTheme } from "@tui/context/theme"
|
import { useTheme } from "@tui/context/theme"
|
||||||
import { useKeybind } from "@tui/context/keybind"
|
|
||||||
import { Logo } from "../component/logo"
|
import { Logo } from "../component/logo"
|
||||||
import { Tips } from "../component/tips"
|
import { DidYouKnow, randomizeTip } from "../component/did-you-know"
|
||||||
import { Locale } from "@/util/locale"
|
import { Locale } from "@/util/locale"
|
||||||
import { useSync } from "../context/sync"
|
import { useSync } from "../context/sync"
|
||||||
import { Toast } from "../ui/toast"
|
import { Toast } from "../ui/toast"
|
||||||
@@ -37,6 +36,7 @@ export function Home() {
|
|||||||
const isFirstTimeUser = createMemo(() => sync.data.session.length === 0)
|
const isFirstTimeUser = createMemo(() => sync.data.session.length === 0)
|
||||||
const tipsHidden = createMemo(() => kv.get("tips_hidden", false))
|
const tipsHidden = createMemo(() => kv.get("tips_hidden", false))
|
||||||
const showTips = createMemo(() => {
|
const showTips = createMemo(() => {
|
||||||
|
return false
|
||||||
// Don't show tips for first-time users
|
// Don't show tips for first-time users
|
||||||
if (isFirstTimeUser()) return false
|
if (isFirstTimeUser()) return false
|
||||||
return !tipsHidden()
|
return !tipsHidden()
|
||||||
@@ -77,6 +77,7 @@ export function Home() {
|
|||||||
let prompt: PromptRef
|
let prompt: PromptRef
|
||||||
const args = useArgs()
|
const args = useArgs()
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
randomizeTip()
|
||||||
if (once) return
|
if (once) return
|
||||||
if (route.initialPrompt) {
|
if (route.initialPrompt) {
|
||||||
prompt.set(route.initialPrompt)
|
prompt.set(route.initialPrompt)
|
||||||
@@ -89,12 +90,9 @@ export function Home() {
|
|||||||
})
|
})
|
||||||
const directory = useDirectory()
|
const directory = useDirectory()
|
||||||
|
|
||||||
const keybind = useKeybind()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<box flexGrow={1} justifyContent="center" alignItems="center" paddingLeft={2} paddingRight={2} gap={1}>
|
<box flexGrow={1} justifyContent="center" alignItems="center" paddingLeft={2} paddingRight={2} gap={1}>
|
||||||
<box height={3} />
|
|
||||||
<Logo />
|
<Logo />
|
||||||
<box width="100%" maxWidth={75} zIndex={1000} paddingTop={1}>
|
<box width="100%" maxWidth={75} zIndex={1000} paddingTop={1}>
|
||||||
<Prompt
|
<Prompt
|
||||||
@@ -105,13 +103,13 @@ export function Home() {
|
|||||||
hint={Hint}
|
hint={Hint}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
<box height={3} width="100%" maxWidth={75} alignItems="center" paddingTop={2}>
|
|
||||||
<Show when={showTips()}>
|
|
||||||
<Tips />
|
|
||||||
</Show>
|
|
||||||
</box>
|
|
||||||
<Toast />
|
<Toast />
|
||||||
</box>
|
</box>
|
||||||
|
<Show when={!isFirstTimeUser()}>
|
||||||
|
<Show when={showTips()}>
|
||||||
|
<DidYouKnow />
|
||||||
|
</Show>
|
||||||
|
</Show>
|
||||||
<box paddingTop={1} paddingBottom={1} paddingLeft={2} paddingRight={2} flexDirection="row" flexShrink={0} gap={2}>
|
<box paddingTop={1} paddingBottom={1} paddingLeft={2} paddingRight={2} flexDirection="row" flexShrink={0} gap={2}>
|
||||||
<text fg={theme.textMuted}>{directory()}</text>
|
<text fg={theme.textMuted}>{directory()}</text>
|
||||||
<box gap={1} flexDirection="row" flexShrink={0}>
|
<box gap={1} flexDirection="row" flexShrink={0}>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { type Accessor, createMemo, createSignal, Match, Show, Switch } from "solid-js"
|
import { type Accessor, createMemo, Match, Show, Switch } from "solid-js"
|
||||||
import { useRouteData } from "@tui/context/route"
|
import { useRouteData } from "@tui/context/route"
|
||||||
import { useSync } from "@tui/context/sync"
|
import { useSync } from "@tui/context/sync"
|
||||||
import { pipe, sumBy } from "remeda"
|
import { pipe, sumBy } from "remeda"
|
||||||
import { useTheme } from "@tui/context/theme"
|
import { useTheme } from "@tui/context/theme"
|
||||||
import { SplitBorder } from "@tui/component/border"
|
import { SplitBorder } from "@tui/component/border"
|
||||||
import type { AssistantMessage, Session } from "@opencode-ai/sdk/v2"
|
import type { AssistantMessage, Session } from "@opencode-ai/sdk/v2"
|
||||||
import { useCommandDialog } from "@tui/component/dialog-command"
|
|
||||||
import { useKeybind } from "../../context/keybind"
|
import { useKeybind } from "../../context/keybind"
|
||||||
|
|
||||||
const Title = (props: { session: Accessor<Session> }) => {
|
const Title = (props: { session: Accessor<Session> }) => {
|
||||||
@@ -60,8 +59,6 @@ export function Header() {
|
|||||||
|
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const keybind = useKeybind()
|
const keybind = useKeybind()
|
||||||
const command = useCommandDialog()
|
|
||||||
const [hover, setHover] = createSignal<"parent" | "prev" | "next" | null>(null)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box flexShrink={0}>
|
<box flexShrink={0}>
|
||||||
@@ -82,36 +79,15 @@ export function Header() {
|
|||||||
<text fg={theme.text}>
|
<text fg={theme.text}>
|
||||||
<b>Subagent session</b>
|
<b>Subagent session</b>
|
||||||
</text>
|
</text>
|
||||||
<box
|
<text fg={theme.text}>
|
||||||
onMouseOver={() => setHover("parent")}
|
Parent <span style={{ fg: theme.textMuted }}>{keybind.print("session_parent")}</span>
|
||||||
onMouseOut={() => setHover(null)}
|
</text>
|
||||||
onMouseUp={() => command.trigger("session.parent")}
|
<text fg={theme.text}>
|
||||||
backgroundColor={hover() === "parent" ? theme.backgroundElement : theme.backgroundPanel}
|
Prev <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle_reverse")}</span>
|
||||||
>
|
</text>
|
||||||
<text fg={theme.text}>
|
<text fg={theme.text}>
|
||||||
Parent <span style={{ fg: theme.textMuted }}>{keybind.print("session_parent")}</span>
|
Next <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle")}</span>
|
||||||
</text>
|
</text>
|
||||||
</box>
|
|
||||||
<box
|
|
||||||
onMouseOver={() => setHover("prev")}
|
|
||||||
onMouseOut={() => setHover(null)}
|
|
||||||
onMouseUp={() => command.trigger("session.child.previous")}
|
|
||||||
backgroundColor={hover() === "prev" ? theme.backgroundElement : theme.backgroundPanel}
|
|
||||||
>
|
|
||||||
<text fg={theme.text}>
|
|
||||||
Prev <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle_reverse")}</span>
|
|
||||||
</text>
|
|
||||||
</box>
|
|
||||||
<box
|
|
||||||
onMouseOver={() => setHover("next")}
|
|
||||||
onMouseOut={() => setHover(null)}
|
|
||||||
onMouseUp={() => command.trigger("session.child.next")}
|
|
||||||
backgroundColor={hover() === "next" ? theme.backgroundElement : theme.backgroundPanel}
|
|
||||||
>
|
|
||||||
<text fg={theme.text}>
|
|
||||||
Next <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle")}</span>
|
|
||||||
</text>
|
|
||||||
</box>
|
|
||||||
<box flexGrow={1} flexShrink={1} />
|
<box flexGrow={1} flexShrink={1} />
|
||||||
<ContextInfo context={context} cost={cost} />
|
<ContextInfo context={context} cost={cost} />
|
||||||
</box>
|
</box>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createMemo, For, Match, Show, Switch } from "solid-js"
|
import { createMemo, For, Match, Show, Switch } from "solid-js"
|
||||||
import { Portal, useKeyboard, useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
|
import { useKeyboard, useTerminalDimensions, type JSX } from "@opentui/solid"
|
||||||
import type { TextareaRenderable } from "@opentui/core"
|
import type { TextareaRenderable } from "@opentui/core"
|
||||||
import { useKeybind } from "../../context/keybind"
|
import { useKeybind } from "../../context/keybind"
|
||||||
import { useTheme, selectedForeground } from "../../context/theme"
|
import { useTheme, selectedForeground } from "../../context/theme"
|
||||||
@@ -11,7 +11,6 @@ import { useSync } from "../../context/sync"
|
|||||||
import { useTextareaKeybindings } from "../../component/textarea-keybindings"
|
import { useTextareaKeybindings } from "../../component/textarea-keybindings"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
||||||
import { Keybind } from "@/util/keybind"
|
|
||||||
import { Locale } from "@/util/locale"
|
import { Locale } from "@/util/locale"
|
||||||
|
|
||||||
type PermissionStage = "permission" | "always" | "reject"
|
type PermissionStage = "permission" | "always" | "reject"
|
||||||
@@ -33,9 +32,7 @@ function filetype(input?: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function EditBody(props: { request: PermissionRequest }) {
|
function EditBody(props: { request: PermissionRequest }) {
|
||||||
const themeState = useTheme()
|
const { theme, syntax } = useTheme()
|
||||||
const theme = themeState.theme
|
|
||||||
const syntax = themeState.syntax
|
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
|
|
||||||
@@ -57,7 +54,7 @@ function EditBody(props: { request: PermissionRequest }) {
|
|||||||
<text fg={theme.textMuted}>Edit {normalizePath(filepath())}</text>
|
<text fg={theme.textMuted}>Edit {normalizePath(filepath())}</text>
|
||||||
</box>
|
</box>
|
||||||
<Show when={diff()}>
|
<Show when={diff()}>
|
||||||
<scrollbox height="100%">
|
<box maxHeight={Math.floor(dimensions().height / 4)} overflow="scroll">
|
||||||
<diff
|
<diff
|
||||||
diff={diff()}
|
diff={diff()}
|
||||||
view={view()}
|
view={view()}
|
||||||
@@ -77,7 +74,7 @@ function EditBody(props: { request: PermissionRequest }) {
|
|||||||
addedLineNumberBg={theme.diffAddedLineNumberBg}
|
addedLineNumberBg={theme.diffAddedLineNumberBg}
|
||||||
removedLineNumberBg={theme.diffRemovedLineNumberBg}
|
removedLineNumberBg={theme.diffRemovedLineNumberBg}
|
||||||
/>
|
/>
|
||||||
</scrollbox>
|
</box>
|
||||||
</Show>
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
@@ -175,95 +172,86 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
|
|||||||
message: message || undefined,
|
message: message || undefined,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
onCancel={() => {
|
onCancel={() => setStore("stage", "permission")}
|
||||||
setStore("stage", "permission")
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={store.stage === "permission"}>
|
<Match when={store.stage === "permission"}>
|
||||||
{(() => {
|
<Prompt
|
||||||
const body = (
|
title="Permission required"
|
||||||
<Prompt
|
body={
|
||||||
title="Permission required"
|
<Switch>
|
||||||
body={
|
<Match when={props.request.permission === "edit"}>
|
||||||
<Switch>
|
<EditBody request={props.request} />
|
||||||
<Match when={props.request.permission === "edit"}>
|
</Match>
|
||||||
<EditBody request={props.request} />
|
<Match when={props.request.permission === "read"}>
|
||||||
</Match>
|
<TextBody icon="→" title={`Read ` + normalizePath(input().filePath as string)} />
|
||||||
<Match when={props.request.permission === "read"}>
|
</Match>
|
||||||
<TextBody icon="→" title={`Read ` + normalizePath(input().filePath as string)} />
|
<Match when={props.request.permission === "glob"}>
|
||||||
</Match>
|
<TextBody icon="✱" title={`Glob "` + (input().pattern ?? "") + `"`} />
|
||||||
<Match when={props.request.permission === "glob"}>
|
</Match>
|
||||||
<TextBody icon="✱" title={`Glob "` + (input().pattern ?? "") + `"`} />
|
<Match when={props.request.permission === "grep"}>
|
||||||
</Match>
|
<TextBody icon="✱" title={`Grep "` + (input().pattern ?? "") + `"`} />
|
||||||
<Match when={props.request.permission === "grep"}>
|
</Match>
|
||||||
<TextBody icon="✱" title={`Grep "` + (input().pattern ?? "") + `"`} />
|
<Match when={props.request.permission === "list"}>
|
||||||
</Match>
|
<TextBody icon="→" title={`List ` + normalizePath(input().path as string)} />
|
||||||
<Match when={props.request.permission === "list"}>
|
</Match>
|
||||||
<TextBody icon="→" title={`List ` + normalizePath(input().path as string)} />
|
<Match when={props.request.permission === "bash"}>
|
||||||
</Match>
|
<TextBody
|
||||||
<Match when={props.request.permission === "bash"}>
|
icon="#"
|
||||||
<TextBody
|
title={(input().description as string) ?? ""}
|
||||||
icon="#"
|
description={("$ " + input().command) as string}
|
||||||
title={(input().description as string) ?? ""}
|
/>
|
||||||
description={("$ " + input().command) as string}
|
</Match>
|
||||||
/>
|
<Match when={props.request.permission === "task"}>
|
||||||
</Match>
|
<TextBody
|
||||||
<Match when={props.request.permission === "task"}>
|
icon="#"
|
||||||
<TextBody
|
title={`${Locale.titlecase((input().subagent_type as string) ?? "Unknown")} Task`}
|
||||||
icon="#"
|
description={"◉ " + input().description}
|
||||||
title={`${Locale.titlecase((input().subagent_type as string) ?? "Unknown")} Task`}
|
/>
|
||||||
description={"◉ " + input().description}
|
</Match>
|
||||||
/>
|
<Match when={props.request.permission === "webfetch"}>
|
||||||
</Match>
|
<TextBody icon="%" title={`WebFetch ` + (input().url ?? "")} />
|
||||||
<Match when={props.request.permission === "webfetch"}>
|
</Match>
|
||||||
<TextBody icon="%" title={`WebFetch ` + (input().url ?? "")} />
|
<Match when={props.request.permission === "websearch"}>
|
||||||
</Match>
|
<TextBody icon="◈" title={`Exa Web Search "` + (input().query ?? "") + `"`} />
|
||||||
<Match when={props.request.permission === "websearch"}>
|
</Match>
|
||||||
<TextBody icon="◈" title={`Exa Web Search "` + (input().query ?? "") + `"`} />
|
<Match when={props.request.permission === "codesearch"}>
|
||||||
</Match>
|
<TextBody icon="◇" title={`Exa Code Search "` + (input().query ?? "") + `"`} />
|
||||||
<Match when={props.request.permission === "codesearch"}>
|
</Match>
|
||||||
<TextBody icon="◇" title={`Exa Code Search "` + (input().query ?? "") + `"`} />
|
<Match when={props.request.permission === "external_directory"}>
|
||||||
</Match>
|
<TextBody icon="←" title={`Access external directory ` + normalizePath(input().path as string)} />
|
||||||
<Match when={props.request.permission === "external_directory"}>
|
</Match>
|
||||||
<TextBody icon="←" title={`Access external directory ` + normalizePath(input().path as string)} />
|
<Match when={props.request.permission === "doom_loop"}>
|
||||||
</Match>
|
<TextBody icon="⟳" title="Continue after repeated failures" />
|
||||||
<Match when={props.request.permission === "doom_loop"}>
|
</Match>
|
||||||
<TextBody icon="⟳" title="Continue after repeated failures" />
|
<Match when={true}>
|
||||||
</Match>
|
<TextBody icon="⚙" title={`Call tool ` + props.request.permission} />
|
||||||
<Match when={true}>
|
</Match>
|
||||||
<TextBody icon="⚙" title={`Call tool ` + props.request.permission} />
|
</Switch>
|
||||||
</Match>
|
}
|
||||||
</Switch>
|
options={{ once: "Allow once", always: "Allow always", reject: "Reject" }}
|
||||||
|
escapeKey="reject"
|
||||||
|
onSelect={(option) => {
|
||||||
|
if (option === "always") {
|
||||||
|
setStore("stage", "always")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (option === "reject") {
|
||||||
|
if (session()?.parentID) {
|
||||||
|
setStore("stage", "reject")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
options={{ once: "Allow once", always: "Allow always", reject: "Reject" }}
|
sdk.client.permission.reply({
|
||||||
escapeKey="reject"
|
reply: "reject",
|
||||||
fullscreen
|
requestID: props.request.id,
|
||||||
onSelect={(option) => {
|
})
|
||||||
if (option === "always") {
|
}
|
||||||
setStore("stage", "always")
|
sdk.client.permission.reply({
|
||||||
return
|
reply: "once",
|
||||||
}
|
requestID: props.request.id,
|
||||||
if (option === "reject") {
|
})
|
||||||
if (session()?.parentID) {
|
}}
|
||||||
setStore("stage", "reject")
|
/>
|
||||||
return
|
|
||||||
}
|
|
||||||
sdk.client.permission.reply({
|
|
||||||
reply: "reject",
|
|
||||||
requestID: props.request.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
sdk.client.permission.reply({
|
|
||||||
reply: "once",
|
|
||||||
requestID: props.request.id,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
return body
|
|
||||||
})()}
|
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
)
|
)
|
||||||
@@ -339,18 +327,14 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||||||
body: JSX.Element
|
body: JSX.Element
|
||||||
options: T
|
options: T
|
||||||
escapeKey?: keyof T
|
escapeKey?: keyof T
|
||||||
fullscreen?: boolean
|
|
||||||
onSelect: (option: keyof T) => void
|
onSelect: (option: keyof T) => void
|
||||||
}) {
|
}) {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const keybind = useKeybind()
|
const keybind = useKeybind()
|
||||||
const dimensions = useTerminalDimensions()
|
|
||||||
const keys = Object.keys(props.options) as (keyof T)[]
|
const keys = Object.keys(props.options) as (keyof T)[]
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
selected: keys[0],
|
selected: keys[0],
|
||||||
expanded: false,
|
|
||||||
})
|
})
|
||||||
const diffKey = Keybind.parse("ctrl+f")[0]
|
|
||||||
|
|
||||||
useKeyboard((evt) => {
|
useKeyboard((evt) => {
|
||||||
if (evt.name === "left" || evt.name == "h") {
|
if (evt.name === "left" || evt.name == "h") {
|
||||||
@@ -376,36 +360,17 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
props.onSelect(props.escapeKey)
|
props.onSelect(props.escapeKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.fullscreen && diffKey && Keybind.match(diffKey, keybind.parse(evt))) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
setStore("expanded", (v) => !v)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const hint = createMemo(() => (store.expanded ? "minimize" : "fullscreen"))
|
return (
|
||||||
const renderer = useRenderer()
|
|
||||||
|
|
||||||
const content = () => (
|
|
||||||
<box
|
<box
|
||||||
backgroundColor={theme.backgroundPanel}
|
backgroundColor={theme.backgroundPanel}
|
||||||
border={["left"]}
|
border={["left"]}
|
||||||
borderColor={theme.warning}
|
borderColor={theme.warning}
|
||||||
customBorderChars={SplitBorder.customBorderChars}
|
customBorderChars={SplitBorder.customBorderChars}
|
||||||
{...(store.expanded
|
|
||||||
? { top: dimensions().height * -1 + 1, bottom: 1, left: 2, right: 2, position: "absolute" }
|
|
||||||
: {
|
|
||||||
top: 0,
|
|
||||||
maxHeight: 15,
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
position: "relative",
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<box gap={1} paddingLeft={1} paddingRight={3} paddingTop={1} paddingBottom={1} flexGrow={1}>
|
<box gap={1} paddingLeft={1} paddingRight={3} paddingTop={1} paddingBottom={1}>
|
||||||
<box flexDirection="row" gap={1} paddingLeft={1} flexShrink={0}>
|
<box flexDirection="row" gap={1} paddingLeft={1}>
|
||||||
<text fg={theme.warning}>{"△"}</text>
|
<text fg={theme.warning}>{"△"}</text>
|
||||||
<text fg={theme.text}>{props.title}</text>
|
<text fg={theme.text}>{props.title}</text>
|
||||||
</box>
|
</box>
|
||||||
@@ -438,11 +403,6 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||||||
</For>
|
</For>
|
||||||
</box>
|
</box>
|
||||||
<box flexDirection="row" gap={2}>
|
<box flexDirection="row" gap={2}>
|
||||||
<Show when={props.fullscreen}>
|
|
||||||
<text fg={theme.text}>
|
|
||||||
{"ctrl+f"} <span style={{ fg: theme.textMuted }}>{hint()}</span>
|
|
||||||
</text>
|
|
||||||
</Show>
|
|
||||||
<text fg={theme.text}>
|
<text fg={theme.text}>
|
||||||
{"⇆"} <span style={{ fg: theme.textMuted }}>select</span>
|
{"⇆"} <span style={{ fg: theme.textMuted }}>select</span>
|
||||||
</text>
|
</text>
|
||||||
@@ -453,10 +413,4 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
|
||||||
<Show when={!store.expanded} fallback={<Portal>{content()}</Portal>}>
|
|
||||||
{content()}
|
|
||||||
</Show>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,44 +86,9 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
|||||||
setStore("answers", answers)
|
setStore("answers", answers)
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveTo(index: number) {
|
|
||||||
setStore("selected", index)
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectTab(index: number) {
|
|
||||||
setStore("tab", index)
|
|
||||||
setStore("selected", 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectOption() {
|
|
||||||
if (other()) {
|
|
||||||
if (!multi()) {
|
|
||||||
setStore("editing", true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const value = input()
|
|
||||||
if (value && customPicked()) {
|
|
||||||
toggle(value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setStore("editing", true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const opt = options()[store.selected]
|
|
||||||
if (!opt) return
|
|
||||||
if (multi()) {
|
|
||||||
toggle(opt.label)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pick(opt.label)
|
|
||||||
}
|
|
||||||
|
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
|
||||||
useKeyboard((evt) => {
|
useKeyboard((evt) => {
|
||||||
// Skip processing if a dialog (e.g., command palette) is open
|
|
||||||
if (dialog.stack.length > 0) return
|
|
||||||
|
|
||||||
// When editing "Other" textarea
|
// When editing "Other" textarea
|
||||||
if (store.editing && !confirm()) {
|
if (store.editing && !confirm()) {
|
||||||
if (evt.name === "escape") {
|
if (evt.name === "escape") {
|
||||||
@@ -184,12 +149,16 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
|||||||
|
|
||||||
if (evt.name === "left" || evt.name === "h") {
|
if (evt.name === "left" || evt.name === "h") {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
selectTab((store.tab - 1 + tabs()) % tabs())
|
const next = (store.tab - 1 + tabs()) % tabs()
|
||||||
|
setStore("tab", next)
|
||||||
|
setStore("selected", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt.name === "right" || evt.name === "l") {
|
if (evt.name === "right" || evt.name === "l") {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
selectTab((store.tab + 1) % tabs())
|
const next = (store.tab + 1) % tabs()
|
||||||
|
setStore("tab", next)
|
||||||
|
setStore("selected", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (confirm()) {
|
if (confirm()) {
|
||||||
@@ -207,17 +176,36 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
|||||||
|
|
||||||
if (evt.name === "up" || evt.name === "k") {
|
if (evt.name === "up" || evt.name === "k") {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
moveTo((store.selected - 1 + total) % total)
|
setStore("selected", (store.selected - 1 + total) % total)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt.name === "down" || evt.name === "j") {
|
if (evt.name === "down" || evt.name === "j") {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
moveTo((store.selected + 1) % total)
|
setStore("selected", (store.selected + 1) % total)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt.name === "return") {
|
if (evt.name === "return") {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
selectOption()
|
if (other()) {
|
||||||
|
if (!multi()) {
|
||||||
|
setStore("editing", true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const value = input()
|
||||||
|
if (value && customPicked()) {
|
||||||
|
toggle(value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setStore("editing", true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const opt = opts[store.selected]
|
||||||
|
if (!opt) return
|
||||||
|
if (multi()) {
|
||||||
|
toggle(opt.label)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pick(opt.label)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt.name === "escape" || keybind.match("app_exit", evt)) {
|
if (evt.name === "escape" || keybind.match("app_exit", evt)) {
|
||||||
@@ -248,7 +236,6 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
|||||||
paddingLeft={1}
|
paddingLeft={1}
|
||||||
paddingRight={1}
|
paddingRight={1}
|
||||||
backgroundColor={isActive() ? theme.accent : theme.backgroundElement}
|
backgroundColor={isActive() ? theme.accent : theme.backgroundElement}
|
||||||
onMouseUp={() => selectTab(index())}
|
|
||||||
>
|
>
|
||||||
<text fg={isActive() ? theme.selectedListItemText : isAnswered() ? theme.text : theme.textMuted}>
|
<text fg={isActive() ? theme.selectedListItemText : isAnswered() ? theme.text : theme.textMuted}>
|
||||||
{q.header}
|
{q.header}
|
||||||
@@ -257,12 +244,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</For>
|
</For>
|
||||||
<box
|
<box paddingLeft={1} paddingRight={1} backgroundColor={confirm() ? theme.accent : theme.backgroundElement}>
|
||||||
paddingLeft={1}
|
|
||||||
paddingRight={1}
|
|
||||||
backgroundColor={confirm() ? theme.accent : theme.backgroundElement}
|
|
||||||
onMouseUp={() => selectTab(questions().length)}
|
|
||||||
>
|
|
||||||
<text fg={confirm() ? theme.selectedListItemText : theme.textMuted}>Confirm</text>
|
<text fg={confirm() ? theme.selectedListItemText : theme.textMuted}>Confirm</text>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
@@ -282,7 +264,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
|||||||
const active = () => i() === store.selected
|
const active = () => i() === store.selected
|
||||||
const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false
|
const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false
|
||||||
return (
|
return (
|
||||||
<box onMouseOver={() => moveTo(i())} onMouseUp={() => selectOption()}>
|
<box>
|
||||||
<box flexDirection="row" gap={1}>
|
<box flexDirection="row" gap={1}>
|
||||||
<box backgroundColor={active() ? theme.backgroundElement : undefined}>
|
<box backgroundColor={active() ? theme.backgroundElement : undefined}>
|
||||||
<text fg={active() ? theme.secondary : picked() ? theme.success : theme.text}>
|
<text fg={active() ? theme.secondary : picked() ? theme.success : theme.text}>
|
||||||
@@ -298,7 +280,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</For>
|
</For>
|
||||||
<box onMouseOver={() => moveTo(options().length)} onMouseUp={() => selectOption()}>
|
<box>
|
||||||
<box flexDirection="row" gap={1}>
|
<box flexDirection="row" gap={1}>
|
||||||
<box backgroundColor={other() ? theme.backgroundElement : undefined}>
|
<box backgroundColor={other() ? theme.backgroundElement : undefined}>
|
||||||
<text fg={other() ? theme.secondary : customPicked() ? theme.success : theme.text}>
|
<text fg={other() ? theme.secondary : customPicked() ? theme.success : theme.text}>
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { cmd } from "@/cli/cmd/cmd"
|
||||||
|
import { Instance } from "@/project/instance"
|
||||||
|
import path from "path"
|
||||||
|
import { Server } from "@/server/server"
|
||||||
|
import { upgrade } from "@/cli/upgrade"
|
||||||
|
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
|
||||||
|
|
||||||
|
export const TuiSpawnCommand = cmd({
|
||||||
|
command: "spawn [project]",
|
||||||
|
builder: (yargs) =>
|
||||||
|
withNetworkOptions(yargs).positional("project", {
|
||||||
|
type: "string",
|
||||||
|
describe: "path to start opencode in",
|
||||||
|
}),
|
||||||
|
handler: async (args) => {
|
||||||
|
upgrade()
|
||||||
|
const opts = await resolveNetworkOptions(args)
|
||||||
|
const server = Server.listen(opts)
|
||||||
|
const bin = process.execPath
|
||||||
|
const cmd = []
|
||||||
|
let cwd = process.cwd()
|
||||||
|
if (bin.endsWith("bun")) {
|
||||||
|
cmd.push(
|
||||||
|
process.execPath,
|
||||||
|
"run",
|
||||||
|
"--conditions",
|
||||||
|
"browser",
|
||||||
|
new URL("../../../index.ts", import.meta.url).pathname,
|
||||||
|
)
|
||||||
|
cwd = new URL("../../../../", import.meta.url).pathname
|
||||||
|
} else cmd.push(process.execPath)
|
||||||
|
cmd.push("attach", server.url.toString(), "--dir", args.project ? path.resolve(args.project) : process.cwd())
|
||||||
|
const proc = Bun.spawn({
|
||||||
|
cmd,
|
||||||
|
cwd,
|
||||||
|
stdout: "inherit",
|
||||||
|
stderr: "inherit",
|
||||||
|
stdin: "inherit",
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
BUN_OPTIONS: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await proc.exited
|
||||||
|
await Instance.disposeAll()
|
||||||
|
await server.stop(true)
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -7,39 +7,11 @@ import { UI } from "@/cli/ui"
|
|||||||
import { iife } from "@/util/iife"
|
import { iife } from "@/util/iife"
|
||||||
import { Log } from "@/util/log"
|
import { Log } from "@/util/log"
|
||||||
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
|
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
|
||||||
import type { Event } from "@opencode-ai/sdk/v2"
|
|
||||||
import type { EventSource } from "./context/sdk"
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
const OPENCODE_WORKER_PATH: string
|
const OPENCODE_WORKER_PATH: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RpcClient = ReturnType<typeof Rpc.client<typeof rpc>>
|
|
||||||
|
|
||||||
function createWorkerFetch(client: RpcClient): typeof fetch {
|
|
||||||
const fn = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
||||||
const request = new Request(input, init)
|
|
||||||
const body = request.body ? await request.text() : undefined
|
|
||||||
const result = await client.call("fetch", {
|
|
||||||
url: request.url,
|
|
||||||
method: request.method,
|
|
||||||
headers: Object.fromEntries(request.headers.entries()),
|
|
||||||
body,
|
|
||||||
})
|
|
||||||
return new Response(result.body, {
|
|
||||||
status: result.status,
|
|
||||||
headers: result.headers,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return fn as typeof fetch
|
|
||||||
}
|
|
||||||
|
|
||||||
function createEventSource(client: RpcClient): EventSource {
|
|
||||||
return {
|
|
||||||
on: (handler) => client.on<Event>("event", handler),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TuiThreadCommand = cmd({
|
export const TuiThreadCommand = cmd({
|
||||||
command: "$0 [project]",
|
command: "$0 [project]",
|
||||||
describe: "start opencode tui",
|
describe: "start opencode tui",
|
||||||
@@ -108,42 +80,16 @@ export const TuiThreadCommand = cmd({
|
|||||||
process.on("SIGUSR2", async () => {
|
process.on("SIGUSR2", async () => {
|
||||||
await client.call("reload", undefined)
|
await client.call("reload", undefined)
|
||||||
})
|
})
|
||||||
|
const opts = await resolveNetworkOptions(args)
|
||||||
|
const server = await client.call("server", opts)
|
||||||
const prompt = await iife(async () => {
|
const prompt = await iife(async () => {
|
||||||
const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined
|
const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined
|
||||||
if (!args.prompt) return piped
|
if (!args.prompt) return piped
|
||||||
return piped ? piped + "\n" + args.prompt : args.prompt
|
return piped ? piped + "\n" + args.prompt : args.prompt
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check if server should be started (port or hostname explicitly set in CLI or config)
|
|
||||||
const networkOpts = await resolveNetworkOptions(args)
|
|
||||||
const shouldStartServer =
|
|
||||||
process.argv.includes("--port") ||
|
|
||||||
process.argv.includes("--hostname") ||
|
|
||||||
process.argv.includes("--mdns") ||
|
|
||||||
networkOpts.mdns ||
|
|
||||||
networkOpts.port !== 0 ||
|
|
||||||
networkOpts.hostname !== "127.0.0.1"
|
|
||||||
|
|
||||||
let url: string
|
|
||||||
let customFetch: typeof fetch | undefined
|
|
||||||
let events: EventSource | undefined
|
|
||||||
|
|
||||||
if (shouldStartServer) {
|
|
||||||
// Start HTTP server for external access
|
|
||||||
const server = await client.call("server", networkOpts)
|
|
||||||
url = server.url
|
|
||||||
} else {
|
|
||||||
// Use direct RPC communication (no HTTP)
|
|
||||||
url = "http://opencode.internal"
|
|
||||||
customFetch = createWorkerFetch(client)
|
|
||||||
events = createEventSource(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tuiPromise = tui({
|
const tuiPromise = tui({
|
||||||
url,
|
url: server.url,
|
||||||
fetch: customFetch,
|
|
||||||
events,
|
|
||||||
args: {
|
args: {
|
||||||
continue: args.continue,
|
continue: args.continue,
|
||||||
sessionID: args.session,
|
sessionID: args.session,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user