mirror of
https://github.com/anomalyco/opencode.git
synced 2026-02-10 02:44:21 +00:00
Compare commits
24 Commits
sqlite2
...
ripgrep-tr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c34d87a16 | ||
|
|
204f6f3d03 | ||
|
|
1db70ac06a | ||
|
|
a36db14008 | ||
|
|
596563f8c1 | ||
|
|
97b3e39d4c | ||
|
|
4cfcc422f4 | ||
|
|
08a4916744 | ||
|
|
0833265440 | ||
|
|
512ddf3963 | ||
|
|
89109da9c2 | ||
|
|
a4509af4ac | ||
|
|
7a9e1fcb40 | ||
|
|
f148d38b49 | ||
|
|
0ccae0cea6 | ||
|
|
f890f45df3 | ||
|
|
2c12ee5a20 | ||
|
|
bcf8c33b11 | ||
|
|
8aba574555 | ||
|
|
f51a9c4fce | ||
|
|
2d99b33466 | ||
|
|
14dc940aac | ||
|
|
cb3337f01e | ||
|
|
b2121c963f |
10
.github/workflows/publish.yml
vendored
10
.github/workflows/publish.yml
vendored
@@ -33,9 +33,10 @@ permissions:
|
||||
jobs:
|
||||
version:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
if: github.repository == 'anomalyco/opencode'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -63,7 +64,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: github.repository == 'anomalyco/opencode'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-tags: true
|
||||
|
||||
@@ -107,7 +108,7 @@ jobs:
|
||||
target: aarch64-unknown-linux-gnu
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-tags: true
|
||||
|
||||
@@ -208,8 +209,9 @@ jobs:
|
||||
- build-cli
|
||||
- build-tauri
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
sst-env.d.ts
|
||||
sst-env.d.ts
|
||||
desktop/src/bindings.ts
|
||||
|
||||
20
bun.lock
20
bun.lock
@@ -298,8 +298,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.5.4",
|
||||
"@opentui/core": "0.1.75",
|
||||
"@opentui/solid": "0.1.75",
|
||||
"@opentui/core": "0.1.77",
|
||||
"@opentui/solid": "0.1.77",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -1227,21 +1227,21 @@
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.1.75", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.75", "@opentui/core-darwin-x64": "0.1.75", "@opentui/core-linux-arm64": "0.1.75", "@opentui/core-linux-x64": "0.1.75", "@opentui/core-win32-arm64": "0.1.75", "@opentui/core-win32-x64": "0.1.75", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-8ARRZxSG+BXkJmEVtM2DQ4se7DAF1ZCKD07d+AklgTr2mxCzmdxxPbOwRzboSQ6FM7qGuTVPVbV4O2W9DpUmoA=="],
|
||||
"@opentui/core": ["@opentui/core@0.1.77", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.77", "@opentui/core-darwin-x64": "0.1.77", "@opentui/core-linux-arm64": "0.1.77", "@opentui/core-linux-x64": "0.1.77", "@opentui/core-win32-arm64": "0.1.77", "@opentui/core-win32-x64": "0.1.77", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-lE3kabm6jdqK3AuBq+O0zZrXdxt6ulmibTc57sf+AsPny6cmwYHnWI4wD6hcreFiYoQVNVvdiJchVgPtowMlEg=="],
|
||||
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.75", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gGaGZjkFpqcXJk6321JzhRl66pM2VxBlI470L8W4DQUW4S6iDT1R9L7awSzGB4Cn9toUl7DTV8BemaXZYXV4SA=="],
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.77", "", { "os": "darwin", "cpu": "arm64" }, "sha512-SNqmygCMEsPCW7xWjzCZ5caBf36xaprwVdAnFijGDOuIzLA4iaDa6um8cj3TJh7awenN3NTRsuRc7OuH42UH+g=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.75", "", { "os": "darwin", "cpu": "x64" }, "sha512-tPlvqQI0whZ76amHydpJs5kN+QeWAIcFbI8RAtlAo9baj2EbxTDC+JGwgb9Fnt0/YQx831humbtaNDhV2Jt1bw=="],
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.77", "", { "os": "darwin", "cpu": "x64" }, "sha512-/8fsa03swEHTQt/9NrGm98kemlU+VuTURI/OFZiH53vPDRrOYIYoa4Jyga/H7ZMcG+iFhkq97zIe+0Kw95LGmA=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.75", "", { "os": "linux", "cpu": "arm64" }, "sha512-nVxIQ4Hqf84uBergDpWiVzU6pzpjy6tqBHRQpySxZ2flkJ/U6/aMEizVrQ1jcgIdxZtvqWDETZhzxhG0yDx+cw=="],
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.77", "", { "os": "linux", "cpu": "arm64" }, "sha512-QfUXZJPc69OvqoMu+AlLgjqXrwu4IeqcBuUWYMuH8nPTeLsVUc3CBbXdV2lv9UDxWzxzrxdS4ALPaxvmEv9lsQ=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.75", "", { "os": "linux", "cpu": "x64" }, "sha512-1CnApef4kxA+ORyLfbuCLgZfEjp4wr3HjFnt7FAfOb73kIZH82cb7JYixeqRyy9eOcKfKqxLmBYy3o8IDkc4Rg=="],
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.77", "", { "os": "linux", "cpu": "x64" }, "sha512-Kmfx0yUKnPj67AoXYIgL7qQo0QVsUG5Iw8aRtv6XFzXqa5SzBPhaKkKZ9yHPjOmTalZquUs+9zcCRNKpYYuL7A=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.75", "", { "os": "win32", "cpu": "arm64" }, "sha512-j0UB95nmkYGNzmOrs6GqaddO1S90R0YC6IhbKnbKBdjchFPNVLz9JpexAs6MBDXPZwdKAywMxtwG2h3aTJtxng=="],
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.77", "", { "os": "win32", "cpu": "arm64" }, "sha512-HGTscPXc7gdd23Nh1DbzUNjog1I+5IZp95XPtLftGTpjrWs60VcetXcyJqK2rQcXNxewJK5yDyaa5QyMjfEhCQ=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.75", "", { "os": "win32", "cpu": "x64" }, "sha512-ESpVZVGewe3JkB2TwrG3VRbkxT909iPdtvgNT7xTCIYH2VB4jqZomJfvERPTE0tvqAZJm19mHECzJFI8asSJgQ=="],
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.77", "", { "os": "win32", "cpu": "x64" }, "sha512-c7GijsbvVgnlzd2murIbwuwrGbcv76KdUw6WlVv7a0vex50z6xJCpv1keGzpe0QfxrZ/6fFEFX7JnwGLno0wjA=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.1.75", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.75", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-WjKsZIfrm29znfRlcD9w3uUn/+uvoy2MmeoDwTvg1YOa0OjCTCmjZ43L9imp0m9S4HmVU8ma6o2bR4COzcyDdg=="],
|
||||
"@opentui/solid": ["@opentui/solid@0.1.77", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.77", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-JY+hUbXVV+XCk6bC8dvcwawWCEmC3Gid6GDs23AJWBgHZ3TU2kRKrgwTdltm45DOq2cZXrYCt690/yE8bP+Gxg=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-06Otz3loT4vn0578VDxUqVudtzQvV7oM3EIzjZnsejo=",
|
||||
"aarch64-linux": "sha256-88Qai5RkSenCZkakOg52b6xU2ok+h/Ns4/5L3+55sFY=",
|
||||
"aarch64-darwin": "sha256-x8dgCF0CJBWi2dZLDHMGdlTqys1X755ok0PM6x0HAGo=",
|
||||
"x86_64-darwin": "sha256-FkLDqorfIfOw+tB7SW5vgyhOIoI0IV9lqPW1iEmvUiI="
|
||||
"x86_64-linux": "sha256-4I0lpBnbAi7IZMURTMLysjrqdsNvXJf8802NrJnpdks=",
|
||||
"aarch64-linux": "sha256-WOGKsPlcQVSbL8TDr1JYO/2ucPTV2Hy0TXJKWv8EoVw=",
|
||||
"aarch64-darwin": "sha256-LuvjwGm1QsHoLxuvSSp4VsDIv02Z/rTONsU32arQMuw=",
|
||||
"x86_64-darwin": "sha256-AbglfgCWj/r+wHfle+e+D3b/xPcwwg4IK7j5iwn9nzw="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ import type { JSX } from "solid-js"
|
||||
import { createSortable } from "@thisbeyond/solid-dnd"
|
||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { getFilename } from "@opencode-ai/util/path"
|
||||
import { useFile } from "@/context/file"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useCommand } from "@/context/command"
|
||||
|
||||
export function FileVisual(props: { path: string; active?: boolean }): JSX.Element {
|
||||
return (
|
||||
@@ -27,6 +28,7 @@ export function FileVisual(props: { path: string; active?: boolean }): JSX.Eleme
|
||||
export function SortableTab(props: { tab: string; onTabClose: (tab: string) => void }): JSX.Element {
|
||||
const file = useFile()
|
||||
const language = useLanguage()
|
||||
const command = useCommand()
|
||||
const sortable = createSortable(props.tab)
|
||||
const path = createMemo(() => file.pathFromTab(props.tab))
|
||||
return (
|
||||
@@ -36,7 +38,11 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
|
||||
<Tabs.Trigger
|
||||
value={props.tab}
|
||||
closeButton={
|
||||
<Tooltip value={language.t("common.closeTab")} placement="bottom">
|
||||
<TooltipKeybind
|
||||
title={language.t("common.closeTab")}
|
||||
keybind={command.keybind("tab.close")}
|
||||
placement="bottom"
|
||||
>
|
||||
<IconButton
|
||||
icon="close-small"
|
||||
variant="ghost"
|
||||
@@ -44,7 +50,7 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
|
||||
onClick={() => props.onTabClose(props.tab)}
|
||||
aria-label={language.t("common.closeTab")}
|
||||
/>
|
||||
</Tooltip>
|
||||
</TooltipKeybind>
|
||||
}
|
||||
hideCloseButton
|
||||
onMiddleClick={() => props.onTabClose(props.tab)}
|
||||
|
||||
@@ -24,6 +24,8 @@ export function Titlebar() {
|
||||
const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
|
||||
const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
|
||||
const web = createMemo(() => platform.platform === "web")
|
||||
const zoom = () => platform.webviewZoom?.() ?? 1
|
||||
const minHeight = () => (mac() ? `${40 / zoom()}px` : undefined)
|
||||
|
||||
const [history, setHistory] = createStore({
|
||||
stack: [] as string[],
|
||||
@@ -134,6 +136,7 @@ export function Titlebar() {
|
||||
return (
|
||||
<header
|
||||
class="h-10 shrink-0 bg-background-base relative grid grid-cols-[auto_minmax(0,1fr)_auto] items-center"
|
||||
style={{ "min-height": minHeight() }}
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<div
|
||||
@@ -145,7 +148,7 @@ export function Titlebar() {
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<Show when={mac()}>
|
||||
<div class="w-[72px] h-full shrink-0" data-tauri-drag-region />
|
||||
<div class="h-full shrink-0" style={{ width: `${72 / zoom()}px` }} data-tauri-drag-region />
|
||||
<div class="xl:hidden w-10 shrink-0 flex items-center justify-center">
|
||||
<IconButton
|
||||
icon="menu"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { AsyncStorage, SyncStorage } from "@solid-primitives/storage"
|
||||
import type { Accessor } from "solid-js"
|
||||
|
||||
export type Platform = {
|
||||
/** Platform discriminator */
|
||||
@@ -55,6 +56,9 @@ export type Platform = {
|
||||
|
||||
/** Parse markdown to HTML using native parser (desktop only, returns unprocessed code blocks) */
|
||||
parseMarkdown?(markdown: string): Promise<string>
|
||||
|
||||
/** Webview zoom level (desktop only) */
|
||||
webviewZoom?: Accessor<number>
|
||||
}
|
||||
|
||||
export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({
|
||||
|
||||
@@ -28,6 +28,8 @@ export const dict = {
|
||||
"command.settings.open": "فتح الإعدادات",
|
||||
"command.session.previous": "الجلسة السابقة",
|
||||
"command.session.next": "الجلسة التالية",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "أرشفة الجلسة",
|
||||
|
||||
"command.palette": "لوحة الأوامر",
|
||||
|
||||
@@ -28,6 +28,8 @@ export const dict = {
|
||||
"command.settings.open": "Abrir configurações",
|
||||
"command.session.previous": "Sessão anterior",
|
||||
"command.session.next": "Próxima sessão",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "Arquivar sessão",
|
||||
|
||||
"command.palette": "Paleta de comandos",
|
||||
|
||||
@@ -28,6 +28,8 @@ export const dict = {
|
||||
"command.settings.open": "Åbn indstillinger",
|
||||
"command.session.previous": "Forrige session",
|
||||
"command.session.next": "Næste session",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "Arkivér session",
|
||||
|
||||
"command.palette": "Kommandopalette",
|
||||
|
||||
@@ -32,6 +32,8 @@ export const dict = {
|
||||
"command.settings.open": "Einstellungen öffnen",
|
||||
"command.session.previous": "Vorherige Sitzung",
|
||||
"command.session.next": "Nächste Sitzung",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "Sitzung archivieren",
|
||||
|
||||
"command.palette": "Befehlspalette",
|
||||
|
||||
@@ -28,6 +28,8 @@ export const dict = {
|
||||
"command.settings.open": "Open settings",
|
||||
"command.session.previous": "Previous session",
|
||||
"command.session.next": "Next session",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "Archive session",
|
||||
|
||||
"command.palette": "Command palette",
|
||||
@@ -43,6 +45,7 @@ export const dict = {
|
||||
"command.session.new": "New session",
|
||||
"command.file.open": "Open file",
|
||||
"command.file.open.description": "Search files and commands",
|
||||
"command.tab.close": "Close tab",
|
||||
"command.context.addSelection": "Add selection to context",
|
||||
"command.context.addSelection.description": "Add selected lines from the current file",
|
||||
"command.terminal.toggle": "Toggle terminal",
|
||||
|
||||
@@ -28,6 +28,8 @@ export const dict = {
|
||||
"command.settings.open": "Abrir ajustes",
|
||||
"command.session.previous": "Sesión anterior",
|
||||
"command.session.next": "Siguiente sesión",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "Archivar sesión",
|
||||
|
||||
"command.palette": "Paleta de comandos",
|
||||
|
||||
@@ -28,6 +28,8 @@ export const dict = {
|
||||
"command.settings.open": "Ouvrir les paramètres",
|
||||
"command.session.previous": "Session précédente",
|
||||
"command.session.next": "Session suivante",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "Archiver la session",
|
||||
|
||||
"command.palette": "Palette de commandes",
|
||||
|
||||
@@ -28,6 +28,8 @@ export const dict = {
|
||||
"command.settings.open": "設定を開く",
|
||||
"command.session.previous": "前のセッション",
|
||||
"command.session.next": "次のセッション",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "セッションをアーカイブ",
|
||||
|
||||
"command.palette": "コマンドパレット",
|
||||
|
||||
@@ -32,6 +32,8 @@ export const dict = {
|
||||
"command.settings.open": "설정 열기",
|
||||
"command.session.previous": "이전 세션",
|
||||
"command.session.next": "다음 세션",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "세션 보관",
|
||||
|
||||
"command.palette": "명령 팔레트",
|
||||
|
||||
@@ -31,6 +31,8 @@ export const dict = {
|
||||
"command.settings.open": "Åpne innstillinger",
|
||||
"command.session.previous": "Forrige sesjon",
|
||||
"command.session.next": "Neste sesjon",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "Arkiver sesjon",
|
||||
|
||||
"command.palette": "Kommandopalett",
|
||||
|
||||
@@ -28,6 +28,8 @@ export const dict = {
|
||||
"command.settings.open": "Otwórz ustawienia",
|
||||
"command.session.previous": "Poprzednia sesja",
|
||||
"command.session.next": "Następna sesja",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "Zarchiwizuj sesję",
|
||||
|
||||
"command.palette": "Paleta poleceń",
|
||||
|
||||
@@ -28,6 +28,8 @@ export const dict = {
|
||||
"command.settings.open": "Открыть настройки",
|
||||
"command.session.previous": "Предыдущая сессия",
|
||||
"command.session.next": "Следующая сессия",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "Архивировать сессию",
|
||||
|
||||
"command.palette": "Палитра команд",
|
||||
|
||||
@@ -28,6 +28,8 @@ export const dict = {
|
||||
"command.settings.open": "เปิดการตั้งค่า",
|
||||
"command.session.previous": "เซสชันก่อนหน้า",
|
||||
"command.session.next": "เซสชันถัดไป",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "จัดเก็บเซสชัน",
|
||||
|
||||
"command.palette": "คำสั่งค้นหา",
|
||||
|
||||
@@ -32,6 +32,8 @@ export const dict = {
|
||||
"command.settings.open": "打开设置",
|
||||
"command.session.previous": "上一个会话",
|
||||
"command.session.next": "下一个会话",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "归档会话",
|
||||
|
||||
"command.palette": "命令面板",
|
||||
|
||||
@@ -32,6 +32,8 @@ export const dict = {
|
||||
"command.settings.open": "開啟設定",
|
||||
"command.session.previous": "上一個工作階段",
|
||||
"command.session.next": "下一個工作階段",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
"command.session.next.unseen": "Next unread session",
|
||||
"command.session.archive": "封存工作階段",
|
||||
|
||||
"command.palette": "命令面板",
|
||||
|
||||
@@ -886,6 +886,52 @@ export default function Layout(props: ParentProps) {
|
||||
queueMicrotask(() => scrollToSession(session.id, `${session.directory}:${session.id}`))
|
||||
}
|
||||
|
||||
function navigateSessionByUnseen(offset: number) {
|
||||
const sessions = currentSessions()
|
||||
if (sessions.length === 0) return
|
||||
|
||||
const hasUnseen = sessions.some((session) => notification.session.unseen(session.id).length > 0)
|
||||
if (!hasUnseen) return
|
||||
|
||||
const activeIndex = params.id ? sessions.findIndex((s) => s.id === params.id) : -1
|
||||
const start = activeIndex === -1 ? (offset > 0 ? -1 : 0) : activeIndex
|
||||
|
||||
for (let i = 1; i <= sessions.length; i++) {
|
||||
const index = offset > 0 ? (start + i) % sessions.length : (start - i + sessions.length) % sessions.length
|
||||
const session = sessions[index]
|
||||
if (!session) continue
|
||||
if (notification.session.unseen(session.id).length === 0) continue
|
||||
|
||||
prefetchSession(session, "high")
|
||||
|
||||
const next = sessions[(index + 1) % sessions.length]
|
||||
const prev = sessions[(index - 1 + sessions.length) % sessions.length]
|
||||
|
||||
if (offset > 0) {
|
||||
if (next) prefetchSession(next, "high")
|
||||
if (prev) prefetchSession(prev)
|
||||
}
|
||||
|
||||
if (offset < 0) {
|
||||
if (prev) prefetchSession(prev, "high")
|
||||
if (next) prefetchSession(next)
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
navStart({
|
||||
dir: base64Encode(session.directory),
|
||||
from: params.id,
|
||||
to: session.id,
|
||||
trigger: offset > 0 ? "shift+alt+arrowdown" : "shift+alt+arrowup",
|
||||
})
|
||||
}
|
||||
|
||||
navigateToSession(session)
|
||||
queueMicrotask(() => scrollToSession(session.id, `${session.directory}:${session.id}`))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
async function archiveSession(session: Session) {
|
||||
const [store, setStore] = globalSync.child(session.directory)
|
||||
const sessions = store.session ?? []
|
||||
@@ -1024,6 +1070,20 @@ export default function Layout(props: ParentProps) {
|
||||
keybind: "alt+arrowdown",
|
||||
onSelect: () => navigateSessionByOffset(1),
|
||||
},
|
||||
{
|
||||
id: "session.previous.unseen",
|
||||
title: language.t("command.session.previous.unseen"),
|
||||
category: language.t("command.category.session"),
|
||||
keybind: "shift+alt+arrowup",
|
||||
onSelect: () => navigateSessionByUnseen(-1),
|
||||
},
|
||||
{
|
||||
id: "session.next.unseen",
|
||||
title: language.t("command.session.next.unseen"),
|
||||
category: language.t("command.category.session"),
|
||||
keybind: "shift+alt+arrowdown",
|
||||
onSelect: () => navigateSessionByUnseen(1),
|
||||
},
|
||||
{
|
||||
id: "session.archive",
|
||||
title: language.t("command.session.archive"),
|
||||
|
||||
@@ -689,6 +689,18 @@ export default function Page() {
|
||||
slash: "open",
|
||||
onSelect: () => dialog.show(() => <DialogSelectFile onOpenFile={() => showAllFiles()} />),
|
||||
},
|
||||
{
|
||||
id: "tab.close",
|
||||
title: language.t("command.tab.close"),
|
||||
category: language.t("command.category.file"),
|
||||
keybind: "mod+w",
|
||||
disabled: !tabs().active(),
|
||||
onSelect: () => {
|
||||
const active = tabs().active()
|
||||
if (!active) return
|
||||
tabs().close(active)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "context.addSelection",
|
||||
title: language.t("command.context.addSelection"),
|
||||
@@ -1940,7 +1952,8 @@ export default function Page() {
|
||||
"sticky top-0 z-30 bg-background-stronger": true,
|
||||
"w-full": true,
|
||||
"px-4 md:px-6": true,
|
||||
"md:max-w-200 md:mx-auto": centered(),
|
||||
"md:max-w-200 md:mx-auto 3xl:max-w-[1200px] 3xl:mx-auto 4xl:max-w-[1600px] 4xl:mx-auto 5xl:max-w-[1900px] 5xl:mx-auto":
|
||||
centered(),
|
||||
}}
|
||||
>
|
||||
<div class="h-10 flex items-center gap-1">
|
||||
@@ -1968,7 +1981,8 @@ export default function Page() {
|
||||
class="flex flex-col gap-32 items-start justify-start pb-[calc(var(--prompt-height,8rem)+64px)] md:pb-[calc(var(--prompt-height,10rem)+64px)] transition-[margin]"
|
||||
classList={{
|
||||
"w-full": true,
|
||||
"md:max-w-200 md:mx-auto": centered(),
|
||||
"md:max-w-200 md:mx-auto 3xl:max-w-[1200px] 3xl:mx-auto 4xl:max-w-[1600px] 4xl:mx-auto 5xl:max-w-[1900px] 5xl:mx-auto":
|
||||
centered(),
|
||||
"mt-0.5": centered(),
|
||||
"mt-0": !centered(),
|
||||
}}
|
||||
@@ -2021,7 +2035,7 @@ export default function Page() {
|
||||
data-message-id={message.id}
|
||||
classList={{
|
||||
"min-w-0 w-full max-w-full": true,
|
||||
"md:max-w-200": centered(),
|
||||
"md:max-w-200 3xl:max-w-[1200px] 4xl:max-w-[1600px] 5xl:max-w-[1900px]": centered(),
|
||||
}}
|
||||
>
|
||||
<SessionTurn
|
||||
@@ -2078,7 +2092,7 @@ export default function Page() {
|
||||
<div
|
||||
classList={{
|
||||
"w-full px-4 pointer-events-auto": true,
|
||||
"md:max-w-200 md:mx-auto": centered(),
|
||||
"md:max-w-200 3xl:max-w-[1200px] 4xl:max-w-[1600px] 5xl:max-w-[1900px]": centered(),
|
||||
}}
|
||||
>
|
||||
<Show when={request()} keyed>
|
||||
|
||||
@@ -9,8 +9,8 @@ export const config = {
|
||||
github: {
|
||||
repoUrl: "https://github.com/anomalyco/opencode",
|
||||
starsFormatted: {
|
||||
compact: "80K",
|
||||
full: "80,000",
|
||||
compact: "95K",
|
||||
full: "95,000",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,8 +22,8 @@ export const config = {
|
||||
|
||||
// Static stats (used on landing page)
|
||||
stats: {
|
||||
contributors: "600",
|
||||
commits: "7,500",
|
||||
monthlyUsers: "1.5M",
|
||||
contributors: "650",
|
||||
commits: "8,500",
|
||||
monthlyUsers: "2.5M",
|
||||
},
|
||||
} as const
|
||||
|
||||
@@ -2,13 +2,17 @@ import { Database, eq, and, sql, inArray } from "@opencode-ai/console-core/drizz
|
||||
import { IpRateLimitTable } from "@opencode-ai/console-core/schema/ip.sql.js"
|
||||
import { RateLimitError } from "./error"
|
||||
import { logger } from "./logger"
|
||||
import { ZenData } from "@opencode-ai/console-core/model.js"
|
||||
|
||||
export function createRateLimiter(limit: number | undefined, rawIp: string) {
|
||||
export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: string) {
|
||||
if (!limit) return
|
||||
|
||||
const ip = !rawIp.length ? "unknown" : rawIp
|
||||
const now = Date.now()
|
||||
const intervals = [buildYYYYMMDDHH(now), buildYYYYMMDDHH(now - 3_600_000), buildYYYYMMDDHH(now - 7_200_000)]
|
||||
const intervals =
|
||||
limit.period === "day"
|
||||
? [buildYYYYMMDD(now)]
|
||||
: [buildYYYYMMDDHH(now), buildYYYYMMDDHH(now - 3_600_000), buildYYYYMMDDHH(now - 7_200_000)]
|
||||
|
||||
return {
|
||||
track: async () => {
|
||||
@@ -28,11 +32,18 @@ export function createRateLimiter(limit: number | undefined, rawIp: string) {
|
||||
)
|
||||
const total = rows.reduce((sum, r) => sum + r.count, 0)
|
||||
logger.debug(`rate limit total: ${total}`)
|
||||
if (total >= limit) throw new RateLimitError(`Rate limit exceeded. Please try again later.`)
|
||||
if (total >= limit.value) throw new RateLimitError(`Rate limit exceeded. Please try again later.`)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function buildYYYYMMDD(timestamp: number) {
|
||||
return new Date(timestamp)
|
||||
.toISOString()
|
||||
.replace(/[^0-9]/g, "")
|
||||
.substring(0, 8)
|
||||
}
|
||||
|
||||
function buildYYYYMMDDHH(timestamp: number) {
|
||||
return new Date(timestamp)
|
||||
.toISOString()
|
||||
|
||||
@@ -18,8 +18,13 @@ export namespace ZenData {
|
||||
}),
|
||||
),
|
||||
})
|
||||
const RateLimitSchema = z.object({
|
||||
period: z.enum(["day", "rolling"]),
|
||||
value: z.number().int(),
|
||||
})
|
||||
export type Format = z.infer<typeof FormatSchema>
|
||||
export type Trial = z.infer<typeof TrialSchema>
|
||||
export type RateLimit = z.infer<typeof RateLimitSchema>
|
||||
|
||||
const ModelCostSchema = z.object({
|
||||
input: z.number(),
|
||||
@@ -37,7 +42,7 @@ export namespace ZenData {
|
||||
byokProvider: z.enum(["openai", "anthropic", "google"]).optional(),
|
||||
stickyProvider: z.enum(["strict", "prefer"]).optional(),
|
||||
trial: TrialSchema.optional(),
|
||||
rateLimit: z.number().optional(),
|
||||
rateLimit: RateLimitSchema.optional(),
|
||||
fallbackProvider: z.string().optional(),
|
||||
providers: z.array(
|
||||
z.object({
|
||||
|
||||
247
packages/desktop/src-tauri/Cargo.lock
generated
247
packages/desktop/src-tauri/Cargo.lock
generated
@@ -2,6 +2,12 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
@@ -1994,9 +2000,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ico"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98"
|
||||
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"png 0.17.16",
|
||||
@@ -3065,12 +3071,14 @@ dependencies = [
|
||||
"listeners",
|
||||
"objc2 0.6.3",
|
||||
"objc2-web-kit",
|
||||
"reqwest",
|
||||
"reqwest 0.12.24",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta",
|
||||
"specta-typescript",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-build 2.5.2",
|
||||
"tauri-plugin-clipboard-manager",
|
||||
"tauri-plugin-decorum",
|
||||
"tauri-plugin-deep-link",
|
||||
@@ -3085,6 +3093,7 @@ dependencies = [
|
||||
"tauri-plugin-store",
|
||||
"tauri-plugin-updater",
|
||||
"tauri-plugin-window-state",
|
||||
"tauri-specta",
|
||||
"tokio",
|
||||
"uuid",
|
||||
"webkit2gtk",
|
||||
@@ -3221,6 +3230,12 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.3"
|
||||
@@ -3947,6 +3962,40 @@ dependencies = [
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.15.4"
|
||||
@@ -4497,6 +4546,44 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "specta"
|
||||
version = "2.0.0-rc.22"
|
||||
source = "git+https://github.com/specta-rs/specta?rev=106425eac4964d8ff34d3a02f1612e33117b08bb#106425eac4964d8ff34d3a02f1612e33117b08bb"
|
||||
dependencies = [
|
||||
"paste",
|
||||
"rustc_version",
|
||||
"specta-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "specta-macros"
|
||||
version = "2.0.0-rc.18"
|
||||
source = "git+https://github.com/specta-rs/specta?rev=106425eac4964d8ff34d3a02f1612e33117b08bb#106425eac4964d8ff34d3a02f1612e33117b08bb"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "specta-serde"
|
||||
version = "0.0.9"
|
||||
source = "git+https://github.com/specta-rs/specta?rev=106425eac4964d8ff34d3a02f1612e33117b08bb#106425eac4964d8ff34d3a02f1612e33117b08bb"
|
||||
dependencies = [
|
||||
"specta",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "specta-typescript"
|
||||
version = "0.0.9"
|
||||
source = "git+https://github.com/specta-rs/specta?rev=106425eac4964d8ff34d3a02f1612e33117b08bb#106425eac4964d8ff34d3a02f1612e33117b08bb"
|
||||
dependencies = [
|
||||
"specta",
|
||||
"specta-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
@@ -4712,9 +4799,8 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e492485dd390b35f7497401f67694f46161a2a00ffd800938d5dd3c898fb9d8"
|
||||
version = "2.9.5"
|
||||
source = "git+https://github.com/tauri-apps/tauri?rev=4d5d78daf636feaac20c5bc48a6071491c4291ee#4d5d78daf636feaac20c5bc48a6071491c4291ee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -4740,17 +4826,18 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
"reqwest",
|
||||
"reqwest 0.13.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"serialize-to-javascript",
|
||||
"specta",
|
||||
"swift-rs",
|
||||
"tauri-build",
|
||||
"tauri-build 2.5.3",
|
||||
"tauri-macros",
|
||||
"tauri-runtime",
|
||||
"tauri-runtime-wry",
|
||||
"tauri-utils",
|
||||
"tauri-utils 2.8.1",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tray-icon",
|
||||
@@ -4777,7 +4864,28 @@ dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"tauri-utils 2.8.0",
|
||||
"tauri-winres",
|
||||
"toml 0.9.8",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "2.5.3"
|
||||
source = "git+https://github.com/tauri-apps/tauri?rev=4d5d78daf636feaac20c5bc48a6071491c4291ee#4d5d78daf636feaac20c5bc48a6071491c4291ee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
"dirs",
|
||||
"glob",
|
||||
"heck 0.5.0",
|
||||
"json-patch",
|
||||
"schemars 0.8.22",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils 2.8.1",
|
||||
"tauri-winres",
|
||||
"toml 0.9.8",
|
||||
"walkdir",
|
||||
@@ -4785,9 +4893,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "2.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7ef707148f0755110ca54377560ab891d722de4d53297595380a748026f139f"
|
||||
version = "2.5.2"
|
||||
source = "git+https://github.com/tauri-apps/tauri?rev=4d5d78daf636feaac20c5bc48a6071491c4291ee#4d5d78daf636feaac20c5bc48a6071491c4291ee"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"brotli",
|
||||
@@ -4802,7 +4909,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"syn 2.0.110",
|
||||
"tauri-utils",
|
||||
"tauri-utils 2.8.1",
|
||||
"thiserror 2.0.17",
|
||||
"time",
|
||||
"url",
|
||||
@@ -4812,16 +4919,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "2.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71664fd715ee6e382c05345ad258d6d1d50f90cf1b58c0aa726638b33c2a075d"
|
||||
version = "2.5.2"
|
||||
source = "git+https://github.com/tauri-apps/tauri?rev=4d5d78daf636feaac20c5bc48a6071491c4291ee#4d5d78daf636feaac20c5bc48a6071491c4291ee"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.110",
|
||||
"tauri-codegen",
|
||||
"tauri-utils",
|
||||
"tauri-utils 2.8.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4836,7 +4942,7 @@ dependencies = [
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"tauri-utils 2.8.0",
|
||||
"toml 0.9.8",
|
||||
"walkdir",
|
||||
]
|
||||
@@ -4886,7 +4992,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"tauri-utils 2.8.0",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"url",
|
||||
@@ -4928,7 +5034,7 @@ dependencies = [
|
||||
"serde_repr",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"tauri-utils 2.8.0",
|
||||
"thiserror 2.0.17",
|
||||
"toml 0.9.8",
|
||||
"url",
|
||||
@@ -4945,7 +5051,7 @@ dependencies = [
|
||||
"data-url",
|
||||
"http",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"reqwest 0.12.24",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -5096,7 +5202,7 @@ dependencies = [
|
||||
"minisign-verify",
|
||||
"osakit",
|
||||
"percent-encoding",
|
||||
"reqwest",
|
||||
"reqwest 0.12.24",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -5129,9 +5235,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9368f09358496f2229313fccb37682ad116b7f46fa76981efe116994a0628926"
|
||||
version = "2.9.2"
|
||||
source = "git+https://github.com/tauri-apps/tauri?rev=4d5d78daf636feaac20c5bc48a6071491c4291ee#4d5d78daf636feaac20c5bc48a6071491c4291ee"
|
||||
dependencies = [
|
||||
"cookie",
|
||||
"dpi",
|
||||
@@ -5144,7 +5249,7 @@ dependencies = [
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"tauri-utils 2.8.1",
|
||||
"thiserror 2.0.17",
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
@@ -5154,9 +5259,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "929f5df216f5c02a9e894554401bcdab6eec3e39ec6a4a7731c7067fc8688a93"
|
||||
version = "2.9.3"
|
||||
source = "git+https://github.com/tauri-apps/tauri?rev=4d5d78daf636feaac20c5bc48a6071491c4291ee#4d5d78daf636feaac20c5bc48a6071491c4291ee"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
@@ -5171,7 +5275,7 @@ dependencies = [
|
||||
"softbuffer",
|
||||
"tao",
|
||||
"tauri-runtime",
|
||||
"tauri-utils",
|
||||
"tauri-utils 2.8.1",
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
@@ -5179,11 +5283,74 @@ dependencies = [
|
||||
"wry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-specta"
|
||||
version = "2.0.0-rc.21"
|
||||
source = "git+https://github.com/specta-rs/tauri-specta?rev=6720b2848eff9a3e40af54c48d65f6d56b640c0b#6720b2848eff9a3e40af54c48d65f6d56b640c0b"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta",
|
||||
"specta-typescript",
|
||||
"tauri",
|
||||
"tauri-specta-macros",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-specta-macros"
|
||||
version = "2.0.0-rc.16"
|
||||
source = "git+https://github.com/specta-rs/tauri-specta?rev=6720b2848eff9a3e40af54c48d65f6d56b640c0b#6720b2848eff9a3e40af54c48d65f6d56b640c0b"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b8bbe426abdbf52d050e52ed693130dbd68375b9ad82a3fb17efb4c8d85673"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_metadata",
|
||||
"ctor",
|
||||
"dunce",
|
||||
"glob",
|
||||
"html5ever",
|
||||
"http",
|
||||
"infer",
|
||||
"json-patch",
|
||||
"kuchikiki",
|
||||
"log",
|
||||
"memchr",
|
||||
"phf 0.11.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"schemars 0.8.22",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde-untagged",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"swift-rs",
|
||||
"thiserror 2.0.17",
|
||||
"toml 0.9.8",
|
||||
"url",
|
||||
"urlpattern",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "2.8.1"
|
||||
source = "git+https://github.com/tauri-apps/tauri?rev=4d5d78daf636feaac20c5bc48a6071491c4291ee#4d5d78daf636feaac20c5bc48a6071491c4291ee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"brotli",
|
||||
@@ -5547,9 +5714,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.6"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bytes",
|
||||
@@ -6034,9 +6201,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webkit2gtk"
|
||||
version = "2.0.1"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a"
|
||||
checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cairo-rs",
|
||||
@@ -6058,9 +6225,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webkit2gtk-sys"
|
||||
version = "2.0.1"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c"
|
||||
checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cairo-sys-rs",
|
||||
@@ -6719,9 +6886,9 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.53.5"
|
||||
version = "0.54.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2"
|
||||
checksum = "5ed1a195b0375491dd15a7066a10251be217ce743cf4bbbbdcf5391d6473bee0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.6.2",
|
||||
|
||||
@@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = ["macos-private-api", "devtools"] }
|
||||
tauri = { version = "2.9.5", features = ["macos-private-api", "devtools"] }
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-deep-link = "2.4.6"
|
||||
tauri-plugin-shell = "2"
|
||||
@@ -43,10 +43,13 @@ reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"
|
||||
uuid = { version = "1.19.0", features = ["v4"] }
|
||||
tauri-plugin-decorum = "1.1.1"
|
||||
comrak = { version = "0.50", default-features = false }
|
||||
specta = "=2.0.0-rc.22"
|
||||
specta-typescript = "0.0.9"
|
||||
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
gtk = "0.18.2"
|
||||
webkit2gtk = "=2.0.1"
|
||||
webkit2gtk = "=2.0.2"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = "0.6"
|
||||
@@ -59,3 +62,10 @@ windows = { version = "0.61", features = [
|
||||
"Win32_System_Threading",
|
||||
"Win32_Security"
|
||||
] }
|
||||
|
||||
[patch.crates-io]
|
||||
specta = { git = "https://github.com/specta-rs/specta", rev = "106425eac4964d8ff34d3a02f1612e33117b08bb" }
|
||||
specta-typescript = { git = "https://github.com/specta-rs/specta", rev = "106425eac4964d8ff34d3a02f1612e33117b08bb" }
|
||||
tauri-specta = { git = "https://github.com/specta-rs/tauri-specta", rev = "6720b2848eff9a3e40af54c48d65f6d56b640c0b" }
|
||||
# TODO: https://github.com/tauri-apps/tauri/pull/14812
|
||||
tauri = { git = "https://github.com/tauri-apps/tauri", rev = "4d5d78daf636feaac20c5bc48a6071491c4291ee" }
|
||||
|
||||
@@ -51,6 +51,7 @@ fn is_cli_installed() -> bool {
|
||||
const INSTALL_SCRIPT: &str = include_str!("../../../../install");
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn install_cli(app: tauri::AppHandle) -> Result<String, String> {
|
||||
if cfg!(not(unix)) {
|
||||
return Err("CLI installation is only supported on macOS & Linux".to_string());
|
||||
|
||||
@@ -16,21 +16,26 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tauri::{AppHandle, LogicalSize, Manager, RunEvent, State, WebviewWindowBuilder};
|
||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
#[cfg(windows)]
|
||||
use tauri_plugin_decorum::WebviewWindowExt;
|
||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
|
||||
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
||||
use tauri_plugin_store::StoreExt;
|
||||
use tokio::sync::oneshot;
|
||||
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
use crate::window_customizer::PinchZoomDisablePlugin;
|
||||
|
||||
const SETTINGS_STORE: &str = "opencode.settings.dat";
|
||||
const DEFAULT_SERVER_URL_KEY: &str = "defaultServerUrl";
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
fn window_state_flags() -> StateFlags {
|
||||
StateFlags::all() - StateFlags::DECORATIONS
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize, specta::Type)]
|
||||
struct ServerReadyData {
|
||||
url: String,
|
||||
password: Option<String>,
|
||||
@@ -64,6 +69,7 @@ struct LogState(Arc<Mutex<VecDeque<String>>>);
|
||||
const MAX_LOG_ENTRIES: usize = 200;
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
fn kill_sidecar(app: AppHandle) {
|
||||
let Some(server_state) = app.try_state::<ServerState>() else {
|
||||
println!("Server not running");
|
||||
@@ -97,6 +103,7 @@ async fn get_logs(app: AppHandle) -> Result<String, String> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn ensure_server_ready(state: State<'_, ServerState>) -> Result<ServerReadyData, String> {
|
||||
state
|
||||
.status
|
||||
@@ -106,6 +113,7 @@ async fn ensure_server_ready(state: State<'_, ServerState>) -> Result<ServerRead
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
fn get_default_server_url(app: AppHandle) -> Result<Option<String>, String> {
|
||||
let store = app
|
||||
.store(SETTINGS_STORE)
|
||||
@@ -119,6 +127,7 @@ fn get_default_server_url(app: AppHandle) -> Result<Option<String>, String> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn set_default_server_url(app: AppHandle, url: Option<String>) -> Result<(), String> {
|
||||
let store = app
|
||||
.store(SETTINGS_STORE)
|
||||
@@ -252,6 +261,26 @@ async fn check_server_health(url: &str, password: Option<&str>) -> bool {
|
||||
pub fn run() {
|
||||
let updater_enabled = option_env!("TAURI_SIGNING_PRIVATE_KEY").is_some();
|
||||
|
||||
let builder = tauri_specta::Builder::<tauri::Wry>::new()
|
||||
// Then register them (separated by a comma)
|
||||
.commands(tauri_specta::collect_commands![
|
||||
kill_sidecar,
|
||||
install_cli,
|
||||
ensure_server_ready,
|
||||
get_default_server_url,
|
||||
set_default_server_url,
|
||||
markdown::parse_markdown_command
|
||||
])
|
||||
.error_handling(tauri_specta::ErrorHandlingMode::Throw);
|
||||
|
||||
#[cfg(debug_assertions)] // <- Only export on non-release builds
|
||||
builder
|
||||
.export(
|
||||
specta_typescript::Typescript::default(),
|
||||
"../src/bindings.ts",
|
||||
)
|
||||
.expect("Failed to export typescript bindings");
|
||||
|
||||
#[cfg(all(target_os = "macos", not(debug_assertions)))]
|
||||
let _ = std::process::Command::new("killall")
|
||||
.arg("opencode-cli")
|
||||
@@ -269,10 +298,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(
|
||||
tauri_plugin_window_state::Builder::new()
|
||||
.with_state_flags(
|
||||
tauri_plugin_window_state::StateFlags::all()
|
||||
- tauri_plugin_window_state::StateFlags::DECORATIONS,
|
||||
)
|
||||
.with_state_flags(window_state_flags())
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
@@ -285,15 +311,10 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(PinchZoomDisablePlugin)
|
||||
.plugin(tauri_plugin_decorum::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
kill_sidecar,
|
||||
install_cli,
|
||||
ensure_server_ready,
|
||||
get_default_server_url,
|
||||
set_default_server_url,
|
||||
markdown::parse_markdown_command
|
||||
])
|
||||
.invoke_handler(builder.invoke_handler())
|
||||
.setup(move |app| {
|
||||
builder.mount_events(app);
|
||||
|
||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||
app.deep_link().register_all().ok();
|
||||
|
||||
@@ -346,6 +367,8 @@ pub fn run() {
|
||||
|
||||
let window = window_builder.build().expect("Failed to create window");
|
||||
|
||||
setup_window_state_listener(&app, &window);
|
||||
|
||||
#[cfg(windows)]
|
||||
let _ = window.create_overlay_titlebar();
|
||||
|
||||
@@ -526,6 +549,7 @@ async fn spawn_local_server(
|
||||
let timestamp = Instant::now();
|
||||
loop {
|
||||
if timestamp.elapsed() > Duration::from_secs(30) {
|
||||
let _ = child.kill();
|
||||
break Err(format!(
|
||||
"Failed to spawn OpenCode Server. Logs:\n{}",
|
||||
get_logs(app.clone()).await.unwrap()
|
||||
@@ -540,3 +564,35 @@ async fn spawn_local_server(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_window_state_listener(app: &tauri::AppHandle, window: &tauri::WebviewWindow) {
|
||||
let (tx, mut rx) = mpsc::channel::<()>(1);
|
||||
|
||||
window.on_window_event(move |event| {
|
||||
use tauri::WindowEvent;
|
||||
if !matches!(event, WindowEvent::Moved(_) | WindowEvent::Resized(_)) {
|
||||
return;
|
||||
}
|
||||
let _ = tx.try_send(());
|
||||
});
|
||||
|
||||
tauri::async_runtime::spawn({
|
||||
let app = app.clone();
|
||||
|
||||
async move {
|
||||
let save = || {
|
||||
let handle = app.clone();
|
||||
let app = app.clone();
|
||||
let _ = handle.run_on_main_thread(move || {
|
||||
let _ = app.save_window_state(window_state_flags());
|
||||
});
|
||||
};
|
||||
|
||||
while rx.recv().await.is_some() {
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
|
||||
save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use comrak::{create_formatter, parse_document, Arena, Options, html::ChildRendering, nodes::NodeValue};
|
||||
use comrak::{
|
||||
Arena, Options, create_formatter, html::ChildRendering, nodes::NodeValue, parse_document,
|
||||
};
|
||||
use std::fmt::Write;
|
||||
|
||||
create_formatter!(ExternalLinkFormatter, {
|
||||
@@ -55,6 +57,7 @@ pub fn parse_markdown(input: &str) -> String {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn parse_markdown_command(markdown: String) -> Result<String, String> {
|
||||
Ok(parse_markdown(&markdown))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use tauri::{plugin::Plugin, Manager, Runtime, Window};
|
||||
use tauri::{Manager, Runtime, Window, plugin::Plugin};
|
||||
|
||||
pub struct PinchZoomDisablePlugin;
|
||||
|
||||
@@ -21,8 +21,8 @@ impl<R: Runtime> Plugin<R> for PinchZoomDisablePlugin {
|
||||
let _ = webview_window.with_webview(|_webview| {
|
||||
#[cfg(target_os = "linux")]
|
||||
unsafe {
|
||||
use gtk::glib::ObjectExt;
|
||||
use gtk::GestureZoom;
|
||||
use gtk::glib::ObjectExt;
|
||||
use webkit2gtk::glib::gobject_ffi;
|
||||
|
||||
if let Some(data) = _webview.inner().data::<GestureZoom>("wk-view-zoom-gesture") {
|
||||
|
||||
19
packages/desktop/src/bindings.ts
Normal file
19
packages/desktop/src/bindings.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// This file has been generated by Tauri Specta. Do not edit this file manually.
|
||||
|
||||
import { invoke as __TAURI_INVOKE, Channel } from "@tauri-apps/api/core"
|
||||
|
||||
/** Commands */
|
||||
export const commands = {
|
||||
killSidecar: () => __TAURI_INVOKE<void>("kill_sidecar"),
|
||||
installCli: () => __TAURI_INVOKE<string>("install_cli"),
|
||||
ensureServerReady: () => __TAURI_INVOKE<ServerReadyData>("ensure_server_ready"),
|
||||
getDefaultServerUrl: () => __TAURI_INVOKE<string | null>("get_default_server_url"),
|
||||
setDefaultServerUrl: (url: string | null) => __TAURI_INVOKE<null>("set_default_server_url", { url }),
|
||||
parseMarkdownCommand: (markdown: string) => __TAURI_INVOKE<string>("parse_markdown_command", { markdown }),
|
||||
}
|
||||
|
||||
/* Types */
|
||||
export type ServerReadyData = {
|
||||
url: string
|
||||
password: string | null
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { message } from "@tauri-apps/plugin-dialog"
|
||||
|
||||
import { initI18n, t } from "./i18n"
|
||||
import { commands } from "./bindings"
|
||||
|
||||
export async function installCli(): Promise<void> {
|
||||
await initI18n()
|
||||
|
||||
try {
|
||||
const path = await invoke<string>("install_cli")
|
||||
const path = await commands.installCli()
|
||||
await message(t("desktop.cli.installed.message", { path }), { title: t("desktop.cli.installed.title") })
|
||||
} catch (e) {
|
||||
await message(t("desktop.cli.failed.message", { error: String(e) }), { title: t("desktop.cli.failed.title") })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @refresh reload
|
||||
import "./webview-zoom"
|
||||
import { webviewZoom } from "./webview-zoom"
|
||||
import { render } from "solid-js/web"
|
||||
import { AppBaseProviders, AppInterface, PlatformProvider, Platform } from "@opencode-ai/app"
|
||||
import { open, save } from "@tauri-apps/plugin-dialog"
|
||||
@@ -7,7 +7,6 @@ import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link"
|
||||
import { open as shellOpen } from "@tauri-apps/plugin-shell"
|
||||
import { type as ostype } from "@tauri-apps/plugin-os"
|
||||
import { check, Update } from "@tauri-apps/plugin-updater"
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||
import { isPermissionGranted, requestPermission } from "@tauri-apps/plugin-notification"
|
||||
import { relaunch } from "@tauri-apps/plugin-process"
|
||||
@@ -22,6 +21,7 @@ import { createMenu } from "./menu"
|
||||
import { initI18n, t } from "./i18n"
|
||||
import pkg from "../package.json"
|
||||
import "./styles.css"
|
||||
import { commands } from "./bindings"
|
||||
|
||||
const root = document.getElementById("root")
|
||||
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||
@@ -274,12 +274,12 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
|
||||
|
||||
update: async () => {
|
||||
if (!UPDATER_ENABLED || !update) return
|
||||
if (ostype() === "windows") await invoke("kill_sidecar").catch(() => undefined)
|
||||
if (ostype() === "windows") await commands.killSidecar().catch(() => undefined)
|
||||
await update.install().catch(() => undefined)
|
||||
},
|
||||
|
||||
restart: async () => {
|
||||
await invoke("kill_sidecar").catch(() => undefined)
|
||||
await commands.killSidecar().catch(() => undefined)
|
||||
await relaunch()
|
||||
},
|
||||
|
||||
@@ -335,17 +335,17 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
|
||||
},
|
||||
|
||||
getDefaultServerUrl: async () => {
|
||||
const result = await invoke<string | null>("get_default_server_url").catch(() => null)
|
||||
const result = await commands.getDefaultServerUrl().catch(() => null)
|
||||
return result
|
||||
},
|
||||
|
||||
setDefaultServerUrl: async (url: string | null) => {
|
||||
await invoke("set_default_server_url", { url })
|
||||
await commands.setDefaultServerUrl(url)
|
||||
},
|
||||
|
||||
parseMarkdown: async (markdown: string) => {
|
||||
return invoke<string>("parse_markdown_command", { markdown })
|
||||
},
|
||||
parseMarkdown: (markdown: string) => commands.parseMarkdownCommand(markdown),
|
||||
|
||||
webviewZoom,
|
||||
})
|
||||
|
||||
createMenu()
|
||||
@@ -391,11 +391,7 @@ type ServerReadyData = { url: string; password: string | null }
|
||||
|
||||
// Gate component that waits for the server to be ready
|
||||
function ServerGate(props: { children: (data: Accessor<ServerReadyData>) => JSX.Element }) {
|
||||
const [serverData] = createResource<ServerReadyData>(() =>
|
||||
invoke("ensure_server_ready").then((v) => {
|
||||
return new Promise((res) => setTimeout(() => res(v as ServerReadyData), 2000))
|
||||
}),
|
||||
)
|
||||
const [serverData] = createResource(() => commands.ensureServerReady())
|
||||
|
||||
const errorMessage = () => {
|
||||
const error = serverData.error
|
||||
@@ -406,7 +402,7 @@ function ServerGate(props: { children: (data: Accessor<ServerReadyData>) => JSX.
|
||||
}
|
||||
|
||||
const restartApp = async () => {
|
||||
await invoke("kill_sidecar").catch(() => undefined)
|
||||
await commands.killSidecar().catch(() => undefined)
|
||||
await relaunch().catch(() => undefined)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Menu, MenuItem, PredefinedMenuItem, Submenu } from "@tauri-apps/api/menu"
|
||||
import { type as ostype } from "@tauri-apps/plugin-os"
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { relaunch } from "@tauri-apps/plugin-process"
|
||||
|
||||
import { runUpdater, UPDATER_ENABLED } from "./updater"
|
||||
import { installCli } from "./cli"
|
||||
import { initI18n, t } from "./i18n"
|
||||
import { commands } from "./bindings"
|
||||
|
||||
export async function createMenu() {
|
||||
if (ostype() !== "macos") return
|
||||
@@ -35,7 +35,7 @@ export async function createMenu() {
|
||||
}),
|
||||
await MenuItem.new({
|
||||
action: async () => {
|
||||
await invoke("kill_sidecar").catch(() => undefined)
|
||||
await commands.killSidecar().catch(() => undefined)
|
||||
await relaunch().catch(() => undefined)
|
||||
},
|
||||
text: t("desktop.menu.restart"),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { check } 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"
|
||||
import { type as ostype } from "@tauri-apps/plugin-os"
|
||||
|
||||
import { initI18n, t } from "./i18n"
|
||||
import { commands } from "./bindings"
|
||||
|
||||
export const UPDATER_ENABLED = window.__OPENCODE__?.updaterEnabled ?? false
|
||||
|
||||
@@ -39,13 +39,13 @@ export async function runUpdater({ alertOnFail }: { alertOnFail: boolean }) {
|
||||
if (!shouldUpdate) return
|
||||
|
||||
try {
|
||||
if (ostype() === "windows") await invoke("kill_sidecar")
|
||||
if (ostype() === "windows") await commands.killSidecar()
|
||||
await update.install()
|
||||
} catch {
|
||||
await message(t("desktop.updater.installFailed.message"), { title: t("desktop.updater.installFailed.title") })
|
||||
return
|
||||
}
|
||||
|
||||
await invoke("kill_sidecar")
|
||||
await commands.killSidecar()
|
||||
await relaunch()
|
||||
}
|
||||
|
||||
@@ -4,28 +4,34 @@
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { type as ostype } from "@tauri-apps/plugin-os"
|
||||
import { createSignal } from "solid-js"
|
||||
|
||||
const OS_NAME = ostype()
|
||||
|
||||
let zoomLevel = 1
|
||||
const [webviewZoom, setWebviewZoom] = createSignal(1)
|
||||
|
||||
const MAX_ZOOM_LEVEL = 10
|
||||
const MIN_ZOOM_LEVEL = 0.2
|
||||
|
||||
const clamp = (value: number) => Math.min(Math.max(value, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL)
|
||||
|
||||
const applyZoom = (next: number) => {
|
||||
setWebviewZoom(next)
|
||||
invoke("plugin:webview|set_webview_zoom", {
|
||||
value: next,
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (OS_NAME === "macos" ? event.metaKey : event.ctrlKey) {
|
||||
if (event.key === "-") {
|
||||
zoomLevel -= 0.2
|
||||
} else if (event.key === "=" || event.key === "+") {
|
||||
zoomLevel += 0.2
|
||||
} else if (event.key === "0") {
|
||||
zoomLevel = 1
|
||||
} else {
|
||||
return
|
||||
}
|
||||
zoomLevel = Math.min(Math.max(zoomLevel, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL)
|
||||
invoke("plugin:webview|set_webview_zoom", {
|
||||
value: zoomLevel,
|
||||
})
|
||||
}
|
||||
if (!(OS_NAME === "macos" ? event.metaKey : event.ctrlKey)) return
|
||||
|
||||
let newZoom = webviewZoom()
|
||||
|
||||
if (event.key === "-") newZoom -= 0.2
|
||||
if (event.key === "=" || event.key === "+") newZoom += 0.2
|
||||
if (event.key === "0") newZoom = 1
|
||||
|
||||
applyZoom(clamp(newZoom))
|
||||
})
|
||||
|
||||
export { webviewZoom }
|
||||
|
||||
@@ -82,8 +82,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.5.4",
|
||||
"@opentui/core": "0.1.75",
|
||||
"@opentui/solid": "0.1.75",
|
||||
"@opentui/core": "0.1.77",
|
||||
"@opentui/solid": "0.1.77",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
|
||||
@@ -169,6 +169,7 @@ export function tui(input: {
|
||||
gatherStats: false,
|
||||
exitOnCtrlC: false,
|
||||
useKittyKeyboard: {},
|
||||
autoFocus: false,
|
||||
consoleOptions: {
|
||||
keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }],
|
||||
onCopySelection: (text) => {
|
||||
@@ -186,6 +187,7 @@ function App() {
|
||||
const route = useRoute()
|
||||
const dimensions = useTerminalDimensions()
|
||||
const renderer = useRenderer()
|
||||
Clipboard.setRenderer(renderer)
|
||||
renderer.disableStdoutInterception()
|
||||
const dialog = useDialog()
|
||||
const local = useLocal()
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
import { $ } from "bun"
|
||||
import type { CliRenderer } from "@opentui/core"
|
||||
import { platform, release } from "os"
|
||||
import clipboardy from "clipboardy"
|
||||
import { lazy } from "../../../../util/lazy.js"
|
||||
import { tmpdir } from "os"
|
||||
import path from "path"
|
||||
|
||||
/**
|
||||
* Writes text to clipboard via OSC 52 escape sequence.
|
||||
* This allows clipboard operations to work over SSH by having
|
||||
* the terminal emulator handle the clipboard locally.
|
||||
*/
|
||||
function writeOsc52(text: string): void {
|
||||
if (!process.stdout.isTTY) return
|
||||
const base64 = Buffer.from(text).toString("base64")
|
||||
const osc52 = `\x1b]52;c;${base64}\x07`
|
||||
// tmux and screen require DCS passthrough wrapping
|
||||
const passthrough = process.env["TMUX"] || process.env["STY"]
|
||||
const sequence = passthrough ? `\x1bPtmux;\x1b${osc52}\x1b\\` : osc52
|
||||
process.stdout.write(sequence)
|
||||
}
|
||||
const rendererRef = { current: undefined as CliRenderer | undefined }
|
||||
|
||||
export namespace Clipboard {
|
||||
export interface Content {
|
||||
@@ -26,6 +14,10 @@ export namespace Clipboard {
|
||||
mime: string
|
||||
}
|
||||
|
||||
export function setRenderer(renderer: CliRenderer | undefined): void {
|
||||
rendererRef.current = renderer
|
||||
}
|
||||
|
||||
export async function read(): Promise<Content | undefined> {
|
||||
const os = platform()
|
||||
|
||||
@@ -154,7 +146,11 @@ export namespace Clipboard {
|
||||
})
|
||||
|
||||
export async function copy(text: string): Promise<void> {
|
||||
writeOsc52(text)
|
||||
const renderer = rendererRef.current
|
||||
if (renderer) {
|
||||
const copied = renderer.copyToClipboardOSC52(text)
|
||||
if (copied) return
|
||||
}
|
||||
await getCopyMethod()(text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ export namespace Flag {
|
||||
export const OPENCODE_EXPERIMENTAL_LSP_TY = truthy("OPENCODE_EXPERIMENTAL_LSP_TY")
|
||||
export const OPENCODE_EXPERIMENTAL_LSP_TOOL = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL")
|
||||
export const OPENCODE_DISABLE_FILETIME_CHECK = truthy("OPENCODE_DISABLE_FILETIME_CHECK")
|
||||
export const OPENCODE_EXPERIMENTAL_PLAN_MODE = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE")
|
||||
export const OPENCODE_EXPERIMENTAL_MARKDOWN = truthy("OPENCODE_EXPERIMENTAL_MARKDOWN")
|
||||
export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"]
|
||||
export const OPENCODE_MODELS_PATH = process.env["OPENCODE_MODELS_PATH"]
|
||||
|
||||
@@ -355,3 +355,12 @@ export const pint: Info = {
|
||||
return false
|
||||
},
|
||||
}
|
||||
|
||||
export const ormolu: Info = {
|
||||
name: "ormolu",
|
||||
command: ["ormolu", "-i", "$FILE"],
|
||||
extensions: [".hs"],
|
||||
async enabled() {
|
||||
return Bun.which("ormolu") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -18,12 +18,7 @@ export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Pro
|
||||
case "system": {
|
||||
messages.push({
|
||||
role: "system",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: content,
|
||||
},
|
||||
],
|
||||
content: content,
|
||||
...metadata,
|
||||
})
|
||||
break
|
||||
|
||||
@@ -17,7 +17,6 @@ import { ProviderTransform } from "../provider/transform"
|
||||
import { SystemPrompt } from "./system"
|
||||
import { InstructionPrompt } from "./instruction"
|
||||
import { Plugin } from "../plugin"
|
||||
import PROMPT_PLAN from "../session/prompt/plan.txt"
|
||||
import BUILD_SWITCH from "../session/prompt/build-switch.txt"
|
||||
import MAX_STEPS from "../session/prompt/max-steps.txt"
|
||||
import { defer } from "../util/defer"
|
||||
@@ -1232,33 +1231,6 @@ export namespace SessionPrompt {
|
||||
const userMessage = input.messages.findLast((msg) => msg.info.role === "user")
|
||||
if (!userMessage) return input.messages
|
||||
|
||||
// Original logic when experimental plan mode is disabled
|
||||
if (!Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE) {
|
||||
if (input.agent.name === "plan") {
|
||||
userMessage.parts.push({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMessage.info.id,
|
||||
sessionID: userMessage.info.sessionID,
|
||||
type: "text",
|
||||
text: PROMPT_PLAN,
|
||||
synthetic: true,
|
||||
})
|
||||
}
|
||||
const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan")
|
||||
if (wasPlan && input.agent.name === "build") {
|
||||
userMessage.parts.push({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMessage.info.id,
|
||||
sessionID: userMessage.info.sessionID,
|
||||
type: "text",
|
||||
text: BUILD_SWITCH,
|
||||
synthetic: true,
|
||||
})
|
||||
}
|
||||
return input.messages
|
||||
}
|
||||
|
||||
// New plan mode logic when flag is enabled
|
||||
const assistantMessage = input.messages.findLast((msg) => msg.info.role === "assistant")
|
||||
|
||||
// Switching from plan mode to build mode
|
||||
|
||||
@@ -117,7 +117,7 @@ export namespace ToolRegistry {
|
||||
ApplyPatchTool,
|
||||
...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []),
|
||||
...(config.experimental?.batch_tool === true ? [BatchTool] : []),
|
||||
...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [PlanExitTool, PlanEnterTool] : []),
|
||||
...(Flag.OPENCODE_CLIENT === "cli" ? [PlanExitTool, PlanEnterTool] : []),
|
||||
...custom,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
import { convertToOpenAICompatibleChatMessages as convertToCopilotMessages } from "@/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages"
|
||||
import { describe, test, expect } from "bun:test"
|
||||
|
||||
describe("system messages", () => {
|
||||
test("should convert system message content to string", () => {
|
||||
const result = convertToCopilotMessages([
|
||||
{
|
||||
role: "system",
|
||||
content: "You are a helpful assistant with AGENTS.md instructions.",
|
||||
},
|
||||
])
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
role: "system",
|
||||
content: "You are a helpful assistant with AGENTS.md instructions.",
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("user messages", () => {
|
||||
test("should convert messages with only a text part to a string content", () => {
|
||||
const result = convertToCopilotMessages([
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
display: flex;
|
||||
padding: 4px 4px 6px 4px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-items: start;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface-raised-stronger-non-alpha);
|
||||
max-height: calc(100vh - 6rem);
|
||||
|
||||
@@ -610,7 +610,7 @@ export function SessionTurn(
|
||||
<Match when={working()}>
|
||||
<Spinner />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<Match when={!props.stepsExpanded}>
|
||||
<svg
|
||||
width="10"
|
||||
height="10"
|
||||
@@ -627,6 +627,23 @@ export function SessionTurn(
|
||||
/>
|
||||
</svg>
|
||||
</Match>
|
||||
<Match when={props.stepsExpanded}>
|
||||
<svg
|
||||
width="10"
|
||||
height="10"
|
||||
viewBox="0 0 10 10"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-icon-base"
|
||||
>
|
||||
<path
|
||||
d="M8.125 8.125H1.875L5 1.875L8.125 8.125Z"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Match>
|
||||
</Switch>
|
||||
<Switch>
|
||||
<Match when={retry()}>
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
--breakpoint-lg: 64rem;
|
||||
--breakpoint-xl: 80rem;
|
||||
--breakpoint-2xl: 96rem;
|
||||
--breakpoint-3xl: 112rem;
|
||||
--breakpoint-4xl: 128rem;
|
||||
--breakpoint-5xl: 144rem;
|
||||
|
||||
--container-3xs: 16rem;
|
||||
--container-2xs: 18rem;
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
"background-base": "#FFF",
|
||||
"background-weak": "#F8F8F8",
|
||||
"background-strong": "#F0F0F0",
|
||||
"background-stronger": "#E8E8E8",
|
||||
"border-weak-base": "#E8E8E8",
|
||||
"background-stronger": "#FBFBFB",
|
||||
"border-weak-hover": "#E0E0E0",
|
||||
"border-weak-active": "#D8D8D8",
|
||||
"border-weak-selected": "#D0D0D0",
|
||||
@@ -41,14 +40,15 @@
|
||||
"surface-diff-delete-base": "#f5e8e8",
|
||||
"surface-diff-hidden-base": "#F0F0F0",
|
||||
"text-base": "#101010",
|
||||
"text-weak": "#A0A0A0",
|
||||
"text-invert-strong": "var(--smoke-dark-alpha-12)",
|
||||
"text-weak": "#606060",
|
||||
"text-strong": "#000000",
|
||||
"syntax-string": "#99FFE4",
|
||||
"syntax-primitive": "#FF8080",
|
||||
"syntax-property": "#FFC799",
|
||||
"syntax-type": "#FFC799",
|
||||
"syntax-constant": "#A0A0A0",
|
||||
"syntax-info": "#A0A0A0",
|
||||
"syntax-string": "#0D5C4F",
|
||||
"syntax-primitive": "#B30000",
|
||||
"syntax-property": "#C66C00",
|
||||
"syntax-type": "#9C5C12",
|
||||
"syntax-constant": "#404040",
|
||||
"syntax-info": "#606060",
|
||||
"markdown-heading": "#FFC799",
|
||||
"markdown-text": "#101010",
|
||||
"markdown-link": "#FFC799",
|
||||
|
||||
@@ -36,6 +36,7 @@ OpenCode comes with several built-in formatters for popular languages and framew
|
||||
| shfmt | .sh, .bash | `shfmt` command available |
|
||||
| pint | .php | `laravel/pint` dependency in `composer.json` |
|
||||
| oxfmt (Experimental) | .js, .jsx, .ts, .tsx | `oxfmt` dependency in `package.json` and an [experimental env variable flag](/docs/cli/#experimental) |
|
||||
| ormolu | .hs | `ormolu` command available |
|
||||
|
||||
So if your project has `prettier` in your `package.json`, OpenCode will automatically use it.
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ OpenCode comes with several built-in LSP servers for popular languages:
|
||||
| fsharp | .fs, .fsi, .fsx, .fsscript | `.NET SDK` installed |
|
||||
| gleam | .gleam | `gleam` command available |
|
||||
| gopls | .go | `go` command available |
|
||||
| hls | .hs, .lhs | `haskell-language-server-wrapper` command available |
|
||||
| jdtls | .java | `Java SDK (version 21+)` installed |
|
||||
| kotlin-ls | .kt, .kts | Auto-installs for Kotlin projects |
|
||||
| lua-ls | .lua | Auto-installs for Lua projects |
|
||||
|
||||
Reference in New Issue
Block a user