mirror of
https://github.com/sst/opencode.git
synced 2025-12-23 10:11:41 +00:00
Merge branch 'dev' into opentui
This commit is contained in:
commit
58576cf07a
44 changed files with 486 additions and 675 deletions
20
.github/actions/setup-bun/action.yml
vendored
Normal file
20
.github/actions/setup-bun/action.yml
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
name: "Setup Bun"
|
||||
description: "Setup Bun with caching and install dependencies"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Cache ~/.bun
|
||||
id: cache-bun
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lockb', 'bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
shell: bash
|
||||
6
.github/workflows/deploy.yml
vendored
6
.github/workflows/deploy.yml
vendored
|
|
@ -15,11 +15,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.3.0
|
||||
|
||||
- run: bun install
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- run: bun sst deploy --stage=${{ github.ref_name }}
|
||||
env:
|
||||
|
|
|
|||
5
.github/workflows/format.yml
vendored
5
.github/workflows/format.yml
vendored
|
|
@ -20,13 +20,10 @@ jobs:
|
|||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.3.0
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: run
|
||||
run: |
|
||||
bun install
|
||||
./script/format.ts
|
||||
env:
|
||||
CI: true
|
||||
|
|
|
|||
5
.github/workflows/publish-vscode.yml
vendored
5
.github/workflows/publish-vscode.yml
vendored
|
|
@ -19,16 +19,13 @@ jobs:
|
|||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.0
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- run: git fetch --force --tags
|
||||
- run: bun install -g @vscode/vsce
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
bun install
|
||||
./script/publish
|
||||
working-directory: ./sdks/vscode
|
||||
env:
|
||||
|
|
|
|||
15
.github/workflows/publish.yml
vendored
15
.github/workflows/publish.yml
vendored
|
|
@ -35,18 +35,7 @@ jobs:
|
|||
cache: true
|
||||
cache-dependency-path: go.sum
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.0
|
||||
|
||||
- name: Cache ~/.bun
|
||||
id: cache-bun
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-1-3-0-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-1-3-0-
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install makepkg
|
||||
run: |
|
||||
|
|
@ -60,8 +49,6 @@ jobs:
|
|||
git config --global user.email "opencode@sst.dev"
|
||||
git config --global user.name "opencode"
|
||||
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Install OpenCode
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
|
|
|||
16
.github/workflows/snapshot.yml
vendored
16
.github/workflows/snapshot.yml
vendored
|
|
@ -24,21 +24,7 @@ jobs:
|
|||
cache: true
|
||||
cache-dependency-path: go.sum
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.0
|
||||
|
||||
- name: Cache ~/.bun
|
||||
id: cache-bun
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-1-3-0-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-1-3-0-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
|
|
|
|||
4
.github/workflows/stats.yml
vendored
4
.github/workflows/stats.yml
vendored
|
|
@ -16,9 +16,7 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Run stats script
|
||||
run: bun script/stats.ts
|
||||
|
|
|
|||
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
|
|
@ -18,15 +18,12 @@ jobs:
|
|||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.3.0
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: run
|
||||
run: |
|
||||
git config --global user.email "bot@opencode.ai"
|
||||
git config --global user.name "opencode"
|
||||
bun install
|
||||
bun turbo test
|
||||
env:
|
||||
CI: true
|
||||
|
|
|
|||
7
.github/workflows/typecheck.yml
vendored
7
.github/workflows/typecheck.yml
vendored
|
|
@ -13,12 +13,7 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.3.0
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Run typecheck
|
||||
run: bun typecheck
|
||||
|
|
|
|||
1
STATS.md
1
STATS.md
|
|
@ -105,3 +105,4 @@
|
|||
| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
|
||||
| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
|
||||
| 2025-10-10 | 484,374 (+5,171) | 406,015 (+5,489) | 890,389 (+10,660) |
|
||||
| 2025-10-11 | 488,427 (+4,053) | 414,699 (+8,684) | 903,126 (+12,737) |
|
||||
|
|
|
|||
47
bun.lock
47
bun.lock
|
|
@ -3,11 +3,14 @@
|
|||
"workspaces": {
|
||||
"": {
|
||||
"name": "opencode",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"husky": "9.1.7",
|
||||
"prettier": "3.6.2",
|
||||
"sst": "3.17.13",
|
||||
"sst": "3.17.19",
|
||||
"turbo": "2.5.6",
|
||||
},
|
||||
},
|
||||
|
|
@ -28,10 +31,13 @@
|
|||
"vinxi": "^0.5.7",
|
||||
"zod": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
|
|
@ -52,11 +58,12 @@
|
|||
"@types/node": "24.7.1",
|
||||
"drizzle-kit": "0.30.5",
|
||||
"mysql2": "3.14.4",
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
|
|
@ -79,7 +86,7 @@
|
|||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
|
@ -100,14 +107,14 @@
|
|||
},
|
||||
"packages/console/scripts": {
|
||||
"name": "@opencode-ai/console-scripts",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"dependencies": {
|
||||
"@opencode-ai/console-core": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
|
|
@ -142,7 +149,7 @@
|
|||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
|
|
@ -157,7 +164,7 @@
|
|||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
|
|
@ -223,7 +230,7 @@
|
|||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
|
|
@ -236,7 +243,7 @@
|
|||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.81.0",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
|
|
@ -246,7 +253,7 @@
|
|||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
|
|
@ -2881,23 +2888,21 @@
|
|||
|
||||
"sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="],
|
||||
|
||||
"sst": ["sst@3.17.13", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.13", "sst-darwin-x64": "3.17.13", "sst-linux-arm64": "3.17.13", "sst-linux-x64": "3.17.13", "sst-linux-x86": "3.17.13", "sst-win32-arm64": "3.17.13", "sst-win32-x64": "3.17.13", "sst-win32-x86": "3.17.13" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-NaNTZL7uk2AsXzPBySQy7aqXlStXorR+bA785NxvCbskwkc44nVSQcEsvX5tdsD6/jrWpw9tDy4sStv2ycLAng=="],
|
||||
"sst": ["sst@3.17.19", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.19", "sst-darwin-x64": "3.17.19", "sst-linux-arm64": "3.17.19", "sst-linux-x64": "3.17.19", "sst-linux-x86": "3.17.19", "sst-win32-arm64": "3.17.19", "sst-win32-x64": "3.17.19", "sst-win32-x86": "3.17.19" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-j0FlQhFZW+QWCczzqfPr6fZAF0Um7lP1tbGdd7zkbjFlxdk9BUBI4CYXUnopC6KaTMtjvpfg3XRF7v0bDc9g+A=="],
|
||||
|
||||
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HZaDReT/c+2CcEnFkYjMty63II2ckQrUniiSdoEH6eAWyU1Iy7UwKK4I2GYm+5dy9xeSBaOKga6FMdLqFxIiUg=="],
|
||||
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6FeEgPqXkRT3o5qV0xktJ1eUiscJiPLBcGaxOxIEClpkVggZM83hO7Nizx/cAaAMhr1XQhbOZcKYueDHPdUY+Q=="],
|
||||
|
||||
"sst-darwin-x64": ["sst-darwin-x64@3.17.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-1DlYMrmrI5RY3/Ob039JatgvDKZ5QNtyRkVu0WcnsOvcxFE4dzrCiYKYHg2A+FMDl+H1qcwy2gGA3BTwC9in1w=="],
|
||||
"sst-darwin-x64": ["sst-darwin-x64@3.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-/z78dxfLHG8FtOhpjMnYSpKSdQjfdyKbq+cL3eud2+g2BQr7IyQ8BWNGimk2oadh38V3r6dO1/5aVJh3x3l1rg=="],
|
||||
|
||||
"sst-linux-arm64": ["sst-linux-arm64@3.17.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-A4+ZamchUdaX0pqvYWZ+r7OP1bruwEj9qgWT5kU7Q5pqaotIsEitYQi0q9nZFKH+5mXYesUwSy5FA+Q8T3X/Rg=="],
|
||||
"sst-linux-arm64": ["sst-linux-arm64@3.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-vbcMjiuLVxZ7352ajGlMqsS4J5AkAYvjLmsEALySUBVQhJUO9U7pk2P+Orfn702ZcO+6+NkGG9AL/g3K9EM1Tg=="],
|
||||
|
||||
"sst-linux-x64": ["sst-linux-x64@3.17.13", "", { "os": "linux", "cpu": "x64" }, "sha512-yhKZc5CojqjB2DnyeVka5jeRb4oc3lBx8Qf6or0w4cs47SBIqyvO0iR/3IeKvRRL1hiEUeUF8r/q83rQo9jZMw=="],
|
||||
"sst-linux-x64": ["sst-linux-x64@3.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-gkNNmuHyvKjcb7RwMyoUH4wtgd7/bH7vUlMbcVsDzwt38y7+iTxyPMbcihucw42wDQRaDJtkDneSqj08U+MTFQ=="],
|
||||
|
||||
"sst-linux-x86": ["sst-linux-x86@3.17.13", "", { "os": "linux", "cpu": "none" }, "sha512-G1FIUmpUaECB/3CV5EO/y1QmV5mQ8RUkFeZq64oyILEEaMzSWWKz0glHzQ3+p316VE74MzbktiWRqsCKQy8GeA=="],
|
||||
"sst-linux-x86": ["sst-linux-x86@3.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-Bsvunkh4onZRVv4Rxq7bT/63qQOg2KJoQKhAQtFkJdbri/cOA2QWkzqH8+pC5Sv9rSvbcIJAEIhMXILC0pqCJw=="],
|
||||
|
||||
"sst-win32-arm64": ["sst-win32-arm64@3.17.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-9uCiIXmsGoxGeNWgM81x/d6V/vecjoiHXhBUPDGlQ1Dct1SbtHAgaf/g2ec5XwSQb9B/tne4qk81eMlTl83Z1A=="],
|
||||
"sst-win32-x64": ["sst-win32-x64@3.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-zgxSkGWZ1dewAr4R3slN/d3X9yumQDvAUOlJiX/6QE9Z67t/XNlow4+5i3L2oz4WHAFi59Un12YxbfM+RsBDmA=="],
|
||||
|
||||
"sst-win32-x64": ["sst-win32-x64@3.17.13", "", { "os": "win32", "cpu": "x64" }, "sha512-hTuj4rFSCI/9tX4NMUpNJ69Q9td/giekELDRNv03ys8TpJGoGvPT8D6VD1eX7j1CQnOZIgeEphfW9WmCXkLaIA=="],
|
||||
|
||||
"sst-win32-x86": ["sst-win32-x86@3.17.13", "", { "os": "win32", "cpu": "none" }, "sha512-AuMDGux+H1kPckKJ7Szgi04EpBoOKh/v5zFNAPjvWSkcWcGZ+hsBUx3h/FO/AkGK3RnlLMRj4CQQLoa10RSSIg=="],
|
||||
"sst-win32-x86": ["sst-win32-x86@3.17.19", "", { "os": "win32", "cpu": "none" }, "sha512-z8S0kyb0ibz9Q3cNYDpcKYX47jys7j/mdebC8HUhtED1qKEAfqQ1vsR+zvWyN64Z9Ijj7aPi1KwNV6Et3d7F8g=="],
|
||||
|
||||
"stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -39,9 +39,12 @@
|
|||
"@tsconfig/bun": "1.0.9",
|
||||
"husky": "9.1.7",
|
||||
"prettier": "3.6.2",
|
||||
"sst": "3.17.13",
|
||||
"sst": "3.17.19",
|
||||
"turbo": "2.5.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sst/opencode"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
||||
"build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
||||
"start": "vinxi start",
|
||||
"version": "0.14.7"
|
||||
"version": "0.15.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
|
|
@ -24,6 +24,9 @@
|
|||
"vinxi": "^0.5.7",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
}
|
||||
|
|
|
|||
80
packages/console/app/src/component/dropdown.css
Normal file
80
packages/console/app/src/component/dropdown.css
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
[data-component="dropdown"] {
|
||||
position: relative;
|
||||
|
||||
[data-slot="trigger"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: none;
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: transparent;
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surface-hover);
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="chevron"] {
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
[data-slot="dropdown"] {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
z-index: 1000;
|
||||
margin-top: var(--space-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
min-width: 160px;
|
||||
|
||||
&[data-align="left"] {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&[data-align="right"] {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="item"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--space-2-5) var(--space-3);
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-surface);
|
||||
}
|
||||
|
||||
&[data-selected="true"] {
|
||||
background-color: var(--color-accent-alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
79
packages/console/app/src/component/dropdown.tsx
Normal file
79
packages/console/app/src/component/dropdown.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { JSX, Show, createEffect, onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { IconChevron } from "./icon"
|
||||
import "./dropdown.css"
|
||||
|
||||
interface DropdownProps {
|
||||
trigger: JSX.Element | string
|
||||
children: JSX.Element
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
align?: "left" | "right"
|
||||
class?: string
|
||||
}
|
||||
|
||||
export function Dropdown(props: DropdownProps) {
|
||||
const [store, setStore] = createStore({
|
||||
isOpen: props.open ?? false,
|
||||
})
|
||||
let dropdownRef: HTMLDivElement | undefined
|
||||
|
||||
createEffect(() => {
|
||||
if (props.open !== undefined) {
|
||||
setStore("isOpen", props.open)
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef && !dropdownRef.contains(event.target as Node)) {
|
||||
setStore("isOpen", false)
|
||||
props.onOpenChange?.(false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", handleClickOutside)
|
||||
onCleanup(() => document.removeEventListener("click", handleClickOutside))
|
||||
})
|
||||
|
||||
const toggle = () => {
|
||||
const newValue = !store.isOpen
|
||||
setStore("isOpen", newValue)
|
||||
props.onOpenChange?.(newValue)
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-component="dropdown" class={props.class} ref={dropdownRef}>
|
||||
<button data-slot="trigger" type="button" onClick={toggle}>
|
||||
{typeof props.trigger === "string" ? <span>{props.trigger}</span> : props.trigger}
|
||||
<IconChevron data-slot="chevron" />
|
||||
</button>
|
||||
|
||||
<Show when={store.isOpen}>
|
||||
<div data-slot="dropdown" data-align={props.align ?? "left"}>
|
||||
{props.children}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface DropdownItemProps {
|
||||
children: JSX.Element
|
||||
selected?: boolean
|
||||
onClick?: () => void
|
||||
type?: "button" | "submit" | "reset"
|
||||
}
|
||||
|
||||
export function DropdownItem(props: DropdownItemProps) {
|
||||
return (
|
||||
<button
|
||||
data-slot="item"
|
||||
data-selected={props.selected ?? false}
|
||||
type={props.type ?? "button"}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ export function IconCopy(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
|||
<svg {...props} width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8.75 8.75V2.75H21.25V15.25H15.25M15.25 8.75H2.75V21.25H15.25V8.75Z"
|
||||
stroke="#8E8B8B"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,68 +1,17 @@
|
|||
[data-component="user-menu"] {
|
||||
position: relative;
|
||||
|
||||
[data-slot="trigger"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: none;
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: transparent;
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surface-hover);
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
[data-component="dropdown"] {
|
||||
[data-slot="trigger"] span {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="chevron"] {
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
[data-slot="dropdown"] {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
margin-top: var(--space-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
min-width: 160px;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
[data-slot="dropdown"] {
|
||||
form {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
width: 100%;
|
||||
[data-slot="item"] {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="item"],
|
||||
[data-slot="create-item"] {
|
||||
width: 100%;
|
||||
padding: var(--space-2-5) var(--space-3);
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--color-danger);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
import { Show, onCleanup, createEffect } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { action, redirect } from "@solidjs/router"
|
||||
import { getRequestEvent } from "solid-js/web"
|
||||
import { useAuthSession } from "~/context/auth.session"
|
||||
import { IconChevron } from "~/component/icon"
|
||||
import { Dropdown } from "~/component/dropdown"
|
||||
import "./user-menu.css"
|
||||
|
||||
const logout = action(async () => {
|
||||
|
|
@ -23,41 +21,15 @@ const logout = action(async () => {
|
|||
})
|
||||
|
||||
export function UserMenu(props: { email: string | null | undefined }) {
|
||||
const [store, setStore] = createStore({
|
||||
showDropdown: false,
|
||||
})
|
||||
let dropdownRef: HTMLDivElement | undefined
|
||||
|
||||
createEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef && !dropdownRef.contains(event.target as Node)) {
|
||||
setStore("showDropdown", false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", handleClickOutside)
|
||||
|
||||
onCleanup(() => document.removeEventListener("click", handleClickOutside))
|
||||
})
|
||||
|
||||
return (
|
||||
<div data-component="user-menu">
|
||||
<div ref={dropdownRef}>
|
||||
<button data-slot="trigger" type="button" onClick={() => setStore("showDropdown", !store.showDropdown)}>
|
||||
<span>{props.email}</span>
|
||||
<IconChevron data-slot="chevron" />
|
||||
</button>
|
||||
|
||||
<Show when={store.showDropdown}>
|
||||
<div data-slot="dropdown">
|
||||
<form action={logout} method="post">
|
||||
<button type="submit" formaction={logout} data-slot="item">
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<Dropdown trigger={props.email ?? ""} align="right">
|
||||
<form action={logout} method="post">
|
||||
<button type="submit" formaction={logout} data-slot="item">
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +1,23 @@
|
|||
[data-component="workspace-picker"] {
|
||||
position: relative;
|
||||
|
||||
[data-slot="trigger"] {
|
||||
/* Override blue accent colors with neutral colors for dropdown trigger */
|
||||
--color-accent: var(--color-border);
|
||||
--color-accent-hover: var(--color-border);
|
||||
--color-accent-active: var(--color-border);
|
||||
--color-primary: var(--color-border);
|
||||
--color-primary-hover: var(--color-border);
|
||||
--color-primary-active: var(--color-border);
|
||||
--color-primary-alpha-20: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: none;
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: transparent;
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surface-hover);
|
||||
[data-component="dropdown"] {
|
||||
[data-slot="trigger"] {
|
||||
/* Override blue accent colors with neutral colors for dropdown trigger */
|
||||
--color-accent: var(--color-border);
|
||||
--color-accent-hover: var(--color-border);
|
||||
--color-accent-active: var(--color-border);
|
||||
--color-primary: var(--color-border);
|
||||
--color-primary-hover: var(--color-border);
|
||||
--color-primary-active: var(--color-border);
|
||||
--color-primary-alpha-20: transparent;
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
[data-slot="dropdown"] {
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="chevron"] {
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
[data-slot="dropdown"] {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
margin-top: var(--space-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
min-width: 200px;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="item"],
|
||||
[data-slot="create-item"] {
|
||||
width: 100%;
|
||||
padding: var(--space-2-5) var(--space-3);
|
||||
|
|
@ -70,6 +27,12 @@
|
|||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-surface);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="create-form"] {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { query, useParams, action, createAsync, redirect, useSubmission } from "@solidjs/router"
|
||||
import { For, Show, createEffect, onCleanup } from "solid-js"
|
||||
import { For, Show, createEffect } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||
|
|
@ -7,7 +7,7 @@ import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/ind
|
|||
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
|
||||
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||
import { Workspace } from "@opencode-ai/console-core/workspace.js"
|
||||
import { IconChevron } from "~/component/icon"
|
||||
import { Dropdown, DropdownItem } from "~/component/dropdown"
|
||||
import { Modal } from "~/component/modal"
|
||||
import "./workspace-picker.css"
|
||||
|
||||
|
|
@ -45,9 +45,7 @@ export function WorkspacePicker() {
|
|||
const submission = useSubmission(createWorkspace)
|
||||
const [store, setStore] = createStore({
|
||||
showForm: false,
|
||||
showDropdown: false,
|
||||
})
|
||||
let dropdownRef: HTMLDivElement | undefined
|
||||
let inputRef: HTMLInputElement | undefined
|
||||
|
||||
const currentWorkspace = () => {
|
||||
|
|
@ -56,7 +54,7 @@ export function WorkspacePicker() {
|
|||
}
|
||||
|
||||
const handleWorkspaceNew = () => {
|
||||
setStore({ showForm: true, showDropdown: false })
|
||||
setStore("showForm", true)
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
|
|
@ -66,11 +64,7 @@ export function WorkspacePicker() {
|
|||
})
|
||||
|
||||
const handleSelectWorkspace = (workspaceID: string) => {
|
||||
if (workspaceID === params.id) {
|
||||
setStore("showDropdown", false)
|
||||
return
|
||||
}
|
||||
|
||||
if (workspaceID === params.id) return
|
||||
window.location.href = `/workspace/${workspaceID}`
|
||||
}
|
||||
|
||||
|
|
@ -78,48 +72,22 @@ export function WorkspacePicker() {
|
|||
createEffect(() => {
|
||||
params.id
|
||||
setStore("showForm", false)
|
||||
setStore("showDropdown", false)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef && !dropdownRef.contains(event.target as Node)) {
|
||||
setStore("showDropdown", false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", handleClickOutside)
|
||||
onCleanup(() => document.removeEventListener("click", handleClickOutside))
|
||||
})
|
||||
|
||||
return (
|
||||
<div data-component="workspace-picker">
|
||||
<div ref={dropdownRef}>
|
||||
<button data-slot="trigger" type="button" onClick={() => setStore("showDropdown", !store.showDropdown)}>
|
||||
<span>{currentWorkspace()}</span>
|
||||
<IconChevron data-slot="chevron" />
|
||||
<Dropdown trigger={currentWorkspace()} align="left">
|
||||
<For each={workspaces()}>
|
||||
{(workspace) => (
|
||||
<DropdownItem selected={workspace.id === params.id} onClick={() => handleSelectWorkspace(workspace.id)}>
|
||||
{workspace.name || workspace.slug}
|
||||
</DropdownItem>
|
||||
)}
|
||||
</For>
|
||||
<button data-slot="create-item" type="button" onClick={() => handleWorkspaceNew()}>
|
||||
+ Create New Workspace
|
||||
</button>
|
||||
|
||||
<Show when={store.showDropdown}>
|
||||
<div data-slot="dropdown">
|
||||
<For each={workspaces()}>
|
||||
{(workspace) => (
|
||||
<button
|
||||
data-slot="item"
|
||||
data-selected={workspace.id === params.id}
|
||||
type="button"
|
||||
onClick={() => handleSelectWorkspace(workspace.id)}
|
||||
>
|
||||
{workspace.name || workspace.slug}
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
<button data-slot="create-item" type="button" onClick={() => handleWorkspaceNew()}>
|
||||
+ Create New Workspace
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
<Modal open={store.showForm} onClose={() => setStore("showForm", false)} title="Create New Workspace">
|
||||
<form data-slot="create-form" action={createWorkspace} method="post">
|
||||
|
|
|
|||
|
|
@ -30,31 +30,33 @@ export default function () {
|
|||
</a>
|
||||
.
|
||||
</span>
|
||||
<span data-slot="billing-info">
|
||||
<Show
|
||||
when={billingInfo()?.reload}
|
||||
fallback={
|
||||
<button
|
||||
data-color="primary"
|
||||
data-size="sm"
|
||||
disabled={createCheckoutUrlSubmission.pending}
|
||||
onClick={async () => {
|
||||
const baseUrl = window.location.href
|
||||
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
|
||||
if (checkoutUrl) {
|
||||
window.location.href = checkoutUrl
|
||||
}
|
||||
}}
|
||||
>
|
||||
{createCheckoutUrlSubmission.pending ? "Loading..." : "Enable billing"}
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<span data-slot="balance">
|
||||
Current balance: <b>${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()}</b>
|
||||
</span>
|
||||
</Show>
|
||||
</span>
|
||||
<Show when={userInfo()?.isAdmin}>
|
||||
<span data-slot="billing-info">
|
||||
<Show
|
||||
when={billingInfo()?.reload}
|
||||
fallback={
|
||||
<button
|
||||
data-color="primary"
|
||||
data-size="sm"
|
||||
disabled={createCheckoutUrlSubmission.pending}
|
||||
onClick={async () => {
|
||||
const baseUrl = window.location.href
|
||||
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
|
||||
if (checkoutUrl) {
|
||||
window.location.href = checkoutUrl
|
||||
}
|
||||
}}
|
||||
>
|
||||
{createCheckoutUrlSubmission.pending ? "Loading..." : "Enable billing"}
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<span data-slot="balance">
|
||||
Current balance: <b>${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()}</b>
|
||||
</span>
|
||||
</Show>
|
||||
</span>
|
||||
</Show>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
|
|
|
|||
|
|
@ -92,97 +92,12 @@
|
|||
line-height: 1.4;
|
||||
margin-top: calc(var(--space-1) * -1);
|
||||
}
|
||||
|
||||
[data-slot="role-selector"] {
|
||||
position: relative;
|
||||
|
||||
[data-slot="trigger"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
width: 100%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 3px var(--color-accent-alpha);
|
||||
}
|
||||
|
||||
[data-slot="chevron"] {
|
||||
opacity: 0.6;
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="dropdown"] {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
margin-top: var(--space-1);
|
||||
padding: var(--space-1);
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
min-width: 280px;
|
||||
width: max-content;
|
||||
|
||||
[data-slot="item"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
border-radius: var(--border-radius-sm);
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-surface);
|
||||
}
|
||||
|
||||
&[data-selected="true"] {
|
||||
background-color: var(--color-accent-alpha);
|
||||
}
|
||||
|
||||
div {
|
||||
strong {
|
||||
display: block;
|
||||
color: var(--color-text);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="members-table"] {
|
||||
overflow-x: auto;
|
||||
padding-bottom: 200px;
|
||||
margin-bottom: -200px;
|
||||
}
|
||||
|
||||
[data-slot="members-table-element"] {
|
||||
|
|
@ -224,125 +139,7 @@
|
|||
|
||||
&[data-slot="member-role"] {
|
||||
font-family: var(--font-mono);
|
||||
|
||||
[data-slot="role-selector"] {
|
||||
position: relative;
|
||||
|
||||
[data-slot="trigger"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
width: 100%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
font-family: var(--font-sans);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 3px var(--color-accent-alpha);
|
||||
}
|
||||
|
||||
[data-slot="chevron"] {
|
||||
opacity: 0.6;
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="dropdown"] {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
margin-top: var(--space-1);
|
||||
padding: var(--space-1);
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
min-width: 280px;
|
||||
width: max-content;
|
||||
|
||||
[data-slot="item"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
border-radius: var(--border-radius-sm);
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-surface);
|
||||
}
|
||||
|
||||
&[data-selected="true"] {
|
||||
background-color: var(--color-accent-alpha);
|
||||
}
|
||||
|
||||
div {
|
||||
strong {
|
||||
display: block;
|
||||
color: var(--color-text);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 400;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
border-radius: var(--border-radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
text-transform: none;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: var(--color-bg-surface);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
span {
|
||||
font-family: inherit;
|
||||
}
|
||||
}
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
&[data-slot="member-usage"] {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router"
|
||||
import { createEffect, createSignal, For, Show, onCleanup } from "solid-js"
|
||||
import { createEffect, For, Show } from "solid-js"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { createStore } from "solid-js/store"
|
||||
import styles from "./member-section.module.css"
|
||||
import { UserRole } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||
import { User } from "@opencode-ai/console-core/user.js"
|
||||
import { IconChevron } from "~/component/icon"
|
||||
import { RoleDropdown } from "./role-dropdown"
|
||||
|
||||
const listMembers = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
|
|
@ -92,29 +92,15 @@ function MemberRow(props: { member: any; workspaceID: string; actorID: string; a
|
|||
const [store, setStore] = createStore({
|
||||
editing: false,
|
||||
selectedRole: props.member.role as (typeof UserRole)[number],
|
||||
showRoleDropdown: false,
|
||||
limit: "",
|
||||
})
|
||||
|
||||
let roleDropdownRef: HTMLDivElement | undefined
|
||||
|
||||
createEffect(() => {
|
||||
if (!submission.pending && submission.result && !submission.result.error) {
|
||||
setStore("editing", false)
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (roleDropdownRef && !roleDropdownRef.contains(event.target as Node)) {
|
||||
setStore("showRoleDropdown", false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", handleClickOutside)
|
||||
onCleanup(() => document.removeEventListener("click", handleClickOutside))
|
||||
})
|
||||
|
||||
function show() {
|
||||
while (true) {
|
||||
submission.clear()
|
||||
|
|
@ -127,7 +113,6 @@ function MemberRow(props: { member: any; workspaceID: string; actorID: string; a
|
|||
|
||||
function hide() {
|
||||
setStore("editing", false)
|
||||
setStore("showRoleDropdown", false)
|
||||
}
|
||||
|
||||
function getUsageDisplay() {
|
||||
|
|
@ -145,17 +130,11 @@ function MemberRow(props: { member: any; workspaceID: string; actorID: string; a
|
|||
month: "long",
|
||||
timeZone: "UTC",
|
||||
})
|
||||
const usage = current === lastUsed ? (props.member.monthlyUsage ?? 0) : 0
|
||||
return (usage / 100000000).toFixed(2)
|
||||
return current === lastUsed ? (props.member.monthlyUsage ?? 0) : 0
|
||||
})()
|
||||
|
||||
const limit = props.member.monthlyLimit ? `$${props.member.monthlyLimit}` : "no limit"
|
||||
return `$${currentUsage} / ${limit}`
|
||||
}
|
||||
|
||||
const roleLabels = {
|
||||
admin: { title: "Admin", description: "Can manage models, members, and billing" },
|
||||
member: { title: "Member", description: "Can only generate API keys for themselves" },
|
||||
return `$${(currentUsage / 100000000).toFixed(2)} / ${limit}`
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -163,48 +142,11 @@ function MemberRow(props: { member: any; workspaceID: string; actorID: string; a
|
|||
<td data-slot="member-email">{props.member.accountEmail ?? props.member.email}</td>
|
||||
<td data-slot="member-role">
|
||||
<Show when={store.editing && !isCurrentUser()} fallback={<span>{props.member.role}</span>}>
|
||||
<div data-slot="role-selector" ref={roleDropdownRef}>
|
||||
<button
|
||||
data-slot="trigger"
|
||||
type="button"
|
||||
onClick={() => setStore("showRoleDropdown", !store.showRoleDropdown)}
|
||||
>
|
||||
<span>{roleLabels[store.selectedRole].title}</span>
|
||||
<IconChevron data-slot="chevron" />
|
||||
</button>
|
||||
<Show when={store.showRoleDropdown}>
|
||||
<div data-slot="dropdown">
|
||||
<button
|
||||
data-slot="item"
|
||||
data-selected={store.selectedRole === "admin"}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setStore("selectedRole", "admin")
|
||||
setStore("showRoleDropdown", false)
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<strong>Admin</strong>
|
||||
<p>{roleLabels.admin.description}</p>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
data-slot="item"
|
||||
data-selected={store.selectedRole === "member"}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setStore("selectedRole", "member")
|
||||
setStore("showRoleDropdown", false)
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<strong>{roleLabels.member.title}</strong>
|
||||
<p>{roleLabels.member.description}</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<RoleDropdown
|
||||
value={store.selectedRole}
|
||||
options={roleOptions}
|
||||
onChange={(value) => setStore("selectedRole", value as (typeof UserRole)[number])}
|
||||
/>
|
||||
</Show>
|
||||
</td>
|
||||
<td data-slot="member-usage">
|
||||
|
|
@ -260,6 +202,11 @@ function MemberRow(props: { member: any; workspaceID: string; actorID: string; a
|
|||
)
|
||||
}
|
||||
|
||||
const roleOptions = [
|
||||
{ value: "admin", description: "Can manage models, members, and billing" },
|
||||
{ value: "member", description: "Can only generate API keys for themselves" },
|
||||
]
|
||||
|
||||
export function MemberSection() {
|
||||
const params = useParams()
|
||||
const data = createAsync(() => listMembers(params.id))
|
||||
|
|
@ -267,12 +214,10 @@ export function MemberSection() {
|
|||
const [store, setStore] = createStore({
|
||||
show: false,
|
||||
selectedRole: "member" as (typeof UserRole)[number],
|
||||
showRoleDropdown: false,
|
||||
limit: "",
|
||||
})
|
||||
|
||||
let input: HTMLInputElement
|
||||
let roleDropdownRef: HTMLDivElement | undefined
|
||||
|
||||
createEffect(() => {
|
||||
if (!submission.pending && submission.result && !submission.result.error) {
|
||||
|
|
@ -280,17 +225,6 @@ export function MemberSection() {
|
|||
}
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (roleDropdownRef && !roleDropdownRef.contains(event.target as Node)) {
|
||||
setStore("showRoleDropdown", false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", handleClickOutside)
|
||||
onCleanup(() => document.removeEventListener("click", handleClickOutside))
|
||||
})
|
||||
|
||||
function show() {
|
||||
while (true) {
|
||||
submission.clear()
|
||||
|
|
@ -304,12 +238,6 @@ export function MemberSection() {
|
|||
|
||||
function hide() {
|
||||
setStore("show", false)
|
||||
setStore("showRoleDropdown", false)
|
||||
}
|
||||
|
||||
const roleLabels = {
|
||||
admin: { title: "Admin", description: "Can manage models, members, and billing" },
|
||||
member: { title: "Member", description: "Can only generate API keys for themselves" },
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -340,48 +268,11 @@ export function MemberSection() {
|
|||
</div>
|
||||
<div data-slot="input-field">
|
||||
<p>Role</p>
|
||||
<div data-slot="role-selector" ref={roleDropdownRef}>
|
||||
<button
|
||||
data-slot="trigger"
|
||||
type="button"
|
||||
onClick={() => setStore("showRoleDropdown", !store.showRoleDropdown)}
|
||||
>
|
||||
<span>{roleLabels[store.selectedRole].title}</span>
|
||||
<IconChevron data-slot="chevron" />
|
||||
</button>
|
||||
<Show when={store.showRoleDropdown}>
|
||||
<div data-slot="dropdown">
|
||||
<button
|
||||
data-slot="item"
|
||||
data-selected={store.selectedRole === "admin"}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setStore("selectedRole", "admin")
|
||||
setStore("showRoleDropdown", false)
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<strong>Admin</strong>
|
||||
<p>{roleLabels.admin.description}</p>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
data-slot="item"
|
||||
data-selected={store.selectedRole === "member"}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setStore("selectedRole", "member")
|
||||
setStore("showRoleDropdown", false)
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<strong>{roleLabels.member.title}</strong>
|
||||
<p>{roleLabels.member.description}</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<RoleDropdown
|
||||
value={store.selectedRole}
|
||||
options={roleOptions}
|
||||
onChange={(value) => setStore("selectedRole", value as (typeof UserRole)[number])}
|
||||
/>
|
||||
</div>
|
||||
<div data-slot="input-field">
|
||||
<p>Monthly spending limit</p>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
.role-dropdown {
|
||||
[data-slot="trigger"] {
|
||||
border: 1px solid var(--color-border);
|
||||
background-color: var(--color-bg);
|
||||
width: 100%;
|
||||
text-transform: capitalize;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--border-radius-sm);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.5;
|
||||
min-width: 0;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-accent);
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 3px var(--color-accent-alpha);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="chevron"] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
[data-slot="dropdown"] {
|
||||
padding: var(--space-1);
|
||||
min-width: 280px;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
[data-slot="role-item"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
border-radius: var(--border-radius-sm);
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-surface);
|
||||
}
|
||||
|
||||
&[data-selected="true"] {
|
||||
background-color: var(--color-accent-alpha);
|
||||
}
|
||||
|
||||
div {
|
||||
strong {
|
||||
display: block;
|
||||
color: var(--color-text);
|
||||
margin-bottom: var(--space-1);
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { createSignal } from "solid-js"
|
||||
import { Dropdown } from "~/component/dropdown"
|
||||
import "./role-dropdown.css"
|
||||
|
||||
interface RoleOption {
|
||||
value: string
|
||||
description: string
|
||||
}
|
||||
|
||||
interface RoleDropdownProps {
|
||||
value: string
|
||||
options: RoleOption[]
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
export function RoleDropdown(props: RoleDropdownProps) {
|
||||
const [open, setOpen] = createSignal(false)
|
||||
|
||||
const handleSelect = (value: string) => {
|
||||
props.onChange(value)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown trigger={props.value} open={open()} onOpenChange={setOpen} class="role-dropdown">
|
||||
<>
|
||||
{props.options.map((option) => (
|
||||
<button
|
||||
data-slot="role-item"
|
||||
data-selected={props.value === option.value}
|
||||
type="button"
|
||||
onClick={() => handleSelect(option.value)}
|
||||
>
|
||||
<div>
|
||||
<strong>{option.value}</strong>
|
||||
<p>{option.description}</p>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
|
@ -10,9 +10,10 @@ const getModelsInfo = query(async (workspaceID: string) => {
|
|||
"use server"
|
||||
return withActor(async () => {
|
||||
return {
|
||||
all: Object.keys(ZenModel.list())
|
||||
.filter((model) => !["claude-3-5-haiku", "glm-4.6", "qwen3-max"].includes(model))
|
||||
.sort(([a], [b]) => a.localeCompare(b)),
|
||||
all: Object.entries(ZenModel.list())
|
||||
.filter(([id, _model]) => !["claude-3-5-haiku", "qwen3-max"].includes(id))
|
||||
.sort(([_idA, modelA], [_idB, modelB]) => modelA.name.localeCompare(modelB.name))
|
||||
.map(([id, model]) => ({ id, name: model.name })),
|
||||
disabled: await Model.listDisabled(),
|
||||
}
|
||||
}, workspaceID)
|
||||
|
|
@ -62,14 +63,14 @@ export function ModelSection() {
|
|||
</thead>
|
||||
<tbody>
|
||||
<For each={modelsInfo()!.all}>
|
||||
{(modelId) => {
|
||||
const isEnabled = createMemo(() => !modelsInfo()!.disabled.includes(modelId))
|
||||
{({ id, name }) => {
|
||||
const isEnabled = createMemo(() => !modelsInfo()!.disabled.includes(id))
|
||||
return (
|
||||
<tr data-slot="model-row" data-disabled={!isEnabled()}>
|
||||
<td data-slot="model-name">{modelId}</td>
|
||||
<td data-slot="model-name">{name}</td>
|
||||
<td data-slot="model-toggle">
|
||||
<form action={updateModel} method="post">
|
||||
<input type="hidden" name="model" value={modelId} />
|
||||
<input type="hidden" name="model" value={id} />
|
||||
<input type="hidden" name="workspaceID" value={params.id} />
|
||||
<input type="hidden" name="enabled" value={isEnabled().toString()} />
|
||||
<label data-slot="model-toggle-label">
|
||||
|
|
|
|||
|
|
@ -28,7 +28,14 @@ export function NewUserSection() {
|
|||
const usageList = usage()
|
||||
return keysList?.length === 1 && (!usageList || usageList.length === 0)
|
||||
})
|
||||
const defaultKey = createMemo(() => keys()?.at(-1)?.key)
|
||||
const defaultKey = createMemo(() => {
|
||||
const key = keys()?.at(-1)?.key
|
||||
if (!key) return undefined
|
||||
return {
|
||||
actual: key,
|
||||
masked: key.slice(0, 8) + "*".repeat(key.length - 12) + key.slice(-4),
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Show when={isNew()}>
|
||||
|
|
@ -52,12 +59,12 @@ export function NewUserSection() {
|
|||
<Show when={defaultKey()}>
|
||||
<div data-slot="key-display">
|
||||
<div data-slot="key-container">
|
||||
<code data-slot="key-value">{defaultKey()}</code>
|
||||
<code data-slot="key-value">{defaultKey()?.masked}</code>
|
||||
<button
|
||||
data-color="primary"
|
||||
disabled={copiedKey()}
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(defaultKey() ?? "")
|
||||
await navigator.clipboard.writeText(defaultKey()?.actual ?? "")
|
||||
setCopiedKey(true)
|
||||
setTimeout(() => setCopiedKey(false), 2000)
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
"@types/bun": "1.3.0",
|
||||
"@types/node": "24.7.1",
|
||||
"drizzle-kit": "0.30.5",
|
||||
"mysql2": "3.14.4"
|
||||
"mysql2": "3.14.4",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export namespace Billing {
|
|||
export const stripe = () =>
|
||||
new Stripe(Resource.STRIPE_SECRET_KEY.value, {
|
||||
apiVersion: "2025-03-31.basil",
|
||||
httpClient: Stripe.createFetchHttpClient(),
|
||||
})
|
||||
|
||||
export const get = async () => {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export namespace ZenModel {
|
|||
})
|
||||
|
||||
export const ModelSchema = z.object({
|
||||
name: z.string(),
|
||||
cost: ModelCostSchema,
|
||||
cost200K: ModelCostSchema.optional(),
|
||||
allowAnonymous: z.boolean().optional(),
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ export namespace User {
|
|||
const { InviteEmail } = await import("@opencode-ai/console-mail/InviteEmail.jsx")
|
||||
await AWS.sendEmail({
|
||||
to: email,
|
||||
subject: `You've been invited to join the ${workspaceID} workspace on OpenCode Zen`,
|
||||
subject: `You've been invited to join the ${emailInfo.workspaceName} workspace on OpenCode Console`,
|
||||
body: render(
|
||||
// @ts-ignore
|
||||
InviteEmail({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export const InviteEmail = ({
|
|||
assetsUrl = LOCAL_ASSETS_URL,
|
||||
}: InviteEmailProps) => {
|
||||
const subject = `You've been invited to join the ${workspaceName} workspace on OpenCode Console`
|
||||
const messagePlain = `${inviter} invited you to join the ${workspaceName} workspace (${workspaceID}).`
|
||||
const messagePlain = `${inviter} invited you to join the ${workspaceName} workspace.`
|
||||
const url = `${CONSOLE_URL}workspace/${workspaceID}`
|
||||
return (
|
||||
<Html lang="en">
|
||||
|
|
@ -67,7 +67,7 @@ export const InviteEmail = ({
|
|||
<Link style={medium} href={url}>
|
||||
<B>{workspaceName}</B>
|
||||
</Link>{" "}
|
||||
workspace ({workspaceID}) in the{" "}
|
||||
workspace in the{" "}
|
||||
<Link style={medium} href={`${CONSOLE_URL}zen`}>
|
||||
OpenCode Console
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/console-scripts",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ if (!snapshot) {
|
|||
const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
|
||||
// arch
|
||||
/*
|
||||
const binaryPkgbuild = [
|
||||
"# Maintainer: dax",
|
||||
"# Maintainer: adam",
|
||||
|
|
@ -132,23 +131,22 @@ if (!snapshot) {
|
|||
["opencode-bin", binaryPkgbuild],
|
||||
["opencode", sourcePkgbuild],
|
||||
]) {
|
||||
await $`rm -rf ./dist/aur-${pkg}`
|
||||
while (true) {
|
||||
for (let i = 0; i < 30; i++) {
|
||||
try {
|
||||
await $`rm -rf ./dist/aur-${pkg}`
|
||||
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
|
||||
await $`cd ./dist/aur-${pkg} && git checkout master`
|
||||
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild)
|
||||
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
|
||||
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
|
||||
await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${version}"`
|
||||
await $`cd ./dist/aur-${pkg} && git push`
|
||||
break
|
||||
} catch (e) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
await $`cd ./dist/aur-${pkg} && git checkout master`
|
||||
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild)
|
||||
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
|
||||
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
|
||||
await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${version}"`
|
||||
await $`cd ./dist/aur-${pkg} && git push`
|
||||
}
|
||||
*/
|
||||
|
||||
// Homebrew formula
|
||||
const homebrewFormula = [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import z from "zod/v4"
|
||||
import { exec } from "child_process"
|
||||
|
||||
import { spawn } from "child_process"
|
||||
import { Tool } from "./tool"
|
||||
import DESCRIPTION from "./bash.txt"
|
||||
import { lazy } from "../util/lazy"
|
||||
|
|
@ -148,9 +147,11 @@ export const BashTool = Tool.define("bash", {
|
|||
}
|
||||
*/
|
||||
|
||||
const process = exec(params.command, {
|
||||
const process = spawn(params.command, {
|
||||
shell: true,
|
||||
cwd: Instance.directory,
|
||||
signal: ctx.abort,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
timeout,
|
||||
})
|
||||
process.stdin?.end()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.0",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue