mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-04 09:16:20 -04:00
Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 03f0b6dd7a | |||
| f7f3ed1fa9 | |||
| be8116e2ea | |||
| f0ed1e38c9 | |||
| ac0f1dbbdd | |||
| 275a352e81 | |||
| 9f3bc0e352 | |||
| 6c1a1a77b7 | |||
| 2e21c62320 | |||
| 19c6fec4d1 | |||
| 4779d99a13 | |||
| 05e0759878 | |||
| 2330ec6dc3 | |||
| 75e5130cf8 | |||
| 87efd27459 | |||
| 62f080b0e4 | |||
| ae3990a557 | |||
| d7b5b431d6 | |||
| e2fbd098d2 | |||
| ef78fd8bae | |||
| 72ebaeb8f7 | |||
| 0dc62d5dad | |||
| d118782a10 | |||
| ff05647350 | |||
| 0e1c711c4e | |||
| bfb254dac6 | |||
| 92fe927785 | |||
| 2e25fe9d5d | |||
| 38c5f23f4a | |||
| 112c58abf5 | |||
| 0dce5173cc | |||
| 2c70c0b00f | |||
| 34024c2504 | |||
| 27e826eba6 | |||
| 89a4f1c1ae | |||
| c0c61b25ff | |||
| 0d1c6e0ca9 | |||
| 002db3abf4 | |||
| 416a919c6d | |||
| dbbcf0b8d0 | |||
| efac8cebb3 | |||
| 4f2baf1a72 | |||
| 48b2bde6e5 | |||
| 88314148e6 | |||
| 56452d886d | |||
| f3e64cfb19 | |||
| 8fcc80bc20 | |||
| 0beccc406e | |||
| b82ea693db | |||
| 4fd9a19fbb | |||
| e16487b804 | |||
| 5388192aac | |||
| 8010448ba1 | |||
| 66f3e69867 | |||
| ca599ab8fc | |||
| c3b3b133b0 | |||
| 300ec0e0af | |||
| 6632987827 | |||
| e555e893c4 | |||
| 81134cf61e | |||
| 37e4c1e619 | |||
| 02b5e7d72c | |||
| 7abc2a947e | |||
| 337a7e9646 | |||
| 62cc532ecc | |||
| d5a506d4ae | |||
| 9c5f94bd66 | |||
| 83390314d6 | |||
| 8b08e9cda5 | |||
| b1b1df824c | |||
| 7d1733c752 | |||
| cf05e6e02b | |||
| 7e49d0fb15 | |||
| c4f63824df | |||
| 4236744fb5 | |||
| 284c045795 | |||
| 2c53abd70c | |||
| b7a9cbfc68 | |||
| 46a35dfc1b | |||
| b7597c12dd | |||
| 6830590183 |
@@ -1,63 +0,0 @@
|
||||
name: Auto-label TUI Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
auto-label:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- name: Auto-label and assign issues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const title = issue.title;
|
||||
const description = issue.body || '';
|
||||
|
||||
// Check for "opencode web" keyword
|
||||
const webPattern = /(opencode web)/i;
|
||||
const isWebRelated = webPattern.test(title) || webPattern.test(description);
|
||||
|
||||
// Check for version patterns like v1.0.x or 1.0.x
|
||||
const versionPattern = /[v]?1\.0\./i;
|
||||
const isVersionRelated = versionPattern.test(title) || versionPattern.test(description);
|
||||
|
||||
// Check for "nix" keyword
|
||||
const nixPattern = /\bnix\b/i;
|
||||
const isNixRelated = nixPattern.test(title) || nixPattern.test(description);
|
||||
|
||||
const labels = [];
|
||||
|
||||
if (isWebRelated) {
|
||||
labels.push('web');
|
||||
|
||||
// Assign to adamdotdevin
|
||||
await github.rest.issues.addAssignees({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
assignees: ['adamdotdevin']
|
||||
});
|
||||
} else if (isVersionRelated) {
|
||||
// Only add opentui if NOT web-related
|
||||
labels.push('opentui');
|
||||
}
|
||||
|
||||
if (isNixRelated) {
|
||||
labels.push('nix');
|
||||
}
|
||||
|
||||
if (labels.length > 0) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: labels
|
||||
});
|
||||
}
|
||||
@@ -109,6 +109,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ needs.publish.outputs.tagName }}
|
||||
|
||||
- uses: apple-actions/import-codesign-certs@v2
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
@@ -196,3 +197,11 @@ jobs:
|
||||
releaseId: ${{ needs.publish.outputs.releaseId }}
|
||||
tagName: ${{ needs.publish.outputs.tagName }}
|
||||
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
|
||||
|
||||
publish-release:
|
||||
needs:
|
||||
- publish
|
||||
- publish-tauri
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- run: gh release edit ${{ steps.publish.outputs.tagName }} --draft=false
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Issue Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install opencode
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Triage issue
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
run: |
|
||||
opencode run --agent triage "The following issue was just opened, triage it:
|
||||
|
||||
Title: $ISSUE_TITLE
|
||||
|
||||
$ISSUE_BODY"
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
mode: primary
|
||||
hidden: true
|
||||
model: opencode/gpt-5-nano
|
||||
tools:
|
||||
"*": false
|
||||
"github-triage": true
|
||||
---
|
||||
|
||||
You are a triage agent responsible for triaging github issues.
|
||||
|
||||
Use your github-triage tool to triage issues.
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@octokit/rest": "^22.0.1",
|
||||
"@opencode-ai/plugin": "1.0.143",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="],
|
||||
|
||||
"@octokit/core": ["@octokit/core@7.0.6", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q=="],
|
||||
|
||||
"@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="],
|
||||
|
||||
"@octokit/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="],
|
||||
|
||||
"@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||
|
||||
"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@14.0.0", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw=="],
|
||||
|
||||
"@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="],
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@17.0.0", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw=="],
|
||||
|
||||
"@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="],
|
||||
|
||||
"@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="],
|
||||
|
||||
"@octokit/rest": ["@octokit/rest@22.0.1", "", { "dependencies": { "@octokit/core": "^7.0.6", "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^17.0.0" } }, "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw=="],
|
||||
|
||||
"@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
||||
|
||||
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.0.143", "", { "dependencies": { "@opencode-ai/sdk": "1.0.143", "zod": "4.1.8" } }, "sha512-yzaCmdazVJMDADJLbMM8KGp1X+Hd/HVyIXMlNt9qcvz/fcs/ET4EwHJsJaQi/9m/jLJ+plwBJAeIW08BMrECPg=="],
|
||||
|
||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.0.143", "", {}, "sha512-dtmkBfJ7IIAHzL6KCzAlwc9GybfJONVeCsF6ePYySpkuhslDbRkZBJYb5vqGd1H5zdsgjc6JjuvmOf0rPWUL6A=="],
|
||||
|
||||
"before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="],
|
||||
|
||||
"fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="],
|
||||
|
||||
"universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="],
|
||||
|
||||
"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
|
||||
}
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
declare module "*.txt" {
|
||||
const content: string
|
||||
export default content
|
||||
}
|
||||
@@ -11,4 +11,7 @@
|
||||
},
|
||||
},
|
||||
"mcp": {},
|
||||
"tools": {
|
||||
"github-triage": false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@octokit/rest": "^22.0.1",
|
||||
"@opencode-ai/plugin": "1.0.143"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Octokit } from "@octokit/rest"
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import DESCRIPTION from "./github-triage.txt"
|
||||
|
||||
function getIssueNumber(): number {
|
||||
const issue = parseInt(process.env.ISSUE_NUMBER ?? "", 10)
|
||||
if (!issue) throw new Error("ISSUE_NUMBER env var not set")
|
||||
return issue
|
||||
}
|
||||
|
||||
export default tool({
|
||||
description: DESCRIPTION,
|
||||
args: {
|
||||
assignee: tool.schema
|
||||
.enum(["thdxr", "adamdotdevin", "rekram1-node", "fwang", "jayair", "kommander"])
|
||||
.describe("The username of the assignee")
|
||||
.default("rekram1-node"),
|
||||
labels: tool.schema
|
||||
.array(tool.schema.enum(["nix", "opentui", "perf", "web", "zen", "docs"]))
|
||||
.describe("The labels(s) to add to the issue")
|
||||
.optional(),
|
||||
},
|
||||
async execute(args) {
|
||||
const issue = getIssueNumber()
|
||||
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
|
||||
const owner = "sst"
|
||||
const repo = "opencode"
|
||||
|
||||
const results: string[] = []
|
||||
|
||||
await octokit.rest.issues.addAssignees({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue,
|
||||
assignees: [args.assignee],
|
||||
})
|
||||
results.push(`Assigned @${args.assignee} to issue #${issue}`)
|
||||
|
||||
if (args.labels && args.labels.length > 0) {
|
||||
await octokit.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue,
|
||||
labels: args.labels,
|
||||
})
|
||||
results.push(`Added labels: ${args.labels.join(", ")}`)
|
||||
}
|
||||
|
||||
return results.join("\n")
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,80 @@
|
||||
Use this tool to assign and/or label a Github issue.
|
||||
|
||||
You can assign the following users:
|
||||
- thdxr
|
||||
- adamdotdevin
|
||||
- fwang
|
||||
- jayair
|
||||
- kommander
|
||||
- rekram1-node
|
||||
|
||||
|
||||
You can use the following labels:
|
||||
- nix
|
||||
- opentui
|
||||
- perf
|
||||
- web
|
||||
- zen
|
||||
- docs
|
||||
|
||||
Always try to assign an issue, if in doubt, assign rekram1-node to it.
|
||||
|
||||
## Breakdown of responsibilities:
|
||||
|
||||
### thdxr
|
||||
|
||||
Dax is responsible for managing core parts of the application, for large feature requests, api changes, or things that require significant changes to the codebase assign him.
|
||||
|
||||
This relates to OpenCode server primarily but has overlap with just about anything
|
||||
|
||||
### adamdotdevin
|
||||
|
||||
Adam is responsible for managing the Desktop/Web app. If there is an issue relating to the desktop app or `opencode web` command. Assign him.
|
||||
|
||||
|
||||
### fwang
|
||||
|
||||
Frank is responsible for managing Zen, if you see complaints about OpenCode Zen, maybe it's the dashboard, the model quality, billing issues, etc. Assign him to the issue.
|
||||
|
||||
### jayair
|
||||
|
||||
Jay is responsible for documentation. If there is an issue relating to documentation assign him.
|
||||
|
||||
### kommander
|
||||
|
||||
Sebastian is responsible for managing an OpenTUI (a library for building terminal user interfaces). OpenCode's TUI is built with OpenTUI. If there are issues about:
|
||||
- random characters on screen
|
||||
- keybinds not working on different terminals
|
||||
- general terminal stuff
|
||||
Then assign the issue to Him.
|
||||
|
||||
### rekram1-node
|
||||
|
||||
Assign Aiden to an issue as a catch all, if you can't assign anyone else. Most of the time this will be bugs/polish things.
|
||||
If no one else makes sense to assign, assign rekram1-node to it.
|
||||
|
||||
## Breakdown of Labels:
|
||||
|
||||
### nix
|
||||
|
||||
Any issue that mentions nix, or nixos should have a nix label
|
||||
|
||||
### opentui
|
||||
|
||||
Anything relating to the TUI itself should have an opentui label
|
||||
|
||||
### perf
|
||||
|
||||
Anything related to slow performance, high ram, high cpu usage, or any other performance related issue should have a perf label
|
||||
|
||||
### web
|
||||
|
||||
Anything related to `opencode web` or the desktop app should have a web label. Never add this label for anything terminal/tui related
|
||||
|
||||
### zen
|
||||
|
||||
Anything related to OpenCode Zen, billing, or model quality from Zen should have a zen label
|
||||
|
||||
### docs
|
||||
|
||||
Anything related to the documentation should have a docs label
|
||||
@@ -171,3 +171,4 @@
|
||||
| 2025-12-13 | 1,073,561 (+12,221) | 1,044,608 (+13,770) | 2,118,169 (+25,991) |
|
||||
| 2025-12-14 | 1,082,042 (+8,481) | 1,052,425 (+7,817) | 2,134,467 (+16,298) |
|
||||
| 2025-12-15 | 1,093,632 (+11,590) | 1,059,078 (+6,653) | 2,152,710 (+18,243) |
|
||||
| 2025-12-16 | 1,120,477 (+26,845) | 1,078,022 (+18,944) | 2,198,499 (+45,789) |
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -75,7 +75,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -99,7 +99,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -123,7 +123,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -170,11 +170,11 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solidjs/meta": "catalog:",
|
||||
"@solidjs/router": "catalog:",
|
||||
"@solidjs/start": "catalog:",
|
||||
@@ -199,10 +199,10 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@octokit/rest": "catalog:",
|
||||
"hono": "catalog:",
|
||||
"jose": "6.0.11",
|
||||
},
|
||||
@@ -215,7 +215,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -238,7 +238,7 @@
|
||||
"@hono/zod-validator": "catalog:",
|
||||
"@modelcontextprotocol/sdk": "1.15.1",
|
||||
"@octokit/graphql": "9.0.2",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@octokit/rest": "catalog:",
|
||||
"@openauthjs/openauth": "catalog:",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
@@ -248,7 +248,7 @@
|
||||
"@opentui/core": "0.0.0-20251211-4403a69a",
|
||||
"@opentui/solid": "0.0.0-20251211-4403a69a",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
@@ -307,7 +307,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -327,7 +327,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.88.1",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -338,7 +338,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -351,7 +351,7 @@
|
||||
},
|
||||
"packages/tauri": {
|
||||
"name": "@opencode-ai/tauri",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@opencode-ai/desktop": "workspace:*",
|
||||
"@tauri-apps/api": "^2",
|
||||
@@ -376,12 +376,12 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@shikijs/transformers": "3.9.2",
|
||||
"@solid-primitives/bounds": "0.1.3",
|
||||
"@solid-primitives/resize-observer": "2.1.3",
|
||||
@@ -411,7 +411,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -422,7 +422,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -470,8 +470,9 @@
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/precision-diffs": "0.6.1",
|
||||
"@pierre/diffs": "1.0.0-beta.3",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
|
||||
@@ -1286,7 +1287,7 @@
|
||||
|
||||
"@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
|
||||
|
||||
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.6.1", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-HXafRSOly6B0rRt6fuP0yy1MimHJMQ2NNnBGcIHhHwsgK4WWs+SBWRWt1usdgz0NIuSgXdIyQn8HY3F1jKyDBQ=="],
|
||||
"@pierre/diffs": ["@pierre/diffs@1.0.0-beta.3", "", { "dependencies": { "@shikijs/core": "3.19.0", "@shikijs/engine-javascript": "3.19.0", "@shikijs/transformers": "3.19.0", "diff": "8.0.2", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "3.19.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-W3dFWdFOBZ9OskGSOgN16aci8dsUyAavCxz3ZvbbVLTb2qRzMZ7H90qdfON13/N2l1HTyh84lkrCs1/sDvnRjQ=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
@@ -4112,11 +4113,13 @@
|
||||
|
||||
"@parcel/watcher/node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
|
||||
|
||||
"@pierre/precision-diffs/@shikijs/core": ["@shikijs/core@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg=="],
|
||||
"@pierre/diffs/@shikijs/core": ["@shikijs/core@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA=="],
|
||||
|
||||
"@pierre/precision-diffs/@shikijs/transformers": ["@shikijs/transformers@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/types": "3.15.0" } }, "sha512-Hmwip5ovvSkg+Kc41JTvSHHVfCYF+C8Cp1omb5AJj4Xvd+y9IXz2rKJwmFRGsuN0vpHxywcXJ1+Y4B9S7EG1/A=="],
|
||||
"@pierre/diffs/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-ZfWJNm2VMhKkQIKT9qXbs76RRcT0SF/CAvEz0+RkpUDAoDaCx0uFdCGzSRiD9gSlhm6AHkjdieOBJMaO2eC1rQ=="],
|
||||
|
||||
"@pierre/precision-diffs/shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
|
||||
"@pierre/diffs/@shikijs/transformers": ["@shikijs/transformers@3.19.0", "", { "dependencies": { "@shikijs/core": "3.19.0", "@shikijs/types": "3.19.0" } }, "sha512-e6vwrsyw+wx4OkcrDbL+FVCxwx8jgKiCoXzakVur++mIWVcgpzIi8vxf4/b4dVTYrV/nUx5RjinMf4tq8YV8Fw=="],
|
||||
|
||||
"@pierre/diffs/shiki": ["shiki@3.19.0", "", { "dependencies": { "@shikijs/core": "3.19.0", "@shikijs/engine-javascript": "3.19.0", "@shikijs/engine-oniguruma": "3.19.0", "@shikijs/langs": "3.19.0", "@shikijs/themes": "3.19.0", "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-77VJr3OR/VUZzPiStyRhADmO2jApMM0V2b1qf0RpfWya8Zr1PeZev5AEpPGAAKWdiYUtcZGBE4F5QvJml1PvWA=="],
|
||||
|
||||
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
||||
|
||||
@@ -4670,19 +4673,19 @@
|
||||
|
||||
"@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@pierre/precision-diffs/@shikijs/core/@shikijs/types": ["@shikijs/types@3.15.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw=="],
|
||||
"@pierre/diffs/@shikijs/core/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="],
|
||||
|
||||
"@pierre/precision-diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.15.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw=="],
|
||||
"@pierre/diffs/@shikijs/engine-javascript/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="],
|
||||
|
||||
"@pierre/precision-diffs/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg=="],
|
||||
"@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="],
|
||||
|
||||
"@pierre/precision-diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA=="],
|
||||
"@pierre/diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg=="],
|
||||
|
||||
"@pierre/precision-diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0" } }, "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A=="],
|
||||
"@pierre/diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0" } }, "sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg=="],
|
||||
|
||||
"@pierre/precision-diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0" } }, "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ=="],
|
||||
"@pierre/diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0" } }, "sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A=="],
|
||||
|
||||
"@pierre/precision-diffs/shiki/@shikijs/types": ["@shikijs/types@3.15.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw=="],
|
||||
"@pierre/diffs/shiki/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="],
|
||||
|
||||
"@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
|
||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1765644376,
|
||||
"narHash": "sha256-yqHBL2wYGwjGL2GUF2w3tofWl8qO9tZEuI4wSqbCrtE=",
|
||||
"lastModified": 1765772535,
|
||||
"narHash": "sha256-aq+dQoaPONOSjtFIBnAXseDm9TUhIbe215TPmkfMYww=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "23735a82a828372c4ef92c660864e82fbe2f5fbe",
|
||||
"rev": "09b8fda8959d761445f12b55f380d90375a1d6bb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
"@actions/core": "1.11.1",
|
||||
"@actions/github": "6.0.1",
|
||||
"@octokit/graphql": "9.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@octokit/rest": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,22 +240,23 @@ download_with_progress() {
|
||||
|
||||
download_and_install() {
|
||||
print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version"
|
||||
mkdir -p opencodetmp && cd opencodetmp
|
||||
local tmp_dir="${TMPDIR:-/tmp}/opencode_install_$$"
|
||||
mkdir -p "$tmp_dir"
|
||||
|
||||
if [[ "$os" == "windows" ]] || ! download_with_progress "$url" "$filename"; then
|
||||
# Fallback to standard curl on Windows or if custom progress fails
|
||||
curl -# -L -o "$filename" "$url"
|
||||
if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
|
||||
# Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails
|
||||
curl -# -L -o "$tmp_dir/$filename" "$url"
|
||||
fi
|
||||
|
||||
if [ "$os" = "linux" ]; then
|
||||
tar -xzf "$filename"
|
||||
tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
|
||||
else
|
||||
unzip -q "$filename"
|
||||
unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
|
||||
fi
|
||||
|
||||
mv opencode "$INSTALL_DIR"
|
||||
mv "$tmp_dir/opencode" "$INSTALL_DIR"
|
||||
chmod 755 "${INSTALL_DIR}/opencode"
|
||||
cd .. && rm -rf opencodetmp
|
||||
rm -rf "$tmp_dir"
|
||||
}
|
||||
|
||||
check_version
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"nodeModules": "sha256-lgPsYtNJT7a+mDk5cTiEJLlBnTMTjxZCl8bw5WxcuaM="
|
||||
"nodeModules": "sha256-PyoVOza+3WnwZbtpPF6uSN1zkyLsSG2VsgBfIMvIFAs="
|
||||
}
|
||||
|
||||
+2
-1
@@ -21,6 +21,7 @@
|
||||
],
|
||||
"catalog": {
|
||||
"@types/bun": "1.3.4",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"ulid": "3.0.1",
|
||||
"@kobalte/core": "0.13.11",
|
||||
@@ -30,7 +31,7 @@
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/precision-diffs": "0.6.1",
|
||||
"@pierre/diffs": "1.0.0-beta.3",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"diff": "8.0.2",
|
||||
"ai": "5.0.97",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -119,8 +119,8 @@ export function Header(props: { zen?: boolean; hideGetStarted?: boolean }) {
|
||||
<section data-component="top">
|
||||
<div onContextMenu={handleLogoContextMenu}>
|
||||
<A href="/">
|
||||
<img data-slot="logo light" src={logoLight} alt="opencode logo light" />
|
||||
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
|
||||
<img data-slot="logo light" src={logoLight} alt="opencode logo light" width="189" height="34" />
|
||||
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" width="189" height="34" />
|
||||
</A>
|
||||
</div>
|
||||
|
||||
@@ -169,6 +169,25 @@ export function Header(props: { zen?: boolean; hideGetStarted?: boolean }) {
|
||||
</Match>
|
||||
</Switch>
|
||||
</li>
|
||||
<Show when={!props.hideGetStarted}>
|
||||
{" "}
|
||||
<li>
|
||||
{" "}
|
||||
<A href="/download" data-slot="cta-button">
|
||||
{" "}
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
{" "}
|
||||
<path
|
||||
d="M12.1875 9.75L9.00001 12.9375L5.8125 9.75M9.00001 2.0625L9 12.375M14.4375 15.9375H3.5625"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="square"
|
||||
/>{" "}
|
||||
</svg>{" "}
|
||||
Free{" "}
|
||||
</A>{" "}
|
||||
</li>
|
||||
</Show>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav data-component="nav-mobile">
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// @refresh reload
|
||||
import { createHandler, StartServer } from "@solidjs/start/server"
|
||||
|
||||
const criticalCSS = `[data-component="top"]{min-height:80px;display:flex;align-items:center}`
|
||||
|
||||
export default createHandler(
|
||||
() => (
|
||||
<StartServer
|
||||
@@ -11,6 +13,7 @@ export default createHandler(
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta property="og:image" content="/social-share.png" />
|
||||
<meta property="twitter:image" content="/social-share.png" />
|
||||
<style>{criticalCSS}</style>
|
||||
{assets}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -206,6 +206,7 @@ body {
|
||||
[data-component="top"] {
|
||||
padding: 24px var(--padding);
|
||||
height: 80px;
|
||||
min-height: 80px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
display: flex;
|
||||
|
||||
@@ -52,6 +52,21 @@ export default function Home() {
|
||||
|
||||
<div data-component="content">
|
||||
<section data-component="hero">
|
||||
<div data-component="desktop-app-banner">
|
||||
<span data-slot="badge">New</span>
|
||||
<div data-slot="content">
|
||||
<span data-slot="text">
|
||||
Desktop app available in beta<span data-slot="platforms"> on macOS, Windows, and Linux</span>.
|
||||
</span>
|
||||
<a href="/download" data-slot="link">
|
||||
Download now
|
||||
</a>
|
||||
<a href="/download" data-slot="link-mobile">
|
||||
Download the desktop beta now
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-slot="hero-copy">
|
||||
{/*<a data-slot="releases"*/}
|
||||
{/* href={release()?.url ?? `${config.github.repoUrl}/releases`}*/}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -36,42 +36,46 @@ const url =
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<MarkedProvider>
|
||||
<DiffComponentProvider component={Diff}>
|
||||
<GlobalSDKProvider url={url}>
|
||||
<GlobalSyncProvider>
|
||||
<LayoutProvider>
|
||||
<DialogProvider>
|
||||
<CommandProvider>
|
||||
<NotificationProvider>
|
||||
<MetaProvider>
|
||||
<Font />
|
||||
<Router root={Layout}>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/:dir" component={DirectoryLayout}>
|
||||
<Route path="/" component={() => <Navigate href="session" />} />
|
||||
<Route
|
||||
path="/session/:id?"
|
||||
component={(p) => (
|
||||
<Show when={p.params.id || true} keyed>
|
||||
<TerminalProvider>
|
||||
<PromptProvider>
|
||||
<Session />
|
||||
</PromptProvider>
|
||||
</TerminalProvider>
|
||||
</Show>
|
||||
)}
|
||||
/>
|
||||
</Route>
|
||||
</Router>
|
||||
</MetaProvider>
|
||||
</NotificationProvider>
|
||||
</CommandProvider>
|
||||
</DialogProvider>
|
||||
</LayoutProvider>
|
||||
</GlobalSyncProvider>
|
||||
</GlobalSDKProvider>
|
||||
</DiffComponentProvider>
|
||||
</MarkedProvider>
|
||||
<DialogProvider>
|
||||
<MarkedProvider>
|
||||
<DiffComponentProvider component={Diff}>
|
||||
<GlobalSDKProvider url={url}>
|
||||
<GlobalSyncProvider>
|
||||
<LayoutProvider>
|
||||
<NotificationProvider>
|
||||
<MetaProvider>
|
||||
<Font />
|
||||
<Router
|
||||
root={(props) => (
|
||||
<CommandProvider>
|
||||
<Layout>{props.children}</Layout>
|
||||
</CommandProvider>
|
||||
)}
|
||||
>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/:dir" component={DirectoryLayout}>
|
||||
<Route path="/" component={() => <Navigate href="session" />} />
|
||||
<Route
|
||||
path="/session/:id?"
|
||||
component={(p) => (
|
||||
<Show when={p.params.id || true} keyed>
|
||||
<TerminalProvider>
|
||||
<PromptProvider>
|
||||
<Session />
|
||||
</PromptProvider>
|
||||
</TerminalProvider>
|
||||
</Show>
|
||||
)}
|
||||
/>
|
||||
</Route>
|
||||
</Router>
|
||||
</MetaProvider>
|
||||
</NotificationProvider>
|
||||
</LayoutProvider>
|
||||
</GlobalSyncProvider>
|
||||
</GlobalSDKProvider>
|
||||
</DiffComponentProvider>
|
||||
</MarkedProvider>
|
||||
</DialogProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -108,20 +108,18 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
|
||||
async function complete() {
|
||||
await globalSDK.client.global.dispose()
|
||||
setTimeout(() => {
|
||||
showToast({
|
||||
variant: "success",
|
||||
icon: "circle-check",
|
||||
title: `${provider().name} connected`,
|
||||
description: `${provider().name} models are now available to use.`,
|
||||
})
|
||||
dialog.replace(() => <DialogSelectModel provider={props.provider} />)
|
||||
}, 1000)
|
||||
dialog.close()
|
||||
showToast({
|
||||
variant: "success",
|
||||
icon: "circle-check",
|
||||
title: `${provider().name} connected`,
|
||||
description: `${provider().name} models are now available to use.`,
|
||||
})
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (methods().length === 1) {
|
||||
dialog.replace(() => <DialogSelectProvider />)
|
||||
dialog.show(() => <DialogSelectProvider />)
|
||||
return
|
||||
}
|
||||
if (store.authorization) {
|
||||
@@ -133,7 +131,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
setStore("methodIndex", undefined)
|
||||
return
|
||||
}
|
||||
dialog.replace(() => <DialogSelectProvider />)
|
||||
dialog.show(() => <DialogSelectProvider />)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -352,7 +350,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
})
|
||||
if (result.error) {
|
||||
// TODO: show error
|
||||
dialog.clear()
|
||||
dialog.close()
|
||||
return
|
||||
}
|
||||
await complete()
|
||||
|
||||
@@ -27,7 +27,7 @@ export function DialogSelectFile() {
|
||||
if (path) {
|
||||
tabs().open("file://" + path)
|
||||
}
|
||||
dialog.clear()
|
||||
dialog.close()
|
||||
}}
|
||||
>
|
||||
{(i) => (
|
||||
|
||||
@@ -42,7 +42,7 @@ export const DialogSelectModelUnpaid: Component = () => {
|
||||
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
|
||||
recent: true,
|
||||
})
|
||||
dialog.clear()
|
||||
dialog.close()
|
||||
}}
|
||||
>
|
||||
{(i) => (
|
||||
@@ -75,7 +75,7 @@ export const DialogSelectModelUnpaid: Component = () => {
|
||||
}}
|
||||
onSelect={(x) => {
|
||||
if (!x) return
|
||||
dialog.replace(() => <DialogConnectProvider provider={x.id} />)
|
||||
dialog.show(() => <DialogConnectProvider provider={x.id} />)
|
||||
}}
|
||||
>
|
||||
{(i) => (
|
||||
@@ -105,7 +105,7 @@ export const DialogSelectModelUnpaid: Component = () => {
|
||||
class="w-full justify-start px-[11px] py-3.5 gap-4.5 text-14-medium"
|
||||
icon="dot-grid"
|
||||
onClick={() => {
|
||||
dialog.replace(() => <DialogSelectProvider />)
|
||||
dialog.show(() => <DialogSelectProvider />)
|
||||
}}
|
||||
>
|
||||
View all providers
|
||||
|
||||
@@ -28,7 +28,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
|
||||
class="h-7 -my-1 text-14-medium"
|
||||
icon="plus-small"
|
||||
tabIndex={-1}
|
||||
onClick={() => dialog.replace(() => <DialogSelectProvider />)}
|
||||
onClick={() => dialog.show(() => <DialogSelectProvider />)}
|
||||
>
|
||||
Connect provider
|
||||
</Button>
|
||||
@@ -57,7 +57,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
|
||||
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
|
||||
recent: true,
|
||||
})
|
||||
dialog.clear()
|
||||
dialog.close()
|
||||
}}
|
||||
>
|
||||
{(i) => (
|
||||
@@ -75,7 +75,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="ml-3 mt-5 mb-6 text-text-base self-start"
|
||||
onClick={() => dialog.replace(() => <DialogManageModels />)}
|
||||
onClick={() => dialog.show(() => <DialogManageModels />)}
|
||||
>
|
||||
Manage models
|
||||
</Button>
|
||||
|
||||
@@ -34,7 +34,7 @@ export const DialogSelectProvider: Component = () => {
|
||||
}}
|
||||
onSelect={(x) => {
|
||||
if (!x) return
|
||||
dialog.replace(() => <DialogConnectProvider provider={x.id} />)
|
||||
dialog.show(() => <DialogConnectProvider provider={x.id} />)
|
||||
}}
|
||||
>
|
||||
{(i) => (
|
||||
|
||||
@@ -79,6 +79,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const providers = useProviders()
|
||||
const command = useCommand()
|
||||
let editorRef!: HTMLDivElement
|
||||
let fileInputRef!: HTMLInputElement
|
||||
|
||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||
const tabs = createMemo(() => layout.tabs(sessionKey()))
|
||||
@@ -791,7 +792,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<Show when={store.dragging}>
|
||||
<div class="absolute inset-0 z-10 flex items-center justify-center bg-surface-raised-stronger-non-alpha/90 pointer-events-none">
|
||||
<div class="flex flex-col items-center gap-2 text-text-weak">
|
||||
<Icon name="plus" class="size-8" />
|
||||
<Icon name="photo" class="size-8" />
|
||||
<span class="text-14-regular">Drop images or PDFs here</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -863,7 +864,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
as="div"
|
||||
variant="ghost"
|
||||
onClick={() =>
|
||||
dialog.push(() => (providers.paid().length > 0 ? <DialogSelectModel /> : <DialogSelectModelUnpaid />))
|
||||
dialog.show(() => (providers.paid().length > 0 ? <DialogSelectModel /> : <DialogSelectModelUnpaid />))
|
||||
}
|
||||
>
|
||||
{local.model.current()?.name ?? "Select model"}
|
||||
@@ -871,34 +872,56 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<Icon name="chevron-down" size="small" />
|
||||
</Button>
|
||||
</div>
|
||||
<Tooltip
|
||||
placement="top"
|
||||
inactive={!prompt.dirty() && !working()}
|
||||
value={
|
||||
<Switch>
|
||||
<Match when={working()}>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>Stop</span>
|
||||
<span class="text-icon-base text-12-medium text-[10px]!">ESC</span>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>Send</span>
|
||||
<Icon name="enter" size="small" class="text-icon-base" />
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
type="submit"
|
||||
disabled={!prompt.dirty() && store.imageAttachments.length === 0 && !working()}
|
||||
icon={working() ? "stop" : "arrow-up"}
|
||||
variant="primary"
|
||||
class="h-10 w-8 absolute right-2 bottom-2"
|
||||
<div class="flex items-center gap-1 absolute right-2 bottom-2">
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept={ACCEPTED_IMAGE_TYPES.join(",")}
|
||||
class="hidden"
|
||||
onChange={(e) => {
|
||||
const file = e.currentTarget.files?.[0]
|
||||
if (file) addImageAttachment(file)
|
||||
e.currentTarget.value = ""
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" value="Attach image">
|
||||
<IconButton
|
||||
type="button"
|
||||
icon="photo"
|
||||
variant="ghost"
|
||||
class="h-10 w-8"
|
||||
onClick={() => fileInputRef.click()}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
placement="top"
|
||||
inactive={!prompt.dirty() && !working()}
|
||||
value={
|
||||
<Switch>
|
||||
<Match when={working()}>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>Stop</span>
|
||||
<span class="text-icon-base text-12-medium text-[10px]!">ESC</span>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>Send</span>
|
||||
<Icon name="enter" size="small" class="text-icon-base" />
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
type="submit"
|
||||
disabled={!prompt.dirty() && store.imageAttachments.length === 0 && !working()}
|
||||
icon={working() ? "stop" : "arrow-up"}
|
||||
variant="primary"
|
||||
class="h-10 w-8"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -128,7 +128,7 @@ function DialogCommand(props: { options: CommandOption[] }) {
|
||||
groupBy={(x) => x.category ?? ""}
|
||||
onSelect={(option) => {
|
||||
if (option) {
|
||||
dialog.clear()
|
||||
dialog.close()
|
||||
option.onSelect?.("palette")
|
||||
}
|
||||
}}
|
||||
@@ -174,8 +174,8 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
||||
const suspended = () => suspendCount() > 0
|
||||
|
||||
const showPalette = () => {
|
||||
if (dialog.stack.length === 0) {
|
||||
dialog.replace(() => <DialogCommand options={options().filter((x) => !x.disabled)} />)
|
||||
if (!dialog.active) {
|
||||
dialog.show(() => <DialogCommand options={options().filter((x) => !x.disabled)} />)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -138,11 +138,12 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
|
||||
}
|
||||
|
||||
globalSDK.event.listen((e) => {
|
||||
console.log(e)
|
||||
const directory = e.name
|
||||
const event = e.details
|
||||
|
||||
if (directory === "global") {
|
||||
switch (event.type) {
|
||||
switch (event?.type) {
|
||||
case "global.disposed": {
|
||||
bootstrap()
|
||||
break
|
||||
@@ -288,9 +289,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
|
||||
globalSDK.client.project.list().then(async (x) => {
|
||||
setGlobalStore(
|
||||
"project",
|
||||
x
|
||||
.data!.filter((p) => !p.worktree.includes("opencode-test") && p.vcs)
|
||||
.sort((a, b) => a.id.localeCompare(b.id)),
|
||||
x.data!.filter((p) => !p.worktree.includes("opencode-test")).sort((a, b) => a.id.localeCompare(b.id)),
|
||||
)
|
||||
}),
|
||||
globalSDK.client.provider.list().then((x) => {
|
||||
|
||||
@@ -51,6 +51,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
||||
// })
|
||||
|
||||
globalSDK.event.listen((e) => {
|
||||
console.log(e)
|
||||
const directory = e.name
|
||||
const event = e.details
|
||||
const base = {
|
||||
|
||||
@@ -6,8 +6,8 @@ import { createMemo } from "solid-js"
|
||||
export const popularProviders = ["opencode", "anthropic", "github-copilot", "openai", "google", "openrouter", "vercel"]
|
||||
|
||||
export function useProviders() {
|
||||
const params = useParams()
|
||||
const globalSync = useGlobalSync()
|
||||
const params = useParams()
|
||||
const currentDirectory = createMemo(() => base64Decode(params.dir ?? ""))
|
||||
const providers = createMemo(() => {
|
||||
if (currentDirectory()) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { LocalProvider } from "@/context/local"
|
||||
import { base64Decode } from "@opencode-ai/util/encode"
|
||||
import { DataProvider } from "@opencode-ai/ui/context"
|
||||
import { iife } from "@opencode-ai/util/iife"
|
||||
import { DialogRoot } from "@opencode-ai/ui/context/dialog"
|
||||
|
||||
export default function Layout(props: ParentProps) {
|
||||
const params = useParams()
|
||||
@@ -21,9 +20,7 @@ export default function Layout(props: ParentProps) {
|
||||
const sync = useSync()
|
||||
return (
|
||||
<DataProvider data={sync.data} directory={directory()}>
|
||||
<LocalProvider>
|
||||
<DialogRoot>{props.children}</DialogRoot>
|
||||
</LocalProvider>
|
||||
<LocalProvider>{props.children}</LocalProvider>
|
||||
</DataProvider>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -194,7 +194,6 @@ export default function Layout(props: ParentProps) {
|
||||
title: "Previous session",
|
||||
category: "Session",
|
||||
keybind: "alt+arrowup",
|
||||
disabled: !params.dir,
|
||||
onSelect: () => navigateSessionByOffset(-1),
|
||||
},
|
||||
{
|
||||
@@ -202,7 +201,6 @@ export default function Layout(props: ParentProps) {
|
||||
title: "Next session",
|
||||
category: "Session",
|
||||
keybind: "alt+arrowdown",
|
||||
disabled: !params.dir,
|
||||
onSelect: () => navigateSessionByOffset(1),
|
||||
},
|
||||
{
|
||||
@@ -219,7 +217,7 @@ export default function Layout(props: ParentProps) {
|
||||
])
|
||||
|
||||
function connectProvider() {
|
||||
dialog.replace(() => <DialogSelectProvider />)
|
||||
dialog.show(() => <DialogSelectProvider />)
|
||||
}
|
||||
|
||||
function navigateToProject(directory: string | undefined) {
|
||||
@@ -496,11 +494,10 @@ export default function Layout(props: ParentProps) {
|
||||
}
|
||||
|
||||
const SortableProject = (props: { project: Project & { expanded: boolean } }): JSX.Element => {
|
||||
const notification = useNotification()
|
||||
const sortable = createSortable(props.project.worktree)
|
||||
const slug = createMemo(() => base64Encode(props.project.worktree))
|
||||
const name = createMemo(() => getFilename(props.project.worktree))
|
||||
const [store, setStore] = globalSync.child(props.project.worktree)
|
||||
const [store] = globalSync.child(props.project.worktree)
|
||||
const sessions = createMemo(() => store.session ?? [])
|
||||
const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID))
|
||||
const childSessionsByParent = createMemo(() => {
|
||||
@@ -713,7 +710,7 @@ export default function Layout(props: ParentProps) {
|
||||
<Match when={true}>
|
||||
<Tooltip placement="right" value="Connect provider" inactive={layout.sidebar.opened()}>
|
||||
<Button
|
||||
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px] rounded-lg px-2"
|
||||
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
|
||||
variant="ghost"
|
||||
size="large"
|
||||
icon="plus"
|
||||
@@ -727,7 +724,7 @@ export default function Layout(props: ParentProps) {
|
||||
<Show when={platform.openDirectoryPickerDialog}>
|
||||
<Tooltip placement="right" value="Open project" inactive={layout.sidebar.opened()}>
|
||||
<Button
|
||||
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px] rounded-lg px-2"
|
||||
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
|
||||
variant="ghost"
|
||||
size="large"
|
||||
icon="folder-add-left"
|
||||
@@ -753,7 +750,7 @@ export default function Layout(props: ParentProps) {
|
||||
as={"a"}
|
||||
href="https://opencode.ai/desktop-feedback"
|
||||
target="_blank"
|
||||
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px] rounded-lg px-2"
|
||||
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
|
||||
variant="ghost"
|
||||
size="large"
|
||||
icon="bubble-5"
|
||||
|
||||
@@ -176,7 +176,7 @@ export default function Page() {
|
||||
category: "File",
|
||||
keybind: "mod+p",
|
||||
slash: "open",
|
||||
onSelect: () => dialog.replace(() => <DialogSelectFile />),
|
||||
onSelect: () => dialog.show(() => <DialogSelectFile />),
|
||||
},
|
||||
// {
|
||||
// id: "theme.toggle",
|
||||
@@ -243,14 +243,16 @@ export default function Page() {
|
||||
title: "Choose model",
|
||||
description: "Select a different model",
|
||||
category: "Model",
|
||||
keybind: "mod+'",
|
||||
slash: "model",
|
||||
onSelect: () => dialog.replace(() => <DialogSelectModel />),
|
||||
onSelect: () => dialog.show(() => <DialogSelectModel />),
|
||||
},
|
||||
{
|
||||
id: "agent.cycle",
|
||||
title: "Cycle agent",
|
||||
description: "Switch to the next agent",
|
||||
category: "Agent",
|
||||
keybind: "mod+.",
|
||||
slash: "agent",
|
||||
onSelect: () => local.agent.move(1),
|
||||
},
|
||||
@@ -318,7 +320,7 @@ export default function Page() {
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if ((document.activeElement as HTMLElement)?.dataset?.component === "terminal") return
|
||||
if (dialog.stack.length > 0) return
|
||||
if (dialog.active) return
|
||||
|
||||
if (event.key === "PageUp" || event.key === "PageDown") {
|
||||
const scrollContainer = document.querySelector('[data-slot="session-turn-content"]') as HTMLElement
|
||||
@@ -611,7 +613,7 @@ export default function Page() {
|
||||
icon="plus-small"
|
||||
variant="ghost"
|
||||
iconSize="large"
|
||||
onClick={() => dialog.replace(() => <DialogSelectFile />)}
|
||||
onClick={() => dialog.show(() => <DialogSelectFile />)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -14,7 +14,7 @@
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"aws4fetch": "^1.0.20",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solidjs/router": "catalog:",
|
||||
"@solidjs/start": "catalog:",
|
||||
"@solidjs/meta": "catalog:",
|
||||
|
||||
@@ -19,7 +19,7 @@ import { createStore } from "solid-js/store"
|
||||
import z from "zod"
|
||||
import NotFound from "../[...404]"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
|
||||
import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||
import { Diff as SSRDiff } from "@opencode-ai/ui/diff-ssr"
|
||||
import { clientOnly } from "@solidjs/start"
|
||||
import { type IconName } from "@opencode-ai/ui/icons/provider"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.0.155"
|
||||
version = "1.0.163"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/sst/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.155/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.163/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.155/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.163/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.155/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.163/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.155/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.163/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.155/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.163/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@octokit/rest": "catalog:",
|
||||
"hono": "catalog:",
|
||||
"jose": "6.0.11"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
@@ -64,7 +64,7 @@
|
||||
"@hono/zod-validator": "catalog:",
|
||||
"@modelcontextprotocol/sdk": "1.15.1",
|
||||
"@octokit/graphql": "9.0.2",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@octokit/rest": "catalog:",
|
||||
"@openauthjs/openauth": "catalog:",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
@@ -74,7 +74,7 @@
|
||||
"@opentui/core": "0.0.0-20251211-4403a69a",
|
||||
"@opentui/solid": "0.0.0-20251211-4403a69a",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
|
||||
@@ -256,9 +256,9 @@ export namespace Agent {
|
||||
return state().then((x) => Object.values(x))
|
||||
}
|
||||
|
||||
export async function generate(input: { description: string }) {
|
||||
export async function generate(input: { description: string; model?: { providerID: string; modelID: string } }) {
|
||||
const cfg = await Config.get()
|
||||
const defaultModel = await Provider.defaultModel()
|
||||
const defaultModel = input.model ?? (await Provider.defaultModel())
|
||||
const model = await Provider.getModel(defaultModel.providerID, defaultModel.modelID)
|
||||
const language = await Provider.getLanguage(model)
|
||||
const system = SystemPrompt.header(defaultModel.providerID)
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as prompts from "@clack/prompts"
|
||||
import { UI } from "../ui"
|
||||
import { Global } from "../../global"
|
||||
import { Agent } from "../../agent/agent"
|
||||
import { Provider } from "../../provider/provider"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import matter from "gray-matter"
|
||||
@@ -47,6 +48,11 @@ const AgentCreateCommand = cmd({
|
||||
.option("tools", {
|
||||
type: "string",
|
||||
describe: `comma-separated list of tools to enable (default: all). Available: "${AVAILABLE_TOOLS.join(", ")}"`,
|
||||
})
|
||||
.option("model", {
|
||||
type: "string",
|
||||
alias: ["m"],
|
||||
describe: "model to use in the format of provider/model",
|
||||
}),
|
||||
async handler(args) {
|
||||
await Instance.provide({
|
||||
@@ -114,7 +120,8 @@ const AgentCreateCommand = cmd({
|
||||
// Generate agent
|
||||
const spinner = prompts.spinner()
|
||||
spinner.start("Generating agent configuration...")
|
||||
const generated = await Agent.generate({ description }).catch((error) => {
|
||||
const model = args.model ? Provider.parseModel(args.model) : undefined
|
||||
const generated = await Agent.generate({ description, model }).catch((error) => {
|
||||
spinner.stop(`LLM failed to generate agent: ${error.message}`, 1)
|
||||
if (isFullyNonInteractive) process.exit(1)
|
||||
throw new UI.CancelledError()
|
||||
|
||||
@@ -278,7 +278,7 @@ export const GithubInstallCommand = cmd({
|
||||
process.platform === "darwin"
|
||||
? `open "${url}"`
|
||||
: process.platform === "win32"
|
||||
? `start "${url}"`
|
||||
? `start "" "${url}"`
|
||||
: `xdg-open "${url}"`
|
||||
|
||||
exec(command, (error) => {
|
||||
|
||||
@@ -88,7 +88,9 @@ export const RunCommand = cmd({
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
let message = [...args.message, ...(args["--"] || [])].join(" ")
|
||||
let message = [...args.message, ...(args["--"] || [])]
|
||||
.map((arg) => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg))
|
||||
.join(" ")
|
||||
|
||||
const fileParts: any[] = []
|
||||
if (args.file) {
|
||||
|
||||
@@ -297,6 +297,24 @@ function App() {
|
||||
local.model.cycle(-1)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Favorite cycle",
|
||||
value: "model.cycle_favorite",
|
||||
keybind: "model_cycle_favorite",
|
||||
category: "Agent",
|
||||
onSelect: () => {
|
||||
local.model.cycleFavorite(1)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Favorite cycle reverse",
|
||||
value: "model.cycle_favorite_reverse",
|
||||
keybind: "model_cycle_favorite_reverse",
|
||||
category: "Agent",
|
||||
onSelect: () => {
|
||||
local.model.cycleFavorite(-1)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Switch agent",
|
||||
value: "agent.list",
|
||||
|
||||
@@ -364,6 +364,13 @@ export function Autocomplete(props: {
|
||||
const result = fuzzysort.go(currentFilter, mixed, {
|
||||
keys: [(obj) => obj.display.trimEnd(), "description", (obj) => obj.aliases?.join(" ") ?? ""],
|
||||
limit: 10,
|
||||
scoreFn: (objResults) => {
|
||||
const displayResult = objResults[0]
|
||||
if (displayResult && displayResult.target.startsWith(store.visible + currentFilter)) {
|
||||
return objResults.score * 2
|
||||
}
|
||||
return objResults.score
|
||||
},
|
||||
})
|
||||
return result.map((arr) => arr.obj)
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { AgentPart, FilePart, TextPart } from "@opencode-ai/sdk/v2"
|
||||
|
||||
export type PromptInfo = {
|
||||
input: string
|
||||
mode?: "normal" | "shell"
|
||||
parts: (
|
||||
| Omit<FilePart, "id" | "messageID" | "sessionID">
|
||||
| Omit<AgentPart, "id" | "messageID" | "sessionID">
|
||||
|
||||
@@ -495,6 +495,9 @@ export function Prompt(props: PromptProps) {
|
||||
// Filter out text parts (pasted content) since they're now expanded inline
|
||||
const nonTextParts = store.prompt.parts.filter((part) => part.type !== "text")
|
||||
|
||||
// Capture mode before it gets reset
|
||||
const currentMode = store.mode
|
||||
|
||||
if (store.mode === "shell") {
|
||||
sdk.client.session.shell({
|
||||
sessionID,
|
||||
@@ -543,7 +546,10 @@ export function Prompt(props: PromptProps) {
|
||||
],
|
||||
})
|
||||
}
|
||||
history.append(store.prompt)
|
||||
history.append({
|
||||
...store.prompt,
|
||||
mode: currentMode,
|
||||
})
|
||||
input.extmarks.clear()
|
||||
setStore("prompt", {
|
||||
input: "",
|
||||
@@ -767,6 +773,7 @@ export function Prompt(props: PromptProps) {
|
||||
if (item) {
|
||||
input.setText(item.input)
|
||||
setStore("prompt", item)
|
||||
setStore("mode", item.mode ?? "normal")
|
||||
restoreExtmarksFromParts(item.parts)
|
||||
e.preventDefault()
|
||||
if (direction === -1) input.cursorOffset = 0
|
||||
|
||||
@@ -64,6 +64,7 @@ import { Editor } from "../../util/editor"
|
||||
import stripAnsi from "strip-ansi"
|
||||
import { Footer } from "./footer.tsx"
|
||||
import { usePromptRef } from "../../context/prompt"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
|
||||
addDefaultParsers(parsers.parsers)
|
||||
|
||||
@@ -1414,7 +1415,10 @@ ToolRegistry.register<typeof WriteTool>({
|
||||
return props.input.content
|
||||
})
|
||||
|
||||
const diagnostics = createMemo(() => props.metadata.diagnostics?.[props.input.filePath ?? ""] ?? [])
|
||||
const diagnostics = createMemo(() => {
|
||||
const filePath = Filesystem.normalizePath(props.input.filePath ?? "")
|
||||
return props.metadata.diagnostics?.[filePath] ?? []
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -1587,7 +1591,8 @@ ToolRegistry.register<typeof EditTool>({
|
||||
const diffContent = createMemo(() => props.metadata.diff ?? props.permission["diff"])
|
||||
|
||||
const diagnostics = createMemo(() => {
|
||||
const arr = props.metadata.diagnostics?.[props.input.filePath ?? ""] ?? []
|
||||
const filePath = Filesystem.normalizePath(props.input.filePath ?? "")
|
||||
const arr = props.metadata.diagnostics?.[filePath] ?? []
|
||||
return arr.filter((x) => x.severity === 1).slice(0, 3)
|
||||
})
|
||||
|
||||
|
||||
@@ -307,10 +307,9 @@ function Option(props: {
|
||||
fg={props.active ? fg : props.current ? theme.primary : theme.text}
|
||||
attributes={props.active ? TextAttributes.BOLD : undefined}
|
||||
overflow="hidden"
|
||||
wrapMode="word"
|
||||
paddingLeft={3}
|
||||
>
|
||||
{Locale.truncate(props.title, 62)}
|
||||
{Locale.truncate(props.title, 61)}
|
||||
<Show when={props.description}>
|
||||
<span style={{ fg: props.active ? fg : theme.textMuted }}> {props.description}</span>
|
||||
</Show>
|
||||
|
||||
@@ -460,6 +460,8 @@ export namespace Config {
|
||||
model_list: z.string().optional().default("<leader>m").describe("List available models"),
|
||||
model_cycle_recent: z.string().optional().default("f2").describe("Next recently used model"),
|
||||
model_cycle_recent_reverse: z.string().optional().default("shift+f2").describe("Previous recently used model"),
|
||||
model_cycle_favorite: z.string().optional().default("none").describe("Next favorite model"),
|
||||
model_cycle_favorite_reverse: z.string().optional().default("none").describe("Previous favorite model"),
|
||||
command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
|
||||
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
|
||||
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
|
||||
|
||||
@@ -11,6 +11,9 @@ import type { LSPServer } from "./server"
|
||||
import { NamedError } from "@opencode-ai/util/error"
|
||||
import { withTimeout } from "../util/timeout"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
|
||||
const DIAGNOSTICS_DEBOUNCE_MS = 150
|
||||
|
||||
export namespace LSPClient {
|
||||
const log = Log.create({ service: "lsp.client" })
|
||||
@@ -47,14 +50,15 @@ export namespace LSPClient {
|
||||
|
||||
const diagnostics = new Map<string, Diagnostic[]>()
|
||||
connection.onNotification("textDocument/publishDiagnostics", (params) => {
|
||||
const path = fileURLToPath(params.uri)
|
||||
const filePath = Filesystem.normalizePath(fileURLToPath(params.uri))
|
||||
l.info("textDocument/publishDiagnostics", {
|
||||
path,
|
||||
path: filePath,
|
||||
count: params.diagnostics.length,
|
||||
})
|
||||
const exists = diagnostics.has(path)
|
||||
diagnostics.set(path, params.diagnostics)
|
||||
const exists = diagnostics.has(filePath)
|
||||
diagnostics.set(filePath, params.diagnostics)
|
||||
if (!exists && input.serverID === "typescript") return
|
||||
Bus.publish(Event.Diagnostics, { path, serverID: input.serverID })
|
||||
Bus.publish(Event.Diagnostics, { path: filePath, serverID: input.serverID })
|
||||
})
|
||||
connection.onRequest("window/workDoneProgress/create", (params) => {
|
||||
l.info("window/workDoneProgress/create", params)
|
||||
@@ -181,16 +185,23 @@ export namespace LSPClient {
|
||||
return diagnostics
|
||||
},
|
||||
async waitForDiagnostics(input: { path: string }) {
|
||||
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path)
|
||||
log.info("waiting for diagnostics", input)
|
||||
const normalizedPath = Filesystem.normalizePath(
|
||||
path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path),
|
||||
)
|
||||
log.info("waiting for diagnostics", { path: normalizedPath })
|
||||
let unsub: () => void
|
||||
let debounceTimer: ReturnType<typeof setTimeout> | undefined
|
||||
return await withTimeout(
|
||||
new Promise<void>((resolve) => {
|
||||
unsub = Bus.subscribe(Event.Diagnostics, (event) => {
|
||||
if (event.properties.path === input.path && event.properties.serverID === result.serverID) {
|
||||
log.info("got diagnostics", input)
|
||||
unsub?.()
|
||||
resolve()
|
||||
if (event.properties.path === normalizedPath && event.properties.serverID === result.serverID) {
|
||||
// Debounce to allow LSP to send follow-up diagnostics (e.g., semantic after syntax)
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
debounceTimer = setTimeout(() => {
|
||||
log.info("got diagnostics", { path: normalizedPath })
|
||||
unsub?.()
|
||||
resolve()
|
||||
}, DIAGNOSTICS_DEBOUNCE_MS)
|
||||
}
|
||||
})
|
||||
}),
|
||||
@@ -198,6 +209,7 @@ export namespace LSPClient {
|
||||
)
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
unsub?.()
|
||||
})
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ import fs from "fs/promises"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { Archive } from "../util/archive"
|
||||
|
||||
export namespace LSPServer {
|
||||
const log = Log.create({ service: "lsp.server" })
|
||||
@@ -176,7 +177,13 @@ export namespace LSPServer {
|
||||
const zipPath = path.join(Global.Path.bin, "vscode-eslint.zip")
|
||||
await Bun.file(zipPath).write(response)
|
||||
|
||||
await $`unzip -o -q ${zipPath}`.quiet().cwd(Global.Path.bin).nothrow()
|
||||
const ok = await Archive.extractZip(zipPath, Global.Path.bin)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
log.error("Failed to extract vscode-eslint archive", { error })
|
||||
return false
|
||||
})
|
||||
if (!ok) return
|
||||
await fs.rm(zipPath, { force: true })
|
||||
|
||||
const extractedPath = path.join(Global.Path.bin, "vscode-eslint-main")
|
||||
@@ -281,7 +288,7 @@ export namespace LSPServer {
|
||||
extensions: [".go"],
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("gopls", {
|
||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
if (!Bun.which("go")) return
|
||||
@@ -319,7 +326,7 @@ export namespace LSPServer {
|
||||
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("rubocop", {
|
||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
const ruby = Bun.which("ruby")
|
||||
@@ -420,7 +427,7 @@ export namespace LSPServer {
|
||||
Global.Path.bin,
|
||||
"elixir-ls-master",
|
||||
"release",
|
||||
process.platform === "win32" ? "language_server.bar" : "language_server.sh",
|
||||
process.platform === "win32" ? "language_server.bat" : "language_server.sh",
|
||||
)
|
||||
|
||||
if (!(await Bun.file(binary).exists())) {
|
||||
@@ -438,7 +445,13 @@ export namespace LSPServer {
|
||||
const zipPath = path.join(Global.Path.bin, "elixir-ls.zip")
|
||||
await Bun.file(zipPath).write(response)
|
||||
|
||||
await $`unzip -o -q ${zipPath}`.quiet().cwd(Global.Path.bin).nothrow()
|
||||
const ok = await Archive.extractZip(zipPath, Global.Path.bin)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
log.error("Failed to extract elixir-ls archive", { error })
|
||||
return false
|
||||
})
|
||||
if (!ok) return
|
||||
|
||||
await fs.rm(zipPath, {
|
||||
force: true,
|
||||
@@ -470,7 +483,7 @@ export namespace LSPServer {
|
||||
root: NearestRoot(["build.zig"]),
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("zls", {
|
||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
if (!bin) {
|
||||
@@ -541,7 +554,13 @@ export namespace LSPServer {
|
||||
await Bun.file(tempPath).write(downloadResponse)
|
||||
|
||||
if (ext === "zip") {
|
||||
await $`unzip -o -q ${tempPath}`.quiet().cwd(Global.Path.bin).nothrow()
|
||||
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
log.error("Failed to extract zls archive", { error })
|
||||
return false
|
||||
})
|
||||
if (!ok) return
|
||||
} else {
|
||||
await $`tar -xf ${tempPath}`.cwd(Global.Path.bin).nothrow()
|
||||
}
|
||||
@@ -576,7 +595,7 @@ export namespace LSPServer {
|
||||
extensions: [".cs"],
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("csharp-ls", {
|
||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
if (!Bun.which("dotnet")) {
|
||||
@@ -610,6 +629,46 @@ export namespace LSPServer {
|
||||
},
|
||||
}
|
||||
|
||||
export const FSharp: Info = {
|
||||
id: "fsharp",
|
||||
root: NearestRoot([".sln", ".fsproj", "global.json"]),
|
||||
extensions: [".fs", ".fsi", ".fsx", ".fsscript"],
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("fsautocomplete", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
if (!Bun.which("dotnet")) {
|
||||
log.error(".NET SDK is required to install fsautocomplete")
|
||||
return
|
||||
}
|
||||
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
log.info("installing fsautocomplete via dotnet tool")
|
||||
const proc = Bun.spawn({
|
||||
cmd: ["dotnet", "tool", "install", "fsautocomplete", "--tool-path", Global.Path.bin],
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "pipe",
|
||||
})
|
||||
const exit = await proc.exited
|
||||
if (exit !== 0) {
|
||||
log.error("Failed to install fsautocomplete")
|
||||
return
|
||||
}
|
||||
|
||||
bin = path.join(Global.Path.bin, "fsautocomplete" + (process.platform === "win32" ? ".exe" : ""))
|
||||
log.info(`installed fsautocomplete`, { bin })
|
||||
}
|
||||
|
||||
return {
|
||||
process: spawn(bin, {
|
||||
cwd: root,
|
||||
}),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const SourceKit: Info = {
|
||||
id: "sourcekit-lsp",
|
||||
extensions: [".swift", ".objc", "objcpp"],
|
||||
@@ -800,7 +859,13 @@ export namespace LSPServer {
|
||||
}
|
||||
|
||||
if (zip) {
|
||||
await $`unzip -o -q ${archive}`.quiet().cwd(Global.Path.bin).nothrow()
|
||||
const ok = await Archive.extractZip(archive, Global.Path.bin)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
log.error("Failed to extract clangd archive", { error })
|
||||
return false
|
||||
})
|
||||
if (!ok) return
|
||||
}
|
||||
if (tar) {
|
||||
await $`tar -xf ${archive}`.cwd(Global.Path.bin).nothrow()
|
||||
@@ -1070,7 +1135,7 @@ export namespace LSPServer {
|
||||
extensions: [".lua"],
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("lua-language-server", {
|
||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
if (!bin) {
|
||||
@@ -1148,14 +1213,21 @@ export namespace LSPServer {
|
||||
await fs.mkdir(installDir, { recursive: true })
|
||||
|
||||
if (ext === "zip") {
|
||||
const ok = await $`unzip -o -q ${tempPath} -d ${installDir}`.quiet().catch((error) => {
|
||||
log.error("Failed to extract lua-language-server archive", { error })
|
||||
})
|
||||
const ok = await Archive.extractZip(tempPath, installDir)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
log.error("Failed to extract lua-language-server archive", { error })
|
||||
return false
|
||||
})
|
||||
if (!ok) return
|
||||
} else {
|
||||
const ok = await $`tar -xzf ${tempPath} -C ${installDir}`.quiet().catch((error) => {
|
||||
log.error("Failed to extract lua-language-server archive", { error })
|
||||
})
|
||||
const ok = await $`tar -xzf ${tempPath} -C ${installDir}`
|
||||
.quiet()
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
log.error("Failed to extract lua-language-server archive", { error })
|
||||
return false
|
||||
})
|
||||
if (!ok) return
|
||||
}
|
||||
|
||||
@@ -1309,7 +1381,7 @@ export namespace LSPServer {
|
||||
root: NearestRoot([".terraform.lock.hcl", "terraform.tfstate", "*.tf"]),
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("terraform-ls", {
|
||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
if (!bin) {
|
||||
@@ -1356,7 +1428,13 @@ export namespace LSPServer {
|
||||
const tempPath = path.join(Global.Path.bin, assetName)
|
||||
await Bun.file(tempPath).write(downloadResponse)
|
||||
|
||||
await $`unzip -o -q ${tempPath}`.cwd(Global.Path.bin).nothrow()
|
||||
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
log.error("Failed to extract terraform-ls archive", { error })
|
||||
return false
|
||||
})
|
||||
if (!ok) return
|
||||
await fs.rm(tempPath, { force: true })
|
||||
|
||||
bin = path.join(Global.Path.bin, "terraform-ls" + (platform === "win32" ? ".exe" : ""))
|
||||
@@ -1393,7 +1471,7 @@ export namespace LSPServer {
|
||||
root: NearestRoot([".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot"]),
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("texlab", {
|
||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
if (!bin) {
|
||||
@@ -1441,7 +1519,13 @@ export namespace LSPServer {
|
||||
await Bun.file(tempPath).write(downloadResponse)
|
||||
|
||||
if (ext === "zip") {
|
||||
await $`unzip -o -q ${tempPath}`.cwd(Global.Path.bin).nothrow()
|
||||
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
log.error("Failed to extract texlab archive", { error })
|
||||
return false
|
||||
})
|
||||
if (!ok) return
|
||||
}
|
||||
if (ext === "tar.gz") {
|
||||
await $`tar -xzf ${tempPath}`.cwd(Global.Path.bin).nothrow()
|
||||
|
||||
@@ -205,7 +205,12 @@ export namespace ProviderTransform {
|
||||
export function message(msgs: ModelMessage[], model: Provider.Model) {
|
||||
msgs = unsupportedParts(msgs, model)
|
||||
msgs = normalizeMessages(msgs, model)
|
||||
if (model.providerID === "anthropic" || model.api.id.includes("anthropic") || model.api.id.includes("claude")) {
|
||||
if (
|
||||
model.providerID === "anthropic" ||
|
||||
model.api.id.includes("anthropic") ||
|
||||
model.api.id.includes("claude") ||
|
||||
model.api.npm === "@ai-sdk/anthropic"
|
||||
) {
|
||||
msgs = applyCaching(msgs, model.providerID)
|
||||
}
|
||||
|
||||
|
||||
@@ -114,8 +114,12 @@ export namespace Pty {
|
||||
const id = Identifier.create("pty", false)
|
||||
const command = input.command || Shell.preferred()
|
||||
const args = input.args || []
|
||||
if (command.endsWith("sh")) {
|
||||
args.push("-l")
|
||||
}
|
||||
|
||||
const cwd = input.cwd || Instance.directory
|
||||
const env = { ...process.env, ...input.env } as Record<string, string>
|
||||
const env = { ...process.env, ...input.env, TERM: "xterm-256color" } as Record<string, string>
|
||||
log.info("creating session", { id, cmd: command, args, cwd })
|
||||
|
||||
const spawn = await pty()
|
||||
|
||||
@@ -125,6 +125,14 @@ export namespace Server {
|
||||
async (c) => {
|
||||
log.info("global event connected")
|
||||
return streamSSE(c, async (stream) => {
|
||||
stream.writeSSE({
|
||||
data: JSON.stringify({
|
||||
payload: {
|
||||
type: "server.connected",
|
||||
properties: {},
|
||||
},
|
||||
}),
|
||||
})
|
||||
async function handler(event: any) {
|
||||
await stream.writeSSE({
|
||||
data: JSON.stringify(event),
|
||||
|
||||
@@ -60,11 +60,18 @@ export namespace LLM {
|
||||
.join("\n"),
|
||||
)
|
||||
|
||||
const header = system[0]
|
||||
const original = clone(system)
|
||||
await Plugin.trigger("experimental.chat.system.transform", {}, { system })
|
||||
if (system.length === 0) {
|
||||
system.push(...original)
|
||||
}
|
||||
// rejoin to maintain 2-part structure for caching if header unchanged
|
||||
if (system.length > 2 && system[0] === header) {
|
||||
const rest = system.slice(1)
|
||||
system.length = 0
|
||||
system.push(header, rest.join("\n"))
|
||||
}
|
||||
|
||||
const params = await Plugin.trigger(
|
||||
"chat.params",
|
||||
@@ -80,6 +87,7 @@ export namespace LLM {
|
||||
? (input.agent.temperature ?? ProviderTransform.temperature(input.model))
|
||||
: undefined,
|
||||
topP: input.agent.topP ?? ProviderTransform.topP(input.model),
|
||||
topK: ProviderTransform.topK(input.model),
|
||||
options: pipe(
|
||||
{},
|
||||
mergeDeep(ProviderTransform.options(input.model, input.sessionID)),
|
||||
@@ -132,6 +140,7 @@ export namespace LLM {
|
||||
},
|
||||
temperature: params.temperature,
|
||||
topP: params.topP,
|
||||
topK: params.topK,
|
||||
providerOptions: ProviderTransform.providerOptions(input.model, params.options),
|
||||
activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
|
||||
tools,
|
||||
|
||||
@@ -140,16 +140,14 @@ export const EditTool = Tool.define("edit", {
|
||||
let output = ""
|
||||
await LSP.touchFile(filePath, true)
|
||||
const diagnostics = await LSP.diagnostics()
|
||||
for (const [file, issues] of Object.entries(diagnostics)) {
|
||||
if (issues.length === 0) continue
|
||||
if (file === filePath) {
|
||||
const errors = issues.filter((item) => item.severity === 1)
|
||||
const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
|
||||
const suffix =
|
||||
errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
|
||||
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</file_diagnostics>\n`
|
||||
continue
|
||||
}
|
||||
const normalizedFilePath = Filesystem.normalizePath(filePath)
|
||||
const issues = diagnostics[normalizedFilePath] ?? []
|
||||
if (issues.length > 0) {
|
||||
const errors = issues.filter((item) => item.severity === 1)
|
||||
const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
|
||||
const suffix =
|
||||
errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
|
||||
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</file_diagnostics>\n`
|
||||
}
|
||||
|
||||
const filediff: Snapshot.FileDiff = {
|
||||
|
||||
@@ -80,6 +80,7 @@ export const WriteTool = Tool.define("write", {
|
||||
let output = ""
|
||||
await LSP.touchFile(filepath, true)
|
||||
const diagnostics = await LSP.diagnostics()
|
||||
const normalizedFilepath = Filesystem.normalizePath(filepath)
|
||||
let projectDiagnosticsCount = 0
|
||||
for (const [file, issues] of Object.entries(diagnostics)) {
|
||||
if (issues.length === 0) continue
|
||||
@@ -87,7 +88,7 @@ export const WriteTool = Tool.define("write", {
|
||||
const limited = sorted.slice(0, MAX_DIAGNOSTICS_PER_FILE)
|
||||
const suffix =
|
||||
issues.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${issues.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
|
||||
if (file === filepath) {
|
||||
if (file === normalizedFilepath) {
|
||||
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</file_diagnostics>\n`
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { $ } from "bun"
|
||||
import path from "path"
|
||||
|
||||
export namespace Archive {
|
||||
export async function extractZip(zipPath: string, destDir: string) {
|
||||
if (process.platform === "win32") {
|
||||
const winZipPath = path.resolve(zipPath)
|
||||
const winDestDir = path.resolve(destDir)
|
||||
// $global:ProgressPreference suppresses PowerShell's blue progress bar popup
|
||||
const cmd = `$global:ProgressPreference = 'SilentlyContinue'; Expand-Archive -Path '${winZipPath}' -DestinationPath '${winDestDir}' -Force`
|
||||
await $`powershell -NoProfile -NonInteractive -Command ${cmd}`.quiet()
|
||||
} else {
|
||||
await $`unzip -o -q ${zipPath} -d ${destDir}`.quiet()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,21 @@
|
||||
import { realpathSync } from "fs"
|
||||
import { exists } from "fs/promises"
|
||||
import { dirname, join, relative } from "path"
|
||||
|
||||
export namespace Filesystem {
|
||||
/**
|
||||
* On Windows, normalize a path to its canonical casing using the filesystem.
|
||||
* This is needed because Windows paths are case-insensitive but LSP servers
|
||||
* may return paths with different casing than what we send them.
|
||||
*/
|
||||
export function normalizePath(p: string): string {
|
||||
if (process.platform !== "win32") return p
|
||||
try {
|
||||
return realpathSync.native(p)
|
||||
} catch {
|
||||
return p
|
||||
}
|
||||
}
|
||||
export function overlaps(a: string, b: string) {
|
||||
const relA = relative(a, b)
|
||||
const relB = relative(b, a)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -161,7 +161,7 @@ export interface Hooks {
|
||||
*/
|
||||
"chat.params"?: (
|
||||
input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage },
|
||||
output: { temperature: number; topP: number; options: Record<string, any> },
|
||||
output: { temperature: number; topP: number; topK: number; options: Record<string, any> },
|
||||
) => Promise<void>
|
||||
"permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise<void>
|
||||
"tool.execute.before"?: (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -917,6 +917,14 @@ export type KeybindsConfig = {
|
||||
* Previous recently used model
|
||||
*/
|
||||
model_cycle_recent_reverse?: string
|
||||
/**
|
||||
* Next favorite model
|
||||
*/
|
||||
model_cycle_favorite?: string
|
||||
/**
|
||||
* Previous favorite model
|
||||
*/
|
||||
model_cycle_favorite_reverse?: string
|
||||
/**
|
||||
* List available commands
|
||||
*/
|
||||
|
||||
@@ -7169,6 +7169,16 @@
|
||||
"default": "shift+f2",
|
||||
"type": "string"
|
||||
},
|
||||
"model_cycle_favorite": {
|
||||
"description": "Next favorite model",
|
||||
"default": "none",
|
||||
"type": "string"
|
||||
},
|
||||
"model_cycle_favorite_reverse": {
|
||||
"description": "Previous favorite model",
|
||||
"default": "none",
|
||||
"type": "string"
|
||||
},
|
||||
"command_list": {
|
||||
"description": "List available commands",
|
||||
"default": "ctrl+p",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun run src/index.ts",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/tauri",
|
||||
"private": true,
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo -b",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as fs from "node:fs/promises"
|
||||
import { $ } from "bun"
|
||||
|
||||
import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils"
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use std::{
|
||||
net::{SocketAddr, TcpListener},
|
||||
process::Command,
|
||||
sync::{Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::TitleBarStyle;
|
||||
use tauri::{AppHandle, LogicalSize, Manager, Monitor, RunEvent, WebviewUrl, WebviewWindow};
|
||||
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
|
||||
use tauri::{AppHandle, LogicalSize, Manager, RunEvent, WebviewUrl, WebviewWindow};
|
||||
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
use tokio::net::TcpSocket;
|
||||
@@ -15,6 +13,28 @@ use tokio::net::TcpSocket;
|
||||
#[derive(Clone)]
|
||||
struct ServerState(Arc<Mutex<Option<CommandChild>>>);
|
||||
|
||||
#[tauri::command]
|
||||
fn kill_sidecar(app: AppHandle) {
|
||||
let Some(server_state) = app.try_state::<ServerState>() else {
|
||||
println!("Server not running");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(server_state) = server_state
|
||||
.0
|
||||
.lock()
|
||||
.expect("Failed to acquire mutex lock")
|
||||
.take()
|
||||
else {
|
||||
println!("Server state missing");
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = server_state.kill();
|
||||
|
||||
println!("Killed server");
|
||||
}
|
||||
|
||||
fn get_sidecar_port() -> u16 {
|
||||
option_env!("OPENCODE_PORT")
|
||||
.map(|s| s.to_string())
|
||||
@@ -29,40 +49,6 @@ fn get_sidecar_port() -> u16 {
|
||||
})
|
||||
}
|
||||
|
||||
fn find_and_kill_process_on_port(port: u16) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Find all listeners on the specified port
|
||||
let listeners = listeners::get_processes_by_port(port)?;
|
||||
|
||||
if listeners.is_empty() {
|
||||
println!("No processes found listening on port {}", port);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for listener in listeners {
|
||||
let pid = listener.pid;
|
||||
println!("Found process {} listening on port {}", pid, port);
|
||||
|
||||
// Kill the process using platform-appropriate command
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Command::new("taskkill")
|
||||
.args(["/F", "/PID", &pid.to_string()])
|
||||
.output()?;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Command::new("kill")
|
||||
.args(["-9", &pid.to_string()])
|
||||
.output()?;
|
||||
}
|
||||
|
||||
println!("Killed process {}", pid);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_sidecar(app: &AppHandle, port: u16) -> CommandChild {
|
||||
let (mut rx, child) = app
|
||||
.shell()
|
||||
@@ -116,6 +102,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![kill_sidecar])
|
||||
.setup(move |app| {
|
||||
let app = app.handle().clone();
|
||||
|
||||
@@ -124,28 +111,6 @@ pub fn run() {
|
||||
|
||||
let should_spawn_sidecar = !is_server_running(port).await;
|
||||
|
||||
// if server_running {
|
||||
// let res = app
|
||||
// .dialog()
|
||||
// .message(
|
||||
// "OpenCode Server is already running, would you like to restart it?",
|
||||
// )
|
||||
// .buttons(MessageDialogButtons::YesNo)
|
||||
// .blocking_show_with_result();
|
||||
|
||||
// match res {
|
||||
// MessageDialogResult::Yes => {
|
||||
// if let Err(e) = find_and_kill_process_on_port(port) {
|
||||
// eprintln!("Failed to kill process on port {}: {}", port, e);
|
||||
// }
|
||||
// true
|
||||
// }
|
||||
// _ => false,
|
||||
// }
|
||||
// } else {
|
||||
// true
|
||||
// };
|
||||
|
||||
let child = if should_spawn_sidecar {
|
||||
let child = spawn_sidecar(&app, port);
|
||||
|
||||
@@ -183,6 +148,7 @@ pub fn run() {
|
||||
.inner_size(size.width as f64, size.height as f64)
|
||||
.decorations(true)
|
||||
.zoom_hotkeys_enabled(true)
|
||||
.disable_drag_drop_handler()
|
||||
.initialization_script(format!(
|
||||
r#"
|
||||
window.__OPENCODE__ ??= {{}};
|
||||
@@ -217,24 +183,7 @@ pub fn run() {
|
||||
if let RunEvent::Exit = event {
|
||||
println!("Received Exit");
|
||||
|
||||
let Some(server_state) = app.try_state::<ServerState>() else {
|
||||
println!("Server not running");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(server_state) = server_state
|
||||
.0
|
||||
.lock()
|
||||
.expect("Failed to acquire mutex lock")
|
||||
.take()
|
||||
else {
|
||||
println!("Server state missing");
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = server_state.kill();
|
||||
|
||||
println!("Killed server");
|
||||
kill_sidecar(app.clone());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": ["deb", "rpm", "dmg", "nsis"],
|
||||
"targets": ["deb", "rpm", "dmg", "nsis", "app"],
|
||||
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
||||
"externalBin": ["sidecars/opencode-cli"],
|
||||
"createUpdaterArtifacts": true,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// @refresh reload
|
||||
import { render } from "solid-js/web"
|
||||
import { App, PlatformProvider, Platform } from "@opencode-ai/desktop"
|
||||
import { runUpdater } from "./updater"
|
||||
import { onMount } from "solid-js"
|
||||
import { open, save } from "@tauri-apps/plugin-dialog"
|
||||
import { open as shellOpen } from "@tauri-apps/plugin-shell"
|
||||
import { type as ostype } from "@tauri-apps/plugin-os"
|
||||
|
||||
import { runUpdater, UPDATER_ENABLED } from "./updater"
|
||||
import { createMenu } from "./menu"
|
||||
|
||||
const root = document.getElementById("root")
|
||||
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||
throw new Error(
|
||||
@@ -48,9 +50,11 @@ const platform: Platform = {
|
||||
},
|
||||
}
|
||||
|
||||
createMenu()
|
||||
|
||||
render(() => {
|
||||
onMount(() => {
|
||||
if (window.__OPENCODE__?.updaterEnabled) runUpdater()
|
||||
if (UPDATER_ENABLED) runUpdater()
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { Menu, MenuItem, PredefinedMenuItem, Submenu } from "@tauri-apps/api/menu"
|
||||
import { type as ostype } from "@tauri-apps/plugin-os"
|
||||
|
||||
import { runUpdater, UPDATER_ENABLED } from "./updater"
|
||||
|
||||
export async function createMenu() {
|
||||
if (ostype() !== "macos") return
|
||||
|
||||
const menu = await Menu.new({
|
||||
items: [
|
||||
await Submenu.new({
|
||||
text: "OpenCode",
|
||||
items: [
|
||||
await PredefinedMenuItem.new({
|
||||
item: { About: null },
|
||||
}),
|
||||
await MenuItem.new({
|
||||
enabled: UPDATER_ENABLED,
|
||||
action: () => runUpdater(),
|
||||
text: "Check For Updates...",
|
||||
}),
|
||||
await PredefinedMenuItem.new({
|
||||
item: "Separator",
|
||||
}),
|
||||
await PredefinedMenuItem.new({
|
||||
item: "Hide",
|
||||
}),
|
||||
await PredefinedMenuItem.new({
|
||||
item: "HideOthers",
|
||||
}),
|
||||
await PredefinedMenuItem.new({
|
||||
item: "ShowAll",
|
||||
}),
|
||||
await PredefinedMenuItem.new({
|
||||
item: "Separator",
|
||||
}),
|
||||
await PredefinedMenuItem.new({
|
||||
item: "Quit",
|
||||
}),
|
||||
].filter(Boolean),
|
||||
}),
|
||||
// await Submenu.new({
|
||||
// text: "File",
|
||||
// items: [
|
||||
// await MenuItem.new({
|
||||
// enabled: false,
|
||||
// text: "Open Project...",
|
||||
// }),
|
||||
// await PredefinedMenuItem.new({
|
||||
// item: "Separator"
|
||||
// }),
|
||||
// await MenuItem.new({
|
||||
// enabled: false,
|
||||
// text: "New Session",
|
||||
// }),
|
||||
// await PredefinedMenuItem.new({
|
||||
// item: "Separator"
|
||||
// }),
|
||||
// await MenuItem.new({
|
||||
// enabled: false,
|
||||
// text: "Close Project",
|
||||
// })
|
||||
// ]
|
||||
// }),
|
||||
await Submenu.new({
|
||||
text: "Edit",
|
||||
items: [
|
||||
await PredefinedMenuItem.new({
|
||||
item: "Undo",
|
||||
}),
|
||||
await PredefinedMenuItem.new({
|
||||
item: "Redo",
|
||||
}),
|
||||
await PredefinedMenuItem.new({
|
||||
item: "Separator",
|
||||
}),
|
||||
await PredefinedMenuItem.new({
|
||||
item: "Cut",
|
||||
}),
|
||||
await PredefinedMenuItem.new({
|
||||
item: "Copy",
|
||||
}),
|
||||
await PredefinedMenuItem.new({
|
||||
item: "Paste",
|
||||
}),
|
||||
await PredefinedMenuItem.new({
|
||||
item: "SelectAll",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
menu.setAsAppMenu()
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { check, DownloadEvent } from "@tauri-apps/plugin-updater"
|
||||
import { relaunch } from "@tauri-apps/plugin-process"
|
||||
import { ask, message } from "@tauri-apps/plugin-dialog"
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
|
||||
export const UPDATER_ENABLED = window.__OPENCODE__?.updaterEnabled ?? false
|
||||
|
||||
export async function runUpdater(onDownloadEvent?: (progress: DownloadEvent) => void) {
|
||||
let update
|
||||
@@ -32,6 +35,7 @@ export async function runUpdater(onDownloadEvent?: (progress: DownloadEvent) =>
|
||||
return false
|
||||
}
|
||||
|
||||
await invoke("kill_sidecar")
|
||||
await relaunch()
|
||||
|
||||
return true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./*": "./src/components/*.tsx",
|
||||
@@ -35,7 +35,7 @@
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@shikijs/transformers": "3.9.2",
|
||||
"@solid-primitives/bounds": "0.1.3",
|
||||
"@solid-primitives/resize-observer": "2.1.3",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type FileContents, File, FileOptions, LineAnnotation } from "@pierre/precision-diffs"
|
||||
import { type FileContents, File, FileOptions, LineAnnotation } from "@pierre/diffs"
|
||||
import { ComponentProps, createEffect, createMemo, splitProps } from "solid-js"
|
||||
import { createDefaultOptions, styleVariables } from "../pierre"
|
||||
import { workerPool } from "../pierre/worker"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FileDiff } from "@pierre/precision-diffs"
|
||||
import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
|
||||
import { FileDiff } from "@pierre/diffs"
|
||||
import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||
import { onCleanup, onMount, Show, splitProps } from "solid-js"
|
||||
import { isServer } from "solid-js/web"
|
||||
import { createDefaultOptions, styleVariables, type DiffProps } from "../pierre"
|
||||
@@ -65,11 +65,11 @@ export function Diff<T>(props: SSRDiffProps<T>) {
|
||||
|
||||
return (
|
||||
<div data-component="diff" style={styleVariables} ref={container}>
|
||||
<file-diff ref={fileDiffRef} id="ssr-diff">
|
||||
<diffs-container ref={fileDiffRef} id="ssr-diff">
|
||||
<Show when={isServer}>
|
||||
<template shadowrootmode="open" innerHTML={props.preloadedDiff.prerenderedHTML} />
|
||||
</Show>
|
||||
</file-diff>
|
||||
</diffs-container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
position: sticky;
|
||||
background-color: var(--surface-diff-hidden-base);
|
||||
color: var(--text-base);
|
||||
width: var(--pjs-column-content-width);
|
||||
left: var(--pjs-column-number-width);
|
||||
width: var(--diffs-column-content-width);
|
||||
left: var(--diffs-column-number-width);
|
||||
padding-left: 8px;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FileDiff } from "@pierre/precision-diffs"
|
||||
import { FileDiff } from "@pierre/diffs"
|
||||
import { createEffect, createMemo, onCleanup, splitProps } from "solid-js"
|
||||
import { createDefaultOptions, type DiffProps, styleVariables } from "../pierre"
|
||||
import { workerPool } from "../pierre/worker"
|
||||
|
||||
@@ -51,6 +51,7 @@ const icons = {
|
||||
"circle-check": `<path d="M12.4987 7.91732L8.7487 12.5007L7.08203 10.834M17.9154 10.0007C17.9154 14.3729 14.371 17.9173 9.9987 17.9173C5.62644 17.9173 2.08203 14.3729 2.08203 10.0007C2.08203 5.6284 5.62644 2.08398 9.9987 2.08398C14.371 2.08398 17.9154 5.6284 17.9154 10.0007Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
copy: `<path d="M6.2513 6.24935V2.91602H17.0846V13.7493H13.7513M13.7513 6.24935V17.0827H2.91797V6.24935H13.7513Z" stroke="currentColor" stroke-linecap="round"/>`,
|
||||
check: `<path d="M5 11.9657L8.37838 14.7529L15 5.83398" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
photo: `<path d="M16.6665 16.6666L11.6665 11.6666L9.99984 13.3333L6.6665 9.99996L3.08317 13.5833M2.9165 2.91663H17.0832V17.0833H2.9165V2.91663ZM13.3332 7.49996C13.3332 8.30537 12.6803 8.95829 11.8748 8.95829C11.0694 8.95829 10.4165 8.30537 10.4165 7.49996C10.4165 6.69454 11.0694 6.04163 11.8748 6.04163C12.6803 6.04163 13.3332 6.69454 13.3332 7.49996Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
}
|
||||
|
||||
export interface IconProps extends ComponentProps<"svg"> {
|
||||
|
||||
@@ -5,30 +5,69 @@
|
||||
color: var(--text-base);
|
||||
text-wrap: pretty;
|
||||
|
||||
/* text-12-regular */
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-small);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--line-height-large); /* 166.667% */
|
||||
letter-spacing: var(--letter-spacing-normal);
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
strong {
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
/* text-12-regular */
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-base);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--line-height-large);
|
||||
letter-spacing: var(--letter-spacing-normal);
|
||||
|
||||
h1 {
|
||||
margin-top: 40px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--text-strong);
|
||||
|
||||
strong {
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 32px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--text-strong);
|
||||
|
||||
strong {
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 24px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--text-strong);
|
||||
|
||||
strong {
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
li {
|
||||
margin-bottom: 12px;
|
||||
line-height: var(--line-height-large);
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
@@ -37,6 +76,14 @@
|
||||
border-color: var(--border-weaker-base);
|
||||
}
|
||||
|
||||
.shiki {
|
||||
font-size: 13px;
|
||||
background: var(--surface-raised-base) !important; /* temporary fix to test style */
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
border: 0.5px solid var(--border-weak-base);
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
@@ -51,7 +98,7 @@
|
||||
:not(pre) > code {
|
||||
font-family: var(--font-family-mono);
|
||||
font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
font-size: 0.9em;
|
||||
font-size: 13px;
|
||||
/* background-color: var(--surface-base-strong); */
|
||||
/* padding: 0.15em 0.35em; */
|
||||
/* border-radius: var(--radius-sm); */
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
[data-component="user-message"] {
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-small);
|
||||
font-size: var(--font-size-base);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--line-height-large);
|
||||
@@ -72,10 +72,15 @@
|
||||
|
||||
[data-slot="user-message-text"] {
|
||||
display: -webkit-box;
|
||||
white-space: pre-wrap;
|
||||
line-clamp: 3;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
background: var(--surface-inset-base);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
border: 0.5px solid var(--border-weak-base);
|
||||
}
|
||||
|
||||
.text-text-strong {
|
||||
@@ -92,6 +97,7 @@
|
||||
|
||||
[data-component="markdown"] {
|
||||
margin-top: 32px;
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +129,7 @@
|
||||
|
||||
[data-slot="message-part-tool-error-title"] {
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-small);
|
||||
font-size: var(--font-size-base);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: var(--line-height-large);
|
||||
@@ -187,7 +193,7 @@
|
||||
[data-slot="message-part-title"] {
|
||||
flex-shrink: 0;
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-small);
|
||||
font-size: var(--font-size-base);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: var(--line-height-large);
|
||||
|
||||
@@ -19,8 +19,6 @@ import { Checkbox } from "./checkbox"
|
||||
import { DiffChanges } from "./diff-changes"
|
||||
import { Markdown } from "./markdown"
|
||||
import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||
import { Code } from "./code"
|
||||
import { checksum } from "@opencode-ai/util/encode"
|
||||
|
||||
export interface MessageProps {
|
||||
message: MessageType
|
||||
@@ -509,19 +507,19 @@ ToolRegistry.register({
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Show when={props.input.content}>
|
||||
<div data-component="write-content">
|
||||
<Code
|
||||
file={{
|
||||
name: props.input.filePath,
|
||||
contents: props.input.content,
|
||||
cacheKey: checksum(props.input.content),
|
||||
}}
|
||||
overflow="scroll"
|
||||
class="pb-40"
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
{/* <Show when={props.input.content}> */}
|
||||
{/* <div data-component="write-content"> */}
|
||||
{/* <Code */}
|
||||
{/* file={{ */}
|
||||
{/* name: props.input.filePath, */}
|
||||
{/* contents: props.input.content, */}
|
||||
{/* cacheKey: checksum(props.input.content), */}
|
||||
{/* }} */}
|
||||
{/* overflow="scroll" */}
|
||||
{/* class="pb-40" */}
|
||||
{/* /> */}
|
||||
{/* </div> */}
|
||||
{/* </Show> */}
|
||||
</BasicTool>
|
||||
)
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
[data-slot="session-review-title"] {
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-base);
|
||||
font-size: var(--font-size-large);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: var(--line-height-large);
|
||||
color: var(--text-strong);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||
import { For, Match, Show, Switch, type JSX } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { type FileDiff } from "@opencode-ai/sdk/v2"
|
||||
import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
|
||||
import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
import { checksum } from "@opencode-ai/util/encode"
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
[data-slot="session-turn-message-title"] {
|
||||
width: 100%;
|
||||
font-size: 14px; /* text-14-medium */
|
||||
font-size: var(--font-size-large);
|
||||
font-weight: 500;
|
||||
color: var(--text-strong);
|
||||
overflow: hidden;
|
||||
@@ -104,17 +104,22 @@
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
align-self: stretch;
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--line-height-x-large);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="session-turn-summary-title"] {
|
||||
font-size: 12px; /* text-12-medium */
|
||||
font-size: 13px; /* text-12-medium */
|
||||
font-weight: 500;
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
[data-slot="session-turn-markdown"] {
|
||||
&[data-diffs="true"] {
|
||||
font-size: 14px; /* text-14-regular */
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
&[data-fade="true"] > * {
|
||||
@@ -319,7 +324,7 @@
|
||||
}
|
||||
|
||||
[data-slot="session-turn-details-text"] {
|
||||
font-size: 12px; /* text-12-medium */
|
||||
font-size: 13px; /* text-12-medium */
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
@@ -269,6 +269,10 @@ export function SessionTurn(
|
||||
|
||||
createEffect((prev) => {
|
||||
const isWorking = working()
|
||||
if (!prev && isWorking) {
|
||||
setStore("stepsExpanded", true)
|
||||
props.onStepsExpandedChange?.(true)
|
||||
}
|
||||
if (prev && !isWorking && !state.userScrolled) {
|
||||
setStore("stepsExpanded", false)
|
||||
props.onStepsExpandedChange?.(false)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Message, Session, Part, FileDiff, SessionStatus } from "@opencode-ai/sdk/v2"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
|
||||
import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||
|
||||
type Data = {
|
||||
session: Session[]
|
||||
|
||||
@@ -1,79 +1,105 @@
|
||||
import { For, Show, type JSX } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
|
||||
type DialogElement = JSX.Element | (() => JSX.Element)
|
||||
|
||||
export const { use: useDialog, provider: DialogProvider } = createSimpleContext({
|
||||
name: "Dialog",
|
||||
init: () => {
|
||||
const [store, setStore] = createStore({
|
||||
stack: [] as {
|
||||
element: DialogElement
|
||||
onClose?: () => void
|
||||
}[],
|
||||
})
|
||||
|
||||
return {
|
||||
get stack() {
|
||||
return store.stack
|
||||
},
|
||||
push(element: DialogElement, onClose?: () => void) {
|
||||
setStore("stack", (s) => [...s, { element, onClose }])
|
||||
},
|
||||
pop() {
|
||||
const current = store.stack.at(-1)
|
||||
current?.onClose?.()
|
||||
setStore("stack", store.stack.slice(0, -1))
|
||||
},
|
||||
replace(element: DialogElement, onClose?: () => void) {
|
||||
for (const item of store.stack) {
|
||||
item.onClose?.()
|
||||
}
|
||||
setStore("stack", [{ element, onClose }])
|
||||
},
|
||||
clear() {
|
||||
for (const item of store.stack) {
|
||||
item.onClose?.()
|
||||
}
|
||||
setStore("stack", [])
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
import {
|
||||
createContext,
|
||||
createEffect,
|
||||
createSignal,
|
||||
getOwner,
|
||||
Owner,
|
||||
ParentProps,
|
||||
runWithOwner,
|
||||
Show,
|
||||
useContext,
|
||||
type JSX,
|
||||
} from "solid-js"
|
||||
import { Dialog as Kobalte } from "@kobalte/core/dialog"
|
||||
|
||||
export function DialogRoot(props: { children?: JSX.Element }) {
|
||||
const dialog = useDialog()
|
||||
type DialogElement = () => JSX.Element
|
||||
|
||||
const Context = createContext<ReturnType<typeof init>>()
|
||||
|
||||
function init() {
|
||||
const [active, setActive] = createSignal<
|
||||
| {
|
||||
id: string
|
||||
element: DialogElement
|
||||
onClose?: () => void
|
||||
owner: Owner
|
||||
}
|
||||
| undefined
|
||||
>()
|
||||
|
||||
const result = {
|
||||
get active() {
|
||||
return active()
|
||||
},
|
||||
close() {
|
||||
active()?.onClose?.()
|
||||
setActive(undefined)
|
||||
},
|
||||
show(element: DialogElement, owner: Owner, onClose?: () => void) {
|
||||
active()?.onClose?.()
|
||||
const id = Math.random().toString(36).slice(2)
|
||||
setActive({
|
||||
id,
|
||||
element: () =>
|
||||
runWithOwner(owner, () => (
|
||||
<Show when={active()?.id === id}>
|
||||
<Kobalte
|
||||
modal
|
||||
open={true}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
console.log("closing")
|
||||
result.close()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Kobalte.Portal>
|
||||
<Kobalte.Overlay data-component="dialog-overlay" />
|
||||
{element()}
|
||||
</Kobalte.Portal>
|
||||
</Kobalte>
|
||||
</Show>
|
||||
)),
|
||||
onClose,
|
||||
owner,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function DialogProvider(props: ParentProps) {
|
||||
const ctx = init()
|
||||
createEffect(() => {
|
||||
console.log("active", ctx.active)
|
||||
})
|
||||
return (
|
||||
<>
|
||||
<Context.Provider value={ctx}>
|
||||
{props.children}
|
||||
<Show when={dialog.stack.length > 0}>
|
||||
<div data-component="dialog-stack">
|
||||
<For each={dialog.stack}>
|
||||
{(item, index) => (
|
||||
<Show when={index() === dialog.stack.length - 1}>
|
||||
<Kobalte
|
||||
modal
|
||||
defaultOpen
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
item.onClose?.()
|
||||
dialog.pop()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Kobalte.Portal>
|
||||
<Kobalte.Overlay data-component="dialog-overlay" />
|
||||
{typeof item.element === "function" ? item.element() : item.element}
|
||||
</Kobalte.Portal>
|
||||
</Kobalte>
|
||||
</Show>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</>
|
||||
<div data-component="dialog-stack">{ctx.active?.element?.()}</div>
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useDialog() {
|
||||
const ctx = useContext(Context)
|
||||
const owner = getOwner()
|
||||
if (!owner) {
|
||||
throw new Error("useDialog must be used within a DialogProvider")
|
||||
}
|
||||
if (!ctx) {
|
||||
throw new Error("useDialog must be used within a DialogProvider")
|
||||
}
|
||||
return {
|
||||
get active() {
|
||||
return ctx.active
|
||||
},
|
||||
show(element: DialogElement, onClose?: () => void) {
|
||||
ctx.show(element, owner, onClose)
|
||||
},
|
||||
close() {
|
||||
ctx.close()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { marked } from "marked"
|
||||
import markedShiki from "marked-shiki"
|
||||
import { bundledLanguages, type BundledLanguage } from "shiki"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { getSharedHighlighter, registerCustomTheme, ThemeRegistrationResolved } from "@pierre/precision-diffs"
|
||||
import { getSharedHighlighter, registerCustomTheme, ThemeRegistrationResolved } from "@pierre/diffs"
|
||||
|
||||
registerCustomTheme("OpenCode", () => {
|
||||
return Promise.resolve({
|
||||
|
||||
Vendored
+4
-4
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* TypeScript declaration for the <file-diff> custom element.
|
||||
* This tells TypeScript that <file-diff> is a valid JSX element in SolidJS.
|
||||
* Required for using the precision-diffs web component in .tsx files.
|
||||
* TypeScript declaration for the <diffs-container> custom element.
|
||||
* This tells TypeScript that <diffs-container> is a valid JSX element in SolidJS.
|
||||
* Required for using the @pierre/diffs web component in .tsx files.
|
||||
*/
|
||||
declare module "solid-js" {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
"file-diff": HTMLAttributes<HTMLElement>
|
||||
"diffs-container": HTMLAttributes<HTMLElement>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DiffLineAnnotation, FileContents, FileDiffOptions } from "@pierre/precision-diffs"
|
||||
import { DiffLineAnnotation, FileContents, FileDiffOptions } from "@pierre/diffs"
|
||||
import { ComponentProps } from "solid-js"
|
||||
|
||||
export type DiffProps<T = {}> = FileDiffOptions<T> & {
|
||||
@@ -10,8 +10,8 @@ export type DiffProps<T = {}> = FileDiffOptions<T> & {
|
||||
}
|
||||
|
||||
const unsafeCSS = `
|
||||
[data-pjs-header],
|
||||
[data-pjs] {
|
||||
[data-diffs-header],
|
||||
[data-diffs] {
|
||||
[data-separator-wrapper] {
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
@@ -71,12 +71,12 @@ export function createDefaultOptions<T>(style: FileDiffOptions<T>["diffStyle"])
|
||||
}
|
||||
|
||||
export const styleVariables = {
|
||||
"--pjs-font-family": "var(--font-family-mono)",
|
||||
"--pjs-font-size": "var(--font-size-small)",
|
||||
"--pjs-line-height": "24px",
|
||||
"--pjs-tab-size": 2,
|
||||
"--pjs-font-features": "var(--font-family-mono--font-feature-settings)",
|
||||
"--pjs-header-font-family": "var(--font-family-sans)",
|
||||
"--pjs-gap-block": 0,
|
||||
"--pjs-min-number-column-width": "4ch",
|
||||
"--diffs-font-family": "var(--font-family-mono)",
|
||||
"--diffs-font-size": "var(--font-size-small)",
|
||||
"--diffs-line-height": "24px",
|
||||
"--diffs-tab-size": 2,
|
||||
"--diffs-font-features": "var(--font-family-mono--font-feature-settings)",
|
||||
"--diffs-header-font-family": "var(--font-family-sans)",
|
||||
"--diffs-gap-block": 0,
|
||||
"--diffs-min-number-column-width": "4ch",
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getOrCreateWorkerPoolSingleton } from "@pierre/precision-diffs/worker"
|
||||
import ShikiWorkerUrl from "@pierre/precision-diffs/worker/worker.js?worker&url"
|
||||
import { getOrCreateWorkerPoolSingleton } from "@pierre/diffs/worker"
|
||||
import ShikiWorkerUrl from "@pierre/diffs/worker/worker.js?worker&url"
|
||||
|
||||
export function workerFactory(): Worker {
|
||||
return new Worker(ShikiWorkerUrl, { type: "module" })
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
--font-size-x-large: 20px;
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-medium: 500;
|
||||
--line-height-large: 20px;
|
||||
--line-height-x-large: 24px;
|
||||
--line-height-2x-large: 30px;
|
||||
--line-height-large: 150%;
|
||||
--line-height-x-large: 180%;
|
||||
--line-height-2x-large: 200%;
|
||||
--letter-spacing-normal: 0;
|
||||
--letter-spacing-tight: -0.1599999964237213;
|
||||
--letter-spacing-tightest: -0.3199999928474426;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.0.155",
|
||||
"version": "1.0.163",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user