From 5a9f4e5c606ece93653fff11f460caefec5df303 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 4 Dec 2025 13:28:35 +0900 Subject: [PATCH 01/60] fix: ensure checkUpgrade sets `init:` (#5040) --- packages/opencode/src/cli/cmd/tui/worker.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index 50274f44200..7754b4a3953 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -2,6 +2,7 @@ import { Installation } from "@/installation" import { Server } from "@/server/server" import { Log } from "@/util/log" import { Instance } from "@/project/instance" +import { InstanceBootstrap } from "@/project/bootstrap" import { Rpc } from "@/util/rpc" import { upgrade } from "@/cli/upgrade" @@ -43,6 +44,7 @@ export const rpc = { async checkUpgrade(input: { directory: string }) { await Instance.provide({ directory: input.directory, + init: InstanceBootstrap, fn: async () => { await upgrade().catch(() => {}) }, From 2e63fedb767069cbec4cebe8df84d4867712e967 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 4 Dec 2025 04:29:03 +0000 Subject: [PATCH 02/60] chore: format code --- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 2d21e6302f3..da5e3450e3e 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -24,4 +24,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} \ No newline at end of file +} diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index c25c51bcef5..a811885e1dc 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -26,4 +26,4 @@ "publishConfig": { "directory": "dist" } -} \ No newline at end of file +} From 6b80fff2bb4a6c82c9a327162e96906c4067ae90 Mon Sep 17 00:00:00 2001 From: wsx99outlook <247713593+wsx99outlook@users.noreply.github.com> Date: Thu, 4 Dec 2025 04:30:00 +0000 Subject: [PATCH 03/60] ci: use blacksmith runners in review workflow too (#5042) --- .github/workflows/review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index 32c7c7b1144..0739d57c030 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -14,7 +14,7 @@ jobs: (github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/review')) - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: read pull-requests: write From bcf740f98a6396bd8dbc475e08f1acffc5b0f4a0 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 3 Dec 2025 23:33:40 -0500 Subject: [PATCH 04/60] zen: make session provider sticky --- .../console/app/src/routes/zen/util/handler.ts | 14 +++++++++++++- .../src/routes/zen/util/stickyProviderTracker.ts | 16 ++++++++++++++++ packages/console/core/src/model.ts | 1 + 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 packages/console/app/src/routes/zen/util/stickyProviderTracker.ts diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 623c931277e..7844a3ab079 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -21,6 +21,7 @@ import { oaCompatHelper } from "./provider/openai-compatible" import { createRateLimiter } from "./rateLimiter" import { createDataDumper } from "./dataDumper" import { createTrialLimiter } from "./trialLimiter" +import { createStickyTracker } from "./stickyProviderTracker" type ZenData = Awaited> type RetryOptions = { @@ -68,9 +69,11 @@ export async function handler( const isTrial = await trialLimiter?.isTrial() const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip) await rateLimiter?.check() + const stickyTracker = createStickyTracker(modelInfo.stickyProvider ?? false, sessionId) + const stickyProvider = await stickyTracker?.get() const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => { - const providerInfo = selectProvider(zenData, modelInfo, sessionId, isTrial ?? false, retry) + const providerInfo = selectProvider(zenData, modelInfo, sessionId, isTrial ?? false, retry, stickyProvider) const authInfo = await authenticate(modelInfo, providerInfo) validateBilling(authInfo, modelInfo) validateModelSettings(authInfo) @@ -121,6 +124,9 @@ export async function handler( dataDumper?.provideModel(providerInfo.storeModel) dataDumper?.provideRequest(reqBody) + // Store sticky provider + await stickyTracker?.set(providerInfo.id) + // Scrub response headers const resHeaders = new Headers() const keepHeaders = ["content-type", "cache-control"] @@ -289,12 +295,18 @@ export async function handler( sessionId: string, isTrial: boolean, retry: RetryOptions, + stickyProvider: string | undefined, ) { const provider = (() => { if (isTrial) { return modelInfo.providers.find((provider) => provider.id === modelInfo.trial!.provider) } + if (stickyProvider) { + const provider = modelInfo.providers.find((provider) => provider.id === stickyProvider) + if (provider) return provider + } + if (retry.retryCount === MAX_RETRIES) { return modelInfo.providers.find((provider) => provider.id === modelInfo.fallbackProvider) } diff --git a/packages/console/app/src/routes/zen/util/stickyProviderTracker.ts b/packages/console/app/src/routes/zen/util/stickyProviderTracker.ts new file mode 100644 index 00000000000..63cbb0a68c9 --- /dev/null +++ b/packages/console/app/src/routes/zen/util/stickyProviderTracker.ts @@ -0,0 +1,16 @@ +import { Resource } from "@opencode-ai/console-resource" + +export function createStickyTracker(stickyProvider: boolean, session: string) { + if (!stickyProvider) return + if (!session) return + const key = `sticky:${session}` + + return { + get: async () => { + return await Resource.GatewayKv.get(key) + }, + set: async (providerId: string) => { + await Resource.GatewayKv.put(key, providerId, { expirationTtl: 86400 }) + }, + } +} diff --git a/packages/console/core/src/model.ts b/packages/console/core/src/model.ts index 8cc181b7ca8..5a4a98fe94e 100644 --- a/packages/console/core/src/model.ts +++ b/packages/console/core/src/model.ts @@ -24,6 +24,7 @@ export namespace ZenData { cost: ModelCostSchema, cost200K: ModelCostSchema.optional(), allowAnonymous: z.boolean().optional(), + stickyProvider: z.boolean().optional(), trial: z .object({ limit: z.number(), From 088ebb967f514fa08ba10b21c46dc123ffdf3f84 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 4 Dec 2025 01:07:04 -0600 Subject: [PATCH 05/60] ci: only maintainer can trigger --- .github/workflows/review.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index 0739d57c030..ffcf085d2cd 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -1,16 +1,12 @@ name: Guidelines Check on: - pull_request_target: - types: [opened, ready_for_review] issue_comment: types: [created] jobs: check-guidelines: if: | - (github.event_name == 'pull_request_target' && - github.event.pull_request.draft == false) || (github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/review')) From 45bc7a6a9d2560650881be0e554a124b4f925ae5 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 4 Dec 2025 01:14:09 -0600 Subject: [PATCH 06/60] ci: cleaner --- .github/workflows/review.yml | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index ffcf085d2cd..ac93ca94e7e 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -7,25 +7,14 @@ on: jobs: check-guidelines: if: | - (github.event_name == 'issue_comment' && - github.event.issue.pull_request && - startsWith(github.event.comment.body, '/review')) + github.event.issue.pull_request && + startsWith(github.event.comment.body, '/review') && + contains(fromJson('["OWNER","MEMBER"]'), github.event.comment.author_association) runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: read pull-requests: write steps: - - name: Check if user has write permission - if: github.event_name == 'issue_comment' - run: | - PERMISSION=$(gh api /repos/${{ github.repository }}/collaborators/${{ github.event.comment.user.login }}/permission --jq '.permission') - if [[ "$PERMISSION" != "write" && "$PERMISSION" != "admin" ]]; then - echo "User does not have write permission" - exit 1 - fi - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Get PR number id: pr-number run: | From efbb973393755143ee35a5a8d3414744d1ddb910 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 4 Dec 2025 12:04:38 +0000 Subject: [PATCH 07/60] ignore: update download stats 2025-12-04 --- STATS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/STATS.md b/STATS.md index 25678e915d5..72198597234 100644 --- a/STATS.md +++ b/STATS.md @@ -159,3 +159,4 @@ | 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) | | 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) | | 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) | +| 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) | From 1d6e3d477b00df5c114903d30da31a3405f9ad5d Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 4 Dec 2025 06:56:48 -0600 Subject: [PATCH 08/60] fix(tui): cursor color --- .../opencode/src/cli/cmd/tui/component/prompt/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 7271e2fc69e..37603ffa759 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -269,7 +269,7 @@ export function Prompt(props: PromptProps) { createEffect(() => { if (props.disabled) input.cursorColor = theme.backgroundElement - if (!props.disabled) input.cursorColor = theme.primary + if (!props.disabled) input.cursorColor = theme.text }) const [store, setStore] = createStore<{ @@ -805,12 +805,12 @@ export function Prompt(props: PromptProps) { ref={(r: TextareaRenderable) => { input = r setTimeout(() => { - input.cursorColor = highlight() + input.cursorColor = theme.text }, 0) }} onMouseDown={(r: MouseEvent) => r.target?.focus()} focusedBackgroundColor={theme.backgroundElement} - cursorColor={highlight()} + cursorColor={theme.text} syntaxStyle={syntax()} /> From 27c99b46cba5543bb0e7b9e8982140eebfa59041 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Thu, 4 Dec 2025 11:12:58 -0600 Subject: [PATCH 09/60] Preserve prompt input when creating new session (#4993) --- packages/opencode/src/cli/cmd/tui/app.tsx | 10 ++++++++- .../cli/cmd/tui/component/prompt/index.tsx | 4 ++++ .../src/cli/cmd/tui/context/prompt.tsx | 18 +++++++++++++++ .../src/cli/cmd/tui/context/route.tsx | 2 ++ .../opencode/src/cli/cmd/tui/routes/home.tsx | 22 ++++++++++++++----- .../src/cli/cmd/tui/routes/session/index.tsx | 7 +++++- 6 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/context/prompt.tsx diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 30d7b5c6912..3fb20f16797 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -32,6 +32,7 @@ import { KVProvider, useKV } from "./context/kv" import { Provider } from "@/provider/provider" import { ArgsProvider, useArgs, type Args } from "./context/args" import open from "open" +import { PromptRefProvider, usePromptRef } from "./context/prompt" async function getTerminalBackgroundColor(): Promise<"dark" | "light"> { // can't set raw mode if not a TTY @@ -119,7 +120,9 @@ export function tui(input: { url: string; args: Args; onExit?: () => Promise - + + + @@ -160,6 +163,7 @@ function App() { const { theme, mode, setMode } = useTheme() const sync = useSync() const exit = useExit() + const promptRef = usePromptRef() createEffect(() => { console.log(JSON.stringify(route.data)) @@ -225,8 +229,12 @@ function App() { keybind: "session_new", category: "Session", onSelect: () => { + const current = promptRef.current + // Don't require focus - if there's any text, preserve it + const currentPrompt = current?.current?.input ? current.current : undefined route.navigate({ type: "home", + initialPrompt: currentPrompt, }) dialog.clear() }, diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 37603ffa759..84d00301985 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -37,6 +37,7 @@ export type PromptProps = { export type PromptRef = { focused: boolean + current: PromptInfo set(prompt: PromptInfo): void reset(): void blur(): void @@ -377,6 +378,9 @@ export function Prompt(props: PromptProps) { get focused() { return input.focused }, + get current() { + return store.prompt + }, focus() { input.focus() }, diff --git a/packages/opencode/src/cli/cmd/tui/context/prompt.tsx b/packages/opencode/src/cli/cmd/tui/context/prompt.tsx new file mode 100644 index 00000000000..efbb050645e --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/context/prompt.tsx @@ -0,0 +1,18 @@ +import { createSimpleContext } from "./helper" +import type { PromptRef } from "../component/prompt" + +export const { use: usePromptRef, provider: PromptRefProvider } = createSimpleContext({ + name: "PromptRef", + init: () => { + let current: PromptRef | undefined + + return { + get current() { + return current + }, + set(ref: PromptRef | undefined) { + current = ref + }, + } + }, +}) diff --git a/packages/opencode/src/cli/cmd/tui/context/route.tsx b/packages/opencode/src/cli/cmd/tui/context/route.tsx index b906de99b38..22333a0589e 100644 --- a/packages/opencode/src/cli/cmd/tui/context/route.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/route.tsx @@ -1,8 +1,10 @@ import { createStore } from "solid-js/store" import { createSimpleContext } from "./helper" +import type { PromptInfo } from "../component/prompt/history" export type HomeRoute = { type: "home" + initialPrompt?: PromptInfo } export type SessionRoute = { diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx index 33942c2a50b..df99eac885f 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx @@ -1,15 +1,14 @@ import { Prompt, type PromptRef } from "@tui/component/prompt" -import { createMemo, Match, onMount, Show, Switch, type ParentProps } from "solid-js" +import { createMemo, Match, onMount, Show, Switch } from "solid-js" import { useTheme } from "@tui/context/theme" -import { useKeybind } from "../context/keybind" -import type { KeybindsConfig } from "@opencode-ai/sdk" import { Logo } from "../component/logo" import { Locale } from "@/util/locale" import { useSync } from "../context/sync" import { Toast } from "../ui/toast" import { useArgs } from "../context/args" -import { Global } from "@/global" import { useDirectory } from "../context/directory" +import { useRoute, useRouteData } from "@tui/context/route" +import { usePromptRef } from "../context/prompt" // TODO: what is the best way to do this? let once = false @@ -17,6 +16,8 @@ let once = false export function Home() { const sync = useSync() const { theme } = useTheme() + const route = useRouteData("home") + const promptRef = usePromptRef() const mcp = createMemo(() => Object.keys(sync.data.mcp).length > 0) const mcpError = createMemo(() => { return Object.values(sync.data.mcp).some((x) => x.status === "failed") @@ -45,7 +46,10 @@ export function Home() { const args = useArgs() onMount(() => { if (once) return - if (args.prompt) { + if (route.initialPrompt) { + prompt.set(route.initialPrompt) + once = true + } else if (args.prompt) { prompt.set({ input: args.prompt, parts: [] }) once = true } @@ -57,7 +61,13 @@ export function Home() { - (prompt = r)} hint={Hint} /> + { + prompt = r + promptRef.set(r) + }} + hint={Hint} + /> diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 5d9ebbc7a13..ede4e28384b 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -63,6 +63,7 @@ import { useKV } from "../../context/kv.tsx" import { Editor } from "../../util/editor" import stripAnsi from "strip-ansi" import { Footer } from "./footer.tsx" +import { usePromptRef } from "../../context/prompt" addDefaultParsers(parsers.parsers) @@ -99,6 +100,7 @@ export function Session() { const sync = useSync() const kv = useKV() const { theme } = useTheme() + const promptRef = usePromptRef() const session = createMemo(() => sync.session.get(route.sessionID)!) const messages = createMemo(() => sync.data.message[route.sessionID] ?? []) const permissions = createMemo(() => sync.data.permission[route.sessionID] ?? []) @@ -949,7 +951,10 @@ export function Session() { (prompt = r)} + ref={(r) => { + prompt = r + promptRef.set(r) + }} disabled={permissions().length > 0} onSubmit={() => { toBottom() From 350a32274a24e6f5ca3ea30be114a8171a58270f Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 4 Dec 2025 11:15:30 -0600 Subject: [PATCH 10/60] fix: model not being passed correctly to tool --- packages/opencode/src/session/prompt.ts | 5 +++-- packages/opencode/src/tool/read.ts | 10 ++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index d82cbd718c3..ebf0a57d002 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -690,7 +690,7 @@ export namespace SessionPrompt { abort: options.abortSignal!, messageID: input.processor.message.id, callID: options.toolCallId, - extra: input.model, + extra: { model: input.model }, agent: input.agent.name, metadata: async (val) => { const match = input.processor.partFromToolCall(options.toolCallId) @@ -907,12 +907,13 @@ export namespace SessionPrompt { await ReadTool.init() .then(async (t) => { + const model = await Provider.getModel(info.model.providerID, info.model.modelID) const result = await t.execute(args, { sessionID: input.sessionID, abort: new AbortController().signal, agent: input.agent!, messageID: info.id, - extra: { bypassCwdCheck: true, ...info.model }, + extra: { bypassCwdCheck: true, model }, metadata: async () => {}, }) pieces.push({ diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index 7e01246b539..7d01a198148 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -95,14 +95,8 @@ export const ReadTool = Tool.define("read", { } const isImage = isImageFile(filepath) - const supportsImages = await (async () => { - if (!ctx.extra?.["providerID"] || !ctx.extra?.["modelID"]) return false - const providerID = ctx.extra["providerID"] as string - const modelID = ctx.extra["modelID"] as string - const model = await Provider.getModel(providerID, modelID).catch(() => undefined) - if (!model) return false - return model.capabilities.input.image - })() + const model = ctx.extra?.model as Provider.Model | undefined + const supportsImages = model?.capabilities.input.image ?? false if (isImage) { if (!supportsImages) { throw new Error(`Failed to read image: ${filepath}, model may not be able to read images`) From a607f33552b653af5e7ab5233c2da3389b978e68 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Thu, 4 Dec 2025 17:33:00 +0000 Subject: [PATCH 11/60] tweak: bash tool messages regarding timeouts and truncation more clear for agent (#5066) --- packages/opencode/src/tool/bash.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index d8af0a77f46..fa737aaece8 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -316,17 +316,24 @@ export const BashTool = Tool.define("bash", async () => { }) }) + let resultMetadata: String[] = [""] + if (output.length > MAX_OUTPUT_LENGTH) { output = output.slice(0, MAX_OUTPUT_LENGTH) - output += "\n\n(Output was truncated due to length limit)" + resultMetadata.push(`Output exceeded length limit of ${MAX_OUTPUT_LENGTH} chars`) } if (timedOut) { - output += `\n\n(Command timed out after ${timeout} ms)` + resultMetadata.push(`Command terminated after exceeding timeout ${timeout} ms`) } if (aborted) { - output += "\n\n(Command was aborted)" + resultMetadata.push("Command aborted by user") + } + + if (resultMetadata.length > 1) { + resultMetadata.push("") + output += "\n\n" + resultMetadata.join("\n") } return { From a32cf70d7e28c24c1644053f4247a7ec1fd02aab Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 4 Dec 2025 12:01:13 -0600 Subject: [PATCH 12/60] tui: fix /new slash command being persisted in prompt input --- .../src/cli/cmd/tui/component/prompt/autocomplete.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index f74a176ecb2..c397bc23c00 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -352,8 +352,8 @@ export function Autocomplete(props: { function select() { const selected = options()[store.selected] if (!selected) return - selected.onSelect?.() hide() + selected.onSelect?.() } function show(mode: "@" | "/") { @@ -374,6 +374,10 @@ export function Autocomplete(props: { if (store.visible === "/" && !text.endsWith(" ") && text.startsWith("/")) { const cursor = props.input().logicalCursor props.input().deleteRange(0, 0, cursor.row, cursor.col) + // Sync the prompt store immediately since onContentChange is async + props.setPrompt((draft) => { + draft.input = props.input().plainText + }) } command.keybinds(true) setStore("visible", false) From 7f86fe3f6141bef31cdab166a60269143b78dcc5 Mon Sep 17 00:00:00 2001 From: Daniel Polito Date: Thu, 4 Dec 2025 15:10:56 -0300 Subject: [PATCH 13/60] add optional prompt Input to Github Action (#4828) Co-authored-by: Github Action Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: opencode-agent[bot] --- github/action.yml | 5 +++++ packages/opencode/src/cli/cmd/github.ts | 5 +++++ packages/web/src/content/docs/github.mdx | 20 ++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/github/action.yml b/github/action.yml index 0b7367ded42..d22d19990ae 100644 --- a/github/action.yml +++ b/github/action.yml @@ -13,6 +13,10 @@ inputs: description: "Share the opencode session (defaults to true for public repos)" required: false + prompt: + description: "Custom prompt to override the default prompt" + required: false + runs: using: "composite" steps: @@ -27,3 +31,4 @@ runs: env: MODEL: ${{ inputs.model }} SHARE: ${{ inputs.share }} + PROMPT: ${{ inputs.prompt }} diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index b255e17d1b3..99bbb8cc49b 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -562,6 +562,11 @@ export const GithubRunCommand = cmd({ } async function getUserPrompt() { + const customPrompt = process.env["PROMPT"] + if (customPrompt) { + return { userPrompt: customPrompt, promptFiles: [] } + } + const reviewContext = getReviewCommentContext() let prompt = (() => { const body = payload.comment.body.trim() diff --git a/packages/web/src/content/docs/github.mdx b/packages/web/src/content/docs/github.mdx index 19c7782ef04..b0e0397e114 100644 --- a/packages/web/src/content/docs/github.mdx +++ b/packages/web/src/content/docs/github.mdx @@ -82,6 +82,7 @@ Or you can set it up manually. - `model`: The model to use with opencode. Takes the format of `provider/model`. This is **required**. - `share`: Whether to share the opencode session. Defaults to **true** for public repositories. +- `prompt`: Optional custom prompt to override the default behavior. Use this to customize how opencode processes requests. - `token`: Optional GitHub access token for performing operations such as creating comments, committing changes, and opening pull requests. By default, opencode uses the installation access token from the opencode GitHub App, so commits, comments, and pull requests appear as coming from the app. Alternatively, you can use the GitHub Action runner's [built-in `GITHUB_TOKEN`](https://docs.github.com/en/actions/tutorials/authenticate-with-github_token) without installing the opencode GitHub App. Just make sure to grant the required permissions in your workflow: @@ -98,6 +99,25 @@ Or you can set it up manually. --- +## Custom prompts + +Override the default prompt to customize opencode's behavior for your workflow. + +```yaml title=".github/workflows/opencode.yml" +- uses: sst/opencode/github@latest + with: + model: anthropic/claude-sonnet-4-5 + prompt: | + Review this pull request: + - Check for code quality issues + - Look for potential bugs + - Suggest improvements +``` + +This is useful for enforcing specific review criteria, coding standards, or focus areas relevant to your project. + +--- + ## Examples Here are some examples of how you can use opencode in GitHub. From 8a0c86cbdb0f8fb2a7cf281d5a1dffb3291f40c7 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 4 Dec 2025 12:37:14 -0600 Subject: [PATCH 14/60] bump: builtin plugin versions --- packages/opencode/src/global/index.ts | 2 +- packages/opencode/src/plugin/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/global/index.ts b/packages/opencode/src/global/index.ts index de0d0182368..04a9093a529 100644 --- a/packages/opencode/src/global/index.ts +++ b/packages/opencode/src/global/index.ts @@ -30,7 +30,7 @@ await Promise.all([ fs.mkdir(Global.Path.bin, { recursive: true }), ]) -const CACHE_VERSION = "12" +const CACHE_VERSION = "13" const version = await Bun.file(path.join(Global.Path.cache, "version")) .text() diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index e617e045418..9571a63481d 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -28,8 +28,8 @@ export namespace Plugin { } const plugins = [...(config.plugin ?? [])] if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) { - plugins.push("opencode-copilot-auth@0.0.7") - plugins.push("opencode-anthropic-auth@0.0.3") + plugins.push("opencode-copilot-auth@0.0.8") + plugins.push("opencode-anthropic-auth@0.0.4") } for (let plugin of plugins) { log.info("loading plugin", { path: plugin }) From b9c1f100160d663e6372cf114d1d86d4c9575237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Thu, 4 Dec 2025 21:07:23 +0100 Subject: [PATCH 15/60] feat: Add SAP AI Core provider support (#5023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- packages/opencode/src/provider/provider.ts | 13 ++++- packages/web/src/content/docs/providers.mdx | 55 +++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 54367bcfeb3..ee081d87e47 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -285,6 +285,17 @@ export namespace Provider { }, } }, + "sap-ai-core": async () => { + const auth = await Auth.get("sap-ai-core") + const serviceKey = Env.get("SAP_AI_SERVICE_KEY") || (auth?.type === "api" ? auth.key : undefined) + const deploymentId = Env.get("SAP_AI_DEPLOYMENT_ID") || "d65d81e7c077e583" + const resourceGroup = Env.get("SAP_AI_RESOURCE_GROUP") || "default" + + return { + autoload: !!serviceKey, + options: serviceKey ? { serviceKey, deploymentId, resourceGroup } : {}, + } + }, zenmux: async () => { return { autoload: false, @@ -776,7 +787,7 @@ export namespace Provider { const mod = await import(installedPath) const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!] - const loaded = fn({ + const loaded = await fn({ name: model.providerID, ...options, }) diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index bb284901f53..e8534ceee93 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -891,6 +891,61 @@ OpenCode Zen is a list of tested and verified models provided by the OpenCode te --- +### SAP AI Core + +SAP AI Core provides access to 40+ models from OpenAI, Anthropic, Google, Amazon, Meta, Mistral, and AI21 through a unified platform. + +1. Go to your [SAP BTP Cockpit](https://account.hana.ondemand.com/), navigate to your SAP AI Core service instance, and create a service key. + + :::tip + The service key is a JSON object containing `clientid`, `clientsecret`, `url`, and `serviceurls.AI_API_URL`. You can find your AI Core instance under **Services** > **Instances and Subscriptions** in the BTP Cockpit. + ::: + +2. Run the `/connect` command and search for **SAP AI Core**. + + ```txt + /connect + ``` + +3. Enter your service key JSON. + + ```txt + ┌ Service key + │ + │ + └ enter + ``` + + Or set the `SAP_AI_SERVICE_KEY` environment variable: + + ```bash + SAP_AI_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"...","serviceurls":{"AI_API_URL":"..."}}' opencode + ``` + + Or add it to your bash profile: + + ```bash title="~/.bash_profile" + export SAP_AI_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"...","serviceurls":{"AI_API_URL":"..."}}' + ``` + +4. Optionally set deployment ID and resource group: + + ```bash + SAP_AI_DEPLOYMENT_ID=your-deployment-id SAP_AI_RESOURCE_GROUP=your-resource-group opencode + ``` + + :::note + If not set, uses deployment ID `d65d81e7c077e583` (general-purpose) and resource group `default`. Configure these for your specific setup. + ::: + +5. Run the `/models` command to select from 40+ available models. + + ```txt + /models + ``` + +--- + ### OVHcloud AI Endpoints 1. Head over to the [OVHcloud panel](https://ovh.com/manager). Navigate to the `Public Cloud` section, `AI & Machine Learning` > `AI Endpoints` and in `API Keys` tab, click **Create a new API key**. From 668d5a76d54d4b137264d79048ea0b6c9d0ce24d Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 4 Dec 2025 15:39:52 -0500 Subject: [PATCH 16/60] core: ensure model npm package falls back to dev models config when not explicitly defined --- packages/opencode/src/provider/provider.ts | 60 +++++++++++----------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index ee081d87e47..f60bcfa4756 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -457,7 +457,8 @@ export namespace Provider { const state = Instance.state(async () => { using _ = log.time("state") const config = await Config.get() - const database = mapValues(await ModelsDev.get(), fromModelsDevProvider) + const modelsDev = await ModelsDev.get() + const database = mapValues(modelsDev, fromModelsDevProvider) const disabled = new Set(config.disabled_providers ?? []) const enabled = config.enabled_providers ? new Set(config.enabled_providers) : null @@ -515,56 +516,57 @@ export namespace Provider { } for (const [modelID, model] of Object.entries(provider.models ?? {})) { - const existing = parsed.models[model.id ?? modelID] + const existingModel = parsed.models[model.id ?? modelID] const name = iife(() => { if (model.name) return model.name if (model.id && model.id !== modelID) return modelID - return existing?.name ?? modelID + return existingModel?.name ?? modelID }) const parsedModel: Model = { id: modelID, api: { - id: model.id ?? existing?.api.id ?? modelID, - npm: model.provider?.npm ?? provider.npm ?? existing?.api.npm ?? providerID, - url: provider?.api ?? existing?.api.url, + id: model.id ?? existingModel?.api.id ?? modelID, + npm: + model.provider?.npm ?? provider.npm ?? existingModel?.api.npm ?? modelsDev[providerID]?.npm ?? providerID, + url: provider?.api ?? existingModel?.api.url, }, - status: model.status ?? existing?.status ?? "active", + status: model.status ?? existingModel?.status ?? "active", name, providerID, capabilities: { - temperature: model.temperature ?? existing?.capabilities.temperature ?? false, - reasoning: model.reasoning ?? existing?.capabilities.reasoning ?? false, - attachment: model.attachment ?? existing?.capabilities.attachment ?? false, - toolcall: model.tool_call ?? existing?.capabilities.toolcall ?? true, + temperature: model.temperature ?? existingModel?.capabilities.temperature ?? false, + reasoning: model.reasoning ?? existingModel?.capabilities.reasoning ?? false, + attachment: model.attachment ?? existingModel?.capabilities.attachment ?? false, + toolcall: model.tool_call ?? existingModel?.capabilities.toolcall ?? true, input: { - text: model.modalities?.input?.includes("text") ?? existing?.capabilities.input.text ?? true, - audio: model.modalities?.input?.includes("audio") ?? existing?.capabilities.input.audio ?? false, - image: model.modalities?.input?.includes("image") ?? existing?.capabilities.input.image ?? false, - video: model.modalities?.input?.includes("video") ?? existing?.capabilities.input.video ?? false, - pdf: model.modalities?.input?.includes("pdf") ?? existing?.capabilities.input.pdf ?? false, + text: model.modalities?.input?.includes("text") ?? existingModel?.capabilities.input.text ?? true, + audio: model.modalities?.input?.includes("audio") ?? existingModel?.capabilities.input.audio ?? false, + image: model.modalities?.input?.includes("image") ?? existingModel?.capabilities.input.image ?? false, + video: model.modalities?.input?.includes("video") ?? existingModel?.capabilities.input.video ?? false, + pdf: model.modalities?.input?.includes("pdf") ?? existingModel?.capabilities.input.pdf ?? false, }, output: { - text: model.modalities?.output?.includes("text") ?? existing?.capabilities.output.text ?? true, - audio: model.modalities?.output?.includes("audio") ?? existing?.capabilities.output.audio ?? false, - image: model.modalities?.output?.includes("image") ?? existing?.capabilities.output.image ?? false, - video: model.modalities?.output?.includes("video") ?? existing?.capabilities.output.video ?? false, - pdf: model.modalities?.output?.includes("pdf") ?? existing?.capabilities.output.pdf ?? false, + text: model.modalities?.output?.includes("text") ?? existingModel?.capabilities.output.text ?? true, + audio: model.modalities?.output?.includes("audio") ?? existingModel?.capabilities.output.audio ?? false, + image: model.modalities?.output?.includes("image") ?? existingModel?.capabilities.output.image ?? false, + video: model.modalities?.output?.includes("video") ?? existingModel?.capabilities.output.video ?? false, + pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false, }, }, cost: { - input: model?.cost?.input ?? existing?.cost?.input ?? 0, - output: model?.cost?.output ?? existing?.cost?.output ?? 0, + input: model?.cost?.input ?? existingModel?.cost?.input ?? 0, + output: model?.cost?.output ?? existingModel?.cost?.output ?? 0, cache: { - read: model?.cost?.cache_read ?? existing?.cost?.cache.read ?? 0, - write: model?.cost?.cache_write ?? existing?.cost?.cache.write ?? 0, + read: model?.cost?.cache_read ?? existingModel?.cost?.cache.read ?? 0, + write: model?.cost?.cache_write ?? existingModel?.cost?.cache.write ?? 0, }, }, - options: mergeDeep(existing?.options ?? {}, model.options ?? {}), + options: mergeDeep(existingModel?.options ?? {}, model.options ?? {}), limit: { - context: model.limit?.context ?? existing?.limit?.context ?? 0, - output: model.limit?.output ?? existing?.limit?.output ?? 0, + context: model.limit?.context ?? existingModel?.limit?.context ?? 0, + output: model.limit?.output ?? existingModel?.limit?.output ?? 0, }, - headers: mergeDeep(existing?.headers ?? {}, model.headers ?? {}), + headers: mergeDeep(existingModel?.headers ?? {}, model.headers ?? {}), } parsed.models[modelID] = parsedModel } From 48dc520fb85387cf9754f0a80c0e3ce283f6a09c Mon Sep 17 00:00:00 2001 From: Cason Adams Date: Thu, 4 Dec 2025 13:49:51 -0700 Subject: [PATCH 17/60] docs: add CodeCompanion.nvim integration instructions (#5079) --- packages/web/src/content/docs/acp.mdx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/web/src/content/docs/acp.mdx b/packages/web/src/content/docs/acp.mdx index bca30923c25..9d421235c1b 100644 --- a/packages/web/src/content/docs/acp.mdx +++ b/packages/web/src/content/docs/acp.mdx @@ -100,6 +100,27 @@ If you need to pass environment variables: --- +### CodeCompanion.nvim + +To use OpenCode as an ACP agent in [CodeCompanion.nvim](https://github.com/olimorris/codecompanion.nvim), add the following to your Neovim config: + +```lua +require("codecompanion").setup({ + strategies = { + chat = { + adapter = { + name = "opencode", + model = "claude-sonnet-4", + }, + }, + }, +}) +``` + +This config sets up CodeCompanion to use OpenCode as the ACP agent for chat. + +If you need to pass environment variables (like `OPENCODE_API_KEY`), refer to [Configuring Adapters: Environment Variables](https://codecompanion.olimorris.dev/configuration/adapters#environment-variables-setting-an-api-key) in the CodeCompanion.nvim documentation for full details. + ## Support OpenCode works the same via ACP as it does in the terminal. All features are supported: From d469d7d44156ed83aca2607806856e128e71502f Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 4 Dec 2025 15:21:55 -0600 Subject: [PATCH 18/60] tweak: bash tool description re commit stuff --- packages/opencode/src/tool/bash.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/tool/bash.txt b/packages/opencode/src/tool/bash.txt index 0a4d9f16de1..cbb66bba532 100644 --- a/packages/opencode/src/tool/bash.txt +++ b/packages/opencode/src/tool/bash.txt @@ -35,6 +35,8 @@ Usage notes: # Committing changes with git +IMPORTANT: ONLY COMMIT IF THE USER ASKS YOU TO. + If and only if the user asks you to create a new git commit, follow these steps carefully: 1. You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance. ALWAYS run the following bash commands in parallel, each using the Bash tool: @@ -42,9 +44,8 @@ If and only if the user asks you to create a new git commit, follow these steps - Run a git diff command to see both staged and unstaged changes that will be committed. - Run a git log command to see recent commit messages, so that you can follow this repository's commit message style. -2. Analyze all staged changes (both previously staged and newly added) and draft a commit message. Wrap your analysis process in tags: +2. Analyze all staged changes (both previously staged and newly added) and draft a commit message. When analyzing: - - List the files that have been changed or added - Summarize the nature of the changes (eg. new feature, enhancement to an existing feature, bug fix, refactoring, test, docs, etc.) - Brainstorm the purpose or motivation behind these changes @@ -55,7 +56,6 @@ If and only if the user asks you to create a new git commit, follow these steps - Ensure the message accurately reflects the changes and their purpose (i.e. "add" means a wholly new feature, "update" means an enhancement to an existing feature, "fix" means a bug fix, etc.) - Ensure the message is not generic (avoid words like "Update" or "Fix" without context) - Review the draft message to ensure it accurately reflects the changes and their purpose - 3. You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance. ALWAYS run the following commands in parallel: - Add relevant untracked files to the staging area. From b1202ac6db1cbead1e8f205913524f1c47322970 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 4 Dec 2025 16:30:54 -0500 Subject: [PATCH 19/60] core: add test for custom model npm package inheritance --- .../opencode/test/provider/provider.test.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 698fdddfb42..e6eb0c7b464 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1727,3 +1727,39 @@ test("provider options are deeply merged", async () => { }, }) }) + +test("custom model inherits npm package from models.dev provider config", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + openai: { + models: { + "my-custom-model": { + name: "My Custom Model", + tool_call: true, + limit: { context: 8000, output: 2000 }, + }, + }, + }, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + init: async () => { + Env.set("OPENAI_API_KEY", "test-api-key") + }, + fn: async () => { + const providers = await Provider.list() + const model = providers["openai"].models["my-custom-model"] + expect(model).toBeDefined() + expect(model.api.npm).toBe("@ai-sdk/openai") + }, + }) +}) From d763c11a6d5bc57869f11c87f5a293f61e427e0a Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:37:29 -0600 Subject: [PATCH 20/60] feat(desktop): terminal pane (#5081) Co-authored-by: Github Action Co-authored-by: Dax Raad --- bun.lock | 13 +- flake.lock | 6 +- nix/hashes.json | 2 +- package.json | 2 +- packages/desktop/package.json | 2 + packages/desktop/src/addons/serialize.ts | 649 +++++++++++++++++++ packages/desktop/src/components/terminal.tsx | 151 +++++ packages/desktop/src/context/layout.tsx | 22 +- packages/desktop/src/context/sdk.tsx | 2 +- packages/desktop/src/context/session.tsx | 100 ++- packages/desktop/src/pages/layout.tsx | 114 +++- packages/desktop/src/pages/session.tsx | 649 +++++++++++-------- packages/opencode/package.json | 1 + packages/opencode/src/cli/cmd/tui/worker.ts | 7 +- packages/opencode/src/id/id.ts | 1 + packages/opencode/src/pty/index.ts | 199 ++++++ packages/opencode/src/server/error.ts | 36 + packages/opencode/src/server/server.ts | 197 +++++- packages/sdk/js/src/gen/sdk.gen.ts | 88 +++ packages/sdk/js/src/gen/types.gen.ts | 248 ++++++- packages/ui/src/components/icon.tsx | 7 +- packages/ui/src/components/select.css | 2 + packages/ui/src/components/select.tsx | 56 +- packages/ui/src/components/tabs.css | 96 ++- packages/ui/src/components/tabs.tsx | 17 +- packages/ui/src/components/tooltip.css | 7 +- packages/util/src/shell.ts | 13 + 27 files changed, 2292 insertions(+), 395 deletions(-) create mode 100644 packages/desktop/src/addons/serialize.ts create mode 100644 packages/desktop/src/components/terminal.tsx create mode 100644 packages/opencode/src/pty/index.ts create mode 100644 packages/opencode/src/server/error.ts create mode 100644 packages/util/src/shell.ts diff --git a/bun.lock b/bun.lock index aad651621cb..ff4f1444705 100644 --- a/bun.lock +++ b/bun.lock @@ -135,11 +135,13 @@ "@solid-primitives/resize-observer": "2.1.3", "@solid-primitives/scroll": "2.1.3", "@solid-primitives/storage": "4.3.3", + "@solid-primitives/websocket": "1.3.1", "@solidjs/meta": "catalog:", "@solidjs/router": "catalog:", "@thisbeyond/solid-dnd": "0.7.5", "diff": "catalog:", "fuzzysort": "catalog:", + "ghostty-web": "0.3.0", "luxon": "catalog:", "marked": "16.2.0", "marked-shiki": "1.2.1", @@ -246,6 +248,7 @@ "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", + "bun-pty": "0.4.2", "chokidar": "4.0.3", "clipboardy": "4.0.0", "decimal.js": "10.5.0", @@ -457,7 +460,7 @@ "ai": "5.0.97", "diff": "8.0.2", "fuzzysort": "3.1.0", - "hono": "4.7.10", + "hono": "4.10.7", "hono-openapi": "1.1.1", "luxon": "3.6.1", "remeda": "2.26.0", @@ -1506,6 +1509,8 @@ "@solid-primitives/utils": ["@solid-primitives/utils@6.3.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="], + "@solid-primitives/websocket": ["@solid-primitives/websocket@1.3.1", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-F06tA2FKa5VsnS4E4WEc3jHpsJfXRlMTGOtolugTzCqV3JmJTyvk9UVg1oz6PgGHKGi1CQ91OP8iW34myyJgaQ=="], + "@solidjs/meta": ["@solidjs/meta@0.29.4", "", { "peerDependencies": { "solid-js": ">=1.8.4" } }, "sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g=="], "@solidjs/router": ["@solidjs/router@0.15.4", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ=="], @@ -1890,6 +1895,8 @@ "bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="], + "bun-pty": ["bun-pty@0.4.2", "", {}, "sha512-sHImDz6pJDsHAroYpC9ouKVgOyqZ7FP3N+stX5IdMddHve3rf9LIZBDomQcXrACQ7sQDNuwZQHG8BKR7w8krkQ=="], + "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], "bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="], @@ -2334,6 +2341,8 @@ "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], + "ghostty-web": ["ghostty-web@0.3.0", "", {}, "sha512-SAdSHWYF20GMZUB0n8kh1N6Z4ljMnuUqT8iTB2n5FAPswEV10MejEpLlhW/769GL5+BQa1NYwEg9y/XCckV5+A=="], + "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], "giget": ["giget@1.2.5", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.5.4", "pathe": "^2.0.3", "tar": "^6.2.1" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug=="], @@ -2428,7 +2437,7 @@ "hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="], - "hono": ["hono@4.7.10", "", {}, "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ=="], + "hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="], "hono-openapi": ["hono-openapi@1.1.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.8", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-AC3HNhZYPHhnZdSy2Je7GDoTTNxPos6rKRQKVDBbSilY3cWJPqsxRnN6zA4pU7tfxmQEMTqkiLXbw6sAaemB8Q=="], diff --git a/flake.lock b/flake.lock index 4e7cf41e1b7..ca9fd5f8f30 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1764733908, - "narHash": "sha256-QJiih52NU+nm7XQWCj+K8SwUdIEayDQ1FQgjkYISt4I=", + "lastModified": 1764794580, + "narHash": "sha256-UMVihg0OQ980YqmOAPz+zkuCEb9hpE5Xj2v+ZGNjQ+M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "cadcc8de247676e4751c9d4a935acb2c0b059113", + "rev": "ebc94f855ef25347c314258c10393a92794e7ab9", "type": "github" }, "original": { diff --git a/nix/hashes.json b/nix/hashes.json index 47634e2ed82..6bc2eaec159 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,3 +1,3 @@ { - "nodeModules": "sha256-ZGKC7h4ScHDzVYj8qb1lN/weZhyZivPS8kpNAZvgO0I=" + "nodeModules": "sha256-Wrfwnmo0lpck2rbt6ttkAuDGvBvqqWJfNA8QDQxoZ6I=" } diff --git a/package.json b/package.json index a5e7c14621b..a962be92605 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@tailwindcss/vite": "4.1.11", "diff": "8.0.2", "ai": "5.0.97", - "hono": "4.7.10", + "hono": "4.10.7", "hono-openapi": "1.1.1", "fuzzysort": "3.1.0", "luxon": "3.6.1", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index f2f8768cbb8..addff88b046 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -33,11 +33,13 @@ "@solid-primitives/resize-observer": "2.1.3", "@solid-primitives/scroll": "2.1.3", "@solid-primitives/storage": "4.3.3", + "@solid-primitives/websocket": "1.3.1", "@solidjs/meta": "catalog:", "@solidjs/router": "catalog:", "@thisbeyond/solid-dnd": "0.7.5", "diff": "catalog:", "fuzzysort": "catalog:", + "ghostty-web": "0.3.0", "luxon": "catalog:", "marked": "16.2.0", "marked-shiki": "1.2.1", diff --git a/packages/desktop/src/addons/serialize.ts b/packages/desktop/src/addons/serialize.ts new file mode 100644 index 00000000000..03899ff109b --- /dev/null +++ b/packages/desktop/src/addons/serialize.ts @@ -0,0 +1,649 @@ +/** + * SerializeAddon - Serialize terminal buffer contents + * + * Port of xterm.js addon-serialize for ghostty-web. + * Enables serialization of terminal contents to a string that can + * be written back to restore terminal state. + * + * Usage: + * ```typescript + * const serializeAddon = new SerializeAddon(); + * term.loadAddon(serializeAddon); + * const content = serializeAddon.serialize(); + * ``` + */ + +import type { ITerminalAddon, ITerminalCore, IBufferRange } from "ghostty-web" + +// ============================================================================ +// Buffer Types (matching ghostty-web internal interfaces) +// ============================================================================ + +interface IBuffer { + readonly type: "normal" | "alternate" + readonly cursorX: number + readonly cursorY: number + readonly viewportY: number + readonly baseY: number + readonly length: number + getLine(y: number): IBufferLine | undefined + getNullCell(): IBufferCell +} + +interface IBufferLine { + readonly length: number + readonly isWrapped: boolean + getCell(x: number): IBufferCell | undefined + translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string +} + +interface IBufferCell { + getChars(): string + getCode(): number + getWidth(): number + getFgColorMode(): number + getBgColorMode(): number + getFgColor(): number + getBgColor(): number + isBold(): number + isItalic(): number + isUnderline(): number + isStrikethrough(): number + isBlink(): number + isInverse(): number + isInvisible(): number + isFaint(): number + isDim(): boolean +} + +// ============================================================================ +// Types +// ============================================================================ + +export interface ISerializeOptions { + /** + * The row range to serialize. When an explicit range is specified, the cursor + * will get its final repositioning. + */ + range?: ISerializeRange + /** + * The number of rows in the scrollback buffer to serialize, starting from + * the bottom of the scrollback buffer. When not specified, all available + * rows in the scrollback buffer will be serialized. + */ + scrollback?: number + /** + * Whether to exclude the terminal modes from the serialization. + * Default: false + */ + excludeModes?: boolean + /** + * Whether to exclude the alt buffer from the serialization. + * Default: false + */ + excludeAltBuffer?: boolean +} + +export interface ISerializeRange { + /** + * The line to start serializing (inclusive). + */ + start: number + /** + * The line to end serializing (inclusive). + */ + end: number +} + +export interface IHTMLSerializeOptions { + /** + * The number of rows in the scrollback buffer to serialize, starting from + * the bottom of the scrollback buffer. + */ + scrollback?: number + /** + * Whether to only serialize the selection. + * Default: false + */ + onlySelection?: boolean + /** + * Whether to include the global background of the terminal. + * Default: false + */ + includeGlobalBackground?: boolean + /** + * The range to serialize. This is prioritized over onlySelection. + */ + range?: { + startLine: number + endLine: number + startCol: number + } +} + +// ============================================================================ +// Helper Functions +// ============================================================================ + +function constrain(value: number, low: number, high: number): number { + return Math.max(low, Math.min(value, high)) +} + +function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean { + return cell1.getFgColorMode() === cell2.getFgColorMode() && cell1.getFgColor() === cell2.getFgColor() +} + +function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean { + return cell1.getBgColorMode() === cell2.getBgColorMode() && cell1.getBgColor() === cell2.getBgColor() +} + +function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean { + return ( + !!cell1.isInverse() === !!cell2.isInverse() && + !!cell1.isBold() === !!cell2.isBold() && + !!cell1.isUnderline() === !!cell2.isUnderline() && + !!cell1.isBlink() === !!cell2.isBlink() && + !!cell1.isInvisible() === !!cell2.isInvisible() && + !!cell1.isItalic() === !!cell2.isItalic() && + !!cell1.isDim() === !!cell2.isDim() && + !!cell1.isStrikethrough() === !!cell2.isStrikethrough() + ) +} + +// ============================================================================ +// Base Serialize Handler +// ============================================================================ + +abstract class BaseSerializeHandler { + constructor(protected readonly _buffer: IBuffer) {} + + public serialize(range: IBufferRange, excludeFinalCursorPosition?: boolean): string { + let oldCell = this._buffer.getNullCell() + + const startRow = range.start.y + const endRow = range.end.y + const startColumn = range.start.x + const endColumn = range.end.x + + this._beforeSerialize(endRow - startRow, startRow, endRow) + + for (let row = startRow; row <= endRow; row++) { + const line = this._buffer.getLine(row) + if (line) { + const startLineColumn = row === range.start.y ? startColumn : 0 + const endLineColumn = row === range.end.y ? endColumn : line.length + for (let col = startLineColumn; col < endLineColumn; col++) { + const c = line.getCell(col) + if (!c) { + continue + } + this._nextCell(c, oldCell, row, col) + oldCell = c + } + } + this._rowEnd(row, row === endRow) + } + + this._afterSerialize() + + return this._serializeString(excludeFinalCursorPosition) + } + + protected _nextCell(_cell: IBufferCell, _oldCell: IBufferCell, _row: number, _col: number): void {} + protected _rowEnd(_row: number, _isLastRow: boolean): void {} + protected _beforeSerialize(_rows: number, _startRow: number, _endRow: number): void {} + protected _afterSerialize(): void {} + protected _serializeString(_excludeFinalCursorPosition?: boolean): string { + return "" + } +} + +// ============================================================================ +// String Serialize Handler +// ============================================================================ + +class StringSerializeHandler extends BaseSerializeHandler { + private _rowIndex: number = 0 + private _allRows: string[] = [] + private _allRowSeparators: string[] = [] + private _currentRow: string = "" + private _nullCellCount: number = 0 + private _cursorStyle: IBufferCell + private _cursorStyleRow: number = 0 + private _cursorStyleCol: number = 0 + private _backgroundCell: IBufferCell + private _firstRow: number = 0 + private _lastCursorRow: number = 0 + private _lastCursorCol: number = 0 + private _lastContentCursorRow: number = 0 + private _lastContentCursorCol: number = 0 + private _thisRowLastChar: IBufferCell + private _thisRowLastSecondChar: IBufferCell + private _nextRowFirstChar: IBufferCell + + constructor( + buffer: IBuffer, + private readonly _terminal: ITerminalCore, + ) { + super(buffer) + this._cursorStyle = this._buffer.getNullCell() + this._backgroundCell = this._buffer.getNullCell() + this._thisRowLastChar = this._buffer.getNullCell() + this._thisRowLastSecondChar = this._buffer.getNullCell() + this._nextRowFirstChar = this._buffer.getNullCell() + } + + protected _beforeSerialize(rows: number, start: number, _end: number): void { + this._allRows = new Array(rows) + this._lastContentCursorRow = start + this._lastCursorRow = start + this._firstRow = start + } + + protected _rowEnd(row: number, isLastRow: boolean): void { + // if there is colorful empty cell at line end, we must pad it back + if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) { + this._currentRow += `\u001b[${this._nullCellCount}X` + } + + let rowSeparator = "" + + if (!isLastRow) { + // Enable BCE + if (row - this._firstRow >= this._terminal.rows) { + const line = this._buffer.getLine(this._cursorStyleRow) + const cell = line?.getCell(this._cursorStyleCol) + if (cell) { + this._backgroundCell = cell + } + } + + const currentLine = this._buffer.getLine(row)! + const nextLine = this._buffer.getLine(row + 1)! + + if (!nextLine.isWrapped) { + rowSeparator = "\r\n" + this._lastCursorRow = row + 1 + this._lastCursorCol = 0 + } else { + rowSeparator = "" + const thisRowLastChar = currentLine.getCell(currentLine.length - 1) + const thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2) + const nextRowFirstChar = nextLine.getCell(0) + + if (thisRowLastChar) this._thisRowLastChar = thisRowLastChar + if (thisRowLastSecondChar) this._thisRowLastSecondChar = thisRowLastSecondChar + if (nextRowFirstChar) this._nextRowFirstChar = nextRowFirstChar + + const isNextRowFirstCharDoubleWidth = this._nextRowFirstChar.getWidth() > 1 + + let isValid = false + + if ( + this._nextRowFirstChar.getChars() && + (isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0) + ) { + if ( + (this._thisRowLastChar.getChars() || this._thisRowLastChar.getWidth() === 0) && + equalBg(this._thisRowLastChar, this._nextRowFirstChar) + ) { + isValid = true + } + + if ( + isNextRowFirstCharDoubleWidth && + (this._thisRowLastSecondChar.getChars() || this._thisRowLastSecondChar.getWidth() === 0) && + equalBg(this._thisRowLastChar, this._nextRowFirstChar) && + equalBg(this._thisRowLastSecondChar, this._nextRowFirstChar) + ) { + isValid = true + } + } + + if (!isValid) { + rowSeparator = "-".repeat(this._nullCellCount + 1) + rowSeparator += "\u001b[1D\u001b[1X" + + if (this._nullCellCount > 0) { + rowSeparator += "\u001b[A" + rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}C` + rowSeparator += `\u001b[${this._nullCellCount}X` + rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}D` + rowSeparator += "\u001b[B" + } + + this._lastContentCursorRow = row + 1 + this._lastContentCursorCol = 0 + this._lastCursorRow = row + 1 + this._lastCursorCol = 0 + } + } + } + + this._allRows[this._rowIndex] = this._currentRow + this._allRowSeparators[this._rowIndex++] = rowSeparator + this._currentRow = "" + this._nullCellCount = 0 + } + + private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] { + const sgrSeq: number[] = [] + const fgChanged = !equalFg(cell, oldCell) + const bgChanged = !equalBg(cell, oldCell) + const flagsChanged = !equalFlags(cell, oldCell) + + if (fgChanged || bgChanged || flagsChanged) { + if (this._isAttributeDefault(cell)) { + if (!this._isAttributeDefault(oldCell)) { + sgrSeq.push(0) + } + } else { + if (fgChanged) { + const color = cell.getFgColor() + const mode = cell.getFgColorMode() + if (mode === 2) { + // RGB + sgrSeq.push(38, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff) + } else if (mode === 1) { + // Palette + if (color >= 16) { + sgrSeq.push(38, 5, color) + } else { + sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)) + } + } else { + sgrSeq.push(39) + } + } + if (bgChanged) { + const color = cell.getBgColor() + const mode = cell.getBgColorMode() + if (mode === 2) { + // RGB + sgrSeq.push(48, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff) + } else if (mode === 1) { + // Palette + if (color >= 16) { + sgrSeq.push(48, 5, color) + } else { + sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)) + } + } else { + sgrSeq.push(49) + } + } + if (flagsChanged) { + if (!!cell.isInverse() !== !!oldCell.isInverse()) { + sgrSeq.push(cell.isInverse() ? 7 : 27) + } + if (!!cell.isBold() !== !!oldCell.isBold()) { + sgrSeq.push(cell.isBold() ? 1 : 22) + } + if (!!cell.isUnderline() !== !!oldCell.isUnderline()) { + sgrSeq.push(cell.isUnderline() ? 4 : 24) + } + if (!!cell.isBlink() !== !!oldCell.isBlink()) { + sgrSeq.push(cell.isBlink() ? 5 : 25) + } + if (!!cell.isInvisible() !== !!oldCell.isInvisible()) { + sgrSeq.push(cell.isInvisible() ? 8 : 28) + } + if (!!cell.isItalic() !== !!oldCell.isItalic()) { + sgrSeq.push(cell.isItalic() ? 3 : 23) + } + if (!!cell.isDim() !== !!oldCell.isDim()) { + sgrSeq.push(cell.isDim() ? 2 : 22) + } + if (!!cell.isStrikethrough() !== !!oldCell.isStrikethrough()) { + sgrSeq.push(cell.isStrikethrough() ? 9 : 29) + } + } + } + } + + return sgrSeq + } + + private _isAttributeDefault(cell: IBufferCell): boolean { + return ( + cell.getFgColorMode() === 0 && + cell.getBgColorMode() === 0 && + !cell.isBold() && + !cell.isItalic() && + !cell.isUnderline() && + !cell.isBlink() && + !cell.isInverse() && + !cell.isInvisible() && + !cell.isDim() && + !cell.isStrikethrough() + ) + } + + protected _nextCell(cell: IBufferCell, _oldCell: IBufferCell, row: number, col: number): void { + const isPlaceHolderCell = cell.getWidth() === 0 + + if (isPlaceHolderCell) { + return + } + + const isEmptyCell = cell.getChars() === "" + + const sgrSeq = this._diffStyle(cell, this._cursorStyle) + + const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0 + + if (styleChanged) { + if (this._nullCellCount > 0) { + if (!equalBg(this._cursorStyle, this._backgroundCell)) { + this._currentRow += `\u001b[${this._nullCellCount}X` + } + this._currentRow += `\u001b[${this._nullCellCount}C` + this._nullCellCount = 0 + } + + this._lastContentCursorRow = this._lastCursorRow = row + this._lastContentCursorCol = this._lastCursorCol = col + + this._currentRow += `\u001b[${sgrSeq.join(";")}m` + + const line = this._buffer.getLine(row) + const cellFromLine = line?.getCell(col) + if (cellFromLine) { + this._cursorStyle = cellFromLine + this._cursorStyleRow = row + this._cursorStyleCol = col + } + } + + if (isEmptyCell) { + this._nullCellCount += cell.getWidth() + } else { + if (this._nullCellCount > 0) { + if (equalBg(this._cursorStyle, this._backgroundCell)) { + this._currentRow += `\u001b[${this._nullCellCount}C` + } else { + this._currentRow += `\u001b[${this._nullCellCount}X` + this._currentRow += `\u001b[${this._nullCellCount}C` + } + this._nullCellCount = 0 + } + + this._currentRow += cell.getChars() + + this._lastContentCursorRow = this._lastCursorRow = row + this._lastContentCursorCol = this._lastCursorCol = col + cell.getWidth() + } + } + + protected _serializeString(excludeFinalCursorPosition?: boolean): string { + let rowEnd = this._allRows.length + + if (this._buffer.length - this._firstRow <= this._terminal.rows) { + rowEnd = this._lastContentCursorRow + 1 - this._firstRow + this._lastCursorCol = this._lastContentCursorCol + this._lastCursorRow = this._lastContentCursorRow + } + + let content = "" + + for (let i = 0; i < rowEnd; i++) { + content += this._allRows[i] + if (i + 1 < rowEnd) { + content += this._allRowSeparators[i] + } + } + + if (!excludeFinalCursorPosition) { + // Get cursor position relative to viewport (1-indexed for ANSI) + // cursorY is relative to the viewport, cursorX is column position + const cursorRow = this._buffer.cursorY + 1 // 1-indexed + const cursorCol = this._buffer.cursorX + 1 // 1-indexed + + // Use absolute cursor positioning (CUP - Cursor Position) + // This is more reliable than relative moves which depend on knowing + // exactly where the cursor ended up after all the content + content += `\u001b[${cursorRow};${cursorCol}H` + } + + return content + } +} + +// ============================================================================ +// SerializeAddon Class +// ============================================================================ + +export class SerializeAddon implements ITerminalAddon { + private _terminal?: ITerminalCore + + /** + * Activate the addon (called by Terminal.loadAddon) + */ + public activate(terminal: ITerminalCore): void { + this._terminal = terminal + } + + /** + * Dispose the addon and clean up resources + */ + public dispose(): void { + this._terminal = undefined + } + + /** + * Serializes terminal rows into a string that can be written back to the + * terminal to restore the state. The cursor will also be positioned to the + * correct cell. + * + * @param options Custom options to allow control over what gets serialized. + */ + public serialize(options?: ISerializeOptions): string { + if (!this._terminal) { + throw new Error("Cannot use addon until it has been loaded") + } + + const terminal = this._terminal as any + const buffer = terminal.buffer + + if (!buffer) { + return "" + } + + const activeBuffer = buffer.active || buffer.normal + if (!activeBuffer) { + return "" + } + + let content = options?.range + ? this._serializeBufferByRange(activeBuffer, options.range, true) + : this._serializeBufferByScrollback(activeBuffer, options?.scrollback) + + // Handle alternate buffer if active and not excluded + if (!options?.excludeAltBuffer) { + const altBuffer = buffer.alternate + if (altBuffer && buffer.active?.type === "alternate") { + const alternateContent = this._serializeBufferByScrollback(altBuffer, undefined) + content += `\u001b[?1049h\u001b[H${alternateContent}` + } + } + + return content + } + + /** + * Serializes terminal content as plain text (no escape sequences) + * @param options Custom options to allow control over what gets serialized. + */ + public serializeAsText(options?: { scrollback?: number; trimWhitespace?: boolean }): string { + if (!this._terminal) { + throw new Error("Cannot use addon until it has been loaded") + } + + const terminal = this._terminal as any + const buffer = terminal.buffer + + if (!buffer) { + return "" + } + + const activeBuffer = buffer.active || buffer.normal + if (!activeBuffer) { + return "" + } + + const maxRows = activeBuffer.length + const scrollback = options?.scrollback + const correctRows = scrollback === undefined ? maxRows : constrain(scrollback + this._terminal.rows, 0, maxRows) + + const startRow = maxRows - correctRows + const endRow = maxRows - 1 + const lines: string[] = [] + + for (let row = startRow; row <= endRow; row++) { + const line = activeBuffer.getLine(row) + if (line) { + const text = line.translateToString(options?.trimWhitespace ?? true) + lines.push(text) + } + } + + // Trim trailing empty lines if requested + if (options?.trimWhitespace) { + while (lines.length > 0 && lines[lines.length - 1] === "") { + lines.pop() + } + } + + return lines.join("\n") + } + + private _serializeBufferByScrollback(buffer: IBuffer, scrollback?: number): string { + const maxRows = buffer.length + const rows = this._terminal?.rows ?? 24 + const correctRows = scrollback === undefined ? maxRows : constrain(scrollback + rows, 0, maxRows) + return this._serializeBufferByRange( + buffer, + { + start: maxRows - correctRows, + end: maxRows - 1, + }, + false, + ) + } + + private _serializeBufferByRange( + buffer: IBuffer, + range: ISerializeRange, + excludeFinalCursorPosition: boolean, + ): string { + const handler = new StringSerializeHandler(buffer, this._terminal!) + const cols = this._terminal?.cols ?? 80 + return handler.serialize( + { + start: { x: 0, y: range.start }, + end: { x: cols, y: range.end }, + }, + excludeFinalCursorPosition, + ) + } +} diff --git a/packages/desktop/src/components/terminal.tsx b/packages/desktop/src/components/terminal.tsx new file mode 100644 index 00000000000..49a45a432bc --- /dev/null +++ b/packages/desktop/src/components/terminal.tsx @@ -0,0 +1,151 @@ +import { init, Terminal as Term, FitAddon } from "ghostty-web" +import { ComponentProps, onCleanup, onMount, splitProps } from "solid-js" +import { createReconnectingWS, ReconnectingWebSocket } from "@solid-primitives/websocket" +import { useSDK } from "@/context/sdk" +import { SerializeAddon } from "@/addons/serialize" +import { LocalPTY } from "@/context/session" + +await init() + +export interface TerminalProps extends ComponentProps<"div"> { + pty: LocalPTY + onSubmit?: () => void + onCleanup?: (pty: LocalPTY) => void +} + +export const Terminal = (props: TerminalProps) => { + const sdk = useSDK() + let container!: HTMLDivElement + const [local, others] = splitProps(props, ["pty", "class", "classList"]) + let ws: ReconnectingWebSocket + let term: Term + let serializeAddon: SerializeAddon + let fitAddon: FitAddon + + onMount(async () => { + ws = createReconnectingWS(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`) + term = new Term({ + cursorBlink: true, + fontSize: 14, + fontFamily: "TX-02, monospace", + allowTransparency: true, + theme: { + background: "#191515", + foreground: "#d4d4d4", + }, + scrollback: 10_000, + }) + term.attachCustomKeyEventHandler((event) => { + // allow for ctrl-` to toggle terminal in parent + if (event.ctrlKey && event.key.toLowerCase() === "`") { + event.preventDefault() + return true + } + return false + }) + + fitAddon = new FitAddon() + serializeAddon = new SerializeAddon() + term.loadAddon(serializeAddon) + term.loadAddon(fitAddon) + + term.open(container) + + if (local.pty.buffer) { + const originalSize = { cols: term.cols, rows: term.rows } + let resized = false + if (local.pty.rows && local.pty.cols) { + term.resize(local.pty.cols, local.pty.rows) + resized = true + } + term.write(local.pty.buffer) + if (local.pty.scrollY) { + term.scrollToLine(local.pty.scrollY) + } + if (resized) { + term.resize(originalSize.cols, originalSize.rows) + } + } + + container.focus() + + fitAddon.fit() + fitAddon.observeResize() + window.addEventListener("resize", () => fitAddon.fit()) + term.onResize(async (size) => { + if (ws && ws.readyState === WebSocket.OPEN) { + await sdk.client.pty.update({ + path: { id: local.pty.id }, + body: { + size: { + cols: size.cols, + rows: size.rows, + }, + }, + }) + } + }) + term.onData((data) => { + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(data) + } + }) + term.onKey((key) => { + if (key.key == "Enter") { + props.onSubmit?.() + } + }) + // term.onScroll((ydisp) => { + // console.log("Scroll position:", ydisp) + // }) + ws.addEventListener("open", () => { + console.log("WebSocket connected") + sdk.client.pty.update({ + path: { id: local.pty.id }, + body: { + size: { + cols: term.cols, + rows: term.rows, + }, + }, + }) + }) + ws.addEventListener("message", (event) => { + term.write(event.data) + }) + ws.addEventListener("error", (error) => { + console.error("WebSocket error:", error) + }) + ws.addEventListener("close", () => { + console.log("WebSocket disconnected") + }) + }) + + onCleanup(() => { + if (serializeAddon && props.onCleanup) { + const buffer = serializeAddon.serialize() + props.onCleanup({ + ...local.pty, + buffer, + rows: term.rows, + cols: term.cols, + scrollY: term.getViewportY(), + }) + } + ws?.close() + term?.dispose() + }) + + return ( +
+ ) +} diff --git a/packages/desktop/src/context/layout.tsx b/packages/desktop/src/context/layout.tsx index 81e8b537abc..ca736e84e6b 100644 --- a/packages/desktop/src/context/layout.tsx +++ b/packages/desktop/src/context/layout.tsx @@ -15,12 +15,16 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( opened: true, width: 280, }, + terminal: { + opened: false, + height: 280, + }, review: { state: "pane" as "pane" | "tab", }, }), { - name: "___default-layout", + name: "____default-layout", }, ) @@ -61,6 +65,22 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( setStore("sidebar", "width", width) }, }, + terminal: { + opened: createMemo(() => store.terminal.opened), + open() { + setStore("terminal", "opened", true) + }, + close() { + setStore("terminal", "opened", false) + }, + toggle() { + setStore("terminal", "opened", (x) => !x) + }, + height: createMemo(() => store.terminal.height), + resize(height: number) { + setStore("terminal", "height", height) + }, + }, review: { state: createMemo(() => store.review?.state ?? "closed"), pane() { diff --git a/packages/desktop/src/context/sdk.tsx b/packages/desktop/src/context/sdk.tsx index 81b32035a0b..144202ee209 100644 --- a/packages/desktop/src/context/sdk.tsx +++ b/packages/desktop/src/context/sdk.tsx @@ -27,6 +27,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ abort.abort() }) - return { directory: props.directory, client: sdk, event: emitter } + return { directory: props.directory, client: sdk, event: emitter, url: globalSDK.url } }, }) diff --git a/packages/desktop/src/context/session.tsx b/packages/desktop/src/context/session.tsx index 72098a93951..4e9fe71f8a7 100644 --- a/packages/desktop/src/context/session.tsx +++ b/packages/desktop/src/context/session.tsx @@ -8,14 +8,25 @@ import { pipe, sumBy } from "remeda" import { AssistantMessage, UserMessage } from "@opencode-ai/sdk" import { useParams } from "@solidjs/router" import { base64Encode } from "@/utils" +import { useSDK } from "./sdk" + +export type LocalPTY = { + id: string + title: string + rows?: number + cols?: number + buffer?: string + scrollY?: number +} export const { use: useSession, provider: SessionProvider } = createSimpleContext({ name: "Session", init: () => { + const sdk = useSDK() const params = useParams() const sync = useSync() const name = createMemo( - () => `___${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`, + () => `______${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`, ) const [store, setStore] = makePersisted( @@ -23,16 +34,21 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex messageId?: string tabs: { active?: string - opened: string[] + all: string[] } prompt: Prompt cursor?: number + terminals: { + active?: string + all: LocalPTY[] + } }>({ tabs: { - opened: [], + all: [], }, prompt: clonePrompt(DEFAULT_PROMPT), cursor: undefined, + terminals: { all: [] }, }), { name: name(), @@ -138,7 +154,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex setStore("tabs", "active", tab) }, setOpenedTabs(tabs: string[]) { - setStore("tabs", "opened", tabs) + setStore("tabs", "all", tabs) }, async openTab(tab: string) { if (tab === "chat") { @@ -146,8 +162,8 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex return } if (tab !== "review") { - if (!store.tabs.opened.includes(tab)) { - setStore("tabs", "opened", [...store.tabs.opened, tab]) + if (!store.tabs.all.includes(tab)) { + setStore("tabs", "all", [...store.tabs.all, tab]) } } setStore("tabs", "active", tab) @@ -156,28 +172,88 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex batch(() => { setStore( "tabs", - "opened", - store.tabs.opened.filter((x) => x !== tab), + "all", + store.tabs.all.filter((x) => x !== tab), ) if (store.tabs.active === tab) { - const index = store.tabs.opened.findIndex((f) => f === tab) - const previous = store.tabs.opened[Math.max(0, index - 1)] + const index = store.tabs.all.findIndex((f) => f === tab) + const previous = store.tabs.all[Math.max(0, index - 1)] setStore("tabs", "active", previous) } }) }, moveTab(tab: string, to: number) { - const index = store.tabs.opened.findIndex((f) => f === tab) + const index = store.tabs.all.findIndex((f) => f === tab) if (index === -1) return setStore( "tabs", - "opened", + "all", produce((opened) => { opened.splice(to, 0, opened.splice(index, 1)[0]) }), ) }, }, + terminal: { + all: createMemo(() => Object.values(store.terminals.all)), + active: createMemo(() => store.terminals.active), + new() { + sdk.client.pty.create({ body: { title: `Terminal ${store.terminals.all.length + 1}` } }).then((pty) => { + const id = pty.data?.id + if (!id) return + batch(() => { + setStore("terminals", "all", [ + ...store.terminals.all, + { + id, + title: pty.data?.title ?? "Terminal", + // rows: pty.data?.rows ?? 24, + // cols: pty.data?.cols ?? 80, + // buffer: "", + // scrollY: 0, + }, + ]) + setStore("terminals", "active", id) + }) + }) + }, + update(pty: Partial & { id: string }) { + setStore("terminals", "all", (x) => x.map((x) => (x.id === pty.id ? { ...x, ...pty } : x))) + sdk.client.pty.update({ + path: { id: pty.id }, + body: { title: pty.title, size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined }, + }) + }, + open(id: string) { + setStore("terminals", "active", id) + }, + async close(id: string) { + batch(() => { + setStore( + "terminals", + "all", + store.terminals.all.filter((x) => x.id !== id), + ) + if (store.terminals.active === id) { + const index = store.terminals.all.findIndex((f) => f.id === id) + const previous = store.tabs.all[Math.max(0, index - 1)] + setStore("terminals", "active", previous) + } + }) + await sdk.client.pty.remove({ path: { id } }) + }, + move(id: string, to: number) { + const index = store.terminals.all.findIndex((f) => f.id === id) + if (index === -1) return + setStore( + "terminals", + "all", + produce((all) => { + all.splice(to, 0, all.splice(index, 1)[0]) + }), + ) + }, + }, } }, }) diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 15180c88566..106a2e733fb 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -1,9 +1,9 @@ import { createMemo, For, ParentProps, Show } from "solid-js" import { DateTime } from "luxon" -import { A, useParams } from "@solidjs/router" +import { A, useNavigate, useParams } from "@solidjs/router" import { useLayout } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" -import { base64Encode } from "@/utils" +import { base64Decode, base64Encode } from "@/utils" import { Mark } from "@opencode-ai/ui/logo" import { Button } from "@opencode-ai/ui/button" import { Icon } from "@opencode-ai/ui/icon" @@ -12,11 +12,21 @@ import { Tooltip } from "@opencode-ai/ui/tooltip" import { Collapsible } from "@opencode-ai/ui/collapsible" import { DiffChanges } from "@opencode-ai/ui/diff-changes" import { getFilename } from "@opencode-ai/util/path" +import { Select } from "@opencode-ai/ui/select" +import { Session } from "@opencode-ai/sdk/client" export default function Layout(props: ParentProps) { + const navigate = useNavigate() const params = useParams() const globalSync = useGlobalSync() const layout = useLayout() + const currentDirectory = createMemo(() => base64Decode(params.dir ?? "")) + const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? []) + const currentSession = createMemo(() => sessions().find((s) => s.id === params.id) ?? sessions().at(0)) + + function navigateToSession(session: Session | undefined) { + navigate(`/${params.dir}/session/${session?.id}`) + } const handleOpenProject = async () => { // layout.projects.open(dir.) @@ -24,7 +34,7 @@ export default function Layout(props: ParentProps) { return (
-
+
+
+
+
+ x.title} + value={(x) => x.id} + onSelect={navigateToSession} + class="text-14-regular text-text-base max-w-3xs" + variant="ghost" + /> +
+ +
+
+ + Toggle terminal + Ctrl ` +
+ } + > + + +
+
+ +
{ + e.preventDefault() + const startX = e.clientX + const startWidth = layout.sidebar.width() + const maxWidth = window.innerWidth * 0.3 + const minWidth = 150 + const collapseThreshold = 80 + let currentWidth = startWidth + + document.body.style.userSelect = "none" + document.body.style.overflow = "hidden" + + const onMouseMove = (moveEvent: MouseEvent) => { + const deltaX = moveEvent.clientX - startX + currentWidth = startWidth + deltaX + const clampedWidth = Math.min(maxWidth, Math.max(minWidth, currentWidth)) + layout.sidebar.resize(clampedWidth) + } + + const onMouseUp = () => { + document.body.style.userSelect = "" + document.body.style.overflow = "" + document.removeEventListener("mousemove", onMouseMove) + document.removeEventListener("mouseup", onMouseUp) + + if (currentWidth < collapseThreshold) { + layout.sidebar.close() + } + } + + document.addEventListener("mousemove", onMouseMove) + document.addEventListener("mouseup", onMouseUp) + }} + /> +
-
{props.children}
+
{props.children}
) diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx index d6ce62b7030..77362533404 100644 --- a/packages/desktop/src/pages/session.tsx +++ b/packages/desktop/src/pages/session.tsx @@ -1,4 +1,4 @@ -import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo } from "solid-js" +import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo, createEffect } from "solid-js" import { useLocal, type LocalFile } from "@/context/local" import { createStore } from "solid-js/store" import { PromptInput } from "@/components/prompt-input" @@ -31,6 +31,7 @@ import { useSession } from "@/context/session" import { useLayout } from "@/context/layout" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { Diff } from "@opencode-ai/ui/diff" +import { Terminal } from "@/components/terminal" export default function Page() { const layout = useLayout() @@ -54,6 +55,14 @@ export default function Page() { document.removeEventListener("keydown", handleKeyDown) }) + createEffect(() => { + if (layout.terminal.opened()) { + if (session.terminal.all().length === 0) { + session.terminal.new() + } + } + }) + const handleKeyDown = (event: KeyboardEvent) => { if (event.getModifierState(MOD) && event.shiftKey && event.key.toLowerCase() === "p") { event.preventDefault() @@ -73,6 +82,16 @@ export default function Page() { document.documentElement.setAttribute("data-theme", nextTheme) return } + if (event.ctrlKey && event.key.toLowerCase() === "`") { + event.preventDefault() + layout.terminal.toggle() + return + } + + // @ts-expect-error + if (document.activeElement?.dataset?.component === "terminal") { + return + } const focused = document.activeElement === inputRef if (focused) { @@ -141,7 +160,7 @@ export default function Page() { const handleDragOver = (event: DragEvent) => { const { draggable, droppable } = event if (draggable && droppable) { - const currentTabs = session.layout.tabs.opened + const currentTabs = session.layout.tabs.all const fromIndex = currentTabs?.indexOf(draggable.id.toString()) const toIndex = currentTabs?.indexOf(droppable.id.toString()) if (fromIndex !== toIndex && toIndex !== undefined) { @@ -259,317 +278,397 @@ export default function Page() { const wide = createMemo(() => layout.review.state() === "tab" || !session.diffs().length) return ( -
- - - - -
- - -
-
Session
- +
+ + + + +
+ + +
+
Session
+ + +
{session.usage.context() ?? 0}%
+
+
+
+ + + } > - -
{session.usage.context() ?? 0}%
- -
- - - - } - > -
- - - -
-
Review
- -
- {session.info()?.summary?.files ?? 0} -
+
+ + +
+
Review
+ +
+ {session.info()?.summary?.files ?? 0} +
+
+
-
- - - - - {(tab) => } - - -
- - setStore("fileSelectOpen", true)} - /> - -
- -
- -
+ + + + + {(tab) => ( + + )} + + +
+ + setStore("fileSelectOpen", true)} + /> + +
+ +
+
- - -
- - 1 - ? "pr-6 pl-18" - : "px-6"), - }} - diffComponent={Diff} - /> -
-
- -
-
New session
-
- -
- {getDirectory(sync.data.path.directory)} - {getFilename(sync.data.path.directory)} -
+
+ + +
+ + 1 + ? "pr-6 pl-18" + : "px-6"), + }} + diffComponent={Diff} + />
-
- -
- Last modified  - - {DateTime.fromMillis(sync.data.project.time.created).toRelative()} - + + +
+
New session
+
+ +
+ {getDirectory(sync.data.path.directory)} + {getFilename(sync.data.path.directory)} +
+
+
+ +
+ Last modified  + + {DateTime.fromMillis(sync.data.project.time.created).toRelative()} + +
+
+ +
+
+ { + inputRef = el + }} + />
- - -
-
- { - inputRef = el +
+
+ +
+ + { + layout.review.tab() + session.layout.setActiveTab("review") + }} + /> + + } />
-
+
- + + +
- { - layout.review.tab() - session.layout.setActiveTab("review") - }} - /> - - } + split />
-
-
- - - + + + + {(tab) => { + const [file] = createResource( + () => tab, + async (tab) => { + if (tab.startsWith("file://")) { + return local.file.node(tab.replace("file://", "")) + } + return undefined + }, + ) + return ( + + + + {(f) => ( + + )} + + + + ) + }} + + + + + {(draggedFile) => { + const [file] = createResource( + () => draggedFile(), + async (tab) => { + if (tab.startsWith("file://")) { + return local.file.node(tab.replace("file://", "")) + } + return undefined + }, + ) + return ( +
+ {(f) => } +
+ ) + }} +
+
+ + +
+ { + inputRef = el + }} + /> +
+
+ + } + > +
    + + {(path) => ( +
  • + +
  • + )} +
    +
+ +
+ + x} + onOpenChange={(open) => setStore("fileSelectOpen", open)} + onSelect={(x) => { + if (x) { + local.file.open(x) + return session.layout.openTab("file://" + x) + } + return undefined + }} + > + {(i) => (
- -
- -
- - {(tab) => { - const [file] = createResource( - () => tab, - async (tab) => { - if (tab.startsWith("file://")) { - return local.file.node(tab.replace("file://", "")) - } - return undefined - }, - ) - return ( - - - - {(f) => ( - - )} - - - - ) - }} - - - - - {(draggedFile) => { - const [file] = createResource( - () => draggedFile(), - async (tab) => { - if (tab.startsWith("file://")) { - return local.file.node(tab.replace("file://", "")) - } - return undefined - }, - ) - return ( -
- {(f) => } +
+ +
+ + {getDirectory(i)} + + {getFilename(i)} +
- ) - }} - - - - -
- { - inputRef = el +
+
+ )} + +
+
+ +
+
{ + e.preventDefault() + const startY = e.clientY + const startHeight = layout.terminal.height() + const maxHeight = window.innerHeight * 0.6 + const minHeight = 100 + const collapseThreshold = 50 + let currentHeight = startHeight + + document.body.style.userSelect = "none" + document.body.style.overflow = "hidden" + + const onMouseMove = (moveEvent: MouseEvent) => { + const deltaY = startY - moveEvent.clientY + currentHeight = startHeight + deltaY + const clampedHeight = Math.min(maxHeight, Math.max(minHeight, currentHeight)) + layout.terminal.resize(clampedHeight) + } + + const onMouseUp = () => { + document.body.style.userSelect = "" + document.body.style.overflow = "" + document.removeEventListener("mousemove", onMouseMove) + document.removeEventListener("mouseup", onMouseUp) + + if (currentHeight < collapseThreshold) { + layout.terminal.close() + } + } + + document.addEventListener("mousemove", onMouseMove) + document.addEventListener("mouseup", onMouseUp) }} /> -
- - - }> -
    - - {(path) => ( -
  • - -
  • + {terminal.title} + + )} +
    +
    + + + +
    + + + {(terminal) => ( + + + )} -
- -
- - x} - onOpenChange={(open) => setStore("fileSelectOpen", open)} - onSelect={(x) => { - if (x) { - local.file.open(x) - return session.layout.openTab("file://" + x) - } - return undefined - }} - > - {(i) => ( -
-
- -
- - {getDirectory(i)} - - {getFilename(i)} -
-
-
-
- )} -
+ +
) diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 46c8c3200a6..2c5441998b5 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -72,6 +72,7 @@ "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", + "bun-pty": "0.4.2", "chokidar": "4.0.3", "clipboardy": "4.0.0", "decimal.js": "10.5.0", diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index 7754b4a3953..76f78f3faa8 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -5,6 +5,7 @@ import { Instance } from "@/project/instance" import { InstanceBootstrap } from "@/project/bootstrap" import { Rpc } from "@/util/rpc" import { upgrade } from "@/cli/upgrade" +import type { BunWebSocketData } from "hono/bun" await Log.init({ print: process.argv.includes("--print-logs"), @@ -27,7 +28,7 @@ process.on("uncaughtException", (e) => { }) }) -let server: Bun.Server +let server: Bun.Server export const rpc = { async server(input: { port: number; hostname: string }) { if (server) await server.stop(true) @@ -53,7 +54,9 @@ export const rpc = { async shutdown() { Log.Default.info("worker shutting down") await Instance.disposeAll() - await server.stop(true) + // TODO: this should be awaited, but ws connections are + // causing this to hang, need to revisit this + server.stop(true) }, } diff --git a/packages/opencode/src/id/id.ts b/packages/opencode/src/id/id.ts index 99eb6c9ff06..ad6e22e1bee 100644 --- a/packages/opencode/src/id/id.ts +++ b/packages/opencode/src/id/id.ts @@ -8,6 +8,7 @@ export namespace Identifier { permission: "per", user: "usr", part: "prt", + pty: "pty", } as const export function schema(prefix: keyof typeof prefixes) { diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts new file mode 100644 index 00000000000..efb519ff2a7 --- /dev/null +++ b/packages/opencode/src/pty/index.ts @@ -0,0 +1,199 @@ +import { spawn, type IPty } from "bun-pty" +import z from "zod" +import { Identifier } from "../id/id" +import { Log } from "../util/log" +import { Bus } from "../bus" +import type { WSContext } from "hono/ws" +import { Instance } from "../project/instance" +import { shell } from "@opencode-ai/util/shell" + +export namespace Pty { + const log = Log.create({ service: "pty" }) + + export const Info = z + .object({ + id: Identifier.schema("pty"), + title: z.string(), + command: z.string(), + args: z.array(z.string()), + cwd: z.string(), + status: z.enum(["running", "exited"]), + pid: z.number(), + }) + .meta({ ref: "Pty" }) + + export type Info = z.infer + + export const CreateInput = z.object({ + command: z.string().optional(), + args: z.array(z.string()).optional(), + cwd: z.string().optional(), + title: z.string().optional(), + env: z.record(z.string(), z.string()).optional(), + }) + + export type CreateInput = z.infer + + export const UpdateInput = z.object({ + title: z.string().optional(), + size: z + .object({ + rows: z.number(), + cols: z.number(), + }) + .optional(), + }) + + export type UpdateInput = z.infer + + export const Event = { + Created: Bus.event("pty.created", z.object({ info: Info })), + Updated: Bus.event("pty.updated", z.object({ info: Info })), + Exited: Bus.event("pty.exited", z.object({ id: Identifier.schema("pty"), exitCode: z.number() })), + Deleted: Bus.event("pty.deleted", z.object({ id: Identifier.schema("pty") })), + } + + interface ActiveSession { + info: Info + process: IPty + buffer: string + subscribers: Set + } + + const state = Instance.state( + () => new Map(), + async (sessions) => { + for (const session of sessions.values()) { + try { + session.process.kill() + } catch {} + for (const ws of session.subscribers) { + ws.close() + } + } + sessions.clear() + }, + ) + + export function list() { + return Array.from(state().values()).map((s) => s.info) + } + + export function get(id: string) { + return state().get(id)?.info + } + + export async function create(input: CreateInput) { + const id = Identifier.create("pty", false) + const command = input.command || shell() + const args = input.args || [] + const cwd = input.cwd || Instance.directory + const env = { ...process.env, ...input.env } as Record + log.info("creating session", { id, cmd: command, args, cwd }) + + const ptyProcess = spawn(command, args, { + name: "xterm-256color", + cwd, + env, + }) + const info = { + id, + title: input.title || `Terminal ${id.slice(-4)}`, + command, + args, + cwd, + status: "running", + pid: ptyProcess.pid, + } as const + const session: ActiveSession = { + info, + process: ptyProcess, + buffer: "", + subscribers: new Set(), + } + state().set(id, session) + ptyProcess.onData((data) => { + if (session.subscribers.size === 0) { + session.buffer += data + return + } + for (const ws of session.subscribers) { + if (ws.readyState === 1) { + ws.send(data) + } + } + }) + ptyProcess.onExit(({ exitCode }) => { + log.info("session exited", { id, exitCode }) + session.info.status = "exited" + Bus.publish(Event.Exited, { id, exitCode }) + state().delete(id) + }) + Bus.publish(Event.Created, { info }) + return info + } + + export async function update(id: string, input: UpdateInput) { + const session = state().get(id) + if (!session) return + if (input.title) { + session.info.title = input.title + } + if (input.size) { + session.process.resize(input.size.cols, input.size.rows) + } + Bus.publish(Event.Updated, { info: session.info }) + return session.info + } + + export async function remove(id: string) { + const session = state().get(id) + if (!session) return + log.info("removing session", { id }) + try { + session.process.kill() + } catch {} + for (const ws of session.subscribers) { + ws.close() + } + state().delete(id) + Bus.publish(Event.Deleted, { id }) + } + + export function resize(id: string, cols: number, rows: number) { + const session = state().get(id) + if (session && session.info.status === "running") { + session.process.resize(cols, rows) + } + } + + export function write(id: string, data: string) { + const session = state().get(id) + if (session && session.info.status === "running") { + session.process.write(data) + } + } + + export function connect(id: string, ws: WSContext) { + const session = state().get(id) + if (!session) { + ws.close() + return + } + log.info("client connected to session", { id }) + session.subscribers.add(ws) + if (session.buffer) { + ws.send(session.buffer) + session.buffer = "" + } + return { + onMessage: (message: string | ArrayBuffer) => { + session.process.write(String(message)) + }, + onClose: () => { + log.info("client disconnected from session", { id }) + session.subscribers.delete(ws) + }, + } + } +} diff --git a/packages/opencode/src/server/error.ts b/packages/opencode/src/server/error.ts new file mode 100644 index 00000000000..26e2dfcb121 --- /dev/null +++ b/packages/opencode/src/server/error.ts @@ -0,0 +1,36 @@ +import { resolver } from "hono-openapi" +import z from "zod" +import { Storage } from "../storage/storage" + +export const ERRORS = { + 400: { + description: "Bad request", + content: { + "application/json": { + schema: resolver( + z + .object({ + data: z.any(), + errors: z.array(z.record(z.string(), z.any())), + success: z.literal(false), + }) + .meta({ + ref: "BadRequestError", + }), + ), + }, + }, + }, + 404: { + description: "Not found", + content: { + "application/json": { + schema: resolver(Storage.NotFoundError.Schema), + }, + }, + }, +} as const + +export function errors(...codes: number[]) { + return Object.fromEntries(codes.map((code) => [code, ERRORS[code as keyof typeof ERRORS]])) +} diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 31d0822762b..a74b7876f1c 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -43,43 +43,13 @@ import { Snapshot } from "@/snapshot" import { SessionSummary } from "@/session/summary" import { GlobalBus } from "@/bus/global" import { SessionStatus } from "@/session/status" +import { upgradeWebSocket, websocket } from "hono/bun" +import { errors } from "./error" +import { Pty } from "@/pty" // @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 globalThis.AI_SDK_LOG_WARNINGS = false -const ERRORS = { - 400: { - description: "Bad request", - content: { - "application/json": { - schema: resolver( - z - .object({ - data: z.any(), - errors: z.array(z.record(z.string(), z.any())), - success: z.literal(false), - }) - .meta({ - ref: "BadRequestError", - }), - ), - }, - }, - }, - 404: { - description: "Not found", - content: { - "application/json": { - schema: resolver(Storage.NotFoundError.Schema), - }, - }, - }, -} as const - -function errors(...codes: number[]) { - return Object.fromEntries(codes.map((code) => [code, ERRORS[code as keyof typeof ERRORS]])) -} - export namespace Server { const log = Log.create({ service: "server" }) @@ -192,7 +162,167 @@ export namespace Server { }), ) .use(validator("query", z.object({ directory: z.string().optional() }))) + .route("/project", ProjectRoute) + + .get( + "/pty", + describeRoute({ + description: "List all PTY sessions", + operationId: "pty.list", + responses: { + 200: { + description: "List of sessions", + content: { + "application/json": { + schema: resolver(Pty.Info.array()), + }, + }, + }, + }, + }), + async (c) => { + return c.json(Pty.list()) + }, + ) + .post( + "/pty", + describeRoute({ + description: "Create a new PTY session", + operationId: "pty.create", + responses: { + 200: { + description: "Created session", + content: { + "application/json": { + schema: resolver(Pty.Info), + }, + }, + }, + ...errors(400), + }, + }), + validator("json", Pty.CreateInput), + async (c) => { + const info = await Pty.create(c.req.valid("json")) + return c.json(info) + }, + ) + .put( + "/pty/:id", + describeRoute({ + description: "Update PTY session", + operationId: "pty.update", + responses: { + 200: { + description: "Updated session", + content: { + "application/json": { + schema: resolver(Pty.Info), + }, + }, + }, + ...errors(400), + }, + }), + validator("param", z.object({ id: z.string() })), + validator("json", Pty.UpdateInput), + async (c) => { + const info = await Pty.update(c.req.valid("param").id, c.req.valid("json")) + return c.json(info) + }, + ) + .get( + "/pty/:id", + describeRoute({ + description: "Get PTY session info", + operationId: "pty.get", + responses: { + 200: { + description: "Session info", + content: { + "application/json": { + schema: resolver(Pty.Info), + }, + }, + }, + ...errors(404), + }, + }), + validator("param", z.object({ id: z.string() })), + async (c) => { + const info = Pty.get(c.req.valid("param").id) + if (!info) { + throw new Storage.NotFoundError({ message: "Session not found" }) + } + return c.json(info) + }, + ) + .delete( + "/pty/:id", + describeRoute({ + description: "Remove a PTY session", + operationId: "pty.remove", + responses: { + 200: { + description: "Session removed", + content: { + "application/json": { + schema: resolver(z.boolean()), + }, + }, + }, + ...errors(404), + }, + }), + validator("param", z.object({ id: z.string() })), + async (c) => { + await Pty.remove(c.req.valid("param").id) + return c.json(true) + }, + ) + .get( + "/pty/:id/connect", + describeRoute({ + description: "Connect to a PTY session", + operationId: "pty.connect", + responses: { + 200: { + description: "Connected session", + content: { + "application/json": { + schema: resolver(z.boolean()), + }, + }, + }, + 404: { + description: "Session not found", + content: { + "application/json": { + schema: resolver(z.boolean()), + }, + }, + }, + }, + }), + validator("param", z.object({ id: z.string() })), + upgradeWebSocket((c) => { + const id = c.req.param("id") + let handler: ReturnType + return { + onOpen(_event, ws) { + handler = Pty.connect(id, ws) + }, + onMessage(event) { + handler?.onMessage(String(event.data)) + }, + onClose() { + handler?.onClose() + }, + } + }), + ) + .get( "/config", describeRoute({ @@ -2083,6 +2213,7 @@ export namespace Server { hostname: opts.hostname, idleTimeout: 0, fetch: App().fetch, + websocket: websocket, }) return server } diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts index 0dc470566ee..d04277cbc81 100644 --- a/packages/sdk/js/src/gen/sdk.gen.ts +++ b/packages/sdk/js/src/gen/sdk.gen.ts @@ -8,6 +8,23 @@ import type { ProjectListResponses, ProjectCurrentData, ProjectCurrentResponses, + PtyListData, + PtyListResponses, + PtyCreateData, + PtyCreateResponses, + PtyCreateErrors, + PtyRemoveData, + PtyRemoveResponses, + PtyRemoveErrors, + PtyGetData, + PtyGetResponses, + PtyGetErrors, + PtyUpdateData, + PtyUpdateResponses, + PtyUpdateErrors, + PtyConnectData, + PtyConnectResponses, + PtyConnectErrors, ConfigGetData, ConfigGetResponses, ConfigUpdateData, @@ -231,6 +248,76 @@ class Project extends _HeyApiClient { } } +class Pty extends _HeyApiClient { + /** + * List all PTY sessions + */ + public list(options?: Options) { + return (options?.client ?? this._client).get({ + url: "/pty", + ...options, + }) + } + + /** + * Create a new PTY session + */ + public create(options?: Options) { + return (options?.client ?? this._client).post({ + url: "/pty", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }) + } + + /** + * Remove a PTY session + */ + public remove(options: Options) { + return (options.client ?? this._client).delete({ + url: "/pty/{id}", + ...options, + }) + } + + /** + * Get PTY session info + */ + public get(options: Options) { + return (options.client ?? this._client).get({ + url: "/pty/{id}", + ...options, + }) + } + + /** + * Update PTY session + */ + public update(options: Options) { + return (options.client ?? this._client).put({ + url: "/pty/{id}", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }) + } + + /** + * Connect to a PTY session + */ + public connect(options: Options) { + return (options.client ?? this._client).get({ + url: "/pty/{id}/connect", + ...options, + }) + } +} + class Config extends _HeyApiClient { /** * Get config info @@ -1005,6 +1092,7 @@ export class OpencodeClient extends _HeyApiClient { } global = new Global({ client: this._client }) project = new Project({ client: this._client }) + pty = new Pty({ client: this._client }) config = new Config({ client: this._client }) tool = new Tool({ client: this._client }) instance = new Instance({ client: this._client }) diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 6c80f0b7c52..58ba58d359c 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -655,6 +655,45 @@ export type EventTuiToastShow = { } } +export type Pty = { + id: string + title: string + command: string + args: Array + cwd: string + status: "running" | "exited" + pid: number +} + +export type EventPtyCreated = { + type: "pty.created" + properties: { + info: Pty + } +} + +export type EventPtyUpdated = { + type: "pty.updated" + properties: { + info: Pty + } +} + +export type EventPtyExited = { + type: "pty.exited" + properties: { + id: string + exitCode: number + } +} + +export type EventPtyDeleted = { + type: "pty.deleted" + properties: { + id: string + } +} + export type EventServerConnected = { type: "server.connected" properties: { @@ -690,6 +729,10 @@ export type Event = | EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow + | EventPtyCreated + | EventPtyUpdated + | EventPtyExited + | EventPtyDeleted | EventServerConnected export type GlobalEvent = { @@ -708,6 +751,21 @@ export type Project = { } } +export type BadRequestError = { + data: unknown + errors: Array<{ + [key: string]: unknown + }> + success: false +} + +export type NotFoundError = { + name: "NotFoundError" + data: { + message: string + } +} + /** * Custom keybind configurations */ @@ -1266,14 +1324,6 @@ export type Config = { } } -export type BadRequestError = { - data: unknown - errors: Array<{ - [key: string]: unknown - }> - success: false -} - export type ToolIds = Array export type ToolListItem = { @@ -1295,13 +1345,6 @@ export type VcsInfo = { branch: string } -export type NotFoundError = { - name: "NotFoundError" - data: { - message: string - } -} - export type TextPartInput = { id?: string type: "text" @@ -1614,6 +1657,181 @@ export type ProjectCurrentResponses = { export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses] +export type PtyListData = { + body?: never + path?: never + query?: { + directory?: string + } + url: "/pty" +} + +export type PtyListResponses = { + /** + * List of sessions + */ + 200: Array +} + +export type PtyListResponse = PtyListResponses[keyof PtyListResponses] + +export type PtyCreateData = { + body?: { + command?: string + args?: Array + cwd?: string + title?: string + env?: { + [key: string]: string + } + } + path?: never + query?: { + directory?: string + } + url: "/pty" +} + +export type PtyCreateErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type PtyCreateError = PtyCreateErrors[keyof PtyCreateErrors] + +export type PtyCreateResponses = { + /** + * Created session + */ + 200: Pty +} + +export type PtyCreateResponse = PtyCreateResponses[keyof PtyCreateResponses] + +export type PtyRemoveData = { + body?: never + path: { + id: string + } + query?: { + directory?: string + } + url: "/pty/{id}" +} + +export type PtyRemoveErrors = { + /** + * Not found + */ + 404: NotFoundError +} + +export type PtyRemoveError = PtyRemoveErrors[keyof PtyRemoveErrors] + +export type PtyRemoveResponses = { + /** + * Session removed + */ + 200: boolean +} + +export type PtyRemoveResponse = PtyRemoveResponses[keyof PtyRemoveResponses] + +export type PtyGetData = { + body?: never + path: { + id: string + } + query?: { + directory?: string + } + url: "/pty/{id}" +} + +export type PtyGetErrors = { + /** + * Not found + */ + 404: NotFoundError +} + +export type PtyGetError = PtyGetErrors[keyof PtyGetErrors] + +export type PtyGetResponses = { + /** + * Session info + */ + 200: Pty +} + +export type PtyGetResponse = PtyGetResponses[keyof PtyGetResponses] + +export type PtyUpdateData = { + body?: { + title?: string + size?: { + rows: number + cols: number + } + } + path: { + id: string + } + query?: { + directory?: string + } + url: "/pty/{id}" +} + +export type PtyUpdateErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type PtyUpdateError = PtyUpdateErrors[keyof PtyUpdateErrors] + +export type PtyUpdateResponses = { + /** + * Updated session + */ + 200: Pty +} + +export type PtyUpdateResponse = PtyUpdateResponses[keyof PtyUpdateResponses] + +export type PtyConnectData = { + body?: never + path: { + id: string + } + query?: { + directory?: string + } + url: "/pty/{id}/connect" +} + +export type PtyConnectErrors = { + /** + * Session not found + */ + 404: boolean +} + +export type PtyConnectError = PtyConnectErrors[keyof PtyConnectErrors] + +export type PtyConnectResponses = { + /** + * Connected session + */ + 200: boolean +} + +export type PtyConnectResponse = PtyConnectResponses[keyof PtyConnectResponses] + export type ConfigGetData = { body?: never path?: never diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 306d7964988..9e4f00a0de7 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -153,10 +153,10 @@ const newIcons = { stop: ``, enter: ``, "layout-left": ``, - "layout-left-partial": ``, + "layout-left-partial": ``, "layout-left-full": ``, "layout-right": ``, - "layout-right-partial": ``, + "layout-right-partial": ``, "layout-right-full": ``, "speech-bubble": ``, "align-right": ``, @@ -167,6 +167,9 @@ const newIcons = { "bubble-5": ``, github: ``, discord: ``, + "layout-bottom": ``, + "layout-bottom-partial": ``, + "layout-bottom-full": ``, } export interface IconProps extends ComponentProps<"svg"> { diff --git a/packages/ui/src/components/select.css b/packages/ui/src/components/select.css index 421215a78ce..96ddf174cd8 100644 --- a/packages/ui/src/components/select.css +++ b/packages/ui/src/components/select.css @@ -20,6 +20,7 @@ [data-component="select-content"] { min-width: 4rem; + max-width: 23rem; overflow: hidden; border-radius: var(--radius-md); border-width: 1px; @@ -39,6 +40,7 @@ } [data-slot="select-select-content-list"] { + min-height: 2rem; overflow-y: auto; max-height: 12rem; white-space: nowrap; diff --git a/packages/ui/src/components/select.tsx b/packages/ui/src/components/select.tsx index 464900ef97b..9ba1f177b56 100644 --- a/packages/ui/src/components/select.tsx +++ b/packages/ui/src/components/select.tsx @@ -1,10 +1,10 @@ import { Select as Kobalte } from "@kobalte/core/select" -import { createMemo, type ComponentProps } from "solid-js" +import { createMemo, splitProps, type ComponentProps } from "solid-js" import { pipe, groupBy, entries, map } from "remeda" import { Button, ButtonProps } from "./button" import { Icon } from "./icon" -export interface SelectProps { +export type SelectProps = Omit>, "value" | "onSelect"> & { placeholder?: string options: T[] current?: T @@ -17,10 +17,21 @@ export interface SelectProps { } export function Select(props: SelectProps & ButtonProps) { + const [local, others] = splitProps(props, [ + "class", + "classList", + "placeholder", + "options", + "current", + "value", + "label", + "groupBy", + "onSelect", + ]) const grouped = createMemo(() => { const result = pipe( - props.options, - groupBy((x) => (props.groupBy ? props.groupBy(x) : "")), + local.options, + groupBy((x) => (local.groupBy ? local.groupBy(x) : "")), // mapValues((x) => x.sort((a, b) => a.title.localeCompare(b.title))), entries(), map(([k, v]) => ({ category: k, options: v })), @@ -29,28 +40,30 @@ export function Select(props: SelectProps & ButtonProps) { }) return ( + // @ts-ignore + {...others} data-component="select" - value={props.current} + value={local.current} options={grouped()} - optionValue={(x) => (props.value ? props.value(x) : (x as string))} - optionTextValue={(x) => (props.label ? props.label(x) : (x as string))} + optionValue={(x) => (local.value ? local.value(x) : (x as string))} + optionTextValue={(x) => (local.label ? local.label(x) : (x as string))} optionGroupChildren="options" - placeholder={props.placeholder} - sectionComponent={(props) => ( - {props.section.rawValue.category} + placeholder={local.placeholder} + sectionComponent={(local) => ( + {local.section.rawValue.category} )} itemComponent={(itemProps) => ( - {props.label ? props.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)} + {local.label ? local.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)} @@ -58,24 +71,25 @@ export function Select(props: SelectProps & ButtonProps) { )} onChange={(v) => { - props.onSelect?.(v ?? undefined) + local.onSelect?.(v ?? undefined) }} > data-slot="select-select-trigger-value"> {(state) => { - const selected = state.selectedOption() ?? props.current - if (!selected) return props.placeholder || "" - if (props.label) return props.label(selected) + const selected = state.selectedOption() ?? local.current + if (!selected) return local.placeholder || "" + if (local.label) return local.label(selected) return selected as string }} @@ -86,8 +100,8 @@ export function Select(props: SelectProps & ButtonProps) { diff --git a/packages/ui/src/components/tabs.css b/packages/ui/src/components/tabs.css index d03e57320ff..d60edc5c509 100644 --- a/packages/ui/src/components/tabs.css +++ b/packages/ui/src/components/tabs.css @@ -6,7 +6,7 @@ background-color: var(--background-stronger); overflow: clip; - [data-slot="tabs-tabs-list"] { + [data-slot="tabs-list"] { height: 48px; width: 100%; position: relative; @@ -36,7 +36,7 @@ } } - [data-slot="tabs-tabs-trigger-wrapper"] { + [data-slot="tabs-trigger-wrapper"] { position: relative; height: 100%; display: flex; @@ -58,14 +58,14 @@ border-right: 1px solid var(--border-weak-base); background-color: var(--background-base); - [data-slot="tabs-tabs-trigger"] { + [data-slot="tabs-trigger"] { display: flex; align-items: center; justify-content: center; padding: 14px 24px; } - [data-slot="tabs-tabs-trigger-close-button"] { + [data-slot="tabs-trigger-close-button"] { display: flex; align-items: center; justify-content: center; @@ -84,12 +84,12 @@ box-shadow: 0 0 0 2px var(--border-focus); } &:has([data-hidden]) { - [data-slot="tabs-tabs-trigger-close-button"] { + [data-slot="tabs-trigger-close-button"] { opacity: 0; } &:hover { - [data-slot="tabs-tabs-trigger-close-button"] { + [data-slot="tabs-trigger-close-button"] { opacity: 1; } } @@ -98,23 +98,23 @@ color: var(--text-strong); background-color: transparent; border-bottom-color: transparent; - [data-slot="tabs-tabs-trigger-close-button"] { + [data-slot="tabs-trigger-close-button"] { opacity: 1; } } &:hover:not(:disabled):not([data-selected]) { color: var(--text-strong); } - &:has([data-slot="tabs-tabs-trigger-close-button"]) { + &:has([data-slot="tabs-trigger-close-button"]) { padding-right: 12px; - [data-slot="tabs-tabs-trigger"] { + [data-slot="tabs-trigger"] { padding-right: 0; } } } - [data-slot="tabs-tabs-content"] { + [data-slot="tabs-content"] { overflow-y: auto; flex: 1; @@ -129,4 +129,80 @@ outline: none; } } + + &[data-variant="alt"] { + [data-slot="tabs-list"] { + padding-left: 24px; + padding-right: 24px; + gap: 12px; + border-bottom: 1px solid var(--border-weak-base); + background-color: transparent; + + &::after { + border: none; + background-color: transparent; + } + &:empty::after { + display: none; + } + } + + [data-slot="tabs-trigger-wrapper"] { + border: none; + color: var(--text-base); + background-color: transparent; + border-bottom-width: 2px; + border-bottom-style: solid; + border-bottom-color: transparent; + gap: 4px; + + /* text-14-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-x-large); /* 171.429% */ + letter-spacing: var(--letter-spacing-normal); + + [data-slot="tabs-trigger"] { + height: 100%; + padding: 4px; + background-color: transparent; + border-bottom-width: 2px; + border-bottom-color: transparent; + } + + [data-slot="tabs-trigger-close-button"] { + display: flex; + align-items: center; + justify-content: center; + } + + [data-component="icon-button"] { + width: 16px; + height: 16px; + margin: 0; + } + + &:has([data-selected]) { + color: var(--text-strong); + background-color: transparent; + border-bottom-color: var(--icon-strong-base); + } + + &:hover:not(:disabled):not([data-selected]) { + color: var(--text-strong); + } + + &:has([data-slot="tabs-trigger-close-button"]) { + padding-right: 0; + [data-slot="tabs-trigger"] { + padding-right: 0; + } + } + } + + /* [data-slot="tabs-content"] { */ + /* } */ + } } diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx index 68acd88d4e1..d91ad3c4156 100644 --- a/packages/ui/src/components/tabs.tsx +++ b/packages/ui/src/components/tabs.tsx @@ -2,7 +2,9 @@ import { Tabs as Kobalte } from "@kobalte/core/tabs" import { Show, splitProps, type JSX } from "solid-js" import type { ComponentProps, ParentProps } from "solid-js" -export interface TabsProps extends ComponentProps {} +export interface TabsProps extends ComponentProps { + variant?: "normal" | "alt" +} export interface TabsListProps extends ComponentProps {} export interface TabsTriggerProps extends ComponentProps { classes?: { @@ -14,11 +16,12 @@ export interface TabsTriggerProps extends ComponentProps export interface TabsContentProps extends ComponentProps {} function TabsRoot(props: TabsProps) { - const [split, rest] = splitProps(props, ["class", "classList"]) + const [split, rest] = splitProps(props, ["class", "classList", "variant"]) return ( ) { ]) return (
) { > {split.children} {(closeButton) => ( -
+
{closeButton()}
)} @@ -81,7 +84,7 @@ function TabsContent(props: ParentProps) { return ( Date: Thu, 4 Dec 2025 15:57:01 -0600 Subject: [PATCH 21/60] Revert "feat(desktop): terminal pane (#5081)" This reverts commit d763c11a6d5bc57869f11c87f5a293f61e427e0a. --- bun.lock | 13 +- flake.lock | 6 +- nix/hashes.json | 2 +- package.json | 2 +- packages/desktop/package.json | 2 - packages/desktop/src/addons/serialize.ts | 649 ------------------- packages/desktop/src/components/terminal.tsx | 151 ----- packages/desktop/src/context/layout.tsx | 22 +- packages/desktop/src/context/sdk.tsx | 2 +- packages/desktop/src/context/session.tsx | 100 +-- packages/desktop/src/pages/layout.tsx | 114 +--- packages/desktop/src/pages/session.tsx | 649 ++++++++----------- packages/opencode/package.json | 1 - packages/opencode/src/cli/cmd/tui/worker.ts | 7 +- packages/opencode/src/id/id.ts | 1 - packages/opencode/src/pty/index.ts | 199 ------ packages/opencode/src/server/error.ts | 36 - packages/opencode/src/server/server.ts | 197 +----- packages/sdk/js/src/gen/sdk.gen.ts | 88 --- packages/sdk/js/src/gen/types.gen.ts | 248 +------ packages/ui/src/components/icon.tsx | 7 +- packages/ui/src/components/select.css | 2 - packages/ui/src/components/select.tsx | 56 +- packages/ui/src/components/tabs.css | 96 +-- packages/ui/src/components/tabs.tsx | 17 +- packages/ui/src/components/tooltip.css | 7 +- packages/util/src/shell.ts | 13 - 27 files changed, 395 insertions(+), 2292 deletions(-) delete mode 100644 packages/desktop/src/addons/serialize.ts delete mode 100644 packages/desktop/src/components/terminal.tsx delete mode 100644 packages/opencode/src/pty/index.ts delete mode 100644 packages/opencode/src/server/error.ts delete mode 100644 packages/util/src/shell.ts diff --git a/bun.lock b/bun.lock index ff4f1444705..aad651621cb 100644 --- a/bun.lock +++ b/bun.lock @@ -135,13 +135,11 @@ "@solid-primitives/resize-observer": "2.1.3", "@solid-primitives/scroll": "2.1.3", "@solid-primitives/storage": "4.3.3", - "@solid-primitives/websocket": "1.3.1", "@solidjs/meta": "catalog:", "@solidjs/router": "catalog:", "@thisbeyond/solid-dnd": "0.7.5", "diff": "catalog:", "fuzzysort": "catalog:", - "ghostty-web": "0.3.0", "luxon": "catalog:", "marked": "16.2.0", "marked-shiki": "1.2.1", @@ -248,7 +246,6 @@ "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", - "bun-pty": "0.4.2", "chokidar": "4.0.3", "clipboardy": "4.0.0", "decimal.js": "10.5.0", @@ -460,7 +457,7 @@ "ai": "5.0.97", "diff": "8.0.2", "fuzzysort": "3.1.0", - "hono": "4.10.7", + "hono": "4.7.10", "hono-openapi": "1.1.1", "luxon": "3.6.1", "remeda": "2.26.0", @@ -1509,8 +1506,6 @@ "@solid-primitives/utils": ["@solid-primitives/utils@6.3.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="], - "@solid-primitives/websocket": ["@solid-primitives/websocket@1.3.1", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-F06tA2FKa5VsnS4E4WEc3jHpsJfXRlMTGOtolugTzCqV3JmJTyvk9UVg1oz6PgGHKGi1CQ91OP8iW34myyJgaQ=="], - "@solidjs/meta": ["@solidjs/meta@0.29.4", "", { "peerDependencies": { "solid-js": ">=1.8.4" } }, "sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g=="], "@solidjs/router": ["@solidjs/router@0.15.4", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ=="], @@ -1895,8 +1890,6 @@ "bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="], - "bun-pty": ["bun-pty@0.4.2", "", {}, "sha512-sHImDz6pJDsHAroYpC9ouKVgOyqZ7FP3N+stX5IdMddHve3rf9LIZBDomQcXrACQ7sQDNuwZQHG8BKR7w8krkQ=="], - "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], "bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="], @@ -2341,8 +2334,6 @@ "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], - "ghostty-web": ["ghostty-web@0.3.0", "", {}, "sha512-SAdSHWYF20GMZUB0n8kh1N6Z4ljMnuUqT8iTB2n5FAPswEV10MejEpLlhW/769GL5+BQa1NYwEg9y/XCckV5+A=="], - "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], "giget": ["giget@1.2.5", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.5.4", "pathe": "^2.0.3", "tar": "^6.2.1" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug=="], @@ -2437,7 +2428,7 @@ "hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="], - "hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="], + "hono": ["hono@4.7.10", "", {}, "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ=="], "hono-openapi": ["hono-openapi@1.1.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.8", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-AC3HNhZYPHhnZdSy2Je7GDoTTNxPos6rKRQKVDBbSilY3cWJPqsxRnN6zA4pU7tfxmQEMTqkiLXbw6sAaemB8Q=="], diff --git a/flake.lock b/flake.lock index ca9fd5f8f30..4e7cf41e1b7 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1764794580, - "narHash": "sha256-UMVihg0OQ980YqmOAPz+zkuCEb9hpE5Xj2v+ZGNjQ+M=", + "lastModified": 1764733908, + "narHash": "sha256-QJiih52NU+nm7XQWCj+K8SwUdIEayDQ1FQgjkYISt4I=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ebc94f855ef25347c314258c10393a92794e7ab9", + "rev": "cadcc8de247676e4751c9d4a935acb2c0b059113", "type": "github" }, "original": { diff --git a/nix/hashes.json b/nix/hashes.json index 6bc2eaec159..47634e2ed82 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,3 +1,3 @@ { - "nodeModules": "sha256-Wrfwnmo0lpck2rbt6ttkAuDGvBvqqWJfNA8QDQxoZ6I=" + "nodeModules": "sha256-ZGKC7h4ScHDzVYj8qb1lN/weZhyZivPS8kpNAZvgO0I=" } diff --git a/package.json b/package.json index a962be92605..a5e7c14621b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@tailwindcss/vite": "4.1.11", "diff": "8.0.2", "ai": "5.0.97", - "hono": "4.10.7", + "hono": "4.7.10", "hono-openapi": "1.1.1", "fuzzysort": "3.1.0", "luxon": "3.6.1", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index addff88b046..f2f8768cbb8 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -33,13 +33,11 @@ "@solid-primitives/resize-observer": "2.1.3", "@solid-primitives/scroll": "2.1.3", "@solid-primitives/storage": "4.3.3", - "@solid-primitives/websocket": "1.3.1", "@solidjs/meta": "catalog:", "@solidjs/router": "catalog:", "@thisbeyond/solid-dnd": "0.7.5", "diff": "catalog:", "fuzzysort": "catalog:", - "ghostty-web": "0.3.0", "luxon": "catalog:", "marked": "16.2.0", "marked-shiki": "1.2.1", diff --git a/packages/desktop/src/addons/serialize.ts b/packages/desktop/src/addons/serialize.ts deleted file mode 100644 index 03899ff109b..00000000000 --- a/packages/desktop/src/addons/serialize.ts +++ /dev/null @@ -1,649 +0,0 @@ -/** - * SerializeAddon - Serialize terminal buffer contents - * - * Port of xterm.js addon-serialize for ghostty-web. - * Enables serialization of terminal contents to a string that can - * be written back to restore terminal state. - * - * Usage: - * ```typescript - * const serializeAddon = new SerializeAddon(); - * term.loadAddon(serializeAddon); - * const content = serializeAddon.serialize(); - * ``` - */ - -import type { ITerminalAddon, ITerminalCore, IBufferRange } from "ghostty-web" - -// ============================================================================ -// Buffer Types (matching ghostty-web internal interfaces) -// ============================================================================ - -interface IBuffer { - readonly type: "normal" | "alternate" - readonly cursorX: number - readonly cursorY: number - readonly viewportY: number - readonly baseY: number - readonly length: number - getLine(y: number): IBufferLine | undefined - getNullCell(): IBufferCell -} - -interface IBufferLine { - readonly length: number - readonly isWrapped: boolean - getCell(x: number): IBufferCell | undefined - translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string -} - -interface IBufferCell { - getChars(): string - getCode(): number - getWidth(): number - getFgColorMode(): number - getBgColorMode(): number - getFgColor(): number - getBgColor(): number - isBold(): number - isItalic(): number - isUnderline(): number - isStrikethrough(): number - isBlink(): number - isInverse(): number - isInvisible(): number - isFaint(): number - isDim(): boolean -} - -// ============================================================================ -// Types -// ============================================================================ - -export interface ISerializeOptions { - /** - * The row range to serialize. When an explicit range is specified, the cursor - * will get its final repositioning. - */ - range?: ISerializeRange - /** - * The number of rows in the scrollback buffer to serialize, starting from - * the bottom of the scrollback buffer. When not specified, all available - * rows in the scrollback buffer will be serialized. - */ - scrollback?: number - /** - * Whether to exclude the terminal modes from the serialization. - * Default: false - */ - excludeModes?: boolean - /** - * Whether to exclude the alt buffer from the serialization. - * Default: false - */ - excludeAltBuffer?: boolean -} - -export interface ISerializeRange { - /** - * The line to start serializing (inclusive). - */ - start: number - /** - * The line to end serializing (inclusive). - */ - end: number -} - -export interface IHTMLSerializeOptions { - /** - * The number of rows in the scrollback buffer to serialize, starting from - * the bottom of the scrollback buffer. - */ - scrollback?: number - /** - * Whether to only serialize the selection. - * Default: false - */ - onlySelection?: boolean - /** - * Whether to include the global background of the terminal. - * Default: false - */ - includeGlobalBackground?: boolean - /** - * The range to serialize. This is prioritized over onlySelection. - */ - range?: { - startLine: number - endLine: number - startCol: number - } -} - -// ============================================================================ -// Helper Functions -// ============================================================================ - -function constrain(value: number, low: number, high: number): number { - return Math.max(low, Math.min(value, high)) -} - -function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean { - return cell1.getFgColorMode() === cell2.getFgColorMode() && cell1.getFgColor() === cell2.getFgColor() -} - -function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean { - return cell1.getBgColorMode() === cell2.getBgColorMode() && cell1.getBgColor() === cell2.getBgColor() -} - -function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean { - return ( - !!cell1.isInverse() === !!cell2.isInverse() && - !!cell1.isBold() === !!cell2.isBold() && - !!cell1.isUnderline() === !!cell2.isUnderline() && - !!cell1.isBlink() === !!cell2.isBlink() && - !!cell1.isInvisible() === !!cell2.isInvisible() && - !!cell1.isItalic() === !!cell2.isItalic() && - !!cell1.isDim() === !!cell2.isDim() && - !!cell1.isStrikethrough() === !!cell2.isStrikethrough() - ) -} - -// ============================================================================ -// Base Serialize Handler -// ============================================================================ - -abstract class BaseSerializeHandler { - constructor(protected readonly _buffer: IBuffer) {} - - public serialize(range: IBufferRange, excludeFinalCursorPosition?: boolean): string { - let oldCell = this._buffer.getNullCell() - - const startRow = range.start.y - const endRow = range.end.y - const startColumn = range.start.x - const endColumn = range.end.x - - this._beforeSerialize(endRow - startRow, startRow, endRow) - - for (let row = startRow; row <= endRow; row++) { - const line = this._buffer.getLine(row) - if (line) { - const startLineColumn = row === range.start.y ? startColumn : 0 - const endLineColumn = row === range.end.y ? endColumn : line.length - for (let col = startLineColumn; col < endLineColumn; col++) { - const c = line.getCell(col) - if (!c) { - continue - } - this._nextCell(c, oldCell, row, col) - oldCell = c - } - } - this._rowEnd(row, row === endRow) - } - - this._afterSerialize() - - return this._serializeString(excludeFinalCursorPosition) - } - - protected _nextCell(_cell: IBufferCell, _oldCell: IBufferCell, _row: number, _col: number): void {} - protected _rowEnd(_row: number, _isLastRow: boolean): void {} - protected _beforeSerialize(_rows: number, _startRow: number, _endRow: number): void {} - protected _afterSerialize(): void {} - protected _serializeString(_excludeFinalCursorPosition?: boolean): string { - return "" - } -} - -// ============================================================================ -// String Serialize Handler -// ============================================================================ - -class StringSerializeHandler extends BaseSerializeHandler { - private _rowIndex: number = 0 - private _allRows: string[] = [] - private _allRowSeparators: string[] = [] - private _currentRow: string = "" - private _nullCellCount: number = 0 - private _cursorStyle: IBufferCell - private _cursorStyleRow: number = 0 - private _cursorStyleCol: number = 0 - private _backgroundCell: IBufferCell - private _firstRow: number = 0 - private _lastCursorRow: number = 0 - private _lastCursorCol: number = 0 - private _lastContentCursorRow: number = 0 - private _lastContentCursorCol: number = 0 - private _thisRowLastChar: IBufferCell - private _thisRowLastSecondChar: IBufferCell - private _nextRowFirstChar: IBufferCell - - constructor( - buffer: IBuffer, - private readonly _terminal: ITerminalCore, - ) { - super(buffer) - this._cursorStyle = this._buffer.getNullCell() - this._backgroundCell = this._buffer.getNullCell() - this._thisRowLastChar = this._buffer.getNullCell() - this._thisRowLastSecondChar = this._buffer.getNullCell() - this._nextRowFirstChar = this._buffer.getNullCell() - } - - protected _beforeSerialize(rows: number, start: number, _end: number): void { - this._allRows = new Array(rows) - this._lastContentCursorRow = start - this._lastCursorRow = start - this._firstRow = start - } - - protected _rowEnd(row: number, isLastRow: boolean): void { - // if there is colorful empty cell at line end, we must pad it back - if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) { - this._currentRow += `\u001b[${this._nullCellCount}X` - } - - let rowSeparator = "" - - if (!isLastRow) { - // Enable BCE - if (row - this._firstRow >= this._terminal.rows) { - const line = this._buffer.getLine(this._cursorStyleRow) - const cell = line?.getCell(this._cursorStyleCol) - if (cell) { - this._backgroundCell = cell - } - } - - const currentLine = this._buffer.getLine(row)! - const nextLine = this._buffer.getLine(row + 1)! - - if (!nextLine.isWrapped) { - rowSeparator = "\r\n" - this._lastCursorRow = row + 1 - this._lastCursorCol = 0 - } else { - rowSeparator = "" - const thisRowLastChar = currentLine.getCell(currentLine.length - 1) - const thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2) - const nextRowFirstChar = nextLine.getCell(0) - - if (thisRowLastChar) this._thisRowLastChar = thisRowLastChar - if (thisRowLastSecondChar) this._thisRowLastSecondChar = thisRowLastSecondChar - if (nextRowFirstChar) this._nextRowFirstChar = nextRowFirstChar - - const isNextRowFirstCharDoubleWidth = this._nextRowFirstChar.getWidth() > 1 - - let isValid = false - - if ( - this._nextRowFirstChar.getChars() && - (isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0) - ) { - if ( - (this._thisRowLastChar.getChars() || this._thisRowLastChar.getWidth() === 0) && - equalBg(this._thisRowLastChar, this._nextRowFirstChar) - ) { - isValid = true - } - - if ( - isNextRowFirstCharDoubleWidth && - (this._thisRowLastSecondChar.getChars() || this._thisRowLastSecondChar.getWidth() === 0) && - equalBg(this._thisRowLastChar, this._nextRowFirstChar) && - equalBg(this._thisRowLastSecondChar, this._nextRowFirstChar) - ) { - isValid = true - } - } - - if (!isValid) { - rowSeparator = "-".repeat(this._nullCellCount + 1) - rowSeparator += "\u001b[1D\u001b[1X" - - if (this._nullCellCount > 0) { - rowSeparator += "\u001b[A" - rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}C` - rowSeparator += `\u001b[${this._nullCellCount}X` - rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}D` - rowSeparator += "\u001b[B" - } - - this._lastContentCursorRow = row + 1 - this._lastContentCursorCol = 0 - this._lastCursorRow = row + 1 - this._lastCursorCol = 0 - } - } - } - - this._allRows[this._rowIndex] = this._currentRow - this._allRowSeparators[this._rowIndex++] = rowSeparator - this._currentRow = "" - this._nullCellCount = 0 - } - - private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] { - const sgrSeq: number[] = [] - const fgChanged = !equalFg(cell, oldCell) - const bgChanged = !equalBg(cell, oldCell) - const flagsChanged = !equalFlags(cell, oldCell) - - if (fgChanged || bgChanged || flagsChanged) { - if (this._isAttributeDefault(cell)) { - if (!this._isAttributeDefault(oldCell)) { - sgrSeq.push(0) - } - } else { - if (fgChanged) { - const color = cell.getFgColor() - const mode = cell.getFgColorMode() - if (mode === 2) { - // RGB - sgrSeq.push(38, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff) - } else if (mode === 1) { - // Palette - if (color >= 16) { - sgrSeq.push(38, 5, color) - } else { - sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)) - } - } else { - sgrSeq.push(39) - } - } - if (bgChanged) { - const color = cell.getBgColor() - const mode = cell.getBgColorMode() - if (mode === 2) { - // RGB - sgrSeq.push(48, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff) - } else if (mode === 1) { - // Palette - if (color >= 16) { - sgrSeq.push(48, 5, color) - } else { - sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)) - } - } else { - sgrSeq.push(49) - } - } - if (flagsChanged) { - if (!!cell.isInverse() !== !!oldCell.isInverse()) { - sgrSeq.push(cell.isInverse() ? 7 : 27) - } - if (!!cell.isBold() !== !!oldCell.isBold()) { - sgrSeq.push(cell.isBold() ? 1 : 22) - } - if (!!cell.isUnderline() !== !!oldCell.isUnderline()) { - sgrSeq.push(cell.isUnderline() ? 4 : 24) - } - if (!!cell.isBlink() !== !!oldCell.isBlink()) { - sgrSeq.push(cell.isBlink() ? 5 : 25) - } - if (!!cell.isInvisible() !== !!oldCell.isInvisible()) { - sgrSeq.push(cell.isInvisible() ? 8 : 28) - } - if (!!cell.isItalic() !== !!oldCell.isItalic()) { - sgrSeq.push(cell.isItalic() ? 3 : 23) - } - if (!!cell.isDim() !== !!oldCell.isDim()) { - sgrSeq.push(cell.isDim() ? 2 : 22) - } - if (!!cell.isStrikethrough() !== !!oldCell.isStrikethrough()) { - sgrSeq.push(cell.isStrikethrough() ? 9 : 29) - } - } - } - } - - return sgrSeq - } - - private _isAttributeDefault(cell: IBufferCell): boolean { - return ( - cell.getFgColorMode() === 0 && - cell.getBgColorMode() === 0 && - !cell.isBold() && - !cell.isItalic() && - !cell.isUnderline() && - !cell.isBlink() && - !cell.isInverse() && - !cell.isInvisible() && - !cell.isDim() && - !cell.isStrikethrough() - ) - } - - protected _nextCell(cell: IBufferCell, _oldCell: IBufferCell, row: number, col: number): void { - const isPlaceHolderCell = cell.getWidth() === 0 - - if (isPlaceHolderCell) { - return - } - - const isEmptyCell = cell.getChars() === "" - - const sgrSeq = this._diffStyle(cell, this._cursorStyle) - - const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0 - - if (styleChanged) { - if (this._nullCellCount > 0) { - if (!equalBg(this._cursorStyle, this._backgroundCell)) { - this._currentRow += `\u001b[${this._nullCellCount}X` - } - this._currentRow += `\u001b[${this._nullCellCount}C` - this._nullCellCount = 0 - } - - this._lastContentCursorRow = this._lastCursorRow = row - this._lastContentCursorCol = this._lastCursorCol = col - - this._currentRow += `\u001b[${sgrSeq.join(";")}m` - - const line = this._buffer.getLine(row) - const cellFromLine = line?.getCell(col) - if (cellFromLine) { - this._cursorStyle = cellFromLine - this._cursorStyleRow = row - this._cursorStyleCol = col - } - } - - if (isEmptyCell) { - this._nullCellCount += cell.getWidth() - } else { - if (this._nullCellCount > 0) { - if (equalBg(this._cursorStyle, this._backgroundCell)) { - this._currentRow += `\u001b[${this._nullCellCount}C` - } else { - this._currentRow += `\u001b[${this._nullCellCount}X` - this._currentRow += `\u001b[${this._nullCellCount}C` - } - this._nullCellCount = 0 - } - - this._currentRow += cell.getChars() - - this._lastContentCursorRow = this._lastCursorRow = row - this._lastContentCursorCol = this._lastCursorCol = col + cell.getWidth() - } - } - - protected _serializeString(excludeFinalCursorPosition?: boolean): string { - let rowEnd = this._allRows.length - - if (this._buffer.length - this._firstRow <= this._terminal.rows) { - rowEnd = this._lastContentCursorRow + 1 - this._firstRow - this._lastCursorCol = this._lastContentCursorCol - this._lastCursorRow = this._lastContentCursorRow - } - - let content = "" - - for (let i = 0; i < rowEnd; i++) { - content += this._allRows[i] - if (i + 1 < rowEnd) { - content += this._allRowSeparators[i] - } - } - - if (!excludeFinalCursorPosition) { - // Get cursor position relative to viewport (1-indexed for ANSI) - // cursorY is relative to the viewport, cursorX is column position - const cursorRow = this._buffer.cursorY + 1 // 1-indexed - const cursorCol = this._buffer.cursorX + 1 // 1-indexed - - // Use absolute cursor positioning (CUP - Cursor Position) - // This is more reliable than relative moves which depend on knowing - // exactly where the cursor ended up after all the content - content += `\u001b[${cursorRow};${cursorCol}H` - } - - return content - } -} - -// ============================================================================ -// SerializeAddon Class -// ============================================================================ - -export class SerializeAddon implements ITerminalAddon { - private _terminal?: ITerminalCore - - /** - * Activate the addon (called by Terminal.loadAddon) - */ - public activate(terminal: ITerminalCore): void { - this._terminal = terminal - } - - /** - * Dispose the addon and clean up resources - */ - public dispose(): void { - this._terminal = undefined - } - - /** - * Serializes terminal rows into a string that can be written back to the - * terminal to restore the state. The cursor will also be positioned to the - * correct cell. - * - * @param options Custom options to allow control over what gets serialized. - */ - public serialize(options?: ISerializeOptions): string { - if (!this._terminal) { - throw new Error("Cannot use addon until it has been loaded") - } - - const terminal = this._terminal as any - const buffer = terminal.buffer - - if (!buffer) { - return "" - } - - const activeBuffer = buffer.active || buffer.normal - if (!activeBuffer) { - return "" - } - - let content = options?.range - ? this._serializeBufferByRange(activeBuffer, options.range, true) - : this._serializeBufferByScrollback(activeBuffer, options?.scrollback) - - // Handle alternate buffer if active and not excluded - if (!options?.excludeAltBuffer) { - const altBuffer = buffer.alternate - if (altBuffer && buffer.active?.type === "alternate") { - const alternateContent = this._serializeBufferByScrollback(altBuffer, undefined) - content += `\u001b[?1049h\u001b[H${alternateContent}` - } - } - - return content - } - - /** - * Serializes terminal content as plain text (no escape sequences) - * @param options Custom options to allow control over what gets serialized. - */ - public serializeAsText(options?: { scrollback?: number; trimWhitespace?: boolean }): string { - if (!this._terminal) { - throw new Error("Cannot use addon until it has been loaded") - } - - const terminal = this._terminal as any - const buffer = terminal.buffer - - if (!buffer) { - return "" - } - - const activeBuffer = buffer.active || buffer.normal - if (!activeBuffer) { - return "" - } - - const maxRows = activeBuffer.length - const scrollback = options?.scrollback - const correctRows = scrollback === undefined ? maxRows : constrain(scrollback + this._terminal.rows, 0, maxRows) - - const startRow = maxRows - correctRows - const endRow = maxRows - 1 - const lines: string[] = [] - - for (let row = startRow; row <= endRow; row++) { - const line = activeBuffer.getLine(row) - if (line) { - const text = line.translateToString(options?.trimWhitespace ?? true) - lines.push(text) - } - } - - // Trim trailing empty lines if requested - if (options?.trimWhitespace) { - while (lines.length > 0 && lines[lines.length - 1] === "") { - lines.pop() - } - } - - return lines.join("\n") - } - - private _serializeBufferByScrollback(buffer: IBuffer, scrollback?: number): string { - const maxRows = buffer.length - const rows = this._terminal?.rows ?? 24 - const correctRows = scrollback === undefined ? maxRows : constrain(scrollback + rows, 0, maxRows) - return this._serializeBufferByRange( - buffer, - { - start: maxRows - correctRows, - end: maxRows - 1, - }, - false, - ) - } - - private _serializeBufferByRange( - buffer: IBuffer, - range: ISerializeRange, - excludeFinalCursorPosition: boolean, - ): string { - const handler = new StringSerializeHandler(buffer, this._terminal!) - const cols = this._terminal?.cols ?? 80 - return handler.serialize( - { - start: { x: 0, y: range.start }, - end: { x: cols, y: range.end }, - }, - excludeFinalCursorPosition, - ) - } -} diff --git a/packages/desktop/src/components/terminal.tsx b/packages/desktop/src/components/terminal.tsx deleted file mode 100644 index 49a45a432bc..00000000000 --- a/packages/desktop/src/components/terminal.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { init, Terminal as Term, FitAddon } from "ghostty-web" -import { ComponentProps, onCleanup, onMount, splitProps } from "solid-js" -import { createReconnectingWS, ReconnectingWebSocket } from "@solid-primitives/websocket" -import { useSDK } from "@/context/sdk" -import { SerializeAddon } from "@/addons/serialize" -import { LocalPTY } from "@/context/session" - -await init() - -export interface TerminalProps extends ComponentProps<"div"> { - pty: LocalPTY - onSubmit?: () => void - onCleanup?: (pty: LocalPTY) => void -} - -export const Terminal = (props: TerminalProps) => { - const sdk = useSDK() - let container!: HTMLDivElement - const [local, others] = splitProps(props, ["pty", "class", "classList"]) - let ws: ReconnectingWebSocket - let term: Term - let serializeAddon: SerializeAddon - let fitAddon: FitAddon - - onMount(async () => { - ws = createReconnectingWS(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`) - term = new Term({ - cursorBlink: true, - fontSize: 14, - fontFamily: "TX-02, monospace", - allowTransparency: true, - theme: { - background: "#191515", - foreground: "#d4d4d4", - }, - scrollback: 10_000, - }) - term.attachCustomKeyEventHandler((event) => { - // allow for ctrl-` to toggle terminal in parent - if (event.ctrlKey && event.key.toLowerCase() === "`") { - event.preventDefault() - return true - } - return false - }) - - fitAddon = new FitAddon() - serializeAddon = new SerializeAddon() - term.loadAddon(serializeAddon) - term.loadAddon(fitAddon) - - term.open(container) - - if (local.pty.buffer) { - const originalSize = { cols: term.cols, rows: term.rows } - let resized = false - if (local.pty.rows && local.pty.cols) { - term.resize(local.pty.cols, local.pty.rows) - resized = true - } - term.write(local.pty.buffer) - if (local.pty.scrollY) { - term.scrollToLine(local.pty.scrollY) - } - if (resized) { - term.resize(originalSize.cols, originalSize.rows) - } - } - - container.focus() - - fitAddon.fit() - fitAddon.observeResize() - window.addEventListener("resize", () => fitAddon.fit()) - term.onResize(async (size) => { - if (ws && ws.readyState === WebSocket.OPEN) { - await sdk.client.pty.update({ - path: { id: local.pty.id }, - body: { - size: { - cols: size.cols, - rows: size.rows, - }, - }, - }) - } - }) - term.onData((data) => { - if (ws && ws.readyState === WebSocket.OPEN) { - ws.send(data) - } - }) - term.onKey((key) => { - if (key.key == "Enter") { - props.onSubmit?.() - } - }) - // term.onScroll((ydisp) => { - // console.log("Scroll position:", ydisp) - // }) - ws.addEventListener("open", () => { - console.log("WebSocket connected") - sdk.client.pty.update({ - path: { id: local.pty.id }, - body: { - size: { - cols: term.cols, - rows: term.rows, - }, - }, - }) - }) - ws.addEventListener("message", (event) => { - term.write(event.data) - }) - ws.addEventListener("error", (error) => { - console.error("WebSocket error:", error) - }) - ws.addEventListener("close", () => { - console.log("WebSocket disconnected") - }) - }) - - onCleanup(() => { - if (serializeAddon && props.onCleanup) { - const buffer = serializeAddon.serialize() - props.onCleanup({ - ...local.pty, - buffer, - rows: term.rows, - cols: term.cols, - scrollY: term.getViewportY(), - }) - } - ws?.close() - term?.dispose() - }) - - return ( -
- ) -} diff --git a/packages/desktop/src/context/layout.tsx b/packages/desktop/src/context/layout.tsx index ca736e84e6b..81e8b537abc 100644 --- a/packages/desktop/src/context/layout.tsx +++ b/packages/desktop/src/context/layout.tsx @@ -15,16 +15,12 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( opened: true, width: 280, }, - terminal: { - opened: false, - height: 280, - }, review: { state: "pane" as "pane" | "tab", }, }), { - name: "____default-layout", + name: "___default-layout", }, ) @@ -65,22 +61,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( setStore("sidebar", "width", width) }, }, - terminal: { - opened: createMemo(() => store.terminal.opened), - open() { - setStore("terminal", "opened", true) - }, - close() { - setStore("terminal", "opened", false) - }, - toggle() { - setStore("terminal", "opened", (x) => !x) - }, - height: createMemo(() => store.terminal.height), - resize(height: number) { - setStore("terminal", "height", height) - }, - }, review: { state: createMemo(() => store.review?.state ?? "closed"), pane() { diff --git a/packages/desktop/src/context/sdk.tsx b/packages/desktop/src/context/sdk.tsx index 144202ee209..81b32035a0b 100644 --- a/packages/desktop/src/context/sdk.tsx +++ b/packages/desktop/src/context/sdk.tsx @@ -27,6 +27,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ abort.abort() }) - return { directory: props.directory, client: sdk, event: emitter, url: globalSDK.url } + return { directory: props.directory, client: sdk, event: emitter } }, }) diff --git a/packages/desktop/src/context/session.tsx b/packages/desktop/src/context/session.tsx index 4e9fe71f8a7..72098a93951 100644 --- a/packages/desktop/src/context/session.tsx +++ b/packages/desktop/src/context/session.tsx @@ -8,25 +8,14 @@ import { pipe, sumBy } from "remeda" import { AssistantMessage, UserMessage } from "@opencode-ai/sdk" import { useParams } from "@solidjs/router" import { base64Encode } from "@/utils" -import { useSDK } from "./sdk" - -export type LocalPTY = { - id: string - title: string - rows?: number - cols?: number - buffer?: string - scrollY?: number -} export const { use: useSession, provider: SessionProvider } = createSimpleContext({ name: "Session", init: () => { - const sdk = useSDK() const params = useParams() const sync = useSync() const name = createMemo( - () => `______${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`, + () => `___${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`, ) const [store, setStore] = makePersisted( @@ -34,21 +23,16 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex messageId?: string tabs: { active?: string - all: string[] + opened: string[] } prompt: Prompt cursor?: number - terminals: { - active?: string - all: LocalPTY[] - } }>({ tabs: { - all: [], + opened: [], }, prompt: clonePrompt(DEFAULT_PROMPT), cursor: undefined, - terminals: { all: [] }, }), { name: name(), @@ -154,7 +138,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex setStore("tabs", "active", tab) }, setOpenedTabs(tabs: string[]) { - setStore("tabs", "all", tabs) + setStore("tabs", "opened", tabs) }, async openTab(tab: string) { if (tab === "chat") { @@ -162,8 +146,8 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex return } if (tab !== "review") { - if (!store.tabs.all.includes(tab)) { - setStore("tabs", "all", [...store.tabs.all, tab]) + if (!store.tabs.opened.includes(tab)) { + setStore("tabs", "opened", [...store.tabs.opened, tab]) } } setStore("tabs", "active", tab) @@ -172,88 +156,28 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex batch(() => { setStore( "tabs", - "all", - store.tabs.all.filter((x) => x !== tab), + "opened", + store.tabs.opened.filter((x) => x !== tab), ) if (store.tabs.active === tab) { - const index = store.tabs.all.findIndex((f) => f === tab) - const previous = store.tabs.all[Math.max(0, index - 1)] + const index = store.tabs.opened.findIndex((f) => f === tab) + const previous = store.tabs.opened[Math.max(0, index - 1)] setStore("tabs", "active", previous) } }) }, moveTab(tab: string, to: number) { - const index = store.tabs.all.findIndex((f) => f === tab) + const index = store.tabs.opened.findIndex((f) => f === tab) if (index === -1) return setStore( "tabs", - "all", + "opened", produce((opened) => { opened.splice(to, 0, opened.splice(index, 1)[0]) }), ) }, }, - terminal: { - all: createMemo(() => Object.values(store.terminals.all)), - active: createMemo(() => store.terminals.active), - new() { - sdk.client.pty.create({ body: { title: `Terminal ${store.terminals.all.length + 1}` } }).then((pty) => { - const id = pty.data?.id - if (!id) return - batch(() => { - setStore("terminals", "all", [ - ...store.terminals.all, - { - id, - title: pty.data?.title ?? "Terminal", - // rows: pty.data?.rows ?? 24, - // cols: pty.data?.cols ?? 80, - // buffer: "", - // scrollY: 0, - }, - ]) - setStore("terminals", "active", id) - }) - }) - }, - update(pty: Partial & { id: string }) { - setStore("terminals", "all", (x) => x.map((x) => (x.id === pty.id ? { ...x, ...pty } : x))) - sdk.client.pty.update({ - path: { id: pty.id }, - body: { title: pty.title, size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined }, - }) - }, - open(id: string) { - setStore("terminals", "active", id) - }, - async close(id: string) { - batch(() => { - setStore( - "terminals", - "all", - store.terminals.all.filter((x) => x.id !== id), - ) - if (store.terminals.active === id) { - const index = store.terminals.all.findIndex((f) => f.id === id) - const previous = store.tabs.all[Math.max(0, index - 1)] - setStore("terminals", "active", previous) - } - }) - await sdk.client.pty.remove({ path: { id } }) - }, - move(id: string, to: number) { - const index = store.terminals.all.findIndex((f) => f.id === id) - if (index === -1) return - setStore( - "terminals", - "all", - produce((all) => { - all.splice(to, 0, all.splice(index, 1)[0]) - }), - ) - }, - }, } }, }) diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 106a2e733fb..15180c88566 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -1,9 +1,9 @@ import { createMemo, For, ParentProps, Show } from "solid-js" import { DateTime } from "luxon" -import { A, useNavigate, useParams } from "@solidjs/router" +import { A, useParams } from "@solidjs/router" import { useLayout } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" -import { base64Decode, base64Encode } from "@/utils" +import { base64Encode } from "@/utils" import { Mark } from "@opencode-ai/ui/logo" import { Button } from "@opencode-ai/ui/button" import { Icon } from "@opencode-ai/ui/icon" @@ -12,21 +12,11 @@ import { Tooltip } from "@opencode-ai/ui/tooltip" import { Collapsible } from "@opencode-ai/ui/collapsible" import { DiffChanges } from "@opencode-ai/ui/diff-changes" import { getFilename } from "@opencode-ai/util/path" -import { Select } from "@opencode-ai/ui/select" -import { Session } from "@opencode-ai/sdk/client" export default function Layout(props: ParentProps) { - const navigate = useNavigate() const params = useParams() const globalSync = useGlobalSync() const layout = useLayout() - const currentDirectory = createMemo(() => base64Decode(params.dir ?? "")) - const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? []) - const currentSession = createMemo(() => sessions().find((s) => s.id === params.id) ?? sessions().at(0)) - - function navigateToSession(session: Session | undefined) { - navigate(`/${params.dir}/session/${session?.id}`) - } const handleOpenProject = async () => { // layout.projects.open(dir.) @@ -34,7 +24,7 @@ export default function Layout(props: ParentProps) { return (
-
+
-
-
-
- x.title} - value={(x) => x.id} - onSelect={navigateToSession} - class="text-14-regular text-text-base max-w-3xs" - variant="ghost" - /> -
- -
-
- - Toggle terminal - Ctrl ` -
- } - > - - -
-
- -
{ - e.preventDefault() - const startX = e.clientX - const startWidth = layout.sidebar.width() - const maxWidth = window.innerWidth * 0.3 - const minWidth = 150 - const collapseThreshold = 80 - let currentWidth = startWidth - - document.body.style.userSelect = "none" - document.body.style.overflow = "hidden" - - const onMouseMove = (moveEvent: MouseEvent) => { - const deltaX = moveEvent.clientX - startX - currentWidth = startWidth + deltaX - const clampedWidth = Math.min(maxWidth, Math.max(minWidth, currentWidth)) - layout.sidebar.resize(clampedWidth) - } - - const onMouseUp = () => { - document.body.style.userSelect = "" - document.body.style.overflow = "" - document.removeEventListener("mousemove", onMouseMove) - document.removeEventListener("mouseup", onMouseUp) - - if (currentWidth < collapseThreshold) { - layout.sidebar.close() - } - } - - document.addEventListener("mousemove", onMouseMove) - document.addEventListener("mouseup", onMouseUp) - }} - /> -
-
{props.children}
+
{props.children}
) diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx index 77362533404..d6ce62b7030 100644 --- a/packages/desktop/src/pages/session.tsx +++ b/packages/desktop/src/pages/session.tsx @@ -1,4 +1,4 @@ -import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo, createEffect } from "solid-js" +import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo } from "solid-js" import { useLocal, type LocalFile } from "@/context/local" import { createStore } from "solid-js/store" import { PromptInput } from "@/components/prompt-input" @@ -31,7 +31,6 @@ import { useSession } from "@/context/session" import { useLayout } from "@/context/layout" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { Diff } from "@opencode-ai/ui/diff" -import { Terminal } from "@/components/terminal" export default function Page() { const layout = useLayout() @@ -55,14 +54,6 @@ export default function Page() { document.removeEventListener("keydown", handleKeyDown) }) - createEffect(() => { - if (layout.terminal.opened()) { - if (session.terminal.all().length === 0) { - session.terminal.new() - } - } - }) - const handleKeyDown = (event: KeyboardEvent) => { if (event.getModifierState(MOD) && event.shiftKey && event.key.toLowerCase() === "p") { event.preventDefault() @@ -82,16 +73,6 @@ export default function Page() { document.documentElement.setAttribute("data-theme", nextTheme) return } - if (event.ctrlKey && event.key.toLowerCase() === "`") { - event.preventDefault() - layout.terminal.toggle() - return - } - - // @ts-expect-error - if (document.activeElement?.dataset?.component === "terminal") { - return - } const focused = document.activeElement === inputRef if (focused) { @@ -160,7 +141,7 @@ export default function Page() { const handleDragOver = (event: DragEvent) => { const { draggable, droppable } = event if (draggable && droppable) { - const currentTabs = session.layout.tabs.all + const currentTabs = session.layout.tabs.opened const fromIndex = currentTabs?.indexOf(draggable.id.toString()) const toIndex = currentTabs?.indexOf(droppable.id.toString()) if (fromIndex !== toIndex && toIndex !== undefined) { @@ -278,397 +259,317 @@ export default function Page() { const wide = createMemo(() => layout.review.state() === "tab" || !session.diffs().length) return ( -
-
- - - - -
- - -
-
Session
- - -
{session.usage.context() ?? 0}%
-
-
-
- - - } +
+ + + + +
+ + +
+
Session
+ -
- - - -
-
Review
- -
- {session.info()?.summary?.files ?? 0} -
-
-
-
- - - - - {(tab) => ( - - )} - - -
- - setStore("fileSelectOpen", true)} - /> + +
{session.usage.context() ?? 0}%
- -
- +
+ + + } + > +
+ + + +
+
Review
+ +
+ {session.info()?.summary?.files ?? 0} +
+
+
+
+
+
+ + + {(tab) => } + + +
+ + setStore("fileSelectOpen", true)} + /> + +
+
+
+ +
-
- - -
- - 1 - ? "pr-6 pl-18" - : "px-6"), - }} - diffComponent={Diff} - /> -
-
- -
-
New session
-
- -
- {getDirectory(sync.data.path.directory)} - {getFilename(sync.data.path.directory)} -
+ + +
+ + 1 + ? "pr-6 pl-18" + : "px-6"), + }} + diffComponent={Diff} + /> +
+
+ +
+
New session
+
+ +
+ {getDirectory(sync.data.path.directory)} + {getFilename(sync.data.path.directory)}
-
- -
- Last modified  - - {DateTime.fromMillis(sync.data.project.time.created).toRelative()} - -
+
+
+ +
+ Last modified  + + {DateTime.fromMillis(sync.data.project.time.created).toRelative()} +
- - -
-
- { - inputRef = el - }} - />
-
-
- -
- + +
+
+ { + inputRef = el }} - diffs={session.diffs()} - diffComponent={Diff} - actions={ - - { - layout.review.tab() - session.layout.setActiveTab("review") - }} - /> - - } />
- +
- - - +
+ { + layout.review.tab() + session.layout.setActiveTab("review") + }} + /> + + } />
-
-
- - {(tab) => { - const [file] = createResource( - () => tab, - async (tab) => { - if (tab.startsWith("file://")) { - return local.file.node(tab.replace("file://", "")) - } - return undefined - }, - ) - return ( - - - - {(f) => ( - - )} - - - - ) - }} - - - - - {(draggedFile) => { - const [file] = createResource( - () => draggedFile(), - async (tab) => { - if (tab.startsWith("file://")) { - return local.file.node(tab.replace("file://", "")) - } - return undefined - }, - ) - return ( -
- {(f) => } -
- ) - }} -
-
- - -
- { - inputRef = el - }} - /> -
-
- - } - > -
    - - {(path) => ( -
  • - -
  • - )} -
    -
-
-
- - x} - onOpenChange={(open) => setStore("fileSelectOpen", open)} - onSelect={(x) => { - if (x) { - local.file.open(x) - return session.layout.openTab("file://" + x) - } - return undefined - }} - > - {(i) => ( + +
+ + +
-
- -
- - {getDirectory(i)} - - {getFilename(i)} -
-
-
+
- )} - -
-
- -
-
{ - e.preventDefault() - const startY = e.clientY - const startHeight = layout.terminal.height() - const maxHeight = window.innerHeight * 0.6 - const minHeight = 100 - const collapseThreshold = 50 - let currentHeight = startHeight - - document.body.style.userSelect = "none" - document.body.style.overflow = "hidden" - - const onMouseMove = (moveEvent: MouseEvent) => { - const deltaY = startY - moveEvent.clientY - currentHeight = startHeight + deltaY - const clampedHeight = Math.min(maxHeight, Math.max(minHeight, currentHeight)) - layout.terminal.resize(clampedHeight) - } - - const onMouseUp = () => { - document.body.style.userSelect = "" - document.body.style.overflow = "" - document.removeEventListener("mousemove", onMouseMove) - document.removeEventListener("mouseup", onMouseUp) - - if (currentHeight < collapseThreshold) { - layout.terminal.close() - } - } - - document.addEventListener("mousemove", onMouseMove) - document.addEventListener("mouseup", onMouseUp) + + + + {(tab) => { + const [file] = createResource( + () => tab, + async (tab) => { + if (tab.startsWith("file://")) { + return local.file.node(tab.replace("file://", "")) + } + return undefined + }, + ) + return ( + + + + {(f) => ( + + )} + + + + ) + }} + + + + + {(draggedFile) => { + const [file] = createResource( + () => draggedFile(), + async (tab) => { + if (tab.startsWith("file://")) { + return local.file.node(tab.replace("file://", "")) + } + return undefined + }, + ) + return ( +
+ {(f) => } +
+ ) + }} +
+
+ + +
+ { + inputRef = el }} /> - - - - {(terminal) => ( - 1 && ( - session.terminal.close(terminal.id)} /> - ) - } +
+
+ + }> +
    + + {(path) => ( +
  • + +
  • )}
    - -
+ + +
+ + x} + onOpenChange={(open) => setStore("fileSelectOpen", open)} + onSelect={(x) => { + if (x) { + local.file.open(x) + return session.layout.openTab("file://" + x) + } + return undefined + }} + > + {(i) => ( +
+
+ +
+ + {getDirectory(i)} + + {getFilename(i)} +
+
+
+
+ )} +
) diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 2c5441998b5..46c8c3200a6 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -72,7 +72,6 @@ "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", - "bun-pty": "0.4.2", "chokidar": "4.0.3", "clipboardy": "4.0.0", "decimal.js": "10.5.0", diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index 76f78f3faa8..7754b4a3953 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -5,7 +5,6 @@ import { Instance } from "@/project/instance" import { InstanceBootstrap } from "@/project/bootstrap" import { Rpc } from "@/util/rpc" import { upgrade } from "@/cli/upgrade" -import type { BunWebSocketData } from "hono/bun" await Log.init({ print: process.argv.includes("--print-logs"), @@ -28,7 +27,7 @@ process.on("uncaughtException", (e) => { }) }) -let server: Bun.Server +let server: Bun.Server export const rpc = { async server(input: { port: number; hostname: string }) { if (server) await server.stop(true) @@ -54,9 +53,7 @@ export const rpc = { async shutdown() { Log.Default.info("worker shutting down") await Instance.disposeAll() - // TODO: this should be awaited, but ws connections are - // causing this to hang, need to revisit this - server.stop(true) + await server.stop(true) }, } diff --git a/packages/opencode/src/id/id.ts b/packages/opencode/src/id/id.ts index ad6e22e1bee..99eb6c9ff06 100644 --- a/packages/opencode/src/id/id.ts +++ b/packages/opencode/src/id/id.ts @@ -8,7 +8,6 @@ export namespace Identifier { permission: "per", user: "usr", part: "prt", - pty: "pty", } as const export function schema(prefix: keyof typeof prefixes) { diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts deleted file mode 100644 index efb519ff2a7..00000000000 --- a/packages/opencode/src/pty/index.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { spawn, type IPty } from "bun-pty" -import z from "zod" -import { Identifier } from "../id/id" -import { Log } from "../util/log" -import { Bus } from "../bus" -import type { WSContext } from "hono/ws" -import { Instance } from "../project/instance" -import { shell } from "@opencode-ai/util/shell" - -export namespace Pty { - const log = Log.create({ service: "pty" }) - - export const Info = z - .object({ - id: Identifier.schema("pty"), - title: z.string(), - command: z.string(), - args: z.array(z.string()), - cwd: z.string(), - status: z.enum(["running", "exited"]), - pid: z.number(), - }) - .meta({ ref: "Pty" }) - - export type Info = z.infer - - export const CreateInput = z.object({ - command: z.string().optional(), - args: z.array(z.string()).optional(), - cwd: z.string().optional(), - title: z.string().optional(), - env: z.record(z.string(), z.string()).optional(), - }) - - export type CreateInput = z.infer - - export const UpdateInput = z.object({ - title: z.string().optional(), - size: z - .object({ - rows: z.number(), - cols: z.number(), - }) - .optional(), - }) - - export type UpdateInput = z.infer - - export const Event = { - Created: Bus.event("pty.created", z.object({ info: Info })), - Updated: Bus.event("pty.updated", z.object({ info: Info })), - Exited: Bus.event("pty.exited", z.object({ id: Identifier.schema("pty"), exitCode: z.number() })), - Deleted: Bus.event("pty.deleted", z.object({ id: Identifier.schema("pty") })), - } - - interface ActiveSession { - info: Info - process: IPty - buffer: string - subscribers: Set - } - - const state = Instance.state( - () => new Map(), - async (sessions) => { - for (const session of sessions.values()) { - try { - session.process.kill() - } catch {} - for (const ws of session.subscribers) { - ws.close() - } - } - sessions.clear() - }, - ) - - export function list() { - return Array.from(state().values()).map((s) => s.info) - } - - export function get(id: string) { - return state().get(id)?.info - } - - export async function create(input: CreateInput) { - const id = Identifier.create("pty", false) - const command = input.command || shell() - const args = input.args || [] - const cwd = input.cwd || Instance.directory - const env = { ...process.env, ...input.env } as Record - log.info("creating session", { id, cmd: command, args, cwd }) - - const ptyProcess = spawn(command, args, { - name: "xterm-256color", - cwd, - env, - }) - const info = { - id, - title: input.title || `Terminal ${id.slice(-4)}`, - command, - args, - cwd, - status: "running", - pid: ptyProcess.pid, - } as const - const session: ActiveSession = { - info, - process: ptyProcess, - buffer: "", - subscribers: new Set(), - } - state().set(id, session) - ptyProcess.onData((data) => { - if (session.subscribers.size === 0) { - session.buffer += data - return - } - for (const ws of session.subscribers) { - if (ws.readyState === 1) { - ws.send(data) - } - } - }) - ptyProcess.onExit(({ exitCode }) => { - log.info("session exited", { id, exitCode }) - session.info.status = "exited" - Bus.publish(Event.Exited, { id, exitCode }) - state().delete(id) - }) - Bus.publish(Event.Created, { info }) - return info - } - - export async function update(id: string, input: UpdateInput) { - const session = state().get(id) - if (!session) return - if (input.title) { - session.info.title = input.title - } - if (input.size) { - session.process.resize(input.size.cols, input.size.rows) - } - Bus.publish(Event.Updated, { info: session.info }) - return session.info - } - - export async function remove(id: string) { - const session = state().get(id) - if (!session) return - log.info("removing session", { id }) - try { - session.process.kill() - } catch {} - for (const ws of session.subscribers) { - ws.close() - } - state().delete(id) - Bus.publish(Event.Deleted, { id }) - } - - export function resize(id: string, cols: number, rows: number) { - const session = state().get(id) - if (session && session.info.status === "running") { - session.process.resize(cols, rows) - } - } - - export function write(id: string, data: string) { - const session = state().get(id) - if (session && session.info.status === "running") { - session.process.write(data) - } - } - - export function connect(id: string, ws: WSContext) { - const session = state().get(id) - if (!session) { - ws.close() - return - } - log.info("client connected to session", { id }) - session.subscribers.add(ws) - if (session.buffer) { - ws.send(session.buffer) - session.buffer = "" - } - return { - onMessage: (message: string | ArrayBuffer) => { - session.process.write(String(message)) - }, - onClose: () => { - log.info("client disconnected from session", { id }) - session.subscribers.delete(ws) - }, - } - } -} diff --git a/packages/opencode/src/server/error.ts b/packages/opencode/src/server/error.ts deleted file mode 100644 index 26e2dfcb121..00000000000 --- a/packages/opencode/src/server/error.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { resolver } from "hono-openapi" -import z from "zod" -import { Storage } from "../storage/storage" - -export const ERRORS = { - 400: { - description: "Bad request", - content: { - "application/json": { - schema: resolver( - z - .object({ - data: z.any(), - errors: z.array(z.record(z.string(), z.any())), - success: z.literal(false), - }) - .meta({ - ref: "BadRequestError", - }), - ), - }, - }, - }, - 404: { - description: "Not found", - content: { - "application/json": { - schema: resolver(Storage.NotFoundError.Schema), - }, - }, - }, -} as const - -export function errors(...codes: number[]) { - return Object.fromEntries(codes.map((code) => [code, ERRORS[code as keyof typeof ERRORS]])) -} diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index a74b7876f1c..31d0822762b 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -43,13 +43,43 @@ import { Snapshot } from "@/snapshot" import { SessionSummary } from "@/session/summary" import { GlobalBus } from "@/bus/global" import { SessionStatus } from "@/session/status" -import { upgradeWebSocket, websocket } from "hono/bun" -import { errors } from "./error" -import { Pty } from "@/pty" // @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 globalThis.AI_SDK_LOG_WARNINGS = false +const ERRORS = { + 400: { + description: "Bad request", + content: { + "application/json": { + schema: resolver( + z + .object({ + data: z.any(), + errors: z.array(z.record(z.string(), z.any())), + success: z.literal(false), + }) + .meta({ + ref: "BadRequestError", + }), + ), + }, + }, + }, + 404: { + description: "Not found", + content: { + "application/json": { + schema: resolver(Storage.NotFoundError.Schema), + }, + }, + }, +} as const + +function errors(...codes: number[]) { + return Object.fromEntries(codes.map((code) => [code, ERRORS[code as keyof typeof ERRORS]])) +} + export namespace Server { const log = Log.create({ service: "server" }) @@ -162,167 +192,7 @@ export namespace Server { }), ) .use(validator("query", z.object({ directory: z.string().optional() }))) - .route("/project", ProjectRoute) - - .get( - "/pty", - describeRoute({ - description: "List all PTY sessions", - operationId: "pty.list", - responses: { - 200: { - description: "List of sessions", - content: { - "application/json": { - schema: resolver(Pty.Info.array()), - }, - }, - }, - }, - }), - async (c) => { - return c.json(Pty.list()) - }, - ) - .post( - "/pty", - describeRoute({ - description: "Create a new PTY session", - operationId: "pty.create", - responses: { - 200: { - description: "Created session", - content: { - "application/json": { - schema: resolver(Pty.Info), - }, - }, - }, - ...errors(400), - }, - }), - validator("json", Pty.CreateInput), - async (c) => { - const info = await Pty.create(c.req.valid("json")) - return c.json(info) - }, - ) - .put( - "/pty/:id", - describeRoute({ - description: "Update PTY session", - operationId: "pty.update", - responses: { - 200: { - description: "Updated session", - content: { - "application/json": { - schema: resolver(Pty.Info), - }, - }, - }, - ...errors(400), - }, - }), - validator("param", z.object({ id: z.string() })), - validator("json", Pty.UpdateInput), - async (c) => { - const info = await Pty.update(c.req.valid("param").id, c.req.valid("json")) - return c.json(info) - }, - ) - .get( - "/pty/:id", - describeRoute({ - description: "Get PTY session info", - operationId: "pty.get", - responses: { - 200: { - description: "Session info", - content: { - "application/json": { - schema: resolver(Pty.Info), - }, - }, - }, - ...errors(404), - }, - }), - validator("param", z.object({ id: z.string() })), - async (c) => { - const info = Pty.get(c.req.valid("param").id) - if (!info) { - throw new Storage.NotFoundError({ message: "Session not found" }) - } - return c.json(info) - }, - ) - .delete( - "/pty/:id", - describeRoute({ - description: "Remove a PTY session", - operationId: "pty.remove", - responses: { - 200: { - description: "Session removed", - content: { - "application/json": { - schema: resolver(z.boolean()), - }, - }, - }, - ...errors(404), - }, - }), - validator("param", z.object({ id: z.string() })), - async (c) => { - await Pty.remove(c.req.valid("param").id) - return c.json(true) - }, - ) - .get( - "/pty/:id/connect", - describeRoute({ - description: "Connect to a PTY session", - operationId: "pty.connect", - responses: { - 200: { - description: "Connected session", - content: { - "application/json": { - schema: resolver(z.boolean()), - }, - }, - }, - 404: { - description: "Session not found", - content: { - "application/json": { - schema: resolver(z.boolean()), - }, - }, - }, - }, - }), - validator("param", z.object({ id: z.string() })), - upgradeWebSocket((c) => { - const id = c.req.param("id") - let handler: ReturnType - return { - onOpen(_event, ws) { - handler = Pty.connect(id, ws) - }, - onMessage(event) { - handler?.onMessage(String(event.data)) - }, - onClose() { - handler?.onClose() - }, - } - }), - ) - .get( "/config", describeRoute({ @@ -2213,7 +2083,6 @@ export namespace Server { hostname: opts.hostname, idleTimeout: 0, fetch: App().fetch, - websocket: websocket, }) return server } diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts index d04277cbc81..0dc470566ee 100644 --- a/packages/sdk/js/src/gen/sdk.gen.ts +++ b/packages/sdk/js/src/gen/sdk.gen.ts @@ -8,23 +8,6 @@ import type { ProjectListResponses, ProjectCurrentData, ProjectCurrentResponses, - PtyListData, - PtyListResponses, - PtyCreateData, - PtyCreateResponses, - PtyCreateErrors, - PtyRemoveData, - PtyRemoveResponses, - PtyRemoveErrors, - PtyGetData, - PtyGetResponses, - PtyGetErrors, - PtyUpdateData, - PtyUpdateResponses, - PtyUpdateErrors, - PtyConnectData, - PtyConnectResponses, - PtyConnectErrors, ConfigGetData, ConfigGetResponses, ConfigUpdateData, @@ -248,76 +231,6 @@ class Project extends _HeyApiClient { } } -class Pty extends _HeyApiClient { - /** - * List all PTY sessions - */ - public list(options?: Options) { - return (options?.client ?? this._client).get({ - url: "/pty", - ...options, - }) - } - - /** - * Create a new PTY session - */ - public create(options?: Options) { - return (options?.client ?? this._client).post({ - url: "/pty", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }) - } - - /** - * Remove a PTY session - */ - public remove(options: Options) { - return (options.client ?? this._client).delete({ - url: "/pty/{id}", - ...options, - }) - } - - /** - * Get PTY session info - */ - public get(options: Options) { - return (options.client ?? this._client).get({ - url: "/pty/{id}", - ...options, - }) - } - - /** - * Update PTY session - */ - public update(options: Options) { - return (options.client ?? this._client).put({ - url: "/pty/{id}", - ...options, - headers: { - "Content-Type": "application/json", - ...options.headers, - }, - }) - } - - /** - * Connect to a PTY session - */ - public connect(options: Options) { - return (options.client ?? this._client).get({ - url: "/pty/{id}/connect", - ...options, - }) - } -} - class Config extends _HeyApiClient { /** * Get config info @@ -1092,7 +1005,6 @@ export class OpencodeClient extends _HeyApiClient { } global = new Global({ client: this._client }) project = new Project({ client: this._client }) - pty = new Pty({ client: this._client }) config = new Config({ client: this._client }) tool = new Tool({ client: this._client }) instance = new Instance({ client: this._client }) diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 58ba58d359c..6c80f0b7c52 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -655,45 +655,6 @@ export type EventTuiToastShow = { } } -export type Pty = { - id: string - title: string - command: string - args: Array - cwd: string - status: "running" | "exited" - pid: number -} - -export type EventPtyCreated = { - type: "pty.created" - properties: { - info: Pty - } -} - -export type EventPtyUpdated = { - type: "pty.updated" - properties: { - info: Pty - } -} - -export type EventPtyExited = { - type: "pty.exited" - properties: { - id: string - exitCode: number - } -} - -export type EventPtyDeleted = { - type: "pty.deleted" - properties: { - id: string - } -} - export type EventServerConnected = { type: "server.connected" properties: { @@ -729,10 +690,6 @@ export type Event = | EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow - | EventPtyCreated - | EventPtyUpdated - | EventPtyExited - | EventPtyDeleted | EventServerConnected export type GlobalEvent = { @@ -751,21 +708,6 @@ export type Project = { } } -export type BadRequestError = { - data: unknown - errors: Array<{ - [key: string]: unknown - }> - success: false -} - -export type NotFoundError = { - name: "NotFoundError" - data: { - message: string - } -} - /** * Custom keybind configurations */ @@ -1324,6 +1266,14 @@ export type Config = { } } +export type BadRequestError = { + data: unknown + errors: Array<{ + [key: string]: unknown + }> + success: false +} + export type ToolIds = Array export type ToolListItem = { @@ -1345,6 +1295,13 @@ export type VcsInfo = { branch: string } +export type NotFoundError = { + name: "NotFoundError" + data: { + message: string + } +} + export type TextPartInput = { id?: string type: "text" @@ -1657,181 +1614,6 @@ export type ProjectCurrentResponses = { export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses] -export type PtyListData = { - body?: never - path?: never - query?: { - directory?: string - } - url: "/pty" -} - -export type PtyListResponses = { - /** - * List of sessions - */ - 200: Array -} - -export type PtyListResponse = PtyListResponses[keyof PtyListResponses] - -export type PtyCreateData = { - body?: { - command?: string - args?: Array - cwd?: string - title?: string - env?: { - [key: string]: string - } - } - path?: never - query?: { - directory?: string - } - url: "/pty" -} - -export type PtyCreateErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type PtyCreateError = PtyCreateErrors[keyof PtyCreateErrors] - -export type PtyCreateResponses = { - /** - * Created session - */ - 200: Pty -} - -export type PtyCreateResponse = PtyCreateResponses[keyof PtyCreateResponses] - -export type PtyRemoveData = { - body?: never - path: { - id: string - } - query?: { - directory?: string - } - url: "/pty/{id}" -} - -export type PtyRemoveErrors = { - /** - * Not found - */ - 404: NotFoundError -} - -export type PtyRemoveError = PtyRemoveErrors[keyof PtyRemoveErrors] - -export type PtyRemoveResponses = { - /** - * Session removed - */ - 200: boolean -} - -export type PtyRemoveResponse = PtyRemoveResponses[keyof PtyRemoveResponses] - -export type PtyGetData = { - body?: never - path: { - id: string - } - query?: { - directory?: string - } - url: "/pty/{id}" -} - -export type PtyGetErrors = { - /** - * Not found - */ - 404: NotFoundError -} - -export type PtyGetError = PtyGetErrors[keyof PtyGetErrors] - -export type PtyGetResponses = { - /** - * Session info - */ - 200: Pty -} - -export type PtyGetResponse = PtyGetResponses[keyof PtyGetResponses] - -export type PtyUpdateData = { - body?: { - title?: string - size?: { - rows: number - cols: number - } - } - path: { - id: string - } - query?: { - directory?: string - } - url: "/pty/{id}" -} - -export type PtyUpdateErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type PtyUpdateError = PtyUpdateErrors[keyof PtyUpdateErrors] - -export type PtyUpdateResponses = { - /** - * Updated session - */ - 200: Pty -} - -export type PtyUpdateResponse = PtyUpdateResponses[keyof PtyUpdateResponses] - -export type PtyConnectData = { - body?: never - path: { - id: string - } - query?: { - directory?: string - } - url: "/pty/{id}/connect" -} - -export type PtyConnectErrors = { - /** - * Session not found - */ - 404: boolean -} - -export type PtyConnectError = PtyConnectErrors[keyof PtyConnectErrors] - -export type PtyConnectResponses = { - /** - * Connected session - */ - 200: boolean -} - -export type PtyConnectResponse = PtyConnectResponses[keyof PtyConnectResponses] - export type ConfigGetData = { body?: never path?: never diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 9e4f00a0de7..306d7964988 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -153,10 +153,10 @@ const newIcons = { stop: ``, enter: ``, "layout-left": ``, - "layout-left-partial": ``, + "layout-left-partial": ``, "layout-left-full": ``, "layout-right": ``, - "layout-right-partial": ``, + "layout-right-partial": ``, "layout-right-full": ``, "speech-bubble": ``, "align-right": ``, @@ -167,9 +167,6 @@ const newIcons = { "bubble-5": ``, github: ``, discord: ``, - "layout-bottom": ``, - "layout-bottom-partial": ``, - "layout-bottom-full": ``, } export interface IconProps extends ComponentProps<"svg"> { diff --git a/packages/ui/src/components/select.css b/packages/ui/src/components/select.css index 96ddf174cd8..421215a78ce 100644 --- a/packages/ui/src/components/select.css +++ b/packages/ui/src/components/select.css @@ -20,7 +20,6 @@ [data-component="select-content"] { min-width: 4rem; - max-width: 23rem; overflow: hidden; border-radius: var(--radius-md); border-width: 1px; @@ -40,7 +39,6 @@ } [data-slot="select-select-content-list"] { - min-height: 2rem; overflow-y: auto; max-height: 12rem; white-space: nowrap; diff --git a/packages/ui/src/components/select.tsx b/packages/ui/src/components/select.tsx index 9ba1f177b56..464900ef97b 100644 --- a/packages/ui/src/components/select.tsx +++ b/packages/ui/src/components/select.tsx @@ -1,10 +1,10 @@ import { Select as Kobalte } from "@kobalte/core/select" -import { createMemo, splitProps, type ComponentProps } from "solid-js" +import { createMemo, type ComponentProps } from "solid-js" import { pipe, groupBy, entries, map } from "remeda" import { Button, ButtonProps } from "./button" import { Icon } from "./icon" -export type SelectProps = Omit>, "value" | "onSelect"> & { +export interface SelectProps { placeholder?: string options: T[] current?: T @@ -17,21 +17,10 @@ export type SelectProps = Omit>, "value" | " } export function Select(props: SelectProps & ButtonProps) { - const [local, others] = splitProps(props, [ - "class", - "classList", - "placeholder", - "options", - "current", - "value", - "label", - "groupBy", - "onSelect", - ]) const grouped = createMemo(() => { const result = pipe( - local.options, - groupBy((x) => (local.groupBy ? local.groupBy(x) : "")), + props.options, + groupBy((x) => (props.groupBy ? props.groupBy(x) : "")), // mapValues((x) => x.sort((a, b) => a.title.localeCompare(b.title))), entries(), map(([k, v]) => ({ category: k, options: v })), @@ -40,30 +29,28 @@ export function Select(props: SelectProps & ButtonProps) { }) return ( - // @ts-ignore - {...others} data-component="select" - value={local.current} + value={props.current} options={grouped()} - optionValue={(x) => (local.value ? local.value(x) : (x as string))} - optionTextValue={(x) => (local.label ? local.label(x) : (x as string))} + optionValue={(x) => (props.value ? props.value(x) : (x as string))} + optionTextValue={(x) => (props.label ? props.label(x) : (x as string))} optionGroupChildren="options" - placeholder={local.placeholder} - sectionComponent={(local) => ( - {local.section.rawValue.category} + placeholder={props.placeholder} + sectionComponent={(props) => ( + {props.section.rawValue.category} )} itemComponent={(itemProps) => ( - {local.label ? local.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)} + {props.label ? props.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)} @@ -71,25 +58,24 @@ export function Select(props: SelectProps & ButtonProps) { )} onChange={(v) => { - local.onSelect?.(v ?? undefined) + props.onSelect?.(v ?? undefined) }} > data-slot="select-select-trigger-value"> {(state) => { - const selected = state.selectedOption() ?? local.current - if (!selected) return local.placeholder || "" - if (local.label) return local.label(selected) + const selected = state.selectedOption() ?? props.current + if (!selected) return props.placeholder || "" + if (props.label) return props.label(selected) return selected as string }} @@ -100,8 +86,8 @@ export function Select(props: SelectProps & ButtonProps) { diff --git a/packages/ui/src/components/tabs.css b/packages/ui/src/components/tabs.css index d60edc5c509..d03e57320ff 100644 --- a/packages/ui/src/components/tabs.css +++ b/packages/ui/src/components/tabs.css @@ -6,7 +6,7 @@ background-color: var(--background-stronger); overflow: clip; - [data-slot="tabs-list"] { + [data-slot="tabs-tabs-list"] { height: 48px; width: 100%; position: relative; @@ -36,7 +36,7 @@ } } - [data-slot="tabs-trigger-wrapper"] { + [data-slot="tabs-tabs-trigger-wrapper"] { position: relative; height: 100%; display: flex; @@ -58,14 +58,14 @@ border-right: 1px solid var(--border-weak-base); background-color: var(--background-base); - [data-slot="tabs-trigger"] { + [data-slot="tabs-tabs-trigger"] { display: flex; align-items: center; justify-content: center; padding: 14px 24px; } - [data-slot="tabs-trigger-close-button"] { + [data-slot="tabs-tabs-trigger-close-button"] { display: flex; align-items: center; justify-content: center; @@ -84,12 +84,12 @@ box-shadow: 0 0 0 2px var(--border-focus); } &:has([data-hidden]) { - [data-slot="tabs-trigger-close-button"] { + [data-slot="tabs-tabs-trigger-close-button"] { opacity: 0; } &:hover { - [data-slot="tabs-trigger-close-button"] { + [data-slot="tabs-tabs-trigger-close-button"] { opacity: 1; } } @@ -98,23 +98,23 @@ color: var(--text-strong); background-color: transparent; border-bottom-color: transparent; - [data-slot="tabs-trigger-close-button"] { + [data-slot="tabs-tabs-trigger-close-button"] { opacity: 1; } } &:hover:not(:disabled):not([data-selected]) { color: var(--text-strong); } - &:has([data-slot="tabs-trigger-close-button"]) { + &:has([data-slot="tabs-tabs-trigger-close-button"]) { padding-right: 12px; - [data-slot="tabs-trigger"] { + [data-slot="tabs-tabs-trigger"] { padding-right: 0; } } } - [data-slot="tabs-content"] { + [data-slot="tabs-tabs-content"] { overflow-y: auto; flex: 1; @@ -129,80 +129,4 @@ outline: none; } } - - &[data-variant="alt"] { - [data-slot="tabs-list"] { - padding-left: 24px; - padding-right: 24px; - gap: 12px; - border-bottom: 1px solid var(--border-weak-base); - background-color: transparent; - - &::after { - border: none; - background-color: transparent; - } - &:empty::after { - display: none; - } - } - - [data-slot="tabs-trigger-wrapper"] { - border: none; - color: var(--text-base); - background-color: transparent; - border-bottom-width: 2px; - border-bottom-style: solid; - border-bottom-color: transparent; - gap: 4px; - - /* text-14-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-x-large); /* 171.429% */ - letter-spacing: var(--letter-spacing-normal); - - [data-slot="tabs-trigger"] { - height: 100%; - padding: 4px; - background-color: transparent; - border-bottom-width: 2px; - border-bottom-color: transparent; - } - - [data-slot="tabs-trigger-close-button"] { - display: flex; - align-items: center; - justify-content: center; - } - - [data-component="icon-button"] { - width: 16px; - height: 16px; - margin: 0; - } - - &:has([data-selected]) { - color: var(--text-strong); - background-color: transparent; - border-bottom-color: var(--icon-strong-base); - } - - &:hover:not(:disabled):not([data-selected]) { - color: var(--text-strong); - } - - &:has([data-slot="tabs-trigger-close-button"]) { - padding-right: 0; - [data-slot="tabs-trigger"] { - padding-right: 0; - } - } - } - - /* [data-slot="tabs-content"] { */ - /* } */ - } } diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx index d91ad3c4156..68acd88d4e1 100644 --- a/packages/ui/src/components/tabs.tsx +++ b/packages/ui/src/components/tabs.tsx @@ -2,9 +2,7 @@ import { Tabs as Kobalte } from "@kobalte/core/tabs" import { Show, splitProps, type JSX } from "solid-js" import type { ComponentProps, ParentProps } from "solid-js" -export interface TabsProps extends ComponentProps { - variant?: "normal" | "alt" -} +export interface TabsProps extends ComponentProps {} export interface TabsListProps extends ComponentProps {} export interface TabsTriggerProps extends ComponentProps { classes?: { @@ -16,12 +14,11 @@ export interface TabsTriggerProps extends ComponentProps export interface TabsContentProps extends ComponentProps {} function TabsRoot(props: TabsProps) { - const [split, rest] = splitProps(props, ["class", "classList", "variant"]) + const [split, rest] = splitProps(props, ["class", "classList"]) return ( ) { ]) return (
) { > {split.children} {(closeButton) => ( -
+
{closeButton()}
)} @@ -84,7 +81,7 @@ function TabsContent(props: ParentProps) { return ( Date: Thu, 4 Dec 2025 22:02:17 +0000 Subject: [PATCH 22/60] release: v1.0.133 --- bun.lock | 30 +++++++++++++------------- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++------ packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 4 ++-- packages/sdk/js/package.json | 4 ++-- packages/slack/package.json | 2 +- packages/tauri/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 18 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index aad651621cb..204f0df73a5 100644 --- a/bun.lock +++ b/bun.lock @@ -20,7 +20,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.0.132", + "version": "1.0.133", "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.132", + "version": "1.0.133", "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.132", + "version": "1.0.133", "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.132", + "version": "1.0.133", "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.132", + "version": "1.0.133", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -164,7 +164,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.0.132", + "version": "1.0.133", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -192,7 +192,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.0.132", + "version": "1.0.133", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "22.0.0", @@ -208,7 +208,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.0.132", + "version": "1.0.133", "bin": { "opencode": "./bin/opencode", }, @@ -297,7 +297,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.0.132", + "version": "1.0.133", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -317,7 +317,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.0.132", + "version": "1.0.133", "devDependencies": { "@hey-api/openapi-ts": "0.81.0", "@tsconfig/node22": "catalog:", @@ -328,7 +328,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.0.132", + "version": "1.0.133", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -341,7 +341,7 @@ }, "packages/tauri": { "name": "@opencode-ai/tauri", - "version": "1.0.132", + "version": "1.0.133", "dependencies": { "@tauri-apps/api": "^2", "@tauri-apps/plugin-opener": "^2", @@ -354,7 +354,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.0.132", + "version": "1.0.133", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -386,7 +386,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.0.132", + "version": "1.0.133", "dependencies": { "zod": "catalog:", }, @@ -397,7 +397,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.0.132", + "version": "1.0.133", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 0a2c9c61bbb..15f00d6f322 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.0.132", + "version": "1.0.133", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 290127d3ef6..d324589b94b 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.0.132", + "version": "1.0.133", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 4f0955fd79a..0a3149cb1df 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.0.132", + "version": "1.0.133", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 3d6099b4d8b..9f55fd8f26e 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.0.132", + "version": "1.0.133", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index f2f8768cbb8..483cf85ef5a 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/desktop", - "version": "1.0.132", + "version": "1.0.133", "description": "", "type": "module", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 5a99f880ff4..a007bdcd959 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.0.132", + "version": "1.0.133", "private": true, "type": "module", "scripts": { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 89598d4db6e..8a603582aae 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The AI coding agent built for the terminal" -version = "1.0.132" +version = "1.0.133" 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.132/opencode-darwin-arm64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.133/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.132/opencode-darwin-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.133/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.132/opencode-linux-arm64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.133/opencode-linux-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.132/opencode-linux-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.133/opencode-linux-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.132/opencode-windows-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.133/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 7bacb052bb5..be6e1b65c00 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.0.132", + "version": "1.0.133", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 46c8c3200a6..e14aed5ffbb 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.0.132", + "version": "1.0.133", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index da5e3450e3e..7af6a984ec6 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.0.132", + "version": "1.0.133", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", @@ -24,4 +24,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} +} \ No newline at end of file diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index a811885e1dc..470c6aa023c 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.0.132", + "version": "1.0.133", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", @@ -26,4 +26,4 @@ "publishConfig": { "directory": "dist" } -} +} \ No newline at end of file diff --git a/packages/slack/package.json b/packages/slack/package.json index 4808543d592..2570f0877a4 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.0.132", + "version": "1.0.133", "type": "module", "scripts": { "dev": "bun run src/index.ts", diff --git a/packages/tauri/package.json b/packages/tauri/package.json index b368f4a08b6..9c3964dbab1 100644 --- a/packages/tauri/package.json +++ b/packages/tauri/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/tauri", "private": true, - "version": "1.0.132", + "version": "1.0.133", "type": "module", "scripts": { "dev": "vite", diff --git a/packages/ui/package.json b/packages/ui/package.json index ec2d740414a..a20d19f6214 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.0.132", + "version": "1.0.133", "type": "module", "exports": { "./*": "./src/components/*.tsx", diff --git a/packages/util/package.json b/packages/util/package.json index 75f8bec7485..4ac022e0cbe 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.0.132", + "version": "1.0.133", "private": true, "type": "module", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 666d40a33f9..5e5b178f766 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/web", "type": "module", - "version": "1.0.132", + "version": "1.0.133", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 448489ac5a3..f71048523cd 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.0.132", + "version": "1.0.133", "publisher": "sst-dev", "repository": { "type": "git", From d82bd430f68b8227a93c39e0b7b617c9463ceea8 Mon Sep 17 00:00:00 2001 From: Github Action Date: Thu, 4 Dec 2025 21:58:26 +0000 Subject: [PATCH 23/60] Update Nix flake.lock and hashes --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 4e7cf41e1b7..ca9fd5f8f30 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1764733908, - "narHash": "sha256-QJiih52NU+nm7XQWCj+K8SwUdIEayDQ1FQgjkYISt4I=", + "lastModified": 1764794580, + "narHash": "sha256-UMVihg0OQ980YqmOAPz+zkuCEb9hpE5Xj2v+ZGNjQ+M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "cadcc8de247676e4751c9d4a935acb2c0b059113", + "rev": "ebc94f855ef25347c314258c10393a92794e7ab9", "type": "github" }, "original": { From 09f522f0aa698be60c954e58bb7eee0e460c4439 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 4 Dec 2025 20:32:08 -0600 Subject: [PATCH 24/60] Reapply "feat(desktop): terminal pane (#5081)" This reverts commit f9dcd979364acc5172fd0044c1c8b04dcaec9229. --- bun.lock | 13 +- nix/hashes.json | 2 +- package.json | 2 +- packages/desktop/package.json | 2 + packages/desktop/src/addons/serialize.ts | 649 +++++++++++++++++++ packages/desktop/src/components/terminal.tsx | 151 +++++ packages/desktop/src/context/layout.tsx | 22 +- packages/desktop/src/context/sdk.tsx | 2 +- packages/desktop/src/context/session.tsx | 100 ++- packages/desktop/src/pages/layout.tsx | 114 +++- packages/desktop/src/pages/session.tsx | 649 +++++++++++-------- packages/opencode/package.json | 1 + packages/opencode/src/cli/cmd/tui/worker.ts | 7 +- packages/opencode/src/id/id.ts | 1 + packages/opencode/src/pty/index.ts | 199 ++++++ packages/opencode/src/server/error.ts | 36 + packages/opencode/src/server/server.ts | 197 +++++- packages/sdk/js/src/gen/sdk.gen.ts | 88 +++ packages/sdk/js/src/gen/types.gen.ts | 248 ++++++- packages/ui/src/components/icon.tsx | 7 +- packages/ui/src/components/select.css | 2 + packages/ui/src/components/select.tsx | 56 +- packages/ui/src/components/tabs.css | 96 ++- packages/ui/src/components/tabs.tsx | 17 +- packages/ui/src/components/tooltip.css | 7 +- packages/util/src/shell.ts | 13 + 26 files changed, 2289 insertions(+), 392 deletions(-) create mode 100644 packages/desktop/src/addons/serialize.ts create mode 100644 packages/desktop/src/components/terminal.tsx create mode 100644 packages/opencode/src/pty/index.ts create mode 100644 packages/opencode/src/server/error.ts create mode 100644 packages/util/src/shell.ts diff --git a/bun.lock b/bun.lock index 204f0df73a5..7934fcd8cc8 100644 --- a/bun.lock +++ b/bun.lock @@ -135,11 +135,13 @@ "@solid-primitives/resize-observer": "2.1.3", "@solid-primitives/scroll": "2.1.3", "@solid-primitives/storage": "4.3.3", + "@solid-primitives/websocket": "1.3.1", "@solidjs/meta": "catalog:", "@solidjs/router": "catalog:", "@thisbeyond/solid-dnd": "0.7.5", "diff": "catalog:", "fuzzysort": "catalog:", + "ghostty-web": "0.3.0", "luxon": "catalog:", "marked": "16.2.0", "marked-shiki": "1.2.1", @@ -246,6 +248,7 @@ "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", + "bun-pty": "0.4.2", "chokidar": "4.0.3", "clipboardy": "4.0.0", "decimal.js": "10.5.0", @@ -457,7 +460,7 @@ "ai": "5.0.97", "diff": "8.0.2", "fuzzysort": "3.1.0", - "hono": "4.7.10", + "hono": "4.10.7", "hono-openapi": "1.1.1", "luxon": "3.6.1", "remeda": "2.26.0", @@ -1506,6 +1509,8 @@ "@solid-primitives/utils": ["@solid-primitives/utils@6.3.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="], + "@solid-primitives/websocket": ["@solid-primitives/websocket@1.3.1", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-F06tA2FKa5VsnS4E4WEc3jHpsJfXRlMTGOtolugTzCqV3JmJTyvk9UVg1oz6PgGHKGi1CQ91OP8iW34myyJgaQ=="], + "@solidjs/meta": ["@solidjs/meta@0.29.4", "", { "peerDependencies": { "solid-js": ">=1.8.4" } }, "sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g=="], "@solidjs/router": ["@solidjs/router@0.15.4", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ=="], @@ -1890,6 +1895,8 @@ "bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="], + "bun-pty": ["bun-pty@0.4.2", "", {}, "sha512-sHImDz6pJDsHAroYpC9ouKVgOyqZ7FP3N+stX5IdMddHve3rf9LIZBDomQcXrACQ7sQDNuwZQHG8BKR7w8krkQ=="], + "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], "bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="], @@ -2334,6 +2341,8 @@ "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], + "ghostty-web": ["ghostty-web@0.3.0", "", {}, "sha512-SAdSHWYF20GMZUB0n8kh1N6Z4ljMnuUqT8iTB2n5FAPswEV10MejEpLlhW/769GL5+BQa1NYwEg9y/XCckV5+A=="], + "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], "giget": ["giget@1.2.5", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.5.4", "pathe": "^2.0.3", "tar": "^6.2.1" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug=="], @@ -2428,7 +2437,7 @@ "hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="], - "hono": ["hono@4.7.10", "", {}, "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ=="], + "hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="], "hono-openapi": ["hono-openapi@1.1.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.8", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-AC3HNhZYPHhnZdSy2Je7GDoTTNxPos6rKRQKVDBbSilY3cWJPqsxRnN6zA4pU7tfxmQEMTqkiLXbw6sAaemB8Q=="], diff --git a/nix/hashes.json b/nix/hashes.json index 47634e2ed82..6bc2eaec159 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,3 +1,3 @@ { - "nodeModules": "sha256-ZGKC7h4ScHDzVYj8qb1lN/weZhyZivPS8kpNAZvgO0I=" + "nodeModules": "sha256-Wrfwnmo0lpck2rbt6ttkAuDGvBvqqWJfNA8QDQxoZ6I=" } diff --git a/package.json b/package.json index a5e7c14621b..a962be92605 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@tailwindcss/vite": "4.1.11", "diff": "8.0.2", "ai": "5.0.97", - "hono": "4.7.10", + "hono": "4.10.7", "hono-openapi": "1.1.1", "fuzzysort": "3.1.0", "luxon": "3.6.1", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 483cf85ef5a..089bd5687f1 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -33,11 +33,13 @@ "@solid-primitives/resize-observer": "2.1.3", "@solid-primitives/scroll": "2.1.3", "@solid-primitives/storage": "4.3.3", + "@solid-primitives/websocket": "1.3.1", "@solidjs/meta": "catalog:", "@solidjs/router": "catalog:", "@thisbeyond/solid-dnd": "0.7.5", "diff": "catalog:", "fuzzysort": "catalog:", + "ghostty-web": "0.3.0", "luxon": "catalog:", "marked": "16.2.0", "marked-shiki": "1.2.1", diff --git a/packages/desktop/src/addons/serialize.ts b/packages/desktop/src/addons/serialize.ts new file mode 100644 index 00000000000..03899ff109b --- /dev/null +++ b/packages/desktop/src/addons/serialize.ts @@ -0,0 +1,649 @@ +/** + * SerializeAddon - Serialize terminal buffer contents + * + * Port of xterm.js addon-serialize for ghostty-web. + * Enables serialization of terminal contents to a string that can + * be written back to restore terminal state. + * + * Usage: + * ```typescript + * const serializeAddon = new SerializeAddon(); + * term.loadAddon(serializeAddon); + * const content = serializeAddon.serialize(); + * ``` + */ + +import type { ITerminalAddon, ITerminalCore, IBufferRange } from "ghostty-web" + +// ============================================================================ +// Buffer Types (matching ghostty-web internal interfaces) +// ============================================================================ + +interface IBuffer { + readonly type: "normal" | "alternate" + readonly cursorX: number + readonly cursorY: number + readonly viewportY: number + readonly baseY: number + readonly length: number + getLine(y: number): IBufferLine | undefined + getNullCell(): IBufferCell +} + +interface IBufferLine { + readonly length: number + readonly isWrapped: boolean + getCell(x: number): IBufferCell | undefined + translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string +} + +interface IBufferCell { + getChars(): string + getCode(): number + getWidth(): number + getFgColorMode(): number + getBgColorMode(): number + getFgColor(): number + getBgColor(): number + isBold(): number + isItalic(): number + isUnderline(): number + isStrikethrough(): number + isBlink(): number + isInverse(): number + isInvisible(): number + isFaint(): number + isDim(): boolean +} + +// ============================================================================ +// Types +// ============================================================================ + +export interface ISerializeOptions { + /** + * The row range to serialize. When an explicit range is specified, the cursor + * will get its final repositioning. + */ + range?: ISerializeRange + /** + * The number of rows in the scrollback buffer to serialize, starting from + * the bottom of the scrollback buffer. When not specified, all available + * rows in the scrollback buffer will be serialized. + */ + scrollback?: number + /** + * Whether to exclude the terminal modes from the serialization. + * Default: false + */ + excludeModes?: boolean + /** + * Whether to exclude the alt buffer from the serialization. + * Default: false + */ + excludeAltBuffer?: boolean +} + +export interface ISerializeRange { + /** + * The line to start serializing (inclusive). + */ + start: number + /** + * The line to end serializing (inclusive). + */ + end: number +} + +export interface IHTMLSerializeOptions { + /** + * The number of rows in the scrollback buffer to serialize, starting from + * the bottom of the scrollback buffer. + */ + scrollback?: number + /** + * Whether to only serialize the selection. + * Default: false + */ + onlySelection?: boolean + /** + * Whether to include the global background of the terminal. + * Default: false + */ + includeGlobalBackground?: boolean + /** + * The range to serialize. This is prioritized over onlySelection. + */ + range?: { + startLine: number + endLine: number + startCol: number + } +} + +// ============================================================================ +// Helper Functions +// ============================================================================ + +function constrain(value: number, low: number, high: number): number { + return Math.max(low, Math.min(value, high)) +} + +function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean { + return cell1.getFgColorMode() === cell2.getFgColorMode() && cell1.getFgColor() === cell2.getFgColor() +} + +function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean { + return cell1.getBgColorMode() === cell2.getBgColorMode() && cell1.getBgColor() === cell2.getBgColor() +} + +function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean { + return ( + !!cell1.isInverse() === !!cell2.isInverse() && + !!cell1.isBold() === !!cell2.isBold() && + !!cell1.isUnderline() === !!cell2.isUnderline() && + !!cell1.isBlink() === !!cell2.isBlink() && + !!cell1.isInvisible() === !!cell2.isInvisible() && + !!cell1.isItalic() === !!cell2.isItalic() && + !!cell1.isDim() === !!cell2.isDim() && + !!cell1.isStrikethrough() === !!cell2.isStrikethrough() + ) +} + +// ============================================================================ +// Base Serialize Handler +// ============================================================================ + +abstract class BaseSerializeHandler { + constructor(protected readonly _buffer: IBuffer) {} + + public serialize(range: IBufferRange, excludeFinalCursorPosition?: boolean): string { + let oldCell = this._buffer.getNullCell() + + const startRow = range.start.y + const endRow = range.end.y + const startColumn = range.start.x + const endColumn = range.end.x + + this._beforeSerialize(endRow - startRow, startRow, endRow) + + for (let row = startRow; row <= endRow; row++) { + const line = this._buffer.getLine(row) + if (line) { + const startLineColumn = row === range.start.y ? startColumn : 0 + const endLineColumn = row === range.end.y ? endColumn : line.length + for (let col = startLineColumn; col < endLineColumn; col++) { + const c = line.getCell(col) + if (!c) { + continue + } + this._nextCell(c, oldCell, row, col) + oldCell = c + } + } + this._rowEnd(row, row === endRow) + } + + this._afterSerialize() + + return this._serializeString(excludeFinalCursorPosition) + } + + protected _nextCell(_cell: IBufferCell, _oldCell: IBufferCell, _row: number, _col: number): void {} + protected _rowEnd(_row: number, _isLastRow: boolean): void {} + protected _beforeSerialize(_rows: number, _startRow: number, _endRow: number): void {} + protected _afterSerialize(): void {} + protected _serializeString(_excludeFinalCursorPosition?: boolean): string { + return "" + } +} + +// ============================================================================ +// String Serialize Handler +// ============================================================================ + +class StringSerializeHandler extends BaseSerializeHandler { + private _rowIndex: number = 0 + private _allRows: string[] = [] + private _allRowSeparators: string[] = [] + private _currentRow: string = "" + private _nullCellCount: number = 0 + private _cursorStyle: IBufferCell + private _cursorStyleRow: number = 0 + private _cursorStyleCol: number = 0 + private _backgroundCell: IBufferCell + private _firstRow: number = 0 + private _lastCursorRow: number = 0 + private _lastCursorCol: number = 0 + private _lastContentCursorRow: number = 0 + private _lastContentCursorCol: number = 0 + private _thisRowLastChar: IBufferCell + private _thisRowLastSecondChar: IBufferCell + private _nextRowFirstChar: IBufferCell + + constructor( + buffer: IBuffer, + private readonly _terminal: ITerminalCore, + ) { + super(buffer) + this._cursorStyle = this._buffer.getNullCell() + this._backgroundCell = this._buffer.getNullCell() + this._thisRowLastChar = this._buffer.getNullCell() + this._thisRowLastSecondChar = this._buffer.getNullCell() + this._nextRowFirstChar = this._buffer.getNullCell() + } + + protected _beforeSerialize(rows: number, start: number, _end: number): void { + this._allRows = new Array(rows) + this._lastContentCursorRow = start + this._lastCursorRow = start + this._firstRow = start + } + + protected _rowEnd(row: number, isLastRow: boolean): void { + // if there is colorful empty cell at line end, we must pad it back + if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) { + this._currentRow += `\u001b[${this._nullCellCount}X` + } + + let rowSeparator = "" + + if (!isLastRow) { + // Enable BCE + if (row - this._firstRow >= this._terminal.rows) { + const line = this._buffer.getLine(this._cursorStyleRow) + const cell = line?.getCell(this._cursorStyleCol) + if (cell) { + this._backgroundCell = cell + } + } + + const currentLine = this._buffer.getLine(row)! + const nextLine = this._buffer.getLine(row + 1)! + + if (!nextLine.isWrapped) { + rowSeparator = "\r\n" + this._lastCursorRow = row + 1 + this._lastCursorCol = 0 + } else { + rowSeparator = "" + const thisRowLastChar = currentLine.getCell(currentLine.length - 1) + const thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2) + const nextRowFirstChar = nextLine.getCell(0) + + if (thisRowLastChar) this._thisRowLastChar = thisRowLastChar + if (thisRowLastSecondChar) this._thisRowLastSecondChar = thisRowLastSecondChar + if (nextRowFirstChar) this._nextRowFirstChar = nextRowFirstChar + + const isNextRowFirstCharDoubleWidth = this._nextRowFirstChar.getWidth() > 1 + + let isValid = false + + if ( + this._nextRowFirstChar.getChars() && + (isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0) + ) { + if ( + (this._thisRowLastChar.getChars() || this._thisRowLastChar.getWidth() === 0) && + equalBg(this._thisRowLastChar, this._nextRowFirstChar) + ) { + isValid = true + } + + if ( + isNextRowFirstCharDoubleWidth && + (this._thisRowLastSecondChar.getChars() || this._thisRowLastSecondChar.getWidth() === 0) && + equalBg(this._thisRowLastChar, this._nextRowFirstChar) && + equalBg(this._thisRowLastSecondChar, this._nextRowFirstChar) + ) { + isValid = true + } + } + + if (!isValid) { + rowSeparator = "-".repeat(this._nullCellCount + 1) + rowSeparator += "\u001b[1D\u001b[1X" + + if (this._nullCellCount > 0) { + rowSeparator += "\u001b[A" + rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}C` + rowSeparator += `\u001b[${this._nullCellCount}X` + rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}D` + rowSeparator += "\u001b[B" + } + + this._lastContentCursorRow = row + 1 + this._lastContentCursorCol = 0 + this._lastCursorRow = row + 1 + this._lastCursorCol = 0 + } + } + } + + this._allRows[this._rowIndex] = this._currentRow + this._allRowSeparators[this._rowIndex++] = rowSeparator + this._currentRow = "" + this._nullCellCount = 0 + } + + private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] { + const sgrSeq: number[] = [] + const fgChanged = !equalFg(cell, oldCell) + const bgChanged = !equalBg(cell, oldCell) + const flagsChanged = !equalFlags(cell, oldCell) + + if (fgChanged || bgChanged || flagsChanged) { + if (this._isAttributeDefault(cell)) { + if (!this._isAttributeDefault(oldCell)) { + sgrSeq.push(0) + } + } else { + if (fgChanged) { + const color = cell.getFgColor() + const mode = cell.getFgColorMode() + if (mode === 2) { + // RGB + sgrSeq.push(38, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff) + } else if (mode === 1) { + // Palette + if (color >= 16) { + sgrSeq.push(38, 5, color) + } else { + sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)) + } + } else { + sgrSeq.push(39) + } + } + if (bgChanged) { + const color = cell.getBgColor() + const mode = cell.getBgColorMode() + if (mode === 2) { + // RGB + sgrSeq.push(48, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff) + } else if (mode === 1) { + // Palette + if (color >= 16) { + sgrSeq.push(48, 5, color) + } else { + sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)) + } + } else { + sgrSeq.push(49) + } + } + if (flagsChanged) { + if (!!cell.isInverse() !== !!oldCell.isInverse()) { + sgrSeq.push(cell.isInverse() ? 7 : 27) + } + if (!!cell.isBold() !== !!oldCell.isBold()) { + sgrSeq.push(cell.isBold() ? 1 : 22) + } + if (!!cell.isUnderline() !== !!oldCell.isUnderline()) { + sgrSeq.push(cell.isUnderline() ? 4 : 24) + } + if (!!cell.isBlink() !== !!oldCell.isBlink()) { + sgrSeq.push(cell.isBlink() ? 5 : 25) + } + if (!!cell.isInvisible() !== !!oldCell.isInvisible()) { + sgrSeq.push(cell.isInvisible() ? 8 : 28) + } + if (!!cell.isItalic() !== !!oldCell.isItalic()) { + sgrSeq.push(cell.isItalic() ? 3 : 23) + } + if (!!cell.isDim() !== !!oldCell.isDim()) { + sgrSeq.push(cell.isDim() ? 2 : 22) + } + if (!!cell.isStrikethrough() !== !!oldCell.isStrikethrough()) { + sgrSeq.push(cell.isStrikethrough() ? 9 : 29) + } + } + } + } + + return sgrSeq + } + + private _isAttributeDefault(cell: IBufferCell): boolean { + return ( + cell.getFgColorMode() === 0 && + cell.getBgColorMode() === 0 && + !cell.isBold() && + !cell.isItalic() && + !cell.isUnderline() && + !cell.isBlink() && + !cell.isInverse() && + !cell.isInvisible() && + !cell.isDim() && + !cell.isStrikethrough() + ) + } + + protected _nextCell(cell: IBufferCell, _oldCell: IBufferCell, row: number, col: number): void { + const isPlaceHolderCell = cell.getWidth() === 0 + + if (isPlaceHolderCell) { + return + } + + const isEmptyCell = cell.getChars() === "" + + const sgrSeq = this._diffStyle(cell, this._cursorStyle) + + const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0 + + if (styleChanged) { + if (this._nullCellCount > 0) { + if (!equalBg(this._cursorStyle, this._backgroundCell)) { + this._currentRow += `\u001b[${this._nullCellCount}X` + } + this._currentRow += `\u001b[${this._nullCellCount}C` + this._nullCellCount = 0 + } + + this._lastContentCursorRow = this._lastCursorRow = row + this._lastContentCursorCol = this._lastCursorCol = col + + this._currentRow += `\u001b[${sgrSeq.join(";")}m` + + const line = this._buffer.getLine(row) + const cellFromLine = line?.getCell(col) + if (cellFromLine) { + this._cursorStyle = cellFromLine + this._cursorStyleRow = row + this._cursorStyleCol = col + } + } + + if (isEmptyCell) { + this._nullCellCount += cell.getWidth() + } else { + if (this._nullCellCount > 0) { + if (equalBg(this._cursorStyle, this._backgroundCell)) { + this._currentRow += `\u001b[${this._nullCellCount}C` + } else { + this._currentRow += `\u001b[${this._nullCellCount}X` + this._currentRow += `\u001b[${this._nullCellCount}C` + } + this._nullCellCount = 0 + } + + this._currentRow += cell.getChars() + + this._lastContentCursorRow = this._lastCursorRow = row + this._lastContentCursorCol = this._lastCursorCol = col + cell.getWidth() + } + } + + protected _serializeString(excludeFinalCursorPosition?: boolean): string { + let rowEnd = this._allRows.length + + if (this._buffer.length - this._firstRow <= this._terminal.rows) { + rowEnd = this._lastContentCursorRow + 1 - this._firstRow + this._lastCursorCol = this._lastContentCursorCol + this._lastCursorRow = this._lastContentCursorRow + } + + let content = "" + + for (let i = 0; i < rowEnd; i++) { + content += this._allRows[i] + if (i + 1 < rowEnd) { + content += this._allRowSeparators[i] + } + } + + if (!excludeFinalCursorPosition) { + // Get cursor position relative to viewport (1-indexed for ANSI) + // cursorY is relative to the viewport, cursorX is column position + const cursorRow = this._buffer.cursorY + 1 // 1-indexed + const cursorCol = this._buffer.cursorX + 1 // 1-indexed + + // Use absolute cursor positioning (CUP - Cursor Position) + // This is more reliable than relative moves which depend on knowing + // exactly where the cursor ended up after all the content + content += `\u001b[${cursorRow};${cursorCol}H` + } + + return content + } +} + +// ============================================================================ +// SerializeAddon Class +// ============================================================================ + +export class SerializeAddon implements ITerminalAddon { + private _terminal?: ITerminalCore + + /** + * Activate the addon (called by Terminal.loadAddon) + */ + public activate(terminal: ITerminalCore): void { + this._terminal = terminal + } + + /** + * Dispose the addon and clean up resources + */ + public dispose(): void { + this._terminal = undefined + } + + /** + * Serializes terminal rows into a string that can be written back to the + * terminal to restore the state. The cursor will also be positioned to the + * correct cell. + * + * @param options Custom options to allow control over what gets serialized. + */ + public serialize(options?: ISerializeOptions): string { + if (!this._terminal) { + throw new Error("Cannot use addon until it has been loaded") + } + + const terminal = this._terminal as any + const buffer = terminal.buffer + + if (!buffer) { + return "" + } + + const activeBuffer = buffer.active || buffer.normal + if (!activeBuffer) { + return "" + } + + let content = options?.range + ? this._serializeBufferByRange(activeBuffer, options.range, true) + : this._serializeBufferByScrollback(activeBuffer, options?.scrollback) + + // Handle alternate buffer if active and not excluded + if (!options?.excludeAltBuffer) { + const altBuffer = buffer.alternate + if (altBuffer && buffer.active?.type === "alternate") { + const alternateContent = this._serializeBufferByScrollback(altBuffer, undefined) + content += `\u001b[?1049h\u001b[H${alternateContent}` + } + } + + return content + } + + /** + * Serializes terminal content as plain text (no escape sequences) + * @param options Custom options to allow control over what gets serialized. + */ + public serializeAsText(options?: { scrollback?: number; trimWhitespace?: boolean }): string { + if (!this._terminal) { + throw new Error("Cannot use addon until it has been loaded") + } + + const terminal = this._terminal as any + const buffer = terminal.buffer + + if (!buffer) { + return "" + } + + const activeBuffer = buffer.active || buffer.normal + if (!activeBuffer) { + return "" + } + + const maxRows = activeBuffer.length + const scrollback = options?.scrollback + const correctRows = scrollback === undefined ? maxRows : constrain(scrollback + this._terminal.rows, 0, maxRows) + + const startRow = maxRows - correctRows + const endRow = maxRows - 1 + const lines: string[] = [] + + for (let row = startRow; row <= endRow; row++) { + const line = activeBuffer.getLine(row) + if (line) { + const text = line.translateToString(options?.trimWhitespace ?? true) + lines.push(text) + } + } + + // Trim trailing empty lines if requested + if (options?.trimWhitespace) { + while (lines.length > 0 && lines[lines.length - 1] === "") { + lines.pop() + } + } + + return lines.join("\n") + } + + private _serializeBufferByScrollback(buffer: IBuffer, scrollback?: number): string { + const maxRows = buffer.length + const rows = this._terminal?.rows ?? 24 + const correctRows = scrollback === undefined ? maxRows : constrain(scrollback + rows, 0, maxRows) + return this._serializeBufferByRange( + buffer, + { + start: maxRows - correctRows, + end: maxRows - 1, + }, + false, + ) + } + + private _serializeBufferByRange( + buffer: IBuffer, + range: ISerializeRange, + excludeFinalCursorPosition: boolean, + ): string { + const handler = new StringSerializeHandler(buffer, this._terminal!) + const cols = this._terminal?.cols ?? 80 + return handler.serialize( + { + start: { x: 0, y: range.start }, + end: { x: cols, y: range.end }, + }, + excludeFinalCursorPosition, + ) + } +} diff --git a/packages/desktop/src/components/terminal.tsx b/packages/desktop/src/components/terminal.tsx new file mode 100644 index 00000000000..49a45a432bc --- /dev/null +++ b/packages/desktop/src/components/terminal.tsx @@ -0,0 +1,151 @@ +import { init, Terminal as Term, FitAddon } from "ghostty-web" +import { ComponentProps, onCleanup, onMount, splitProps } from "solid-js" +import { createReconnectingWS, ReconnectingWebSocket } from "@solid-primitives/websocket" +import { useSDK } from "@/context/sdk" +import { SerializeAddon } from "@/addons/serialize" +import { LocalPTY } from "@/context/session" + +await init() + +export interface TerminalProps extends ComponentProps<"div"> { + pty: LocalPTY + onSubmit?: () => void + onCleanup?: (pty: LocalPTY) => void +} + +export const Terminal = (props: TerminalProps) => { + const sdk = useSDK() + let container!: HTMLDivElement + const [local, others] = splitProps(props, ["pty", "class", "classList"]) + let ws: ReconnectingWebSocket + let term: Term + let serializeAddon: SerializeAddon + let fitAddon: FitAddon + + onMount(async () => { + ws = createReconnectingWS(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`) + term = new Term({ + cursorBlink: true, + fontSize: 14, + fontFamily: "TX-02, monospace", + allowTransparency: true, + theme: { + background: "#191515", + foreground: "#d4d4d4", + }, + scrollback: 10_000, + }) + term.attachCustomKeyEventHandler((event) => { + // allow for ctrl-` to toggle terminal in parent + if (event.ctrlKey && event.key.toLowerCase() === "`") { + event.preventDefault() + return true + } + return false + }) + + fitAddon = new FitAddon() + serializeAddon = new SerializeAddon() + term.loadAddon(serializeAddon) + term.loadAddon(fitAddon) + + term.open(container) + + if (local.pty.buffer) { + const originalSize = { cols: term.cols, rows: term.rows } + let resized = false + if (local.pty.rows && local.pty.cols) { + term.resize(local.pty.cols, local.pty.rows) + resized = true + } + term.write(local.pty.buffer) + if (local.pty.scrollY) { + term.scrollToLine(local.pty.scrollY) + } + if (resized) { + term.resize(originalSize.cols, originalSize.rows) + } + } + + container.focus() + + fitAddon.fit() + fitAddon.observeResize() + window.addEventListener("resize", () => fitAddon.fit()) + term.onResize(async (size) => { + if (ws && ws.readyState === WebSocket.OPEN) { + await sdk.client.pty.update({ + path: { id: local.pty.id }, + body: { + size: { + cols: size.cols, + rows: size.rows, + }, + }, + }) + } + }) + term.onData((data) => { + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(data) + } + }) + term.onKey((key) => { + if (key.key == "Enter") { + props.onSubmit?.() + } + }) + // term.onScroll((ydisp) => { + // console.log("Scroll position:", ydisp) + // }) + ws.addEventListener("open", () => { + console.log("WebSocket connected") + sdk.client.pty.update({ + path: { id: local.pty.id }, + body: { + size: { + cols: term.cols, + rows: term.rows, + }, + }, + }) + }) + ws.addEventListener("message", (event) => { + term.write(event.data) + }) + ws.addEventListener("error", (error) => { + console.error("WebSocket error:", error) + }) + ws.addEventListener("close", () => { + console.log("WebSocket disconnected") + }) + }) + + onCleanup(() => { + if (serializeAddon && props.onCleanup) { + const buffer = serializeAddon.serialize() + props.onCleanup({ + ...local.pty, + buffer, + rows: term.rows, + cols: term.cols, + scrollY: term.getViewportY(), + }) + } + ws?.close() + term?.dispose() + }) + + return ( +
+ ) +} diff --git a/packages/desktop/src/context/layout.tsx b/packages/desktop/src/context/layout.tsx index 81e8b537abc..ca736e84e6b 100644 --- a/packages/desktop/src/context/layout.tsx +++ b/packages/desktop/src/context/layout.tsx @@ -15,12 +15,16 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( opened: true, width: 280, }, + terminal: { + opened: false, + height: 280, + }, review: { state: "pane" as "pane" | "tab", }, }), { - name: "___default-layout", + name: "____default-layout", }, ) @@ -61,6 +65,22 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( setStore("sidebar", "width", width) }, }, + terminal: { + opened: createMemo(() => store.terminal.opened), + open() { + setStore("terminal", "opened", true) + }, + close() { + setStore("terminal", "opened", false) + }, + toggle() { + setStore("terminal", "opened", (x) => !x) + }, + height: createMemo(() => store.terminal.height), + resize(height: number) { + setStore("terminal", "height", height) + }, + }, review: { state: createMemo(() => store.review?.state ?? "closed"), pane() { diff --git a/packages/desktop/src/context/sdk.tsx b/packages/desktop/src/context/sdk.tsx index 81b32035a0b..144202ee209 100644 --- a/packages/desktop/src/context/sdk.tsx +++ b/packages/desktop/src/context/sdk.tsx @@ -27,6 +27,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ abort.abort() }) - return { directory: props.directory, client: sdk, event: emitter } + return { directory: props.directory, client: sdk, event: emitter, url: globalSDK.url } }, }) diff --git a/packages/desktop/src/context/session.tsx b/packages/desktop/src/context/session.tsx index 72098a93951..4e9fe71f8a7 100644 --- a/packages/desktop/src/context/session.tsx +++ b/packages/desktop/src/context/session.tsx @@ -8,14 +8,25 @@ import { pipe, sumBy } from "remeda" import { AssistantMessage, UserMessage } from "@opencode-ai/sdk" import { useParams } from "@solidjs/router" import { base64Encode } from "@/utils" +import { useSDK } from "./sdk" + +export type LocalPTY = { + id: string + title: string + rows?: number + cols?: number + buffer?: string + scrollY?: number +} export const { use: useSession, provider: SessionProvider } = createSimpleContext({ name: "Session", init: () => { + const sdk = useSDK() const params = useParams() const sync = useSync() const name = createMemo( - () => `___${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`, + () => `______${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`, ) const [store, setStore] = makePersisted( @@ -23,16 +34,21 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex messageId?: string tabs: { active?: string - opened: string[] + all: string[] } prompt: Prompt cursor?: number + terminals: { + active?: string + all: LocalPTY[] + } }>({ tabs: { - opened: [], + all: [], }, prompt: clonePrompt(DEFAULT_PROMPT), cursor: undefined, + terminals: { all: [] }, }), { name: name(), @@ -138,7 +154,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex setStore("tabs", "active", tab) }, setOpenedTabs(tabs: string[]) { - setStore("tabs", "opened", tabs) + setStore("tabs", "all", tabs) }, async openTab(tab: string) { if (tab === "chat") { @@ -146,8 +162,8 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex return } if (tab !== "review") { - if (!store.tabs.opened.includes(tab)) { - setStore("tabs", "opened", [...store.tabs.opened, tab]) + if (!store.tabs.all.includes(tab)) { + setStore("tabs", "all", [...store.tabs.all, tab]) } } setStore("tabs", "active", tab) @@ -156,28 +172,88 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex batch(() => { setStore( "tabs", - "opened", - store.tabs.opened.filter((x) => x !== tab), + "all", + store.tabs.all.filter((x) => x !== tab), ) if (store.tabs.active === tab) { - const index = store.tabs.opened.findIndex((f) => f === tab) - const previous = store.tabs.opened[Math.max(0, index - 1)] + const index = store.tabs.all.findIndex((f) => f === tab) + const previous = store.tabs.all[Math.max(0, index - 1)] setStore("tabs", "active", previous) } }) }, moveTab(tab: string, to: number) { - const index = store.tabs.opened.findIndex((f) => f === tab) + const index = store.tabs.all.findIndex((f) => f === tab) if (index === -1) return setStore( "tabs", - "opened", + "all", produce((opened) => { opened.splice(to, 0, opened.splice(index, 1)[0]) }), ) }, }, + terminal: { + all: createMemo(() => Object.values(store.terminals.all)), + active: createMemo(() => store.terminals.active), + new() { + sdk.client.pty.create({ body: { title: `Terminal ${store.terminals.all.length + 1}` } }).then((pty) => { + const id = pty.data?.id + if (!id) return + batch(() => { + setStore("terminals", "all", [ + ...store.terminals.all, + { + id, + title: pty.data?.title ?? "Terminal", + // rows: pty.data?.rows ?? 24, + // cols: pty.data?.cols ?? 80, + // buffer: "", + // scrollY: 0, + }, + ]) + setStore("terminals", "active", id) + }) + }) + }, + update(pty: Partial & { id: string }) { + setStore("terminals", "all", (x) => x.map((x) => (x.id === pty.id ? { ...x, ...pty } : x))) + sdk.client.pty.update({ + path: { id: pty.id }, + body: { title: pty.title, size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined }, + }) + }, + open(id: string) { + setStore("terminals", "active", id) + }, + async close(id: string) { + batch(() => { + setStore( + "terminals", + "all", + store.terminals.all.filter((x) => x.id !== id), + ) + if (store.terminals.active === id) { + const index = store.terminals.all.findIndex((f) => f.id === id) + const previous = store.tabs.all[Math.max(0, index - 1)] + setStore("terminals", "active", previous) + } + }) + await sdk.client.pty.remove({ path: { id } }) + }, + move(id: string, to: number) { + const index = store.terminals.all.findIndex((f) => f.id === id) + if (index === -1) return + setStore( + "terminals", + "all", + produce((all) => { + all.splice(to, 0, all.splice(index, 1)[0]) + }), + ) + }, + }, } }, }) diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 15180c88566..106a2e733fb 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -1,9 +1,9 @@ import { createMemo, For, ParentProps, Show } from "solid-js" import { DateTime } from "luxon" -import { A, useParams } from "@solidjs/router" +import { A, useNavigate, useParams } from "@solidjs/router" import { useLayout } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" -import { base64Encode } from "@/utils" +import { base64Decode, base64Encode } from "@/utils" import { Mark } from "@opencode-ai/ui/logo" import { Button } from "@opencode-ai/ui/button" import { Icon } from "@opencode-ai/ui/icon" @@ -12,11 +12,21 @@ import { Tooltip } from "@opencode-ai/ui/tooltip" import { Collapsible } from "@opencode-ai/ui/collapsible" import { DiffChanges } from "@opencode-ai/ui/diff-changes" import { getFilename } from "@opencode-ai/util/path" +import { Select } from "@opencode-ai/ui/select" +import { Session } from "@opencode-ai/sdk/client" export default function Layout(props: ParentProps) { + const navigate = useNavigate() const params = useParams() const globalSync = useGlobalSync() const layout = useLayout() + const currentDirectory = createMemo(() => base64Decode(params.dir ?? "")) + const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? []) + const currentSession = createMemo(() => sessions().find((s) => s.id === params.id) ?? sessions().at(0)) + + function navigateToSession(session: Session | undefined) { + navigate(`/${params.dir}/session/${session?.id}`) + } const handleOpenProject = async () => { // layout.projects.open(dir.) @@ -24,7 +34,7 @@ export default function Layout(props: ParentProps) { return (
-
+
+
+
+
+ x.title} + value={(x) => x.id} + onSelect={navigateToSession} + class="text-14-regular text-text-base max-w-3xs" + variant="ghost" + /> +
+ +
+
+ + Toggle terminal + Ctrl ` +
+ } + > + + +
+
+ +
{ + e.preventDefault() + const startX = e.clientX + const startWidth = layout.sidebar.width() + const maxWidth = window.innerWidth * 0.3 + const minWidth = 150 + const collapseThreshold = 80 + let currentWidth = startWidth + + document.body.style.userSelect = "none" + document.body.style.overflow = "hidden" + + const onMouseMove = (moveEvent: MouseEvent) => { + const deltaX = moveEvent.clientX - startX + currentWidth = startWidth + deltaX + const clampedWidth = Math.min(maxWidth, Math.max(minWidth, currentWidth)) + layout.sidebar.resize(clampedWidth) + } + + const onMouseUp = () => { + document.body.style.userSelect = "" + document.body.style.overflow = "" + document.removeEventListener("mousemove", onMouseMove) + document.removeEventListener("mouseup", onMouseUp) + + if (currentWidth < collapseThreshold) { + layout.sidebar.close() + } + } + + document.addEventListener("mousemove", onMouseMove) + document.addEventListener("mouseup", onMouseUp) + }} + /> +
-
{props.children}
+
{props.children}
) diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx index d6ce62b7030..77362533404 100644 --- a/packages/desktop/src/pages/session.tsx +++ b/packages/desktop/src/pages/session.tsx @@ -1,4 +1,4 @@ -import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo } from "solid-js" +import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo, createEffect } from "solid-js" import { useLocal, type LocalFile } from "@/context/local" import { createStore } from "solid-js/store" import { PromptInput } from "@/components/prompt-input" @@ -31,6 +31,7 @@ import { useSession } from "@/context/session" import { useLayout } from "@/context/layout" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { Diff } from "@opencode-ai/ui/diff" +import { Terminal } from "@/components/terminal" export default function Page() { const layout = useLayout() @@ -54,6 +55,14 @@ export default function Page() { document.removeEventListener("keydown", handleKeyDown) }) + createEffect(() => { + if (layout.terminal.opened()) { + if (session.terminal.all().length === 0) { + session.terminal.new() + } + } + }) + const handleKeyDown = (event: KeyboardEvent) => { if (event.getModifierState(MOD) && event.shiftKey && event.key.toLowerCase() === "p") { event.preventDefault() @@ -73,6 +82,16 @@ export default function Page() { document.documentElement.setAttribute("data-theme", nextTheme) return } + if (event.ctrlKey && event.key.toLowerCase() === "`") { + event.preventDefault() + layout.terminal.toggle() + return + } + + // @ts-expect-error + if (document.activeElement?.dataset?.component === "terminal") { + return + } const focused = document.activeElement === inputRef if (focused) { @@ -141,7 +160,7 @@ export default function Page() { const handleDragOver = (event: DragEvent) => { const { draggable, droppable } = event if (draggable && droppable) { - const currentTabs = session.layout.tabs.opened + const currentTabs = session.layout.tabs.all const fromIndex = currentTabs?.indexOf(draggable.id.toString()) const toIndex = currentTabs?.indexOf(droppable.id.toString()) if (fromIndex !== toIndex && toIndex !== undefined) { @@ -259,317 +278,397 @@ export default function Page() { const wide = createMemo(() => layout.review.state() === "tab" || !session.diffs().length) return ( -
- - - - -
- - -
-
Session
- +
+ + + + +
+ + +
+
Session
+ + +
{session.usage.context() ?? 0}%
+
+
+
+ + + } > - -
{session.usage.context() ?? 0}%
- -
- - - - } - > -
- - - -
-
Review
- -
- {session.info()?.summary?.files ?? 0} -
+
+ + +
+
Review
+ +
+ {session.info()?.summary?.files ?? 0} +
+
+
-
- - - - - {(tab) => } - - -
- - setStore("fileSelectOpen", true)} - /> - -
- -
- -
+ + + + + {(tab) => ( + + )} + + +
+ + setStore("fileSelectOpen", true)} + /> + +
+ +
+
- - -
- - 1 - ? "pr-6 pl-18" - : "px-6"), - }} - diffComponent={Diff} - /> -
-
- -
-
New session
-
- -
- {getDirectory(sync.data.path.directory)} - {getFilename(sync.data.path.directory)} -
+
+ + +
+ + 1 + ? "pr-6 pl-18" + : "px-6"), + }} + diffComponent={Diff} + />
-
- -
- Last modified  - - {DateTime.fromMillis(sync.data.project.time.created).toRelative()} - + + +
+
New session
+
+ +
+ {getDirectory(sync.data.path.directory)} + {getFilename(sync.data.path.directory)} +
+
+
+ +
+ Last modified  + + {DateTime.fromMillis(sync.data.project.time.created).toRelative()} + +
+
+ +
+
+ { + inputRef = el + }} + />
- - -
-
- { - inputRef = el +
+
+ +
+ + { + layout.review.tab() + session.layout.setActiveTab("review") + }} + /> + + } />
-
+
- + + +
- { - layout.review.tab() - session.layout.setActiveTab("review") - }} - /> - - } + split />
-
-
- - - + + + + {(tab) => { + const [file] = createResource( + () => tab, + async (tab) => { + if (tab.startsWith("file://")) { + return local.file.node(tab.replace("file://", "")) + } + return undefined + }, + ) + return ( + + + + {(f) => ( + + )} + + + + ) + }} + + + + + {(draggedFile) => { + const [file] = createResource( + () => draggedFile(), + async (tab) => { + if (tab.startsWith("file://")) { + return local.file.node(tab.replace("file://", "")) + } + return undefined + }, + ) + return ( +
+ {(f) => } +
+ ) + }} +
+
+ + +
+ { + inputRef = el + }} + /> +
+
+ + } + > +
    + + {(path) => ( +
  • + +
  • + )} +
    +
+ +
+ + x} + onOpenChange={(open) => setStore("fileSelectOpen", open)} + onSelect={(x) => { + if (x) { + local.file.open(x) + return session.layout.openTab("file://" + x) + } + return undefined + }} + > + {(i) => (
- -
- -
- - {(tab) => { - const [file] = createResource( - () => tab, - async (tab) => { - if (tab.startsWith("file://")) { - return local.file.node(tab.replace("file://", "")) - } - return undefined - }, - ) - return ( - - - - {(f) => ( - - )} - - - - ) - }} - - - - - {(draggedFile) => { - const [file] = createResource( - () => draggedFile(), - async (tab) => { - if (tab.startsWith("file://")) { - return local.file.node(tab.replace("file://", "")) - } - return undefined - }, - ) - return ( -
- {(f) => } +
+ +
+ + {getDirectory(i)} + + {getFilename(i)} +
- ) - }} - - - - -
- { - inputRef = el +
+
+ )} + +
+
+ +
+
{ + e.preventDefault() + const startY = e.clientY + const startHeight = layout.terminal.height() + const maxHeight = window.innerHeight * 0.6 + const minHeight = 100 + const collapseThreshold = 50 + let currentHeight = startHeight + + document.body.style.userSelect = "none" + document.body.style.overflow = "hidden" + + const onMouseMove = (moveEvent: MouseEvent) => { + const deltaY = startY - moveEvent.clientY + currentHeight = startHeight + deltaY + const clampedHeight = Math.min(maxHeight, Math.max(minHeight, currentHeight)) + layout.terminal.resize(clampedHeight) + } + + const onMouseUp = () => { + document.body.style.userSelect = "" + document.body.style.overflow = "" + document.removeEventListener("mousemove", onMouseMove) + document.removeEventListener("mouseup", onMouseUp) + + if (currentHeight < collapseThreshold) { + layout.terminal.close() + } + } + + document.addEventListener("mousemove", onMouseMove) + document.addEventListener("mouseup", onMouseUp) }} /> -
- - - }> -
    - - {(path) => ( -
  • - -
  • + {terminal.title} + + )} +
    +
    + + + +
    + + + {(terminal) => ( + + + )} -
- -
- - x} - onOpenChange={(open) => setStore("fileSelectOpen", open)} - onSelect={(x) => { - if (x) { - local.file.open(x) - return session.layout.openTab("file://" + x) - } - return undefined - }} - > - {(i) => ( -
-
- -
- - {getDirectory(i)} - - {getFilename(i)} -
-
-
-
- )} -
+ +
) diff --git a/packages/opencode/package.json b/packages/opencode/package.json index e14aed5ffbb..a8432322fbe 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -72,6 +72,7 @@ "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", + "bun-pty": "0.4.2", "chokidar": "4.0.3", "clipboardy": "4.0.0", "decimal.js": "10.5.0", diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index 7754b4a3953..76f78f3faa8 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -5,6 +5,7 @@ import { Instance } from "@/project/instance" import { InstanceBootstrap } from "@/project/bootstrap" import { Rpc } from "@/util/rpc" import { upgrade } from "@/cli/upgrade" +import type { BunWebSocketData } from "hono/bun" await Log.init({ print: process.argv.includes("--print-logs"), @@ -27,7 +28,7 @@ process.on("uncaughtException", (e) => { }) }) -let server: Bun.Server +let server: Bun.Server export const rpc = { async server(input: { port: number; hostname: string }) { if (server) await server.stop(true) @@ -53,7 +54,9 @@ export const rpc = { async shutdown() { Log.Default.info("worker shutting down") await Instance.disposeAll() - await server.stop(true) + // TODO: this should be awaited, but ws connections are + // causing this to hang, need to revisit this + server.stop(true) }, } diff --git a/packages/opencode/src/id/id.ts b/packages/opencode/src/id/id.ts index 99eb6c9ff06..ad6e22e1bee 100644 --- a/packages/opencode/src/id/id.ts +++ b/packages/opencode/src/id/id.ts @@ -8,6 +8,7 @@ export namespace Identifier { permission: "per", user: "usr", part: "prt", + pty: "pty", } as const export function schema(prefix: keyof typeof prefixes) { diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts new file mode 100644 index 00000000000..efb519ff2a7 --- /dev/null +++ b/packages/opencode/src/pty/index.ts @@ -0,0 +1,199 @@ +import { spawn, type IPty } from "bun-pty" +import z from "zod" +import { Identifier } from "../id/id" +import { Log } from "../util/log" +import { Bus } from "../bus" +import type { WSContext } from "hono/ws" +import { Instance } from "../project/instance" +import { shell } from "@opencode-ai/util/shell" + +export namespace Pty { + const log = Log.create({ service: "pty" }) + + export const Info = z + .object({ + id: Identifier.schema("pty"), + title: z.string(), + command: z.string(), + args: z.array(z.string()), + cwd: z.string(), + status: z.enum(["running", "exited"]), + pid: z.number(), + }) + .meta({ ref: "Pty" }) + + export type Info = z.infer + + export const CreateInput = z.object({ + command: z.string().optional(), + args: z.array(z.string()).optional(), + cwd: z.string().optional(), + title: z.string().optional(), + env: z.record(z.string(), z.string()).optional(), + }) + + export type CreateInput = z.infer + + export const UpdateInput = z.object({ + title: z.string().optional(), + size: z + .object({ + rows: z.number(), + cols: z.number(), + }) + .optional(), + }) + + export type UpdateInput = z.infer + + export const Event = { + Created: Bus.event("pty.created", z.object({ info: Info })), + Updated: Bus.event("pty.updated", z.object({ info: Info })), + Exited: Bus.event("pty.exited", z.object({ id: Identifier.schema("pty"), exitCode: z.number() })), + Deleted: Bus.event("pty.deleted", z.object({ id: Identifier.schema("pty") })), + } + + interface ActiveSession { + info: Info + process: IPty + buffer: string + subscribers: Set + } + + const state = Instance.state( + () => new Map(), + async (sessions) => { + for (const session of sessions.values()) { + try { + session.process.kill() + } catch {} + for (const ws of session.subscribers) { + ws.close() + } + } + sessions.clear() + }, + ) + + export function list() { + return Array.from(state().values()).map((s) => s.info) + } + + export function get(id: string) { + return state().get(id)?.info + } + + export async function create(input: CreateInput) { + const id = Identifier.create("pty", false) + const command = input.command || shell() + const args = input.args || [] + const cwd = input.cwd || Instance.directory + const env = { ...process.env, ...input.env } as Record + log.info("creating session", { id, cmd: command, args, cwd }) + + const ptyProcess = spawn(command, args, { + name: "xterm-256color", + cwd, + env, + }) + const info = { + id, + title: input.title || `Terminal ${id.slice(-4)}`, + command, + args, + cwd, + status: "running", + pid: ptyProcess.pid, + } as const + const session: ActiveSession = { + info, + process: ptyProcess, + buffer: "", + subscribers: new Set(), + } + state().set(id, session) + ptyProcess.onData((data) => { + if (session.subscribers.size === 0) { + session.buffer += data + return + } + for (const ws of session.subscribers) { + if (ws.readyState === 1) { + ws.send(data) + } + } + }) + ptyProcess.onExit(({ exitCode }) => { + log.info("session exited", { id, exitCode }) + session.info.status = "exited" + Bus.publish(Event.Exited, { id, exitCode }) + state().delete(id) + }) + Bus.publish(Event.Created, { info }) + return info + } + + export async function update(id: string, input: UpdateInput) { + const session = state().get(id) + if (!session) return + if (input.title) { + session.info.title = input.title + } + if (input.size) { + session.process.resize(input.size.cols, input.size.rows) + } + Bus.publish(Event.Updated, { info: session.info }) + return session.info + } + + export async function remove(id: string) { + const session = state().get(id) + if (!session) return + log.info("removing session", { id }) + try { + session.process.kill() + } catch {} + for (const ws of session.subscribers) { + ws.close() + } + state().delete(id) + Bus.publish(Event.Deleted, { id }) + } + + export function resize(id: string, cols: number, rows: number) { + const session = state().get(id) + if (session && session.info.status === "running") { + session.process.resize(cols, rows) + } + } + + export function write(id: string, data: string) { + const session = state().get(id) + if (session && session.info.status === "running") { + session.process.write(data) + } + } + + export function connect(id: string, ws: WSContext) { + const session = state().get(id) + if (!session) { + ws.close() + return + } + log.info("client connected to session", { id }) + session.subscribers.add(ws) + if (session.buffer) { + ws.send(session.buffer) + session.buffer = "" + } + return { + onMessage: (message: string | ArrayBuffer) => { + session.process.write(String(message)) + }, + onClose: () => { + log.info("client disconnected from session", { id }) + session.subscribers.delete(ws) + }, + } + } +} diff --git a/packages/opencode/src/server/error.ts b/packages/opencode/src/server/error.ts new file mode 100644 index 00000000000..26e2dfcb121 --- /dev/null +++ b/packages/opencode/src/server/error.ts @@ -0,0 +1,36 @@ +import { resolver } from "hono-openapi" +import z from "zod" +import { Storage } from "../storage/storage" + +export const ERRORS = { + 400: { + description: "Bad request", + content: { + "application/json": { + schema: resolver( + z + .object({ + data: z.any(), + errors: z.array(z.record(z.string(), z.any())), + success: z.literal(false), + }) + .meta({ + ref: "BadRequestError", + }), + ), + }, + }, + }, + 404: { + description: "Not found", + content: { + "application/json": { + schema: resolver(Storage.NotFoundError.Schema), + }, + }, + }, +} as const + +export function errors(...codes: number[]) { + return Object.fromEntries(codes.map((code) => [code, ERRORS[code as keyof typeof ERRORS]])) +} diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 31d0822762b..a74b7876f1c 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -43,43 +43,13 @@ import { Snapshot } from "@/snapshot" import { SessionSummary } from "@/session/summary" import { GlobalBus } from "@/bus/global" import { SessionStatus } from "@/session/status" +import { upgradeWebSocket, websocket } from "hono/bun" +import { errors } from "./error" +import { Pty } from "@/pty" // @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 globalThis.AI_SDK_LOG_WARNINGS = false -const ERRORS = { - 400: { - description: "Bad request", - content: { - "application/json": { - schema: resolver( - z - .object({ - data: z.any(), - errors: z.array(z.record(z.string(), z.any())), - success: z.literal(false), - }) - .meta({ - ref: "BadRequestError", - }), - ), - }, - }, - }, - 404: { - description: "Not found", - content: { - "application/json": { - schema: resolver(Storage.NotFoundError.Schema), - }, - }, - }, -} as const - -function errors(...codes: number[]) { - return Object.fromEntries(codes.map((code) => [code, ERRORS[code as keyof typeof ERRORS]])) -} - export namespace Server { const log = Log.create({ service: "server" }) @@ -192,7 +162,167 @@ export namespace Server { }), ) .use(validator("query", z.object({ directory: z.string().optional() }))) + .route("/project", ProjectRoute) + + .get( + "/pty", + describeRoute({ + description: "List all PTY sessions", + operationId: "pty.list", + responses: { + 200: { + description: "List of sessions", + content: { + "application/json": { + schema: resolver(Pty.Info.array()), + }, + }, + }, + }, + }), + async (c) => { + return c.json(Pty.list()) + }, + ) + .post( + "/pty", + describeRoute({ + description: "Create a new PTY session", + operationId: "pty.create", + responses: { + 200: { + description: "Created session", + content: { + "application/json": { + schema: resolver(Pty.Info), + }, + }, + }, + ...errors(400), + }, + }), + validator("json", Pty.CreateInput), + async (c) => { + const info = await Pty.create(c.req.valid("json")) + return c.json(info) + }, + ) + .put( + "/pty/:id", + describeRoute({ + description: "Update PTY session", + operationId: "pty.update", + responses: { + 200: { + description: "Updated session", + content: { + "application/json": { + schema: resolver(Pty.Info), + }, + }, + }, + ...errors(400), + }, + }), + validator("param", z.object({ id: z.string() })), + validator("json", Pty.UpdateInput), + async (c) => { + const info = await Pty.update(c.req.valid("param").id, c.req.valid("json")) + return c.json(info) + }, + ) + .get( + "/pty/:id", + describeRoute({ + description: "Get PTY session info", + operationId: "pty.get", + responses: { + 200: { + description: "Session info", + content: { + "application/json": { + schema: resolver(Pty.Info), + }, + }, + }, + ...errors(404), + }, + }), + validator("param", z.object({ id: z.string() })), + async (c) => { + const info = Pty.get(c.req.valid("param").id) + if (!info) { + throw new Storage.NotFoundError({ message: "Session not found" }) + } + return c.json(info) + }, + ) + .delete( + "/pty/:id", + describeRoute({ + description: "Remove a PTY session", + operationId: "pty.remove", + responses: { + 200: { + description: "Session removed", + content: { + "application/json": { + schema: resolver(z.boolean()), + }, + }, + }, + ...errors(404), + }, + }), + validator("param", z.object({ id: z.string() })), + async (c) => { + await Pty.remove(c.req.valid("param").id) + return c.json(true) + }, + ) + .get( + "/pty/:id/connect", + describeRoute({ + description: "Connect to a PTY session", + operationId: "pty.connect", + responses: { + 200: { + description: "Connected session", + content: { + "application/json": { + schema: resolver(z.boolean()), + }, + }, + }, + 404: { + description: "Session not found", + content: { + "application/json": { + schema: resolver(z.boolean()), + }, + }, + }, + }, + }), + validator("param", z.object({ id: z.string() })), + upgradeWebSocket((c) => { + const id = c.req.param("id") + let handler: ReturnType + return { + onOpen(_event, ws) { + handler = Pty.connect(id, ws) + }, + onMessage(event) { + handler?.onMessage(String(event.data)) + }, + onClose() { + handler?.onClose() + }, + } + }), + ) + .get( "/config", describeRoute({ @@ -2083,6 +2213,7 @@ export namespace Server { hostname: opts.hostname, idleTimeout: 0, fetch: App().fetch, + websocket: websocket, }) return server } diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts index 0dc470566ee..d04277cbc81 100644 --- a/packages/sdk/js/src/gen/sdk.gen.ts +++ b/packages/sdk/js/src/gen/sdk.gen.ts @@ -8,6 +8,23 @@ import type { ProjectListResponses, ProjectCurrentData, ProjectCurrentResponses, + PtyListData, + PtyListResponses, + PtyCreateData, + PtyCreateResponses, + PtyCreateErrors, + PtyRemoveData, + PtyRemoveResponses, + PtyRemoveErrors, + PtyGetData, + PtyGetResponses, + PtyGetErrors, + PtyUpdateData, + PtyUpdateResponses, + PtyUpdateErrors, + PtyConnectData, + PtyConnectResponses, + PtyConnectErrors, ConfigGetData, ConfigGetResponses, ConfigUpdateData, @@ -231,6 +248,76 @@ class Project extends _HeyApiClient { } } +class Pty extends _HeyApiClient { + /** + * List all PTY sessions + */ + public list(options?: Options) { + return (options?.client ?? this._client).get({ + url: "/pty", + ...options, + }) + } + + /** + * Create a new PTY session + */ + public create(options?: Options) { + return (options?.client ?? this._client).post({ + url: "/pty", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }) + } + + /** + * Remove a PTY session + */ + public remove(options: Options) { + return (options.client ?? this._client).delete({ + url: "/pty/{id}", + ...options, + }) + } + + /** + * Get PTY session info + */ + public get(options: Options) { + return (options.client ?? this._client).get({ + url: "/pty/{id}", + ...options, + }) + } + + /** + * Update PTY session + */ + public update(options: Options) { + return (options.client ?? this._client).put({ + url: "/pty/{id}", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }) + } + + /** + * Connect to a PTY session + */ + public connect(options: Options) { + return (options.client ?? this._client).get({ + url: "/pty/{id}/connect", + ...options, + }) + } +} + class Config extends _HeyApiClient { /** * Get config info @@ -1005,6 +1092,7 @@ export class OpencodeClient extends _HeyApiClient { } global = new Global({ client: this._client }) project = new Project({ client: this._client }) + pty = new Pty({ client: this._client }) config = new Config({ client: this._client }) tool = new Tool({ client: this._client }) instance = new Instance({ client: this._client }) diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 6c80f0b7c52..58ba58d359c 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -655,6 +655,45 @@ export type EventTuiToastShow = { } } +export type Pty = { + id: string + title: string + command: string + args: Array + cwd: string + status: "running" | "exited" + pid: number +} + +export type EventPtyCreated = { + type: "pty.created" + properties: { + info: Pty + } +} + +export type EventPtyUpdated = { + type: "pty.updated" + properties: { + info: Pty + } +} + +export type EventPtyExited = { + type: "pty.exited" + properties: { + id: string + exitCode: number + } +} + +export type EventPtyDeleted = { + type: "pty.deleted" + properties: { + id: string + } +} + export type EventServerConnected = { type: "server.connected" properties: { @@ -690,6 +729,10 @@ export type Event = | EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow + | EventPtyCreated + | EventPtyUpdated + | EventPtyExited + | EventPtyDeleted | EventServerConnected export type GlobalEvent = { @@ -708,6 +751,21 @@ export type Project = { } } +export type BadRequestError = { + data: unknown + errors: Array<{ + [key: string]: unknown + }> + success: false +} + +export type NotFoundError = { + name: "NotFoundError" + data: { + message: string + } +} + /** * Custom keybind configurations */ @@ -1266,14 +1324,6 @@ export type Config = { } } -export type BadRequestError = { - data: unknown - errors: Array<{ - [key: string]: unknown - }> - success: false -} - export type ToolIds = Array export type ToolListItem = { @@ -1295,13 +1345,6 @@ export type VcsInfo = { branch: string } -export type NotFoundError = { - name: "NotFoundError" - data: { - message: string - } -} - export type TextPartInput = { id?: string type: "text" @@ -1614,6 +1657,181 @@ export type ProjectCurrentResponses = { export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses] +export type PtyListData = { + body?: never + path?: never + query?: { + directory?: string + } + url: "/pty" +} + +export type PtyListResponses = { + /** + * List of sessions + */ + 200: Array +} + +export type PtyListResponse = PtyListResponses[keyof PtyListResponses] + +export type PtyCreateData = { + body?: { + command?: string + args?: Array + cwd?: string + title?: string + env?: { + [key: string]: string + } + } + path?: never + query?: { + directory?: string + } + url: "/pty" +} + +export type PtyCreateErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type PtyCreateError = PtyCreateErrors[keyof PtyCreateErrors] + +export type PtyCreateResponses = { + /** + * Created session + */ + 200: Pty +} + +export type PtyCreateResponse = PtyCreateResponses[keyof PtyCreateResponses] + +export type PtyRemoveData = { + body?: never + path: { + id: string + } + query?: { + directory?: string + } + url: "/pty/{id}" +} + +export type PtyRemoveErrors = { + /** + * Not found + */ + 404: NotFoundError +} + +export type PtyRemoveError = PtyRemoveErrors[keyof PtyRemoveErrors] + +export type PtyRemoveResponses = { + /** + * Session removed + */ + 200: boolean +} + +export type PtyRemoveResponse = PtyRemoveResponses[keyof PtyRemoveResponses] + +export type PtyGetData = { + body?: never + path: { + id: string + } + query?: { + directory?: string + } + url: "/pty/{id}" +} + +export type PtyGetErrors = { + /** + * Not found + */ + 404: NotFoundError +} + +export type PtyGetError = PtyGetErrors[keyof PtyGetErrors] + +export type PtyGetResponses = { + /** + * Session info + */ + 200: Pty +} + +export type PtyGetResponse = PtyGetResponses[keyof PtyGetResponses] + +export type PtyUpdateData = { + body?: { + title?: string + size?: { + rows: number + cols: number + } + } + path: { + id: string + } + query?: { + directory?: string + } + url: "/pty/{id}" +} + +export type PtyUpdateErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type PtyUpdateError = PtyUpdateErrors[keyof PtyUpdateErrors] + +export type PtyUpdateResponses = { + /** + * Updated session + */ + 200: Pty +} + +export type PtyUpdateResponse = PtyUpdateResponses[keyof PtyUpdateResponses] + +export type PtyConnectData = { + body?: never + path: { + id: string + } + query?: { + directory?: string + } + url: "/pty/{id}/connect" +} + +export type PtyConnectErrors = { + /** + * Session not found + */ + 404: boolean +} + +export type PtyConnectError = PtyConnectErrors[keyof PtyConnectErrors] + +export type PtyConnectResponses = { + /** + * Connected session + */ + 200: boolean +} + +export type PtyConnectResponse = PtyConnectResponses[keyof PtyConnectResponses] + export type ConfigGetData = { body?: never path?: never diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 306d7964988..9e4f00a0de7 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -153,10 +153,10 @@ const newIcons = { stop: ``, enter: ``, "layout-left": ``, - "layout-left-partial": ``, + "layout-left-partial": ``, "layout-left-full": ``, "layout-right": ``, - "layout-right-partial": ``, + "layout-right-partial": ``, "layout-right-full": ``, "speech-bubble": ``, "align-right": ``, @@ -167,6 +167,9 @@ const newIcons = { "bubble-5": ``, github: ``, discord: ``, + "layout-bottom": ``, + "layout-bottom-partial": ``, + "layout-bottom-full": ``, } export interface IconProps extends ComponentProps<"svg"> { diff --git a/packages/ui/src/components/select.css b/packages/ui/src/components/select.css index 421215a78ce..96ddf174cd8 100644 --- a/packages/ui/src/components/select.css +++ b/packages/ui/src/components/select.css @@ -20,6 +20,7 @@ [data-component="select-content"] { min-width: 4rem; + max-width: 23rem; overflow: hidden; border-radius: var(--radius-md); border-width: 1px; @@ -39,6 +40,7 @@ } [data-slot="select-select-content-list"] { + min-height: 2rem; overflow-y: auto; max-height: 12rem; white-space: nowrap; diff --git a/packages/ui/src/components/select.tsx b/packages/ui/src/components/select.tsx index 464900ef97b..9ba1f177b56 100644 --- a/packages/ui/src/components/select.tsx +++ b/packages/ui/src/components/select.tsx @@ -1,10 +1,10 @@ import { Select as Kobalte } from "@kobalte/core/select" -import { createMemo, type ComponentProps } from "solid-js" +import { createMemo, splitProps, type ComponentProps } from "solid-js" import { pipe, groupBy, entries, map } from "remeda" import { Button, ButtonProps } from "./button" import { Icon } from "./icon" -export interface SelectProps { +export type SelectProps = Omit>, "value" | "onSelect"> & { placeholder?: string options: T[] current?: T @@ -17,10 +17,21 @@ export interface SelectProps { } export function Select(props: SelectProps & ButtonProps) { + const [local, others] = splitProps(props, [ + "class", + "classList", + "placeholder", + "options", + "current", + "value", + "label", + "groupBy", + "onSelect", + ]) const grouped = createMemo(() => { const result = pipe( - props.options, - groupBy((x) => (props.groupBy ? props.groupBy(x) : "")), + local.options, + groupBy((x) => (local.groupBy ? local.groupBy(x) : "")), // mapValues((x) => x.sort((a, b) => a.title.localeCompare(b.title))), entries(), map(([k, v]) => ({ category: k, options: v })), @@ -29,28 +40,30 @@ export function Select(props: SelectProps & ButtonProps) { }) return ( + // @ts-ignore + {...others} data-component="select" - value={props.current} + value={local.current} options={grouped()} - optionValue={(x) => (props.value ? props.value(x) : (x as string))} - optionTextValue={(x) => (props.label ? props.label(x) : (x as string))} + optionValue={(x) => (local.value ? local.value(x) : (x as string))} + optionTextValue={(x) => (local.label ? local.label(x) : (x as string))} optionGroupChildren="options" - placeholder={props.placeholder} - sectionComponent={(props) => ( - {props.section.rawValue.category} + placeholder={local.placeholder} + sectionComponent={(local) => ( + {local.section.rawValue.category} )} itemComponent={(itemProps) => ( - {props.label ? props.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)} + {local.label ? local.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)} @@ -58,24 +71,25 @@ export function Select(props: SelectProps & ButtonProps) { )} onChange={(v) => { - props.onSelect?.(v ?? undefined) + local.onSelect?.(v ?? undefined) }} > data-slot="select-select-trigger-value"> {(state) => { - const selected = state.selectedOption() ?? props.current - if (!selected) return props.placeholder || "" - if (props.label) return props.label(selected) + const selected = state.selectedOption() ?? local.current + if (!selected) return local.placeholder || "" + if (local.label) return local.label(selected) return selected as string }} @@ -86,8 +100,8 @@ export function Select(props: SelectProps & ButtonProps) { diff --git a/packages/ui/src/components/tabs.css b/packages/ui/src/components/tabs.css index d03e57320ff..d60edc5c509 100644 --- a/packages/ui/src/components/tabs.css +++ b/packages/ui/src/components/tabs.css @@ -6,7 +6,7 @@ background-color: var(--background-stronger); overflow: clip; - [data-slot="tabs-tabs-list"] { + [data-slot="tabs-list"] { height: 48px; width: 100%; position: relative; @@ -36,7 +36,7 @@ } } - [data-slot="tabs-tabs-trigger-wrapper"] { + [data-slot="tabs-trigger-wrapper"] { position: relative; height: 100%; display: flex; @@ -58,14 +58,14 @@ border-right: 1px solid var(--border-weak-base); background-color: var(--background-base); - [data-slot="tabs-tabs-trigger"] { + [data-slot="tabs-trigger"] { display: flex; align-items: center; justify-content: center; padding: 14px 24px; } - [data-slot="tabs-tabs-trigger-close-button"] { + [data-slot="tabs-trigger-close-button"] { display: flex; align-items: center; justify-content: center; @@ -84,12 +84,12 @@ box-shadow: 0 0 0 2px var(--border-focus); } &:has([data-hidden]) { - [data-slot="tabs-tabs-trigger-close-button"] { + [data-slot="tabs-trigger-close-button"] { opacity: 0; } &:hover { - [data-slot="tabs-tabs-trigger-close-button"] { + [data-slot="tabs-trigger-close-button"] { opacity: 1; } } @@ -98,23 +98,23 @@ color: var(--text-strong); background-color: transparent; border-bottom-color: transparent; - [data-slot="tabs-tabs-trigger-close-button"] { + [data-slot="tabs-trigger-close-button"] { opacity: 1; } } &:hover:not(:disabled):not([data-selected]) { color: var(--text-strong); } - &:has([data-slot="tabs-tabs-trigger-close-button"]) { + &:has([data-slot="tabs-trigger-close-button"]) { padding-right: 12px; - [data-slot="tabs-tabs-trigger"] { + [data-slot="tabs-trigger"] { padding-right: 0; } } } - [data-slot="tabs-tabs-content"] { + [data-slot="tabs-content"] { overflow-y: auto; flex: 1; @@ -129,4 +129,80 @@ outline: none; } } + + &[data-variant="alt"] { + [data-slot="tabs-list"] { + padding-left: 24px; + padding-right: 24px; + gap: 12px; + border-bottom: 1px solid var(--border-weak-base); + background-color: transparent; + + &::after { + border: none; + background-color: transparent; + } + &:empty::after { + display: none; + } + } + + [data-slot="tabs-trigger-wrapper"] { + border: none; + color: var(--text-base); + background-color: transparent; + border-bottom-width: 2px; + border-bottom-style: solid; + border-bottom-color: transparent; + gap: 4px; + + /* text-14-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-x-large); /* 171.429% */ + letter-spacing: var(--letter-spacing-normal); + + [data-slot="tabs-trigger"] { + height: 100%; + padding: 4px; + background-color: transparent; + border-bottom-width: 2px; + border-bottom-color: transparent; + } + + [data-slot="tabs-trigger-close-button"] { + display: flex; + align-items: center; + justify-content: center; + } + + [data-component="icon-button"] { + width: 16px; + height: 16px; + margin: 0; + } + + &:has([data-selected]) { + color: var(--text-strong); + background-color: transparent; + border-bottom-color: var(--icon-strong-base); + } + + &:hover:not(:disabled):not([data-selected]) { + color: var(--text-strong); + } + + &:has([data-slot="tabs-trigger-close-button"]) { + padding-right: 0; + [data-slot="tabs-trigger"] { + padding-right: 0; + } + } + } + + /* [data-slot="tabs-content"] { */ + /* } */ + } } diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx index 68acd88d4e1..d91ad3c4156 100644 --- a/packages/ui/src/components/tabs.tsx +++ b/packages/ui/src/components/tabs.tsx @@ -2,7 +2,9 @@ import { Tabs as Kobalte } from "@kobalte/core/tabs" import { Show, splitProps, type JSX } from "solid-js" import type { ComponentProps, ParentProps } from "solid-js" -export interface TabsProps extends ComponentProps {} +export interface TabsProps extends ComponentProps { + variant?: "normal" | "alt" +} export interface TabsListProps extends ComponentProps {} export interface TabsTriggerProps extends ComponentProps { classes?: { @@ -14,11 +16,12 @@ export interface TabsTriggerProps extends ComponentProps export interface TabsContentProps extends ComponentProps {} function TabsRoot(props: TabsProps) { - const [split, rest] = splitProps(props, ["class", "classList"]) + const [split, rest] = splitProps(props, ["class", "classList", "variant"]) return ( ) { ]) return (
) { > {split.children} {(closeButton) => ( -
+
{closeButton()}
)} @@ -81,7 +84,7 @@ function TabsContent(props: ParentProps) { return ( Date: Fri, 5 Dec 2025 02:33:01 +0000 Subject: [PATCH 25/60] chore: format code --- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 7af6a984ec6..b8833364d06 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -24,4 +24,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} \ No newline at end of file +} diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 470c6aa023c..aee34752836 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -26,4 +26,4 @@ "publishConfig": { "directory": "dist" } -} \ No newline at end of file +} From 392d46933bb6d0c4fa18dc3f03d4bc4e78cc7ce0 Mon Sep 17 00:00:00 2001 From: Github Action Date: Fri, 5 Dec 2025 02:33:38 +0000 Subject: [PATCH 26/60] Update Nix flake.lock and hashes --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index ca9fd5f8f30..1660a9b9610 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1764794580, - "narHash": "sha256-UMVihg0OQ980YqmOAPz+zkuCEb9hpE5Xj2v+ZGNjQ+M=", + "lastModified": 1764856222, + "narHash": "sha256-yEJmtoFu4cJre1NuU4fb8q57Oux+NTbocnALtJ64aEI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ebc94f855ef25347c314258c10393a92794e7ab9", + "rev": "ece6e266caf1effab32eceef0403b797b4330373", "type": "github" }, "original": { From 43807277272467fcc9a8af0618d88845c3edad96 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 4 Dec 2025 21:53:31 -0500 Subject: [PATCH 27/60] zen: fix byok --- .../app/src/routes/zen/util/handler.ts | 38 +++++++++++++------ packages/console/core/src/model.ts | 1 + 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 7844a3ab079..a7f025bab4d 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -73,8 +73,16 @@ export async function handler( const stickyProvider = await stickyTracker?.get() const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => { - const providerInfo = selectProvider(zenData, modelInfo, sessionId, isTrial ?? false, retry, stickyProvider) - const authInfo = await authenticate(modelInfo, providerInfo) + const authInfo = await authenticate(modelInfo) + const providerInfo = selectProvider( + zenData, + authInfo, + modelInfo, + sessionId, + isTrial ?? false, + retry, + stickyProvider, + ) validateBilling(authInfo, modelInfo) validateModelSettings(authInfo) updateProviderKey(authInfo, providerInfo) @@ -291,6 +299,7 @@ export async function handler( function selectProvider( zenData: ZenData, + authInfo: AuthInfo, modelInfo: ModelInfo, sessionId: string, isTrial: boolean, @@ -298,6 +307,10 @@ export async function handler( stickyProvider: string | undefined, ) { const provider = (() => { + if (authInfo?.provider?.credentials) { + return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider) + } + if (isTrial) { return modelInfo.providers.find((provider) => provider.id === modelInfo.trial!.provider) } @@ -342,15 +355,15 @@ export async function handler( } } - async function authenticate(modelInfo: ModelInfo, providerInfo: ProviderInfo) { + async function authenticate(modelInfo: ModelInfo) { const apiKey = opts.parseApiKey(input.request.headers) if (!apiKey || apiKey === "public") { if (modelInfo.allowAnonymous) return throw new AuthError("Missing API key.") } - const data = await Database.use((tx) => - tx + const data = await Database.use((tx) => { + const query = tx .select({ apiKey: KeyTable.id, workspaceID: KeyTable.workspaceID, @@ -378,13 +391,15 @@ export async function handler( .innerJoin(BillingTable, eq(BillingTable.workspaceID, KeyTable.workspaceID)) .innerJoin(UserTable, and(eq(UserTable.workspaceID, KeyTable.workspaceID), eq(UserTable.id, KeyTable.userID))) .leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, modelInfo.id))) - .leftJoin( + + if (modelInfo.byokProvider) { + query.leftJoin( ProviderTable, - and(eq(ProviderTable.workspaceID, KeyTable.workspaceID), eq(ProviderTable.provider, providerInfo.id)), + and(eq(ProviderTable.workspaceID, KeyTable.workspaceID), eq(ProviderTable.provider, modelInfo.byokProvider)), ) - .where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted))) - .then((rows) => rows[0]), - ) + } + return query.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted))).then((rows) => rows[0]) + }) if (!data) throw new AuthError("Invalid API key.") logger.metric({ @@ -457,8 +472,7 @@ export async function handler( } function updateProviderKey(authInfo: AuthInfo, providerInfo: ProviderInfo) { - if (!authInfo) return - if (!authInfo.provider?.credentials) return + if (!authInfo?.provider?.credentials) return providerInfo.apiKey = authInfo.provider.credentials } diff --git a/packages/console/core/src/model.ts b/packages/console/core/src/model.ts index 5a4a98fe94e..47ba3e9d838 100644 --- a/packages/console/core/src/model.ts +++ b/packages/console/core/src/model.ts @@ -24,6 +24,7 @@ export namespace ZenData { cost: ModelCostSchema, cost200K: ModelCostSchema.optional(), allowAnonymous: z.boolean().optional(), + byokProvider: z.enum(["openai", "anthropic", "google"]).optional(), stickyProvider: z.boolean().optional(), trial: z .object({ From 71e578eac9dd78324aa9d8e94c72afa767b86dc3 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 4 Dec 2025 22:57:39 -0500 Subject: [PATCH 28/60] ignore: fix provider credentials query for BYOK Provider credentials field was being selected from ProviderTable even when the table wasn't joined (when byokProvider was undefined). Now the join is conditional - when byokProvider exists, we join and get the credentials; when it doesn't, the join condition is always false so provider remains null. --- .../app/src/routes/zen/util/handler.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index a7f025bab4d..e34704f98ed 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -362,8 +362,8 @@ export async function handler( throw new AuthError("Missing API key.") } - const data = await Database.use((tx) => { - const query = tx + const data = await Database.use((tx) => + tx .select({ apiKey: KeyTable.id, workspaceID: KeyTable.workspaceID, @@ -391,15 +391,18 @@ export async function handler( .innerJoin(BillingTable, eq(BillingTable.workspaceID, KeyTable.workspaceID)) .innerJoin(UserTable, and(eq(UserTable.workspaceID, KeyTable.workspaceID), eq(UserTable.id, KeyTable.userID))) .leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, modelInfo.id))) - - if (modelInfo.byokProvider) { - query.leftJoin( + .leftJoin( ProviderTable, - and(eq(ProviderTable.workspaceID, KeyTable.workspaceID), eq(ProviderTable.provider, modelInfo.byokProvider)), + modelInfo.byokProvider + ? and( + eq(ProviderTable.workspaceID, KeyTable.workspaceID), + eq(ProviderTable.provider, modelInfo.byokProvider), + ) + : sql`false`, ) - } - return query.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted))).then((rows) => rows[0]) - }) + .where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted))) + .then((rows) => rows[0]), + ) if (!data) throw new AuthError("Invalid API key.") logger.metric({ From 095a1ab041de65e04234b317f107be9e95fab906 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 22:39:13 -0600 Subject: [PATCH 29/60] docs: llama.cpp docs: `limit` moved under model (#5089) Co-authored-by: opencode-agent[bot] Co-authored-by: GitHub Action --- packages/web/src/content/docs/providers.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index e8534ceee93..3b4bed757aa 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -568,7 +568,7 @@ The `global` region improves availability and reduces errors at no extra cost. U You can configure opencode to use local models through [llama.cpp's](https://github.com/ggml-org/llama.cpp) llama-server utility -```json title="opencode.json" "llama.cpp" {5, 6, 8, 10-14} +```json title="opencode.json" "llama.cpp" {5, 6, 8, 10-15} { "$schema": "https://opencode.ai/config.json", "provider": { @@ -580,12 +580,12 @@ You can configure opencode to use local models through [llama.cpp's](https://git }, "models": { "qwen3-coder:a3b": { - "name": "Qwen3-Coder: a3b-30b (local)" + "name": "Qwen3-Coder: a3b-30b (local)", + "limit": { + "context": 128000, + "output": 65536 + } } - }, - "limit": { - "context": 128000, - "output": 65536 } } } From bef4fdfc4bc22f6d84e2350bffe5da5c41543b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 5 Dec 2025 05:43:22 +0100 Subject: [PATCH 30/60] fix: add getModel to SAP AI Core provider for correct SDK initialization (#5086) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- packages/opencode/src/provider/provider.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index f60bcfa4756..22e0f9d691c 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -279,7 +279,7 @@ export namespace Provider { project, location, }, - async getModel(sdk, modelID) { + async getModel(sdk: any, modelID) { const id = String(modelID).trim() return sdk.languageModel(id) }, @@ -294,6 +294,9 @@ export namespace Provider { return { autoload: !!serviceKey, options: serviceKey ? { serviceKey, deploymentId, resourceGroup } : {}, + async getModel(sdk: any, modelID: string) { + return sdk(modelID) + }, } }, zenmux: async () => { From 856e1e2948ae74d61c0ba83c53bef4018a2964a1 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 4 Dec 2025 23:47:46 -0500 Subject: [PATCH 31/60] fix pty builds --- packages/opencode/src/pty/index.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index efb519ff2a7..93f2647882e 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -1,4 +1,4 @@ -import { spawn, type IPty } from "bun-pty" +import { type IPty } from "bun-pty" import z from "zod" import { Identifier } from "../id/id" import { Log } from "../util/log" @@ -6,10 +6,34 @@ import { Bus } from "../bus" import type { WSContext } from "hono/ws" import { Instance } from "../project/instance" import { shell } from "@opencode-ai/util/shell" +import { lazy } from "@opencode-ai/util/lazy" +import {} from "process" export namespace Pty { const log = Log.create({ service: "pty" }) + const pty = lazy(async () => { + const path = require( + `bun-pty/rust-pty/target/release/${ + process.platform === "win32" + ? "rust_pty.dll" + : process.platform === "linux" && process.arch === "x64" + ? "librust_pty.so" + : process.platform === "darwin" && process.arch === "x64" + ? "librust_pty.dylib" + : process.platform === "darwin" && process.arch === "arm64" + ? "librust_pty_arm64.dylib" + : process.platform === "linux" && process.arch === "arm64" + ? "librust_pty_arm64.so" + : "" + }`, + ) + console.log(path) + process.env.BUN_PTY_LIB = path + const { spawn } = await import("bun-pty") + return spawn + }) + export const Info = z .object({ id: Identifier.schema("pty"), @@ -91,6 +115,7 @@ export namespace Pty { const env = { ...process.env, ...input.env } as Record log.info("creating session", { id, cmd: command, args, cwd }) + const spawn = await pty() const ptyProcess = spawn(command, args, { name: "xterm-256color", cwd, From c264e9c36421cf9794daa20bd033d41faab192c2 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 4 Dec 2025 23:48:32 -0500 Subject: [PATCH 32/60] fix --- packages/opencode/src/pty/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 93f2647882e..4109a09752e 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -28,7 +28,6 @@ export namespace Pty { : "" }`, ) - console.log(path) process.env.BUN_PTY_LIB = path const { spawn } = await import("bun-pty") return spawn From 95168b82677313df829152fa0544c5ae253d5fb9 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 4 Dec 2025 23:54:46 -0500 Subject: [PATCH 33/60] increase default scroll speed --- packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index ede4e28384b..507b2186e7b 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -140,7 +140,7 @@ export function Session() { return new CustomSpeedScroll(tui.scroll_speed) } - return new CustomSpeedScroll(process.platform === "win32" ? 3 : 1) + return new CustomSpeedScroll(3) }) createEffect(async () => { From 78046dac8bbc211ccfb8cf47cb8ec30ddced2441 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 4 Dec 2025 23:00:26 -0600 Subject: [PATCH 34/60] ci: review --- .github/workflows/review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index ac93ca94e7e..c190bb44ad4 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -75,4 +75,4 @@ jobs: -f 'body=[summary of issue]' -f 'commit_id=${{ steps.pr-details.outputs.sha }}' -f 'path=[path-to-file]' -F \"line=[line]\" -f 'side=RIGHT' \`\`\` - Only create comments for actual violations. If the code follows all guidelines, don't run any gh commands." + Only create comments for actual violations. If the code follows all guidelines, comment 'lgtm' AND NOTHING ELSE!!!!." From 767a81f9308c451dbe68451da1623baa255745e9 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 4 Dec 2025 23:02:08 -0600 Subject: [PATCH 35/60] fix: ensure that vcs is still set to git even if no commits in repo --- packages/opencode/src/project/project.ts | 16 +++------------- packages/opencode/test/project/project.test.ts | 4 +++- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 364d8b2473c..0bf50e16cae 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -64,17 +64,6 @@ export namespace Project { if (id) Bun.file(path.join(git, "opencode")).write(id) } timer.stop() - if (!id) { - const project: Info = { - id: "global", - worktree: "/", - time: { - created: Date.now(), - }, - } - await Storage.write(["project", "global"], project) - return project - } worktree = await $`git rev-parse --show-toplevel` .quiet() .nothrow() @@ -87,8 +76,9 @@ export namespace Project { .cwd(worktree) .text() .then((x) => path.resolve(worktree, x.trim())) + const projectID = id || "global" const project: Info = { - id, + id: projectID, worktree, vcsDir, vcs: "git", @@ -96,7 +86,7 @@ export namespace Project { created: Date.now(), }, } - await Storage.write(["project", id], project) + await Storage.write(["project", projectID], project) return project } diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index 8e53c5322f8..17b5c0fa5a8 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -16,7 +16,9 @@ describe("Project.fromDirectory", () => { expect(project).toBeDefined() expect(project.id).toBe("global") - expect(project.worktree).toBe("/") + expect(project.vcs).toBe("git") + expect(project.worktree).toBe(tmp.path) + expect(project.vcsDir).toBe(path.join(tmp.path, ".git")) const opencodeFile = path.join(tmp.path, ".git", "opencode") const fileExists = await Bun.file(opencodeFile).exists() From 5013d64b2887139de0b694ef5df0b829294d1690 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 4 Dec 2025 23:13:41 -0600 Subject: [PATCH 36/60] ignore: rm slop commnand (only for opencode repo this isnt shipping) --- .opencode/command/hello.md | 8 -------- .opencode/command/rmslop.md | 13 +++++++++++++ 2 files changed, 13 insertions(+), 8 deletions(-) delete mode 100644 .opencode/command/hello.md create mode 100644 .opencode/command/rmslop.md diff --git a/.opencode/command/hello.md b/.opencode/command/hello.md deleted file mode 100644 index 003bc4a760b..00000000000 --- a/.opencode/command/hello.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -description: hello world iaosd ioasjdoiasjd oisadjoisajd osiajd oisaj dosaij dsoajsajdaijdoisa jdoias jdoias jdoia jois jo jdois jdoias jdoias j djoasdj ---- - -hey there $ARGUMENTS - -!`ls` -check out @README.md diff --git a/.opencode/command/rmslop.md b/.opencode/command/rmslop.md new file mode 100644 index 00000000000..229dbf05b0b --- /dev/null +++ b/.opencode/command/rmslop.md @@ -0,0 +1,13 @@ +--- +description: Remove AI code slop +--- + +Check the diff against dev, and remove all AI generated slop introduced in this branch. + +This includes: +- Extra comments that a human wouldn't add or is inconsistent with the rest of the file +- Extra defensive checks or try/catch blocks that are abnormal for that area of the codebase (especially if called by trusted / validated codepaths) +- Casts to any to get around type issues +- Any other style that is inconsistent with the file + +Report at the end with only a 1-3 sentence summary of what you changed From 332ebe36c3e00242f38c968f39d33c06487b2b89 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 5 Dec 2025 05:14:21 +0000 Subject: [PATCH 37/60] chore: format code --- .opencode/command/rmslop.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.opencode/command/rmslop.md b/.opencode/command/rmslop.md index 229dbf05b0b..3e94ef5ac53 100644 --- a/.opencode/command/rmslop.md +++ b/.opencode/command/rmslop.md @@ -5,6 +5,7 @@ description: Remove AI code slop Check the diff against dev, and remove all AI generated slop introduced in this branch. This includes: + - Extra comments that a human wouldn't add or is inconsistent with the rest of the file - Extra defensive checks or try/catch blocks that are abnormal for that area of the codebase (especially if called by trusted / validated codepaths) - Casts to any to get around type issues From e53580cb6878edbc802715dd0569cc72e7cb6400 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Fri, 5 Dec 2025 00:02:29 -0600 Subject: [PATCH 38/60] ignore: cmd tweak --- .opencode/command/rmslop.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.opencode/command/rmslop.md b/.opencode/command/rmslop.md index 3e94ef5ac53..02c9fc0844a 100644 --- a/.opencode/command/rmslop.md +++ b/.opencode/command/rmslop.md @@ -10,5 +10,6 @@ This includes: - Extra defensive checks or try/catch blocks that are abnormal for that area of the codebase (especially if called by trusted / validated codepaths) - Casts to any to get around type issues - Any other style that is inconsistent with the file +- Unnecessary emoji usage Report at the end with only a 1-3 sentence summary of what you changed From 03324d4277184039166ded2219065e24b26ab789 Mon Sep 17 00:00:00 2001 From: ry2009 <134240944+ry2009@users.noreply.github.com> Date: Fri, 5 Dec 2025 01:19:48 -0500 Subject: [PATCH 39/60] tui: wrap dialog option descriptions (#5083) --- packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx index f6d79946c63..b6c5b5f8b95 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx @@ -299,11 +299,13 @@ function Option(props: { fg={props.active ? fg : props.current ? theme.primary : theme.text} attributes={props.active ? TextAttributes.BOLD : undefined} overflow="hidden" - wrapMode="none" + wrapMode="word" paddingLeft={3} > {Locale.truncate(props.title, 62)} - {props.description} + + {props.description} + From a4e5a72c36db16dd89f55de7ce97a7b383feee66 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Fri, 5 Dec 2025 00:22:28 -0600 Subject: [PATCH 40/60] ci: keybinds --- .github/workflows/duplicate-issues.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/duplicate-issues.yml b/.github/workflows/duplicate-issues.yml index daf447df1f2..5969d9d41e4 100644 --- a/.github/workflows/duplicate-issues.yml +++ b/.github/workflows/duplicate-issues.yml @@ -55,4 +55,7 @@ jobs: Feel free to ignore if none of these address your specific case.' + Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, please add a comment mentioning the pinned keybinds issue #4997: + 'For keybind-related issues, please also check our pinned keybinds documentation: #4997' + If no clear duplicates are found, do not comment." From f950de95ba3744dad248f51aaaa893afb3a22f4e Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 4 Dec 2025 22:49:07 -0800 Subject: [PATCH 41/60] fix: ensure projects that go from having no commits to having commits have sessions migrated (#5105) Co-authored-by: GitHub Action --- .github/workflows/review.yml | 4 +-- packages/opencode/src/project/project.ts | 31 ++++++++++++++++++++++++ packages/opencode/src/util/queue.ts | 13 ++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index c190bb44ad4..08fcc388d0a 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -48,7 +48,7 @@ jobs: OPENCODE_PERMISSION: '{ "bash": { "gh*": "allow", "gh pr review*": "deny", "*": "deny" } }' run: | PR_BODY=$(jq -r .body pr_data.json) - opencode run -m anthropic/claude-sonnet-4-5 "A new pull request has been created: '${{ steps.pr-details.outputs.title }}' + opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${{ steps.pr-details.outputs.title }}' ${{ steps.pr-number.outputs.number }} @@ -75,4 +75,4 @@ jobs: -f 'body=[summary of issue]' -f 'commit_id=${{ steps.pr-details.outputs.sha }}' -f 'path=[path-to-file]' -F \"line=[line]\" -f 'side=RIGHT' \`\`\` - Only create comments for actual violations. If the code follows all guidelines, comment 'lgtm' AND NOTHING ELSE!!!!." + Only create comments for actual violations. If the code follows all guidelines, comment on the issue using gh cli: 'lgtm' AND NOTHING ELSE!!!!." diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 0bf50e16cae..b3b724005c0 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -5,6 +5,8 @@ import { $ } from "bun" import { Storage } from "../storage/storage" import { Log } from "../util/log" import { Flag } from "@/flag/flag" +import { Session } from "../session" +import { work } from "../util/queue" export namespace Project { const log = Log.create({ service: "project" }) @@ -77,6 +79,10 @@ export namespace Project { .text() .then((x) => path.resolve(worktree, x.trim())) const projectID = id || "global" + const existing = id ? await Storage.read(["project", id]).catch(() => undefined) : undefined + if (!existing) { + await migrateFromGlobal(projectID, worktree) + } const project: Info = { id: projectID, worktree, @@ -90,6 +96,31 @@ export namespace Project { return project } + async function migrateFromGlobal(newProjectID: string, worktree: string) { + const globalProject = await Storage.read(["project", "global"]).catch(() => undefined) + if (!globalProject) return + + const globalSessions = await Storage.list(["session", "global"]).catch(() => []) + if (globalSessions.length === 0) return + + log.info("migrating sessions from global", { newProjectID, worktree, count: globalSessions.length }) + const worktreePrefix = worktree.endsWith(path.sep) ? worktree : worktree + path.sep + + await work(10, globalSessions, async (key) => { + const sessionID = key[key.length - 1] + const session = await Storage.read(key).catch(() => undefined) + if (!session) return + if (session.directory && session.directory !== worktree && !session.directory.startsWith(worktreePrefix)) return + + session.projectID = newProjectID + log.info("migrating session", { sessionID, from: "global", to: newProjectID }) + await Storage.write(["session", newProjectID, sessionID], session) + await Storage.remove(key) + }).catch((error) => { + log.error("failed to migrate sessions from global to project", { error, projectId: newProjectID }) + }) + } + export async function setInitialized(projectID: string) { await Storage.update(["project", projectID], (draft) => { draft.time.initialized = Date.now() diff --git a/packages/opencode/src/util/queue.ts b/packages/opencode/src/util/queue.ts index 259d785cef1..a1af53fe8f0 100644 --- a/packages/opencode/src/util/queue.ts +++ b/packages/opencode/src/util/queue.ts @@ -17,3 +17,16 @@ export class AsyncQueue implements AsyncIterable { while (true) yield await this.next() } } + +export async function work(concurrency: number, items: T[], fn: (item: T) => Promise) { + const pending = [...items] + await Promise.all( + Array.from({ length: concurrency }, async () => { + while (true) { + const item = pending.pop() + if (item === undefined) return + await fn(item) + } + }), + ) +} From 05d2f7052945ff713cd49e034047480e7e6c7688 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Fri, 5 Dec 2025 01:00:47 -0600 Subject: [PATCH 42/60] ignore: tweak --- packages/opencode/src/project/project.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index b3b724005c0..78d2a1fd1f9 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -104,13 +104,12 @@ export namespace Project { if (globalSessions.length === 0) return log.info("migrating sessions from global", { newProjectID, worktree, count: globalSessions.length }) - const worktreePrefix = worktree.endsWith(path.sep) ? worktree : worktree + path.sep await work(10, globalSessions, async (key) => { const sessionID = key[key.length - 1] const session = await Storage.read(key).catch(() => undefined) if (!session) return - if (session.directory && session.directory !== worktree && !session.directory.startsWith(worktreePrefix)) return + if (session.directory && session.directory !== worktree) return session.projectID = newProjectID log.info("migrating session", { sessionID, from: "global", to: newProjectID }) From 5f7ab83de465179ff3074f40b77ed8e020ea7293 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 5 Dec 2025 12:04:29 +0000 Subject: [PATCH 43/60] ignore: update download stats 2025-12-05 --- STATS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/STATS.md b/STATS.md index 72198597234..5bcea153a21 100644 --- a/STATS.md +++ b/STATS.md @@ -160,3 +160,4 @@ | 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) | | 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) | | 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) | +| 2025-12-05 | 977,996 (+12,385) | 930,616 (+14,145) | 1,908,612 (+26,530) | From 53ed1c912bda7a39f60c09968d41b109dfc913de Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 5 Dec 2025 09:04:43 -0500 Subject: [PATCH 44/60] Zen: add codex max --- packages/web/src/content/docs/zen.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 5f266b0c239..2ea266bd49e 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -66,6 +66,7 @@ You can also access our models through the following API endpoints. | ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | | GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -123,6 +124,7 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - | | GPT 5.1 Codex | $1.07 | $8.50 | $0.107 | - | +| GPT 5.1 Codex Max | $1.25 | $10.00 | $0.125 | - | | GPT 5 | $1.07 | $8.50 | $0.107 | - | | GPT 5 Codex | $1.07 | $8.50 | $0.107 | - | | GPT 5 Nano | Free | Free | Free | - | From 60388f7f03d26c272a1695f61af950648a2f8658 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 5 Dec 2025 10:39:44 -0500 Subject: [PATCH 45/60] do not use required pty for local dev --- packages/opencode/src/pty/index.ts | 35 ++++++++++++++++-------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 4109a09752e..d03c697b85f 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -8,27 +8,30 @@ import { Instance } from "../project/instance" import { shell } from "@opencode-ai/util/shell" import { lazy } from "@opencode-ai/util/lazy" import {} from "process" +import { Installation } from "@/installation" export namespace Pty { const log = Log.create({ service: "pty" }) const pty = lazy(async () => { - const path = require( - `bun-pty/rust-pty/target/release/${ - process.platform === "win32" - ? "rust_pty.dll" - : process.platform === "linux" && process.arch === "x64" - ? "librust_pty.so" - : process.platform === "darwin" && process.arch === "x64" - ? "librust_pty.dylib" - : process.platform === "darwin" && process.arch === "arm64" - ? "librust_pty_arm64.dylib" - : process.platform === "linux" && process.arch === "arm64" - ? "librust_pty_arm64.so" - : "" - }`, - ) - process.env.BUN_PTY_LIB = path + if (!Installation.isLocal()) { + const path = require( + `bun-pty/rust-pty/target/release/${ + process.platform === "win32" + ? "rust_pty.dll" + : process.platform === "linux" && process.arch === "x64" + ? "librust_pty.so" + : process.platform === "darwin" && process.arch === "x64" + ? "librust_pty.dylib" + : process.platform === "darwin" && process.arch === "arm64" + ? "librust_pty_arm64.dylib" + : process.platform === "linux" && process.arch === "arm64" + ? "librust_pty_arm64.so" + : "" + }`, + ) + process.env.BUN_PTY_LIB = path + } const { spawn } = await import("bun-pty") return spawn }) From 52db0f23a6bee50f99f7663c4a9b067c977ba7a4 Mon Sep 17 00:00:00 2001 From: Julian Visser <12615757+justmejulian@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:53:32 +0100 Subject: [PATCH 46/60] fix: #5064 ProviderInitError github-copilot-enterprise (#5123) --- packages/opencode/src/provider/provider.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 22e0f9d691c..0ee95de49c6 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -490,6 +490,10 @@ export namespace Provider { ...githubCopilot, id: "github-copilot-enterprise", name: "GitHub Copilot Enterprise", + models: mapValues(githubCopilot.models, (model) => ({ + ...model, + providerID: "github-copilot-enterprise", + })), } } From ada7cca10dd4c5c9fb6aa467e0a12724df2c5e8b Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Fri, 5 Dec 2025 09:01:20 -0700 Subject: [PATCH 47/60] feat(theme): Vercel (#5119) Co-authored-by: opencode-agent[bot] Co-authored-by: rekram1-node --- .../src/cli/cmd/tui/context/theme.tsx | 2 + .../src/cli/cmd/tui/context/theme/vercel.json | 245 ++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 packages/opencode/src/cli/cmd/tui/context/theme/vercel.json diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx index 4223657ba0b..49b85d1c748 100644 --- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx @@ -25,6 +25,7 @@ import rosepine from "./theme/rosepine.json" with { type: "json" } import solarized from "./theme/solarized.json" with { type: "json" } import synthwave84 from "./theme/synthwave84.json" with { type: "json" } import tokyonight from "./theme/tokyonight.json" with { type: "json" } +import vercel from "./theme/vercel.json" with { type: "json" } import vesper from "./theme/vesper.json" with { type: "json" } import zenburn from "./theme/zenburn.json" with { type: "json" } import { useKV } from "./kv" @@ -149,6 +150,7 @@ export const DEFAULT_THEMES: Record = { synthwave84, tokyonight, vesper, + vercel, zenburn, } diff --git a/packages/opencode/src/cli/cmd/tui/context/theme/vercel.json b/packages/opencode/src/cli/cmd/tui/context/theme/vercel.json new file mode 100644 index 00000000000..86b965b10bb --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/context/theme/vercel.json @@ -0,0 +1,245 @@ +{ + "$schema": "https://opencode.ai/theme.json", + "defs": { + "background100": "#0A0A0A", + "background200": "#000000", + "gray100": "#1A1A1A", + "gray200": "#1F1F1F", + "gray300": "#292929", + "gray400": "#2E2E2E", + "gray500": "#454545", + "gray600": "#878787", + "gray700": "#8F8F8F", + "gray900": "#A1A1A1", + "gray1000": "#EDEDED", + "blue600": "#0099FF", + "blue700": "#0070F3", + "blue900": "#52A8FF", + "blue1000": "#EBF8FF", + "red700": "#E5484D", + "red900": "#FF6166", + "red1000": "#FDECED", + "amber700": "#FFB224", + "amber900": "#F2A700", + "amber1000": "#FDF4DC", + "green700": "#46A758", + "green900": "#63C46D", + "green1000": "#E6F9E9", + "teal700": "#12A594", + "teal900": "#0AC7AC", + "purple700": "#8E4EC6", + "purple900": "#BF7AF0", + "pink700": "#E93D82", + "pink900": "#F75590", + "highlightPink": "#FF0080", + "highlightPurple": "#F81CE5", + "cyan": "#50E3C2", + "lightBackground": "#FFFFFF", + "lightGray100": "#FAFAFA", + "lightGray200": "#EAEAEA", + "lightGray600": "#666666", + "lightGray1000": "#171717" + }, + "theme": { + "primary": { + "dark": "blue700", + "light": "blue700" + }, + "secondary": { + "dark": "blue900", + "light": "#0062D1" + }, + "accent": { + "dark": "purple700", + "light": "purple700" + }, + "error": { + "dark": "red700", + "light": "#DC3545" + }, + "warning": { + "dark": "amber700", + "light": "#FF9500" + }, + "success": { + "dark": "green700", + "light": "#388E3C" + }, + "info": { + "dark": "blue900", + "light": "blue700" + }, + "text": { + "dark": "gray1000", + "light": "lightGray1000" + }, + "textMuted": { + "dark": "gray600", + "light": "lightGray600" + }, + "background": { + "dark": "background200", + "light": "lightBackground" + }, + "backgroundPanel": { + "dark": "gray100", + "light": "lightGray100" + }, + "backgroundElement": { + "dark": "gray300", + "light": "lightGray200" + }, + "border": { + "dark": "gray200", + "light": "lightGray200" + }, + "borderActive": { + "dark": "gray500", + "light": "#999999" + }, + "borderSubtle": { + "dark": "gray100", + "light": "#EAEAEA" + }, + "diffAdded": { + "dark": "green900", + "light": "green700" + }, + "diffRemoved": { + "dark": "red900", + "light": "red700" + }, + "diffContext": { + "dark": "gray600", + "light": "lightGray600" + }, + "diffHunkHeader": { + "dark": "gray600", + "light": "lightGray600" + }, + "diffHighlightAdded": { + "dark": "green900", + "light": "green700" + }, + "diffHighlightRemoved": { + "dark": "red900", + "light": "red700" + }, + "diffAddedBg": { + "dark": "#0B1D0F", + "light": "#E6F9E9" + }, + "diffRemovedBg": { + "dark": "#2A1314", + "light": "#FDECED" + }, + "diffContextBg": { + "dark": "background200", + "light": "lightBackground" + }, + "diffLineNumber": { + "dark": "gray600", + "light": "lightGray600" + }, + "diffAddedLineNumberBg": { + "dark": "#0F2613", + "light": "#D6F5D6" + }, + "diffRemovedLineNumberBg": { + "dark": "#3C1618", + "light": "#FFE5E5" + }, + "markdownText": { + "dark": "gray1000", + "light": "lightGray1000" + }, + "markdownHeading": { + "dark": "purple900", + "light": "purple700" + }, + "markdownLink": { + "dark": "blue900", + "light": "blue700" + }, + "markdownLinkText": { + "dark": "teal900", + "light": "teal700" + }, + "markdownCode": { + "dark": "green900", + "light": "green700" + }, + "markdownBlockQuote": { + "dark": "gray600", + "light": "lightGray600" + }, + "markdownEmph": { + "dark": "amber900", + "light": "amber700" + }, + "markdownStrong": { + "dark": "pink900", + "light": "pink700" + }, + "markdownHorizontalRule": { + "dark": "gray500", + "light": "#999999" + }, + "markdownListItem": { + "dark": "gray1000", + "light": "lightGray1000" + }, + "markdownListEnumeration": { + "dark": "blue900", + "light": "blue700" + }, + "markdownImage": { + "dark": "teal900", + "light": "teal700" + }, + "markdownImageText": { + "dark": "cyan", + "light": "teal700" + }, + "markdownCodeBlock": { + "dark": "gray1000", + "light": "lightGray1000" + }, + "syntaxComment": { + "dark": "gray600", + "light": "#888888" + }, + "syntaxKeyword": { + "dark": "pink900", + "light": "pink700" + }, + "syntaxFunction": { + "dark": "purple900", + "light": "purple700" + }, + "syntaxVariable": { + "dark": "blue900", + "light": "blue700" + }, + "syntaxString": { + "dark": "green900", + "light": "green700" + }, + "syntaxNumber": { + "dark": "amber900", + "light": "amber700" + }, + "syntaxType": { + "dark": "teal900", + "light": "teal700" + }, + "syntaxOperator": { + "dark": "pink900", + "light": "pink700" + }, + "syntaxPunctuation": { + "dark": "gray1000", + "light": "lightGray1000" + } + } +} From 87a791fdb9432d457202da85ec5e23e42f91db4d Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:49:21 -0600 Subject: [PATCH 48/60] fix(desktop): new session not selecting tab --- packages/desktop/src/context/session.tsx | 37 +++++++++++++++--------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/desktop/src/context/session.tsx b/packages/desktop/src/context/session.tsx index 4e9fe71f8a7..690653992e6 100644 --- a/packages/desktop/src/context/session.tsx +++ b/packages/desktop/src/context/session.tsx @@ -201,20 +201,14 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex sdk.client.pty.create({ body: { title: `Terminal ${store.terminals.all.length + 1}` } }).then((pty) => { const id = pty.data?.id if (!id) return - batch(() => { - setStore("terminals", "all", [ - ...store.terminals.all, - { - id, - title: pty.data?.title ?? "Terminal", - // rows: pty.data?.rows ?? 24, - // cols: pty.data?.cols ?? 80, - // buffer: "", - // scrollY: 0, - }, - ]) - setStore("terminals", "active", id) - }) + setStore("terminals", "all", [ + ...store.terminals.all, + { + id, + title: pty.data?.title ?? "Terminal", + }, + ]) + setStore("terminals", "active", id) }) }, update(pty: Partial & { id: string }) { @@ -224,6 +218,21 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex body: { title: pty.title, size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined }, }) }, + async clone(id: string) { + const index = store.terminals.all.findIndex((x) => x.id === id) + const pty = store.terminals.all[index] + if (!pty) return + const clone = await sdk.client.pty.create({ + body: { + title: pty.title, + }, + }) + if (!clone.data) return + setStore("terminals", "all", index, { + ...pty, + ...clone.data, + }) + }, open(id: string) { setStore("terminals", "active", id) }, From cfbaf81ef8f360d1df621d800d10b0ac2b3019a8 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:30:44 -0600 Subject: [PATCH 49/60] fix(desktop): clone pty session on reconnect --- packages/desktop/src/components/terminal.tsx | 9 +++-- packages/desktop/src/context/session.tsx | 5 ++- packages/desktop/src/pages/session.tsx | 10 ++++- packages/opencode/src/server/server.ts | 42 +++++++++----------- 4 files changed, 36 insertions(+), 30 deletions(-) diff --git a/packages/desktop/src/components/terminal.tsx b/packages/desktop/src/components/terminal.tsx index 49a45a432bc..f7b44b0aad4 100644 --- a/packages/desktop/src/components/terminal.tsx +++ b/packages/desktop/src/components/terminal.tsx @@ -1,6 +1,5 @@ import { init, Terminal as Term, FitAddon } from "ghostty-web" import { ComponentProps, onCleanup, onMount, splitProps } from "solid-js" -import { createReconnectingWS, ReconnectingWebSocket } from "@solid-primitives/websocket" import { useSDK } from "@/context/sdk" import { SerializeAddon } from "@/addons/serialize" import { LocalPTY } from "@/context/session" @@ -11,19 +10,20 @@ export interface TerminalProps extends ComponentProps<"div"> { pty: LocalPTY onSubmit?: () => void onCleanup?: (pty: LocalPTY) => void + onConnectError?: (error: unknown) => void } export const Terminal = (props: TerminalProps) => { const sdk = useSDK() let container!: HTMLDivElement - const [local, others] = splitProps(props, ["pty", "class", "classList"]) - let ws: ReconnectingWebSocket + const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnectError"]) + let ws: WebSocket let term: Term let serializeAddon: SerializeAddon let fitAddon: FitAddon onMount(async () => { - ws = createReconnectingWS(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`) + ws = new WebSocket(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`) term = new Term({ cursorBlink: true, fontSize: 14, @@ -115,6 +115,7 @@ export const Terminal = (props: TerminalProps) => { }) ws.addEventListener("error", (error) => { console.error("WebSocket error:", error) + props.onConnectError?.(error) }) ws.addEventListener("close", () => { console.log("WebSocket disconnected") diff --git a/packages/desktop/src/context/session.tsx b/packages/desktop/src/context/session.tsx index 690653992e6..b5972f3e30e 100644 --- a/packages/desktop/src/context/session.tsx +++ b/packages/desktop/src/context/session.tsx @@ -26,7 +26,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex const params = useParams() const sync = useSync() const name = createMemo( - () => `______${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`, + () => `${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}.v1`, ) const [store, setStore] = makePersisted( @@ -232,6 +232,9 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex ...pty, ...clone.data, }) + if (store.terminals.active === pty.id) { + setStore("terminals", "active", clone.data.id) + } }, open(id: string) { setStore("terminals", "active", id) diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx index 77362533404..8cd9e9d6538 100644 --- a/packages/desktop/src/pages/session.tsx +++ b/packages/desktop/src/pages/session.tsx @@ -84,6 +84,10 @@ export default function Page() { } if (event.ctrlKey && event.key.toLowerCase() === "`") { event.preventDefault() + if (event.shiftKey) { + session.terminal.new() + return + } layout.terminal.toggle() return } @@ -663,7 +667,11 @@ export default function Page() { {(terminal) => ( - + session.terminal.clone(terminal.id)} + /> )} diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index a74b7876f1c..7a105e7467c 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -208,53 +208,53 @@ export namespace Server { return c.json(info) }, ) - .put( + .get( "/pty/:id", describeRoute({ - description: "Update PTY session", - operationId: "pty.update", + description: "Get PTY session info", + operationId: "pty.get", responses: { 200: { - description: "Updated session", + description: "Session info", content: { "application/json": { schema: resolver(Pty.Info), }, }, }, - ...errors(400), + ...errors(404), }, }), validator("param", z.object({ id: z.string() })), - validator("json", Pty.UpdateInput), async (c) => { - const info = await Pty.update(c.req.valid("param").id, c.req.valid("json")) + const info = Pty.get(c.req.valid("param").id) + if (!info) { + throw new Storage.NotFoundError({ message: "Session not found" }) + } return c.json(info) }, ) - .get( + .put( "/pty/:id", describeRoute({ - description: "Get PTY session info", - operationId: "pty.get", + description: "Update PTY session", + operationId: "pty.update", responses: { 200: { - description: "Session info", + description: "Updated session", content: { "application/json": { schema: resolver(Pty.Info), }, }, }, - ...errors(404), + ...errors(400), }, }), validator("param", z.object({ id: z.string() })), + validator("json", Pty.UpdateInput), async (c) => { - const info = Pty.get(c.req.valid("param").id) - if (!info) { - throw new Storage.NotFoundError({ message: "Session not found" }) - } + const info = await Pty.update(c.req.valid("param").id, c.req.valid("json")) return c.json(info) }, ) @@ -295,20 +295,14 @@ export namespace Server { }, }, }, - 404: { - description: "Session not found", - content: { - "application/json": { - schema: resolver(z.boolean()), - }, - }, - }, + ...errors(404), }, }), validator("param", z.object({ id: z.string() })), upgradeWebSocket((c) => { const id = c.req.param("id") let handler: ReturnType + if (!Pty.get(id)) throw new Error("Session not found") return { onOpen(_event, ws) { handler = Pty.connect(id, ws) From 864c098701c27016afad20bc121cb1c307eb3dbc Mon Sep 17 00:00:00 2001 From: Noam Bressler Date: Fri, 5 Dec 2025 18:48:22 +0200 Subject: [PATCH 50/60] add experimental.open_telemetry config option to enable OTEL spans (#4978) Co-authored-by: noamzbr Co-authored-by: opencode-agent[bot] Co-authored-by: rekram1-node --- packages/opencode/src/agent/agent.ts | 2 ++ packages/opencode/src/config/config.ts | 4 ++++ packages/opencode/src/session/compaction.ts | 3 +++ packages/opencode/src/session/prompt.ts | 5 +++++ packages/opencode/src/session/summary.ts | 4 ++++ 5 files changed, 18 insertions(+) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 0e7a7c5d3bf..ea967616beb 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -222,6 +222,7 @@ export namespace Agent { } export async function generate(input: { description: string }) { + const cfg = await Config.get() const defaultModel = await Provider.defaultModel() const model = await Provider.getModel(defaultModel.providerID, defaultModel.modelID) const language = await Provider.getLanguage(model) @@ -229,6 +230,7 @@ export namespace Agent { system.push(PROMPT_GENERATE) const existing = await list() const result = await generateObject({ + experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry }, temperature: 0.3, prompt: [ ...system.map( diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 2c691cedb5f..03c4a39fb60 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -670,6 +670,10 @@ export namespace Config { chatMaxRetries: z.number().optional().describe("Number of retries for chat completions on failure"), disable_paste_summary: z.boolean().optional(), batch_tool: z.boolean().optional().describe("Enable the batch tool"), + openTelemetry: z + .boolean() + .optional() + .describe("Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag)"), primary_tools: z .array(z.string()) .optional() diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 07468995b67..de75eda6e40 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -10,6 +10,7 @@ import z from "zod" import { SessionPrompt } from "./prompt" import { Flag } from "../flag/flag" import { Token } from "../util/token" +import { Config } from "../config/config" import { Log } from "../util/log" import { ProviderTransform } from "@/provider/transform" import { SessionProcessor } from "./processor" @@ -96,6 +97,7 @@ export namespace SessionCompaction { abort: AbortSignal auto: boolean }) { + const cfg = await Config.get() const model = await Provider.getModel(input.model.providerID, input.model.modelID) const language = await Provider.getLanguage(model) const system = [...SystemPrompt.compaction(model.providerID)] @@ -191,6 +193,7 @@ export namespace SessionCompaction { }, ], }), + experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry }, }) if (result === "continue" && input.auto) { const continueMsg = await Session.updateMessage({ diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index ebf0a57d002..2f0bc09029b 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -42,6 +42,7 @@ import { Command } from "../command" import { $, fileURLToPath } from "bun" import { ConfigMarkdown } from "../config/markdown" import { SessionSummary } from "./summary" +import { Config } from "../config/config" import { NamedError } from "@opencode-ai/util/error" import { fn } from "@/util/fn" import { SessionProcessor } from "./processor" @@ -433,6 +434,7 @@ export namespace SessionPrompt { } // normal processing + const cfg = await Config.get() const agent = await Agent.get(lastUser.agent) msgs = insertReminders({ messages: msgs, @@ -613,6 +615,7 @@ export namespace SessionPrompt { }, ], }), + experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry }, }) if (result === "stop") break continue @@ -1418,6 +1421,7 @@ export namespace SessionPrompt { input.history.filter((m) => m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic)) .length === 1 if (!isFirst) return + const cfg = await Config.get() const small = (await Provider.getSmallModel(input.providerID)) ?? (await Provider.getModel(input.providerID, input.modelID)) const language = await Provider.getLanguage(small) @@ -1464,6 +1468,7 @@ export namespace SessionPrompt { ], headers: small.headers, model: language, + experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry }, }) .then((result) => { if (result.text) diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 8d366e4991c..ba0a1a00c6e 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -1,4 +1,5 @@ import { Provider } from "@/provider/provider" +import { Config } from "@/config/config" import { fn } from "@/util/fn" import z from "zod" import { Session } from "." @@ -60,6 +61,7 @@ export namespace SessionSummary { } async function summarizeMessage(input: { messageID: string; messages: MessageV2.WithParts[] }) { + const cfg = await Config.get() const messages = input.messages.filter( (m) => m.info.id === input.messageID || (m.info.role === "assistant" && m.info.parentID === input.messageID), ) @@ -109,6 +111,7 @@ export namespace SessionSummary { ], headers: small.headers, model: language, + experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry }, }) log.info("title", { title: result.text }) userMsg.summary.title = result.text @@ -150,6 +153,7 @@ export namespace SessionSummary { }, ], headers: small.headers, + experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry }, }).catch(() => {}) if (result) summary = result.text } From 85974e9acd0ab66ba1fe16a078b1c6df57160e9b Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Fri, 5 Dec 2025 10:50:14 -0600 Subject: [PATCH 51/60] ignore: regen sdk --- packages/sdk/js/src/gen/types.gen.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 58ba58d359c..60a68840cb4 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -1317,6 +1317,10 @@ export type Config = { * Enable the batch tool */ batch_tool?: boolean + /** + * Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag) + */ + openTelemetry?: boolean /** * Tools that should only be available to primary agents. */ @@ -1816,9 +1820,9 @@ export type PtyConnectData = { export type PtyConnectErrors = { /** - * Session not found + * Not found */ - 404: boolean + 404: NotFoundError } export type PtyConnectError = PtyConnectErrors[keyof PtyConnectErrors] From 81ee2d2332ce71ba1232387be4ad26ee37bb7a3b Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:51:29 -0600 Subject: [PATCH 52/60] fix(desktop): prompting --- packages/desktop/src/pages/layout.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 106a2e733fb..658ad4bcb9b 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -22,9 +22,10 @@ export default function Layout(props: ParentProps) { const layout = useLayout() const currentDirectory = createMemo(() => base64Decode(params.dir ?? "")) const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? []) - const currentSession = createMemo(() => sessions().find((s) => s.id === params.id) ?? sessions().at(0)) + const currentSession = createMemo(() => sessions().find((s) => s.id === params.id)) function navigateToSession(session: Session | undefined) { + if (!session) return navigate(`/${params.dir}/session/${session?.id}`) } @@ -59,6 +60,7 @@ export default function Layout(props: ParentProps) { - - -

- + + + +
+ diff --git a/packages/tauri/package.json b/packages/tauri/package.json index 9c3964dbab1..ebeb587ec82 100644 --- a/packages/tauri/package.json +++ b/packages/tauri/package.json @@ -4,18 +4,26 @@ "version": "1.0.133", "type": "module", "scripts": { + "predev": "bun ./scripts/predev.ts", "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", "tauri": "tauri" }, "dependencies": { + "@opencode-ai/desktop": "workspace:*", "@tauri-apps/api": "^2", - "@tauri-apps/plugin-opener": "^2" + "@tauri-apps/plugin-dialog": "~2", + "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-shell": "~2", + "@tauri-apps/plugin-updater": "~2", + "solid-js": "catalog:" }, "devDependencies": { + "@actions/artifact": "4.0.0", "@tauri-apps/cli": "^2", - "vite": "^6.0.3", - "typescript": "~5.6.2" + "@types/bun": "catalog:", + "typescript": "~5.6.2", + "vite": "catalog:" } } diff --git a/packages/tauri/scripts/copy-bundles.ts b/packages/tauri/scripts/copy-bundles.ts new file mode 100644 index 00000000000..3fde1c19010 --- /dev/null +++ b/packages/tauri/scripts/copy-bundles.ts @@ -0,0 +1,12 @@ +import { $ } from "bun" +import * as path from "node:path" + +import { RUST_TARGET } from "./utils" + +if (!RUST_TARGET) throw new Error("RUST_TARGET not defined") + +const BUNDLE_DIR = `src-tauri/target/${RUST_TARGET}/release/bundle` +const BUNDLES_OUT_DIR = path.join(process.cwd(), `src-tauri/target/bundles`) + +await $`mkdir -p ${BUNDLES_OUT_DIR}` +await $`cp -r ${BUNDLE_DIR}/*/OpenCode* ${BUNDLES_OUT_DIR}` diff --git a/packages/tauri/scripts/predev.ts b/packages/tauri/scripts/predev.ts new file mode 100644 index 00000000000..bd9320a4a1c --- /dev/null +++ b/packages/tauri/scripts/predev.ts @@ -0,0 +1,17 @@ +import * as fs from "node:fs/promises" +import { $ } from "bun" + +import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils" + +const RUST_TARGET = Bun.env.TAURI_ENV_TARGET_TRIPLE + +const sidecarConfig = getCurrentSidecar(RUST_TARGET) + +const binaryPath = `../opencode/dist/${sidecarConfig.ocBinary}/bin/opencode` + +if (!(await fs.exists(binaryPath))) { + console.log("opencode binary not found, building...") + await $`cd ../opencode && bun run build --single` +} + +await copyBinaryToSidecarFolder(binaryPath, RUST_TARGET) diff --git a/packages/tauri/scripts/prepare.ts b/packages/tauri/scripts/prepare.ts new file mode 100755 index 00000000000..c4975843222 --- /dev/null +++ b/packages/tauri/scripts/prepare.ts @@ -0,0 +1,20 @@ +#!/usr/bin/env bun + +import { $ } from "bun" + +import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils" + +const sidecarConfig = getCurrentSidecar() + +const dir = "src-tauri/target/opencode-binaries" + +await $`mkdir -p ${dir}` +await $`gh release download --pattern ${sidecarConfig.ocBinary}.${sidecarConfig.assetExt} --repo sst/opencode --skip-existing --dir ${dir}` + +if (sidecarConfig.assetExt === "tar.gz") { + await $`tar -xvzf ${dir}/${sidecarConfig.ocBinary}.${sidecarConfig.assetExt} -C ${dir}` +} else { + await $`unzip -o ${dir}/${sidecarConfig.ocBinary}.${sidecarConfig.assetExt} -d ${dir}` +} + +await copyBinaryToSidecarFolder(`${dir}/opencode${process.platform === "win32" ? ".exe" : ""}`) diff --git a/packages/tauri/scripts/utils.ts b/packages/tauri/scripts/utils.ts new file mode 100644 index 00000000000..b2885d00a84 --- /dev/null +++ b/packages/tauri/scripts/utils.ts @@ -0,0 +1,43 @@ +import { $ } from "bun" + +export const SIDECAR_BINARIES: Array<{ rustTarget: string; ocBinary: string; assetExt: string }> = [ + { + rustTarget: "aarch64-apple-darwin", + ocBinary: "opencode-darwin-arm64", + assetExt: "zip", + }, + { + rustTarget: "x86_64-apple-darwin", + ocBinary: "opencode-darwin-x64", + assetExt: "zip", + }, + { + rustTarget: "x86_64-pc-windows-msvc", + ocBinary: "opencode-windows-x64", + assetExt: "zip", + }, + { + rustTarget: "x86_64-unknown-linux-gnu", + ocBinary: "opencode-linux-x64", + assetExt: "tar.gz", + }, +] + +export const RUST_TARGET = Bun.env.RUST_TARGET + +export function getCurrentSidecar(target = RUST_TARGET) { + if (!target && !RUST_TARGET) throw new Error("RUST_TARGET not set") + + const binaryConfig = SIDECAR_BINARIES.find((b) => b.rustTarget === target) + if (!binaryConfig) throw new Error(`Sidecar configuration not available for Rust target '${RUST_TARGET}'`) + + return binaryConfig +} + +export async function copyBinaryToSidecarFolder(source: string, target = RUST_TARGET) { + await $`mkdir -p src-tauri/sidecars` + const dest = `src-tauri/sidecars/opencode-${target}${process.platform === "win32" ? ".exe" : ""}` + await $`cp ${source} ${dest}` + + console.log(`Copied ${source} to ${dest}`) +} diff --git a/packages/tauri/src-tauri/.gitignore b/packages/tauri/src-tauri/.gitignore index b21bd681d99..67554e174c7 100644 --- a/packages/tauri/src-tauri/.gitignore +++ b/packages/tauri/src-tauri/.gitignore @@ -5,3 +5,5 @@ # Generated by Tauri # will have schema files for capabilities auto-completion /gen/schemas + +sidecars diff --git a/packages/tauri/src-tauri/Cargo.lock b/packages/tauri/src-tauri/Cargo.lock index 6fc6d04b935..aaa7429eb26 100644 --- a/packages/tauri/src-tauri/Cargo.lock +++ b/packages/tauri/src-tauri/Cargo.lock @@ -47,6 +47,36 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "ashpd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.2", + "raw-window-handle", + "serde", + "serde_repr", + "tokio", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -656,6 +686,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -713,6 +754,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ "bitflags 2.10.0", + "block2 0.6.2", + "libc", "objc2 0.6.3", ] @@ -727,6 +770,15 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + [[package]] name = "dlopen2" version = "0.8.0" @@ -750,6 +802,12 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dpi" version = "0.1.2" @@ -806,6 +864,15 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.0" @@ -906,6 +973,18 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "filetime" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.60.2", +] + [[package]] name = "find-msvc-tools" version = "0.1.5" @@ -1195,8 +1274,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1206,9 +1287,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", ] [[package]] @@ -1468,6 +1551,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + [[package]] name = "hyper-util" version = "0.1.18" @@ -1861,6 +1961,7 @@ checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.10.0", "libc", + "redox_syscall", ] [[package]] @@ -1869,6 +1970,17 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +[[package]] +name = "listeners" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557f908c6cb431dd2496687aa9ea326507110ee4780517a42ade2df25d31126c" +dependencies = [ + "byteorder", + "rustix", + "windows", +] + [[package]] name = "litemap" version = "0.8.1" @@ -1890,6 +2002,12 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "mac" version = "0.1.1" @@ -1948,6 +2066,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minisign-verify" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2283,6 +2407,18 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-osa-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -2365,14 +2501,19 @@ dependencies = [ ] [[package]] -name = "opencode" -version = "0.1.0" +name = "opencode-desktop" +version = "0.0.0" dependencies = [ + "listeners", "serde", "serde_json", "tauri", "tauri-build", + "tauri-plugin-dialog", "tauri-plugin-opener", + "tauri-plugin-shell", + "tauri-plugin-updater", + "tokio", ] [[package]] @@ -2391,6 +2532,30 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "osakit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" +dependencies = [ + "objc2 0.6.3", + "objc2-foundation 0.3.2", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 2.0.17", +] + [[package]] name = "pango" version = "0.18.3" @@ -2628,7 +2793,7 @@ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", "indexmap 2.12.1", - "quick-xml", + "quick-xml 0.38.4", "serde", "time", ] @@ -2758,6 +2923,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + [[package]] name = "quick-xml" version = "0.38.4" @@ -2767,6 +2941,61 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.42" @@ -2807,6 +3036,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -2827,6 +3066,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -2845,6 +3094,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -2952,16 +3210,21 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -2971,8 +3234,54 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots", +] + +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd", + "block2 0.6.2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2995,6 +3304,41 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -3067,6 +3411,12 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -3282,12 +3632,44 @@ dependencies = [ "digest", ] +[[package]] +name = "shared_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.6" @@ -3428,6 +3810,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swift-rs" version = "1.0.7" @@ -3545,6 +3933,17 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -3682,6 +4081,46 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-dialog" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "313f8138692ddc4a2127c4c9607d616a46f5c042e77b3722450866da0aad2f19" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.17", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.17", + "toml 0.9.8", + "url", +] + [[package]] name = "tauri-plugin-opener" version = "2.5.2" @@ -3704,6 +4143,59 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-shell" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c374b6db45f2a8a304f0273a15080d98c70cde86178855fc24653ba657a1144c" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars 0.8.22", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "tokio", +] + +[[package]] +name = "tauri-plugin-updater" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27cbc31740f4d507712550694749572ec0e43bdd66992db7599b89fbfd6b167b" +dependencies = [ + "base64 0.22.1", + "dirs", + "flate2", + "futures-util", + "http", + "infer", + "log", + "minisign-verify", + "osakit", + "percent-encoding", + "reqwest", + "semver", + "serde", + "serde_json", + "tar", + "tauri", + "tauri-plugin", + "tempfile", + "thiserror 2.0.17", + "time", + "tokio", + "url", + "windows-sys 0.60.2", + "zip", +] + [[package]] name = "tauri-runtime" version = "2.9.1" @@ -3910,6 +4402,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.48.0" @@ -3920,10 +4427,22 @@ dependencies = [ "libc", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", + "tracing", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.17" @@ -4213,6 +4732,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.7" @@ -4404,6 +4929,66 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wayland-backend" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +dependencies = [ + "bitflags 2.10.0", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +dependencies = [ + "proc-macro2", + "quick-xml 0.37.5", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.82" @@ -4414,6 +4999,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webkit2gtk" version = "2.0.1" @@ -4458,6 +5053,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webview2-com" version = "0.38.0" @@ -4688,6 +5292,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -5025,6 +5638,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "yoke" version = "0.8.1" @@ -5072,6 +5695,7 @@ dependencies = [ "ordered-stream", "serde", "serde_repr", + "tokio", "tracing", "uds_windows", "uuid", @@ -5150,6 +5774,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.3" @@ -5183,6 +5813,18 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "zip" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" +dependencies = [ + "arbitrary", + "crc32fast", + "indexmap 2.12.1", + "memchr", +] + [[package]] name = "zvariant" version = "5.8.0" @@ -5192,6 +5834,7 @@ dependencies = [ "endi", "enumflags2", "serde", + "url", "winnow 0.7.13", "zvariant_derive", "zvariant_utils", diff --git a/packages/tauri/src-tauri/Cargo.toml b/packages/tauri/src-tauri/Cargo.toml index f9542007ba3..dd893d6e3f9 100644 --- a/packages/tauri/src-tauri/Cargo.toml +++ b/packages/tauri/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "opencode" -version = "0.1.0" +name = "opencode-desktop" +version = "0.0.0" description = "A Tauri App" authors = ["you"] edition = "2021" @@ -18,8 +18,13 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = [] } +tauri = { version = "2", features = ["macos-private-api", "devtools"] } tauri-plugin-opener = "2" +tauri-plugin-shell = "2" +tauri-plugin-dialog = "2" +tauri-plugin-updater = "2" + serde = { version = "1", features = ["derive"] } serde_json = "1" - +tokio = "1.48.0" +listeners = "0.3" diff --git a/packages/tauri/src-tauri/capabilities/default.json b/packages/tauri/src-tauri/capabilities/default.json index f77836402aa..12f94213e55 100644 --- a/packages/tauri/src-tauri/capabilities/default.json +++ b/packages/tauri/src-tauri/capabilities/default.json @@ -3,5 +3,12 @@ "identifier": "default", "description": "Capability for the main window", "windows": ["main"], - "permissions": ["core:default", "opener:default"] + "permissions": [ + "core:default", + "opener:default", + "core:window:allow-start-dragging", + "shell:default", + "updater:default", + "dialog:default" + ] } diff --git a/packages/tauri/src-tauri/icons/128x128.png b/packages/tauri/src-tauri/icons/128x128.png index 6be5e50e9b9ae84d9e2ee433f32ef446495eaf3b..57d061b44c1d7e4646a30129b52fa4ccf6b64c42 100644 GIT binary patch literal 793 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 diff --git a/packages/tauri/src-tauri/icons/128x128@2x.png b/packages/tauri/src-tauri/icons/128x128@2x.png index e81becee571e96f76aa5667f9324c05e5e7a4479..a87a4c3cc5719f5c720001334cc77ba7b83a5797 100644 GIT binary patch literal 1161 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJEi08bakkcwMx@A_ulc3^0G zc(GrxlS%N2UaM2_;Yr!7HB!|Jr>3r!GBsOR5~=Z{S}tkQgsUF2%2iSg|Gu`+W9_(N z&7#22z`(%7z`;N*28Kxm@w1i{in2scb7h!x_OndApvv+6udNt8_A$i8#ob|LS^w{{ ziK{+C?W-f}wd?2pk^fyQd3@uU<-vcOBPSYu_TGPc>fwh0`|5v(^&L-s`P}Ot~{om@R{`7A5#sxpN=l=eENBlplM?6!Wb|XW|ylO@U z76t(Z2T=Ht##nG;{=Pex@6~WLa569@)c@M@gZHpP6Vrs9rj>L5l-B>+Cc}(zisHqu*fMu{l8elzW$xUD}-z3rN4JzY1sAY`pNy#`_)+= zSe3CT{54CM{o}g-m3sR%|A>zQw1{%p=(@v@o8f7E-o(@brCQ2>I-Gc)LQ&5lEr9I}duYbHl z#XUiSO}d25p3CR5$lo8=m&Y?4e|+(C{RgHCrE}dIUNB#%=AX5XgQJ0)#AHi^uP|~# cgY^e)-lxosrCx@>jKaX+>FVdQ&MBb@087kM-~a#s literal 7012 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 Sh@|l&%60LaZ~OuIqISIbUu7*cU}X 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 Hu0mjf4WD&w diff --git a/packages/tauri/src-tauri/icons/Square142x142Logo.png b/packages/tauri/src-tauri/icons/Square142x142Logo.png index b81f820394d1504ff4b4091473a8263542c9edc4..bb767ed97601af5056617c182193ce3b81325926 100644 GIT binary patch literal 940 zcmeAS@N?(olHy`uVBq!ia0vp^eIU%i1|*;VHQ-=iU@rD_aSW-r_4e*rKjA=$hKK7@ zm2wTYbav+LWK3+d&wd;wUL{|oTOmK=!99V))AT-Yec;Op@-n>*FE#rnAYedf{8Su-EL{Z>|U=YIdxsiF`66s+=! zwhK+IJgUic{q4=0HzRjGdXjv(pzzPy$KTIsK zntQ5k-X{r{c?%=&-_U5kET%iJmFM~2cX>58Zg*Q}K0Ud7t=5y!_u0Sho}X_kd~VN+ zvfURWcEq>pW-;W+u^y&3|KJm2j-m0j=OI*ca58sPJLbB#GkHy1RvkhcBLdXl9s*pK|NuT1qMLv42dUQKYvazu6=fO$F|U~TW5SK*MIRm-|oqgm0ZiUvg&f?ta|2pwy!r> z6h{;$%CUd8un1KIg_DCtg>_%S9$BC(8~>Zkd3JPf=BKc$ceV7sEB+TbH%DTrbs{fY z^C!jRP&>tZ3z=y^2d?;ief9qJdj9k5dVllF9Vx2XlN8!N@!apCsw%1M>}>ltvkS7M z{P><8Jw07cUUyD>zxw@sYyJ4w#2&bgflNWG{ zk?mfjp;es_{r2$8I2$3u?5$BR9lKE|*{@ldHWM`mCyWbkzLb6Mw<&;$S; C6RZ0G literal 3858 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 literal 3966 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 Y2S8W#vM)6=T>t<807*qoM6N<$f*y@n<^TWy diff --git a/packages/tauri/src-tauri/icons/Square284x284Logo.png b/packages/tauri/src-tauri/icons/Square284x284Logo.png index c021d2ba76619c08969ab688db3b27f29257aa6f..5d78834e0e877893efab53a1ff4180555f5acd9d 100644 GIT binary patch literal 1557 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 diff --git a/packages/tauri/src-tauri/icons/Square30x30Logo.png b/packages/tauri/src-tauri/icons/Square30x30Logo.png index 621970023096ed9f494ba18ace15421a45cd65fa..f78bf4c5a5fb68268c878abb45bc2ec87d1197af 100644 GIT binary patch delta 564 zcmV-40?Yk}2h0SJBYy%DNklGF4XW4cwBqr(mcz;% zgU)6%`kYQF%i}}f2Z7V+H0hQ_olb{ddc9awzxMm|F`1B-&(n6hjX*UwlffbD^_mn# zA=|c(QU{iBVSr?_Sx;BQb}k`y41j9%q2HD-CHF|(c;VzL<3L#SHW<)+J||Vv$n`x| z@P#aP!dk6H&wt(SQAT3Lu{>-v8qxcBOc}Ncv`aQNJbrAok_VPjDi%rO?^i07_JEhm zrMGsm%w;pZHaibMC9pS-xsYTL9oVN$x=FQKCDSk{UnuOd5}eM^b#E=WHyMk4C)3!e zicB+NGw58P8*9P6KLh@D$c}1mL5q{8`an{_WrW{k;(zew{^WZsvlsfH#d0ojua>>R z8459rPzruyoxK`sdpe0Ym)|#=O}Z2+CUKPMqaqGiH;zb!R4$B-WXS^YPmyx`A%2~!(9 zlH^ygDKV3p{Mk(=Xu`Wk{D@#IGk_gPQV&dinMYoVnA6*V3f_c6n`cZswXQR5@9G>J znhN<@*4bB}41X%vU2>E|D#2?#wmFX{G-a=92L)iYZ4U2%FU9H$9f`ktCuA&iizSf&?&-uZ+;0$pk>ntSJ}O^`}5`KJhipib(HjhGn0 zN^@i@#!6RG1|rZaEb*cJccER=BB?KFZhV$c^nL*l8x*UYZv4WK|j?~Jt6~~F%{pk~z z5A*>^M`?r5m9@RJ_x|uEtX(6Vk@Y()MVto*93wr)%3m%|#OZ~srm>zF(JvDuTq*@; zd&^>_BJm5hOU`3FjG70L#Vzv9I?`<7$T@Ry00000NkvXX Hu0mjf2(yw1 diff --git a/packages/tauri/src-tauri/icons/Square310x310Logo.png b/packages/tauri/src-tauri/icons/Square310x310Logo.png index f9bc04839491e66c07b16ab03743c0c53b4109cc..2419f9209daf8c23a21512693670437e5a7d7392 100644 GIT binary patch literal 1724 zcmb7_ZA?>V6vuB@R|?V>l^3B}YL*yen4rjIokhe#8O}MGl-PlRtC23F&^TZ+5DQYN zz|o7NRAOVHZ!B8Z4kwNg=N0TU?~s8G}jQc8oo1_k!q+cEWnANHY7+H;kiw=5Uy+(DF>#iAJd*3&N(!vq4CH-ToCYgp9j=ij_Xw z=8113HRY67dch#jN2J4I913Vvy%vLx5!p@851uNjJ5zD=>BRO+lo_Q3G8p*vA&y*q6byIYB_Eu~Vl}1a;$oS1> zvpvbMC5}d`jUBD6ty6kUC|7zyasF?k%4)T`%D70}XCi~ZpjK*m3;iaS1SV%j4SqNb zxLm-Px?nz78lA|DK6EPz(V`LJvmP$$3M^&> zd+%~%&|K_j_W@$eGjZ`lM=O^d0#y4@2qAL9k70T`NdY&shvpFF_ybbXH)ul@V#J7)fB@KmjT9RV=qthV7sa1D zquh=H`gEA3;ecfnl^Q@cDFl*&IUV+nGgT3^9*2N*f7wrzrkv?VNjGh)YeG%ahrLbg zKtNNa@+;OU=dN0N@=T?^MN=0q1ai@#0r{@+H7Re$M)#(srfTMU_nIa1tK~D|l$57z zE;x1UV?}p&_t^A?WbI2bY+YAB5jJ!A4J@Vj?80=FW(Ph5fPpG&F`2-3{rp6^K#C5g z4)F9Yrk1oP;uxi&3iB4`Gos`66!@6D_#ubpa_idN7mbUnw!(Z*?aZK0-@>>g0s~;} z3xER4j5m5U>vwnEAYRTF4!&*!vi_5Cf@iajWsU?4HQnr4fmq9S&N Jmv2iv`VT6?>S_Q0 literal 8591 zcmbtahc}$h_twIy(GxYgAVgi!!xDs*)f2s!wX2s9Bo-?nB+*%-1*_LxM2i}|mu0o+ zU80NN=kxs+esj*8_ssL&Gk4CMdGGr?_s$21o+dQ~D+K`o0kyW4x&Z+JA@IKrAiYI) znp%o(ALO1|uY3pyC>j3igaqjs_isT$9|KJ_g7P8ut=j>Kvnp7XfS~FVJ7pZI}8ladf{o!;c zm1(K;-KkdRXO-n=L1P0pQv0P`U(b2~9nEJ=@_rst-RE_UCEIhCS6ZC{wgP%L=ch&T zC*gow@BgnRJVg7H?|jR*KU64`|5#Jg~WpHZ+L{j}|Li4|snUleLlZI)ZeC zOI^*wECuanft|Cy7L!avUqb|s`zkL-uUniu+&?`PC1In=Ea{>DZXXUSFYUIYtR83C zra$`5(dV9>JAOL}$hJclnH&JSKk%j1Hve%5+nA;Kpc0mQn*Ti~f?BK;JrIBAa$eE+ z@j#pupdkvqx*TZ}?&Ia-L_V0(F#w!2UsUGF^sb*3d{2s?9{L8Tb?6NZ_#{1)7Mm{N zhK+vn?p+Kqf?CgLD02|sP;&<{&SF;h@qwL~*dr1)_9B3E&BtHsceG7qR>%PL;B> zB_F)S$_$6{RbkQlTRg>ezn)f360DC+Y})U`pU@+ouf%$!z|czk5$U9&=5D1k8>Jvm zAv8|7*o77+9P1kQH1BKXo5q-&tu8K{F#3rez}W20aldEBAFYju9G9-dBUkeXND0x! zyV>gDE&8^GTdUO{!K}&NM%s2J;s^f9_oGeJ|Fmy7BDN)+Cjb5J4?!4mbx|T{?NjrxhJ61zx;_vPzEwo7$v&}AL|(FD9o-n zI99cr^aZ_<$bIbA$(l#CNSf84z*f@X7@<^}6y_GHC z9`IfYQ0F(;5Tl!7`I`mtDcjDlKrNQ2=tt20CZ~N+;vby{Nn|&UPE*%!3g<^Rx@(Il zm^fJ}vYu87Q3Lrh?tJXkI8z&Xqy;_Tm@FgYgS};gCyNHdZ%!PIoQNyiP^02Z=J_HZi(^*)}oDJjS!}u4hms?hy7s-Cg?{7h*k= zn=>J?uK9a1;W;kqefG`vB~#EvTZOx(984*jwL$_7jb1Il6iHqj58c{WT<%KXgF?-W z2OhfkK-uw}*Sig_5$VBCZ6C76@O`0FFk_^~b5(YTM9g;K0(-~|`1KW`GJG0c%wav> zv%7*>v1?Qs4IKOAU57cw78`YXOi|IIq<;oVnDAb-P|yk%s68#6T!5H+%|Fh`6lFs> zP!=A>vl8)VAck!0mHn_9wzT5TT8^^#@UBn;X42=E~h@Jd7nVf^qZr65Sp_-rT;j z|Bb`c$Hafo$r7p?HW?gShdf2TYRk4(H8;P-jt1r1-8O(dV#`Nf@Sp7Ts+P0 z1=YjoOaZ2{Sx8kRZIfBY7Q2LJ7<~|(heip|2=-M2Qg$-1%elQ!+RqJ$kNp{xj#iQ!xdt&U}`4h~bXnikM-7RQ+db4QFj$M*0Q( z=6?L;m)xt5u5Yi%bC@ft4gbDV)83>p1_%Q`y|#Z=jA5pJL1%|tHJzpr3i|KkAc6j| zcKS*x-w&RW)-zg@P7w&Z=Z}{7i0?X^`!h#xCkMBoHoN24bl*iw-fEwl+Ej*y4l$U5 zOsmW4+>ixG+JEoiicM8u z{p*QtFrRQulAI=Z>PM>Ce;!sgJG+`9ExIa$=kKD06*FQ&$ehjhGqz~>{E^Lm=?j7l+D#JLlMa0&Se}V*n)qA0`sy&k1DlFLiKVB)AbADG0~~puma1DHs7_NN}_R>+cpikj+ZS+X+C)7 zVxY6LU{AuPUebgMh-2;b!|S^nN*wsabFz%{4w1cay)>fRuhJUuSWQ}3S)qf`a!ixM zQs1maTy)8X_jBSuJ}_CU7dW8wPn*_ltka^fjVn_#GjCim9Jb0dnN-&y8f*@93?xn% z_+znuyU?&s#V?r;{2$7`n05S@8Y~&KF$1X*nwp)1$Bth5yT{K&90C(uCH~Crpr(yN z`o7zm@V=^IYA1?~-|ZSaZ<*qT%CRTy1zyKV8^{kMZ48~feHul}UUw)8s-E^f&_XvK z%_pX3Qm+viH6%4@gzhH!Xoi+#asO$3n|M!J+2mz*$q%l9hq9CouPuiBR(O>YV3?`5 zSMxGTIoLmY@mD((7mg(yHBLA43{IyhG_Jh(!=9aM{j}Mqm2IBvOirget~WJeLbl=g z_BX7*{rRl0D#S&Ubs3?)WDn2nKK99(lbEYJ9KMCAWI6Xaj$uQ(#T9;_H?Je_VhBTi znPgNdj0;+W0tAxUkmW8Ud?T>PDc6=ke>l3g&Z?ig9#kGii0|AEAhZ}A&M zhJ?P0J*r82tj%HsBkc7Yzb`d>xuquI=>J8BjBt!7P^e;{3rBiW=gNhzrc}Imcq%3| zG@>#^nIN`7o(VquCx0}AMwK_+R3UCF5w*J_nBs7Wh^D4N{d0Yzoldki;v=1UiuJgf zS){!BhxB??`yf_bl^}uLW>(Ppqw5z*0G2K-2&tkp!G_4sH?$yb?~$Q$H2msdd`6w4&pX{8p*8W z7M-lhF{$Du3+Ylvyy0b=gdG4Y6%XmxJ!J$X`ixw?+=2zY3%5}qp3$&Dk-Wfwvxz2{ z(#Zx;Q?6#YKNub=gxIedHW7&Jkyvi#h z=Bo>uB!l>JcKaG25qp-Ri(>m-*iTPlCO}9bnD2K9sOx-rc zbIZQ=2)07go5G&MU-Pm1(rEJDbv!^FOU3!%7bIw5{I3cNFqbo0HOv}4@QEq8Z#(!b zrPHiN4P{G-DtEjBJtCIoQOhJVRF|GT({~r#Gyq^;=JLgH_0v$N z%U7R$Cd6{wRO00o7Qq^CRjWD1l#;WOq{~)^x46584tj;Q3mBl*RWheFamkPxl?^ky z!>vq|VV!XVEA%Fp>)IkDA@z=E$Dou@G4@V$z@D+S4#vc4d$;EAUVr8{hNw$iVVXvVC%+nWM zKVP_sgP``51Vri6`Lhy5hnO%FKo-O^xeBM(GR=pVdwb^7!mTQ!NPIB~c^4vZ9+@78 zY$LNeP?|Tae0jluNw@cj@wDfmgt1B29nE8&Q!BjSRc&Xh=I?o=|5E9aU0qS}+DNW- z-Q!_j>0t*J$b_O&%}Y0}0SzaP^$q4{CQ;X2s*1?s2{9eZ_=SUwrY7LUx8uYFGZJ$c z2m)#n0KFL0d4g=CCJY~Fn32Qyd+6Ju>160zkKE+-LzgbV!R#n@@k3 z5`OG@emYkvyTNkQkvyBznrWQ?Icf+6JFYx6lE*oOE2QzoaX(bsGdcy=o^mfCrCgN& zwd6%(Ml?!yp?m>7g88w;`dj5LNAT~R0*Iu20LJIbyBg~$Sfu3M6ij09i`)u5*?KwZ zH_*w_$Im}i;bnYaSg_=`-#tZ$oM`VlEb5jifY8*jl;4pTc_HC-%74kcd4oERH#u$$ zLyY~YE*D##e)ywc`Un(|4;t+w#ZMe@%us%R%FR7tqjgJVl)ss;zK}R5GUDIB%}Fe_ zfnrVRpyE_mGq;3;4q^wbikJN1qEfGL$gp1vL$Pjj`yWV>SbG&Ok~cH08ImZmBa`Xu za*69RmPGf7>LR0wo4!gJ%)c(OsEjP1k{p7z<`E##bT$p~97w1~yOA(X&D0I~nmmWJ zgTB;Es`go*@hxQH=KZ+sbkOb3qB}{DG?A#-@Rp`QITSPsyu)<_^`4<1q|&a0merrB zUYY&q+g1Fml+zZ+FR5Ml_Q))Y0Ld?5J49o&K+S>H?dtwO?j8G;O4WKXb;74qT77s= z65z81Ui>#=s6xe*1i%($1r#=0X##)LMsYu+N?=0>2n@`nA8Is^8Ryyc*NCTZ3f4x8 zJ)|-o6?f4Gn2E(GhZj?6;8)Y6sVW^QkiFEZawFdS;1rFlu)j8qf9;&bw8nn`sQ@-w z2pUxlyD7BV1etmJ>e+84;bIwSDjPKGzE&=Cv*jGtOaWfi;HCR?%0eV&DLti6gT zo{_4;pbM@135?7^UXTZ_7GqG;6JHJQczK=O=j+~aJExu8DCf}h>teRM9}T5O=4Y5v z28WydXtdPSx`fn%Ic?oRy#%9^Ii<$+XbFfi<`P^dB0- zDYRg8Z<^a4)Wl5<2JPS6(lpXGQq#z9x=QsbD?y zxoOtH@m`%JzBaJw=*lQ%X@Djo{buiNl!T~3j) zGUGh;(=u1Qq`Q8L*EML+rvv-kqNa~7;)YG&H=2FPu#j`U!OqFm(z`Gx{%M+}3(n0XU!oB>& z>N0%})PC_3P(K!dPil}y-0j=nVD6%W^2KR(ZkfeD?nkFi^<)~A+ zUqt%8f81vhi}7!b*xY?uM%ii2(W`$?lLID}&x7*&mHvqx^&FmUpN{s9_`p^@a=%|cF#|YANVICIMT%?io8XlzMB7u zOlLz(ZSOwyYg=#j%7%rCg2x0UB4!D75>&3>AB4sFa-3}|^gttoer??X9$z%KaHy1T z5vbaYm)||e_+pvr)C&>cp0BhH;GWtS>4Nqz6_Ff>scg!i)Ry(IX<4ze+DAv9xzW0_ zhTmY$7y52)BJHx*T|E}*Wn(7uBT}2Mpn{(x>t(hOoCS|@ABSIPj0^HRSjFprp4Wsx_qMo>R$QHPmoCMe&Jc&=Wcuceio+`ZQL=SiCr&b9pj7&fx+qO-6Ts331~VhMamuyQ@#6snW-yuSjRv&q05A;Mb_z&|xk6l5 z{o~`0sSLUz7VK(!i~t~@-No$9y%bKhJ>MXYqT&V*;LYq|9T_ptXvw8XQO&I`bKw&7 zt9^r!k3E+ZXEfgSVEW#~qSwI@F?+##vHd1uRg)UN&OGDBPc{VuocbE0-_n#stZo<0fFgZYb6bUqI zab!gC2{LXCKo6VM%YNvP(H)eczGSn)uaITZztR+?Jv|hj(OgC`?b-b*d{HCtczCOR z`V;2DRyU@7vr)LLAb^pIZ5~WRDHYv7+m7ye7ExdY@R!IE{K3EwM(O=`5cKuQWNd}KWuu8W z=!%PNAP;PF_U`RAVsK}l7|)V=f zF(-ewaf3|VGC9lCY9AlyWJ{YoBl)GOufnV)DH*@-7n<|0<`xPr6t{wl^>!)X#LL}} z-m44?nz&nH$o0B@=6P)FD_n~o_$M^Te&||J$Ipq4XwCCTnMhO_$(SBo)x73sm$l_D zH(=PMtk-|)eDK*>vM|}f*Hj1H5ZUnIVsBMt6`8)1IBriRwNiNE`>FhD?J+Lek-*a6 znQ&dnV}C1wj0*8I=8I8`4>YF2qe%W&T}bC5zQz{2e~MW@=55!#m(=F80k@j9r3o|~ zs3}tHIzEZ*J^AnG_v_lvAn`=8(Hudn9hrNm>ElejQLTL(EncKVlDwK4rZo*-gG|hi zIHWhO>ig%9&R(60h^B0Dx^8cnj%T2la=C%(upE6`DB7s-SE8v{{jy!JeL;~LbPAotrW{D%$&V-(1RlqPIW88iKMmhDV23GudMR(% zg6r!9(q5}GNnISBKGNPW#eUKTt*2)Ds6Nvk{=8+73`cMItBGz=V+Tzsv39T3m4)`= zzE1y|XP%8(f~Y{l%P<&)g}E1Rd0W3L$QHUY5U7LqMwj*hyf-@Hv#ffPchCy+0h}aH z6k0F#W8RQ>k|&_>aKx7}4w&4{>P1Y^zbOVf4Vc0ndH_mOfdrnFfgJ6RZ!3}~2g(;wzyAy)r!Qsc zpe;rPb__Y`02<^seV-${o1n$qhywV#kY1Qs_v(0}py&g``$B~b=&652dRYs#FboDmB8#tnYzQ_*^+gGi)d9$pUCHs=Yh(mUQiGoCdx*cs%nQxkY7i0{N z%ULUVd|kdTHYWT((JtL1nN67B3ur2_sBG|=Z8w2C9Ik%xodqDCgN1+otb0gXG*#&? z`f;0DLnyi!-efCsC&K*6ExYT9GDoSYVVHIK!@_LRu zy-BktNmRh9t1FBQN=)@^twC?AQH5(x(R+|hPT*l>;ZC0!s=wt$V5uTiQ!CutSFNvK@S|*s|&sn1wz9#z%$o1c7X&?I>g} zeS9Hhk)}n>xj)lxLk#RE8AtRx1?mX4Ir*_Nv-|p!hl6yQc9^-r=%X%yC)o-P`sccKAHm${4R4(y=z*n)P9IuXE z23YI&)FS7`ad%Bs^_*wOTaok!4X$i>hRDfQpjWoth!n{3P-$zz&w#IMn>%BDMONbw z9S(qWs|yb5@b?o=4~6H_EG`e~a#`Y&9To<~A1^D`tu(AGo*Bw1<%6rV(Xp}nUPa(8 zfjQ+d*seRHrc4#G0=v(JA zXzoSb!F%jE-$!TxceFZ5*qf9S%1Lo8V2oPls9blxY z&bN;{x%7SskKWdY?3j%lZRkm&hf=*=akbhk(v-fcl^nFk?Q7ikBQgelc2(j6wr5IQ zq0&wmJ#vs*>8!Tj)3PZVkj{&}r)9O{?Uc$8Fw-5=Q+blWE;{9&D_*??-IJIEN`W$=~J3n>(DxK~SH)77}VK5s%PoI(c zI1Mb4(`4EEGp4c>Btn9xb70YOVtrBa*GcIMwTk`WC*ejjWg5P_k*|Kx&}P!Yexm*A z3Dv+2W^jbcr`DMd%g9V|ET~*rHKd0-8z6H6smjbnP~Uk%!+IwvEP9V|Ok1}?+5jU`?BGe1>gHDD=@3GHyJKq)}Q_JxJk&qHbBiKF9ldd6)_6rL6 zf<6|j`3A2&Wz{tNnt>)gmpPg;a1 zEy)}|*T@nh0Q-Y)Nq30ye(u+yJ=W~*?aSfoGYKMUJ%mk6rwz?esQFBcz8E2x@X0+A za|bhX^A&rK8}Xmr1BRJVMQff?Il))AoXVR1ha4A<#{@PGol8)Vchm1;I-@Q{MNHq; zI~=)iiJ#3U8?>>}QhU$$G?i$b{!>e-3gNc5Rm;`&74)c6!W{QHHiQ|IDLf`B<__FJ z57;o$!k8ewCJC;185mn%VIC{C&mt}7D+!BW0ZL{OmMt8v52`f&EX|dE&{{8Mo5Jvd zZ8@2(C9b+!L@$57Uudfjd`RwfaD{sraE7l44*c0#a5MUkn()8N5&yr&d8J}TlB+X4 Riu&JN+8TQ5 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 Z9~7@qMtVKz_LBer002ovPDHLkV1m;Dta$(c diff --git a/packages/tauri/src-tauri/icons/StoreLogo.png b/packages/tauri/src-tauri/icons/StoreLogo.png index 455638826192851ad1ae604d66f2ca352979442c..3e78e8d3e4e59b8eb80701ee2163a6ccc7419015 100644 GIT binary patch delta 679 zcmV;Y0$Ba?3%&)ABYy&gNklBb5S=>~1p;`91(p5^t(C&qX(M#Te_`VP z(MD{vGe-Y_wp!CtDg=;^JNIqayEprG=DnSpJ+bY10j)t3v?k30 zu|O;k3uJn8U03xW2*`09*2U;&`o6EaF(5F~?RKfx?~@C|%zwLzZNc*Mxg6#5c?FCe z2)xj4x2aaI(^;*ie&avR1}~IKCAw=gsBwF%en<68EFj;*A?@w&)5+HOk?HYQ3F%n%GH;6RRfd45(v^fP98{ogo}m7XXS3REl>=Sn7kbUGb1Cv~|N!eb=m z6FmwLjEi$}8|TRH*w=j?M`n&m0FcikvSXJa!HOe)EPn<7wD*EErtw0OwOlSo=7^H( zFu|97j_45C@;o{?JfxgVF`~$CU#2*i6Mn`bs^xNWWa^@lj9Df}!s^8_#=hzAYY%KG#m`bpTi>*7ka&( znv)u`>wjI{+1;hft1FcVMtaqiSu*BCO>6!oX~e=knE+O+)zvLNgux(vcOuk;^%$Mb zr;_X|qgtBCoJ0)^25Muz=@bQzFUU#>8fvucyE-ZG!A4*T2;zTJmT%r(U+Llg-srUg zY`|8W%N{4r2LsMT|I>KU4u^`#61!93%AQs5)_5%}!G7j`NHg^C3 N002ovPDHLkV1ikWN;Cif delta 1506 zcmV<81s(dn1@jA#BYyx1a7bBm000XU000XU0RWnu7ytkO0drDELIAGL9O(c600d`2 zO+f$vv5yPN?3#xh7$lM zzK8K=2xV(ktZjJ6YWNPc&1V{V}FP}?(UhfO@8@FySBTh zd-`>CRlRBe#u#G^8xWotR4zFD;evvffXeAKw}1i<0=UAJS_MXcCn3U%p8}<}0BISB zN!ZZ>#8dEtJtpPBfEYOuI)Agp|L^eEDaZ=Mqd^Icj7VsO93HX-)?hgBk)v%UbDCw) zNQi`fps>te9e>MX>=)FE-il(a1dHJwF8{U*DKRvW$RDr@dJ?>FI^cy@@Tcl|Y@9sr z@(MP%-!;r$!sXgAq`>e*-oN4FL?unn0Y@b{@2$i+%7qedhWM2e>uT{aE2i<8p@?)A zaH#3o;+^~%GN5xjPxxyAdRHjcTuBHQZ7++Fm5`Thl*8`FTd%e?v%Ou^T@dFbH ztq~msapEH(z`N%TLgE48F$`$EuOp}1u-I4_5KLr?Ur>!Z@hCv&g?(PyR!?K(D>c)Y zk>wGm+MM!=(?-XRd;nG<3cH>y8g=}o8Eds3*`bKY;-B3H8w7zQ!B*q#+`4vQQMTWY zHP^%^On*_0&WjS#|8snzQ*>+E`2_>}$m~UZH-FM*u>cn2AtPR2G@Z6;pKvrONJx2ntwR0zRj_HCj7Ti`&d}?{ep{75CX38{XcpSw zS0fTBLDmIK(TCzoZBGDy#h(QWQWFtNkn+nc&42qRBxorL^p1{Tj5Hulzf}DL^ec-K zv?_#DM1+Ko1`-In?a9y6pB7$SX$J=&(aF=#EcwTPexLBbf>b`jBz6eM>2BlSp7LS6 zzBF&!@i57;!>{zKW7_3#x@R01KfXjBSdd7Bqm#>vF7>{HJfHlP>!oi!NP&*%o2K{0 z<$tijrz@M7>_`%)cM`DHC&?B;=$8kmcD=Xr9%Mvn6_CTtu7DUJ0P9$*kBDyo^w!*L zjic*@*;F;cAP6;Fyvk%Pmn7w_7V$AyjkhWP&MFEe+vjiE;rVVX&Q%}-m_&hnsXs{) z>?Mu0T7;0?SWB(G^qtYUXD%BD`P=rNe19Cv@{fNvoye+*2>l`o2)&Upg*5~T;kP$Z zNs(iEQX~t@l+E79$;olpcB)llGLur7wb+4{goIpzZAbw-kyr>sGRJa6+|af&nP;~z zw0#{bu*xN!jPY!uGLB3VB|xQ}^!3H>PCU=_j00tpG&*ZNknkWShAVYDxS5>fDt{4; zJBMgYW*WJ}sy8DEft3g!3$OQgpxs$1s1O5V!fFTYW5e6K^;9b?p4?hy646u^5UN_N z!FWXOC!THh2}Bl?gjn(x4GKVfP%I$zen8Vh)%&*2*^l)de%Wq!TYKX3H(s%5JVrCW z)>kor0+MRoPyBssdv=ttOB9g>F@LK0yRRg|7!%a>4|8YyaRwP6Ni=uoyZrU*K~+Oy z%|3fsm-y~>x3wuh?JZ7wFa$)5BC7AhdS8yLln4sk#*cFY-O40JEhOsOp`;|21WBHf z`5)?iAt8!-P!fVfBK5`jBmSs#w@H%A-@PcOl^@;ckmys?Iixih0;%#yo`2f!`tk%H z2eHh|$HqpQIm8cUFB>QNvVabZ6=q7LzSo+&_*CLhdT8jbaNm(12-kR~q;On8P^V9o z?MHSSKl@9483R=y^c^A + + + + \ No newline at end of file diff --git a/packages/tauri/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/packages/tauri/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..6e500bb7e51483a075dc31842d7a4ea5bdfd9c84 GIT binary patch literal 817 zcmV-11J3-3P)L-P{NyQB|LuIZapN_t3NAw8!YD47<-oh ztds3=`h>|=&>o!}$05tIv2yk-?<=R;#B@+Yz?WOAQMcPwV73kd z0G7)oy*@di!C*j(#e!_x&b2w#3W9(djRs976Z&*@MfG|;^IBF`pMb#M@9*j3iC%2osI&QCCujY3DzJifB~!2mfC`)a+MKIUxV zh$4tw5GeeI7UdlPm+$fQ3I!pX3jhd#h?PYN@@{;+QVT!`@#W@*zTV!F+iWUc$HfXG zfAZ#Y8t(7Yvx5V3s#u9I5O6#m(}#-->augL*jIkxI4s57pK(xldv-?e`B?>2L$1+U zcKDpEez2yWv|7sHlOXUL^M1~xwp9?~l<*0MEz)Ep{!0?LPap13sPkD z(m+TCmdKQhJnQaPM>Z9R6v;2`edBxrytg?D*@?Oc0Mz|NCN~ZQA`kKdiobPDKj!hA zk+qX*bWj(uwpFK>$i`umC)9kDLv+#0(E&iZv^S@x^ql+dIw!LLz>@t04jI@&Py79c zkgLNgTInj1gvMv5PsK4-nolhL`zqGX v@-Jo8PZVhh)2;C**(|C$y9z`FB0hcs*;jnFQO_RR00000NkvXXu0mjfVNuGq zH2I}TP}e@)tfQZ&E3N5wTrd8&&VJP@p+~E%=KA#$%Q~O#*^Sl`FIqCmq%0P)u?YQA%?WP;z2X zRDx1RZrrdp3b|>wRHoi$Zd}d*-wefdf{DLa^A8pGp5Dk+=&k(c-`nV#KOYo#ndhZ! zpQ+KC|Lw86P1PTZy!Y?7YaQF<8?AMDQh7~{jDFm{p1i+TJ}GV1oi?}1XLFU$eEZrg zr(>O4!z3Zc??%)$(%hQ(RUU`_rAx|9%6| zy&QsIXQNO_IS;=JiQcsRduLm%mYM(6*~>3YoacTfb+0z0Xaf1tfiw{6dqO&zwrXyW5xQgZ5t#(NaWq;5ER#TGEwFd zG+rcdwj<5uOox-ooqddokBlFw?TKA*`K8JI2lF3u*I&A^t|rD#`B}Q%{NpcQW}Zpg zoD-iv@mNOYy-(lQ+}T-e9w#g37J1%gr*1@M%jaKbgEM<_t5u^`JNc{^lkRmZEGXC@ zxIRnx(X`_;ZcqBNU3FKken-yRqg=3PRQc2C-|<6O9uZ(d??A44WEx>-edE~i=a0Fz zxlT!y6i+*18b0Nh1P|NU^Lyj`BUk?`Ivv0NUJ0MJV%)#LpK;rz zuxpX)-v6ljo(d*9l+IyqpPE~`5~Va+y4?b;2JS5|xOzb~G& wW@o*!?3(YE*57NbmQE%m-+*%1x|{wNvX!sQKmGYWFkdrxy85}Sb4q9e0EnBttN;K2 literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/packages/tauri/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..69d1023ec53a545ccb0d7b445ca4be13379bd747 GIT binary patch literal 944 zcmV;h15f;kP)SL7Z5|!EF;H z(s1~=#5idqz+?|6Z3LL?B}^8~WHRJ94h@I)Xc9uuU@)LwuNN6!A}RsctkGzYH?+?b z$!iyj#n8){MTm@gTb)jazMY&yfxDqHqBaYCb&BuZs)%a(HN+6TdGn>Fd!Ez02nnv=-%t6IrnM=34Ex`ssO|x-o1x zs`z_e4UETptb?M~ox#y*XL5`*gU@Qf3l$s^dUN#6tNvU)Z)~oC){>^0B6<1_$16Z&Nm#HMSu^`H1bs0?$%Ps@LoE{rH$}Z*OUH zbCW#Q_raS5QIhS;)6)}`N+tSnc18yW2Woy?4#p-z-*~Ipq^qkd`g3zbF00612oW%n zZC24Ac6QWw{oN`NCQ#A`$ne2k=0Jhzb z2+Bs#1qrhgV|^CAU}-Dh!Q{aNF=2v@{y{*NQkAk1T0njGV-hB#Z)k0%S%MB=l6j$3 z25?1$fnX1P0oFCl&r&tDg3SV(VLJeWh8m{-rS9j}hJCP31l))wd!~rN`@a7aYV2Aq zrE0a?VVwwQC{ud@H{#RYo+3iU6MRT90XNCP65a4GNZ(#h%R zGb0x!7vMxLmmAIR$K;ANI05TOd@Z9mCK>6OnkPbCWZgxw=F(6VJv+E*Xbb3#=G)44 z%oT9nFvqU8);2cWX}ykGcg}@`Aw|v_JV}TOY&cG?R;zS@=vqy{x$A z2Exmie4j|#aJ>_?Vm60>NGqjDJ=C7O{%4Etdaf{ACf! Scul+j0000-+gY+S3<8 z7*!+;!^1`)$8n5-fbzcY9}WNn4u?Yuc13}-dqs}eXf!e%h!y|{Sg+Squh;3%!vob% zPRQqHtm_g$LopGP2iEin-ZrMt^%f>4m)GMVR)tFcBS_HA?X3i$errWJ4Mke5mUPBSrIKiY#bP19F`31U)z>zO!AN=3t1E&4Js0EO1 zWr@wVyQwHYtM_(hVlEN>zrNCl^FW#NfNW6WiHsFratH;u&|o>?nV=1>3b%sSS64LW za$(yk15}j@J$!3wG-J#HC?9@bUgjqXZDv}X+0JJR`Ng4DHULpC3uQrxEKNOn=Xg_( zJtT-j%!MEb99NBriKv3Tu#f7{bS$DhR>6r)r&D_Bc8w0UuqXD`rIW}q+=UR7&Bqum zerd23rxF^M+zxkDOwV;OXR@y~TFmYGL#-&M4^>XvYK0`d1Qi`w0pMo?cn6*`U4^?M RP4)l)002ovPDHLkV1o7xOB?_I literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/tauri/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..d274f1c9d34ff97ca6bf1a6d1ab1b8ed9e306e7d GIT binary patch literal 891 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz1|<8_!p|}=Fgtm=IEGZ*dOP=Qo|K`$@$|z? zN&)+nR3-!zKe)8V@k;dKXQ7MsInS0^Waqg`i+|!iuJ$ji0wS!MH959zy%t|C1mz?? z|2{p8gRke!{c{yI->NQ8PBRu3=?U7!kiY?i49-BvEWreX2LynSp$!Px48CYJ%F4>R z-t1g`Rm(0Ra@xrftF7}rC%xcL3^vL=aXV;b$hF)q#u;WQ8zVdxU!3s%`}ZL8`QD#n zZA>pbl+|c8nR>E9Xa2f%DLHn*pKXh4l$Y(>qpd6U^zqZBxgkE6W(VG$R@o&H=lm$e zXkyv!Tvy`kGY8*Sd=A@Su57b`z-OyVSxkRHk2!Q zMPFq4Y$?x`AUbD)hlh%akPv5QV(XEV;P=I$_c#Cd^_cQER54|5;^olHQ@1|uaksNd z*lGUtcWq^To!;88?7c^CH38r01o7ds!TnG zJp6|b|0!7#x6bcr+Vv@KzWc=19*Q@w+I7ccm03(4YrkB?J70M{y(f2X-u!ikCp`Jg zvnTK4)|Y>H{#yRay((WOtGRu9Ep}-%Oqk*PdFtL)gK0|_s>pPBu_L9f4fXMAC+90( zo0Xume(@!#gQ5?(T%wwIGzRI_x^wzJ|}o-Da}rlwUO z_Rxc+OBq{TY$c;BbPFsZlQ`I?Pf%3?Ck#;Hz>=UZ#J@6+=CbN$lzq z5f~#Uk#(H7O9aNq$VcZpfBGazdgV>Nb^xO43EoFhTBZD8mx}(3<6x%%IIUY2Oi;J5 za0Mdj7SpcsgEGQ}s3!N5BysjcP{t}j5L5ynO;hKP3S5=PB-80szqx=IS*=#fDsvTx zjTbTqp3P>f5~G3?OfP#3+OhheMK* zcM+e@=UR3)53+g`c|o{T16UiDnM@|SzgR5vggy^sph4!Uuq>)hqD(0>lb{-z$0d|7 zHv8|Ug~6s*sW`|_W-R9dB5&=oxg>oT5~IkErzaYZ$2yMn_kwbVLNFK%=+o)x#)-Nr zAEa*uNmL9=rS!x7Jzd}4(wpOBO4Roh$!M$!kO&vm|MUDz-|z0|>gI+PDse>^dQ~$2 zQnH-9(QHzv-tA6DCuiQeChq?#02rJ7mu)}b%X3T~ta~{5l4RroI&@5Vw0S_UCHS7E z_1jWv<(F#%;vaouYp5%hYe-^1)CGXCY`@>9FX!jp+I>8RWg&L<-t;DEAWy>k3F-|L*M@O1USj(tdi7^IaxlZpSN7$f!y||#y zXJ-^*vtMLkH5SVE)QZh@K*q*}KAfEB$Ms*|aFRCntP3OCq&EJuiRWDNBER5DpM(Xc zDN|9hVDS#swz8}zWD!c67t978j1vtLb2Z3Z7g9F5BUcU1gFw=OZH`8xN{$vGyC|`X zxZ3_4?q(CP-EPyij@K)>AK1nP2iyI?Hm{e;+?9!KUyE|jBX)7O%PD_9?}`7LoBG3+ blA*l-C2Y`>UR*IM00000NkvXXu0mjf%zt_` literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/packages/tauri/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..237b8fb293964f77592153cf33555eaf768b7761 GIT binary patch literal 821 zcmV-51Iqk~P)Rau$&qJXK+yN5aPL$E^UaU%-;5gxYQdDkg*>gO$ z$BrkdpjMS)0ssI20RR99002N81hC99ck4#n=6T}#zX?Wf7e<2N+XXSe_}9^;_F@ul zSp#~1yIz0?5C;H&;Rg=@00IC2f+$Ins14$r>ohgHZmHGf`CXvHd1@O8h{}MS2B5uvx7#(cRJA4MVzG!4Y8sp2 z)ktdm=f_9cyuZtMJoW-%hCdt*vbwvIpRccyW!ay7C;T-X34$DFBt0!6fYA1@`!^i2g2vN!Vvwpn+Z+}8}I1T)^L+6Je^8f`Yj*MIRL0&xcPiu{I(M@UoTSQYgxRSSm926A%WxT3@rB|HED005{50Lo+?Ho3Zp-rKi}d&LW2WnFs* zz?I25EF)D++XFV?Yu%E-og-+v8jWZ*DUxk9#F(%tbQ+x`Wa3uaVz{N0WnZPSPeb=K zO$SEOz7|(OF~2H)6KN}*BNa7q000mG0DvyKs9onHWe5)d0BRst1jCSA?Q42?Kv%M* z;04$DmLy+O?b6p&5vw2oAOHXW0RR990KL#}I+JQ*b}u}Z00000NkvXXu0mjfy@+FD literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/tauri/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..d6299e80746c94d9361853e736ef8add3254aab0 GIT binary patch literal 1286 zcmeAS@N?(olHy`uVBq!ia0vp^H$a$!4M=t>=)PrOU^(mQ;uunK>+RjMe$we84G)cz zXUlV*WbIsAxe12M~ z!Yz~If6VxO#hasru5VoIt2Vjwz^z5AR{ePW`T2@B+t&tMnSQsnB^t96Rnme~-zQ6aMGt9ewyP@k`a-XFNx=Zv@wgt~S4L z@nYbcckj%ef3uud^`uB^{ylAZd3p2OXR0@d&Aq&wclx2IKTk5*zMVJYny&EVYm^Xre6B`>%LFQKCWN(|J$#xZ``@FWg@Fsw)8B^b%%fbeS2GC?mV-@#;bYS zwk<1t@&3(`_3PtI8+EUVHrnpmbnoN0@84(7eDyM9Coqy1Wvy>s4ph zpP0C>BJRq__6US~goou@vghO(mo8kCNn?MuMUSbwX4&s$j7u`j zlJt^qU$Ro%u+`iBck}b}$tF^*~y%EXdCgXOmulW@ovVR=#bMXBCW!`n#0?*sAJRR&U1f)LPK9)S6}NKwfu`U zQ}pUB#(n#0Iwwz_{9@UGTXhFk=uduWvh3^9x9r@FTmN{>2PUT( zPrrt@XYDJ#UvfM2`|kIfl8$~7+dlVf+Qy9=FRr=$X_wLl_vvMa=Ev{(;G(dheUjhu zqAB-+l?<$vfW8A|DSX)q;?xsH8%!dKjb@rOn_Tv9HlF`3aA;8uF0j!Ub%NK zPKT}e)0U;Vp=nhDZ=RL8_&i>H{Bhx*pYLLG@^ej-7bI6cURwG3<*!H2U$J*BmAo0h zUO#Vt&HdYQ=~p+cyZ`@x%(YctcjQJ2n*W!V%ZhxN5Rt!rb5-@y+-Y^^bLHv`Wq4M} z_}limFaB_q_3h6?oxtpRI{2p!b9UUU-FEvkCx5X!qq&F|R6?MZ4*rZdO9T87bLam3 YSMLiqgz`A|1IsrCPgg&ebxsLQ0INbOpa1{> literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/packages/tauri/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..999e00638904c18fdc60a600cfa6135cc689f7d1 GIT binary patch literal 1069 zcmV+|1k(G7P)e<$$yQ7>e80+Y#vdc7Wf zKRxxI{9CI!;)UXzqx<`Ny1cmXpZt4Sb(B;Bmeu=DPfs))4(a#RRaPqK)7~CkUtd$V z+l^`uT|7I z_G+AU7iSd^T*?W)_o)qp)MK-7wq%_&3+Bs$fVd#aATTTS>W^ihQ`?{MkZ0TQ}jBcozTfz#v!v5I(ne57BOm-J_T4A5B|1Jsp+Vxe}X^W%>X^J9>2 zCnvPEwN=Z>TASe9hldCHadyUTy&v4HfqnMm-%6yf$HyLkY*dhh`r(z?@Ath|y1BWz zvO(tlo}Zt6pHi#-e`-jjvSsFn)&u~dFUl!AUDEMlrpIU+9Wyv$Fv0);q|F9sIe2Sg z*P`jz`#rv@lz_KPtINc);njKz=M>%x`hEM$%L`fFLaw-b;Xb%8?vsiF0MHWgL;D8@ zG#CuLBQ{$cMHKHu6BMPWf}-@d^8f9^5&JlI#5BexsRO_=0Q>tsj%Z_JqY+1pC4l36 zEC5)Jc*SyS!hten6$BfwJkvtc)EE2!IAWma03iB7*b~;8QvoUn1ONa42mk;85C8xG zAOO(nD@izGlq|ggBmgQ101)m26ow{B+JQ3Dnrs@=+c=GnrN`s3Z%y6_hX><(#I9TPuG`)$o=9B~6w?vFk7B6H1F_S(Y9>NGyCb@d3V$4x?0Hjny zWkxlvuceG|md{jkOmP-9R|JkG;$>EZC!9+O6G%avyrQmZr*#c-Fsk8^w8n%@O^r=u z!lv+4l((2#pNs~^ryQTFgKiZm2&;-8N{4i1+9^Eht|JN2bH-H#f%o*Be&FZ%c~)sF zdCXNEw>owbiCf#8xb5dUXzpl)w6E?T**2F6%%O;bZF{Xo?G-c( zYRJegG~4DvnK{hMw!K=Z_W2l8(vw|CcAaYXY8*GJsjf>kRn!wkMQv2nMooF;0K^1D n>qY<+P>LZks<*iGtSL*O7@)-Dct~Vr zlg-0P$99BHVDB_j6ZoVM$Xb~&_u~^wfhYUflzs`cb~q?3F=A*`FldbU&mhPl0EG;V z42}PqHM*K4M3Q-!mi}u$mdJYgW=@+!=Dmsb`FSc+)MrSgTfZ&SRLYC_qUrtqNZMx2 z<>~2{a<-=A)nEH>F6Xt_-0P~_t&bTh_x1HtZRB>Jh<>7}t5u}6IB4aG$B!4kdHeQc z|LWBXmTl<&{ky2PRyR62dQ*hXk`>Z#A20Hc)w^}0KL3r#wYO!lE}u#b*Z#fIVJyM( z@0E3M1XsxFQ1OXbTW4Ko%Dlg2eGRwvFX;(irm}Z7IDjAn2MYu?IDE>=cDbj?Si4B( zkk}Lj7Oq@`@b%nqdWG5=HYMR8| z%l|Ynz_Eqn%d>PNeRk9JjAd_G6oO@(*3MnNT>bmW$DxV2$yetoP7SR4V*2pk!NxCT zBdubtvCB;2fY#FPB#9ylOsiW!t6s4F26s&)vnq) zZjLW?U+!>)Pj$YoD(SE7w)pI)61i8*o%XKFKuHt`VJTLCLxG{u$Duh%z{AMXJ!4W) z+Uf(pXU(}%;+?blOe?FL3KMc(<+y5@^4I;Y10+;G zEOnB6tzchyeuq$Fg0ZPkr<2S4t=&0Ffj2%a30f-Jd1$KoR-^qMBFk((&GY>lbinuf ohMm&$PB0#7cEK7)AEv}}{Oe-V+%!iISo$z{y85}Sb4q9e07Po}XaE2J literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/tauri/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..4e04fbca40e267047889148432cbf651b53173d4 GIT binary patch literal 1667 zcmeAS@N?(olHy`uVBq!ia0y~yU~~at4mO}j{44ik3=C{uo-U3d6}R5r_01D5mpJgS z+)ilGv50L>ViPs?y55|sWa_j+y+Jx;4*OTOFA7s{Tu@F4?qua&<>DEl`mLw^r@vyr zR_>hD^QtQ3PHsB%>FN*j_Z4bOY-ae(Vo>l>W)N^;aActg;UK`sB1j^^zD$G5c|mz; ztaOvYx7C@mEUc|x-`o6U^TZ8~@_jpXPA8d2mA>al$~V+GZB%Dr_io#fFUC1GUAZim z%YNKm9Ud4uRb!p;>E@G1mRB5YHju4({L!MW{_ib^7kACe-yi$=v$A|rS&#J}H>WS3+0E0%D5GGDCQ_4}z!XAHtF znD{=ayth}trDjH@y|=okV~TfZsAy{BF@62GBj?Z0KYnno_1*m&kFYP9dtW~P{xSda z^Nvnl?w_C5rLd`0+PA2vNNE4Q8p-&(yF@?#ypu3(!{*Jx^>OjXKQ4B!pRL(+V%n|S zx4TOY+RXK1Eo@bY+go}0tbk(*$LyqCD=+Q4UV3-$kD8aeueY8J5ReowXL1nO`P1kk z4>ZC!?<+Pb2<>xbfhUebzM=vyV1mK%h^jG%QiB2$htk7kTtLFwMZhJeL!sk_fTO@h zpvEMiz9VfMO&z>~EP~QXP_wH@}viNi%kgQS8Vpd#2XGlM(#q%h9aO&PXYFy z$?b+Z0cOUbWRL~N^N@2IXZcJ=0WblL4R~_ipOLo`6AXHv z`}+DG-TwVOx#oA-mG0JqI>JZFoNe-(XQaKj6e)1?+3CICg7jmR>c2m5{GRkzT03(Y zhf?$&)n)vj7Tx8^HP3F@a98B4zksBN2={DWcwhq46j~A?kGn{a0b`4yAj<#% literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/tauri/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..cda7c3e6d29adb064f9219dcbc1ba5aa12523e59 GIT binary patch literal 1471 zcmV;w1wi_VP)J6|c~|K=V-P3#1QGxPKV|>~EI^eqKryEsZ!l#*15C*R9qNqva&3b(P-5CjS-?K zUo2E;27`fITwKUtIBZ&A6F`M2bN(X9;u$pt>cI2aOeT|w%;)nU5SehGb=*gTba!_r zM@L8U`}_AYtN}_{vl^i9pFfw&%S+kb-d@|0L%UEBW2Y@!w7}cjTiM#$dUT+K)H;yH zK);82eyyL75EZfa_!S@xUaA0XDno>VDC)x%+5n~qkd={W9<%$TJ_6fh3_{gc&Sar5 z$;kOY$ZQJ3ebRQJ9C*+=^+*as{iHD52N%MDP(KcY``|)JI8ZDTk)CJK3(xMO06jcB z^j3lT`|VnuB^O#{oviS_7#YJ_QpnYG(+*_CebjZd<^THjt^D%ljf}@*sp>5uRTmr5 z7O-SBxYyU$^7HH0^3$tV|CGs_3l$!nNB0kDW&QEtgZ%dHo$S=R8_O1;j`}?6e1Fxu z2+z;YrQO%8|MTWVh3CVsw|90#+x)@7K`#JZU0pS0@O+|s;d#61(u%(E~vjRPT47_yhbNMEEe90-xZ5C9?~p zwu+)DgXe9nt^qnZIgzuoGuhwYe{`UZHk7FY-Q3(XWdI?pmm%j}_2mufRAOt`t0f5*Cgk+tyu$b;9N2D+WKnMX4 z0w4rH2!Id*AOt`NfDiy7VJSmc&Oj@3fCC{?7y=*!K!{E>9ci>;{o_E06ow){=9e@^ z`oe(_DNI(lXr=w$$Lq!adbRm0WlIhqwKEH^`czBrz|ZRImZsBbbM;fz@%cdd(cjiG zwD09th9FAzzOZG-IX*s?A6~qWz1`hzZ_Zgb&+VsAO&O#oCnSaaPc=6K*0B8e@@4ah z;o2!Hw-s&_AS-FCb%8F=Mf|Vx61|PSk{E?)#l-*+qcE#3lt2ac=|0Q@p>AfPnVH6Y za3BBk}m;~Hx3_{df5wb9$XQ<1?MJ0VN zM@TR0uYceAQ*P+8mQam8`V{t{m&VmWOwEnD+`kkJK%rcyZGC(7Wo^0*!s}Lur$}R< zu?PuM$Sz0L$7S_T0cbE7$m!{+42Hu;2kPR&xCr(R$pRSNO)#I$WHOn^yx!Ru!i7Wk zSF=7YYJDn1x+OHl{?C`4=+S5tB|K60fiz&_@tAS)(qJO?ml&dRAtX`udnEKc5VO>2 z!n1LJfXFvIod5(xe&Oj1ARzJyPp1F@kJz00;pPLI8w1 Z)MwJ(n|O;bx)}ff002ovPDHLkV1m!4s#^d6 literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/packages/tauri/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..cb9d5ef522f2329901f989c639baae42a76c9b63 GIT binary patch literal 1154 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;VDa>HaSW-r_4e*vzuR6S4Hq|x zHaQ3gd@yl!dV4w4CR97FU7)q#Xurp1X15jMp}|&(Zz8?B&c*gWeRF3;`Cp!VW2I%+u?NA$9K8pZKNE;L z>@WXh*79o~f{R(!K3x8c11NA-@Q15JXm;%1m6f6!m=ahEzAnEOdVtHJ$XEt!%Li5o zOnV#{T(f2!+x>^}^wS`Q+HW5|Y^W1%-SG3lam(8`ZwflhU;l4;dbrYOH)nTaef_Fu%N;((GydS5 zb)Oj$7?|PWktgc+?tzFyQMUopiVv^$ci3)Peb#j47J;P4r?P}krx*#YWcZY`zUSfp zZMT%Ib#wZ2wzL1r*~D>9c~?3Hsg8u`k*!W?b{^X7Ncwum`|nGzp1cot5}r$hE=SQVbiN^F2@#3-J)(0_gZ%%hr;XhxA(WX zOxwWw@vU{I?nI^&YYTI*r5dzEgq!OzQ{MB*4VHkl6=LBw;%l}%XF0Cj$IF-^>YrRw zRi*W+OZu?N^WMTYLUnf=FJ>z%l#kBWdcS=`qELPm#`SIAFR?C&z~&* ze*l<6HofqDGx_9c=@-fumZ|64{weQvy{LWQY|Xt0#(xE>yJz~b*NdM1X}jPz$4lh^ z|K8)tm*1>E`%_ckhb=7UA?3OQ2e!_ywbqJYgoN9dXWkq14=tJZj(r<2H)*VNyYX#L zB;y9A2-X!JU;V$&EWlVR^-qVfqhTLU{Wb;{hxlii+Rjm*?(;7_ckq4|gVTa5{sMN( jCfwm>!5vj!>g{_8hg8dEEspTNpfD{an^LB{Ts5!71CT literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/tauri/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..557ddef408ed5a45670d117cee3344cbb22e2b78 GIT binary patch literal 1641 zcmeAS@N?(olHy`uVBq!ia0y~yVB7%09Be?56MhC-3=C`ro-U3d6}R5ryq$U2K%yZq za@B?G1>7vVJgw7SX0-MQDk^O^dYRf_apQ@M!MD%)XVh={pKm`b!;p|ZuLo!rGXyyt zV1OV20|q!^;bDLv3{^n2xK#m7#54|W7Lb8q9Na7*1FjH56;?|L8rSpt#a?NqH7dt4 zIGBz_d1}2+l*PlzG-uWdJ+@2i1)^v|d|LMD)J_jORvP=t#(mQay zxBXs)L}PtIL-}dWK^PP}7G9gB_{QVhO#uM|iH}v5a?Wc_k4W&Q9Ikr5aEg$fzq+9F zfdq+0U1cf-8pS@e!uwbcy$4T9bTHP5|4}0e}kNPe_1Bi`uzNb zTerEK;j7>M`bssw<@2YKEIj*q3;xFB)US0o@F443eX9Nc13f@5+S_?VYGX@ESW_l8 zh4A!?HO;|NCRXF%>6c*2Br!=DGm)A%h<9hR^ozaimGh=s-%jlnF!+%^YuSY2xOvGv zY@0sz9*bDLF-;-CX3vzMivK?%%D%rzK5Tf6hlOXIATzeGBRy288_04#Jr@NI>~3v% z>%Z1?N|4k0y9N*1K1#h8`)y<^&$rp|jXN;MB&e=6buM5^J}WHoz*vdt87s&u5Co4v n;)9QFh3FX$%F5ja>)%ZK-?hnF8^4eMRv`?Yu6{1-oD!M<{v1p; literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/tauri/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..df0abee5176672740cdc8c3c78fc4504addbf562 GIT binary patch literal 1524 zcmY+EX*ASp7{K58o4Ll=h74LXlXOFA4uvA)Z!1cOvERx;CnV~Mn#hbLOJ`*2w1_Mn z%9@c-B+HaN%gH`=L%K4SMvSq{9k=D2`{8}x^ZuUa%k!My^TgZP9GAvy!vFxJtt`#$ zMQ_dKLH{AT?FaqE0YJUAGB+V#XLE<NJss|b zM|61KG3Z zd9u8!YM30EfG&O;1gr92eruoN{e3OeeS;FV|5bm1zFbTJ#7)vOl;fj?D=Xy_ONSfG zCcM=p*xKAhFAMf8!YTiO^uo@mQs3m(L7DCisXZo!JvdF4Esf*8ew>2$XdK~9uQVKd zZ8q`z$}7n|3#W>Y@$b4q#O4^K^X}_+OEHI7i67;CA~I0Lg54K!c8CnfWfSKBJ4C@o zqZ^70a)1-n`vs&4>^t4oTWZr|?2f8%Jj5@ML4>_1M_4T&rlk$%!Ccj=UQ(_^W+54Y z^H?4|9;doS9UgknfVVY^sL{qGU1u7h-tFECLG{2NxnNGs6rVr6bb;$-o~X|n zI7Rn2XsIPB$z=g3L8shMdhKOP1`1wVm;}) zxj?UGNbdbCqlqf}q${K?Wc9#EtFlTQWm!WTH=1gl-PwBjJ>Ql0cvRV)F6Wl9eyjRY z3pd+#GB?z~e4xkZJd??s%nMa(E(!LPD4H@`Sn$n+$ZSvR3Pp8x20eCM!cJg(xoRa$ zcy^BTZ2LgS6ZPOqjp5z$38zs1NlySU*qj2|4b*(>W%b8_(cEv44lIepf?uKE1F{J+ zy1tRoRBWu*gi*QUuUd*E%H)pSmM+2oEry0@qC&wcn3xtiiuz!UK!ZN9K5>GxkO{$( z-WZrRqJFSHZJSD<$6z@9$_{QKX5Oaf>-W#nGYkD2)lP^qPC+m|(>FrLG2;5FpsucN zAiDI@cj$wQ*GDHR&VxuyBN-WKQEzTkQp7^JT#Lr7V~N_K6g|Y!pRE4=$MFPsSno^U zjcMzB!UN0CMT%(7BJqu+(T)`dky<%W;~PP~NG-o&Ic0-DO+W^YV5>6~2Q=Z<5a{OA zNfZr}fgy0FM^b=V&C3ZZNu(A-lSa>{&Otj&q56c20jq7chFy=8r$;|dH`L-{SuJN} z#p)<=-8Df5AIAIda66i>8mvRN+(tN z-f`n83G)quGLEPizf|$wa$5<{;EFeuVUksfK663a$Ao1ri=@|Pv)g6u+XeRtPT`9U zWpz7Rh^LNS!+YqZSmkDWbqU?y`?o7vq%J2UTc1;8ol5{}u=Eih9V%r)hKhq2Fuzk0 zhQ@4(KqA`s9ZMn6DQ#}+~+7P6(0P0>dYpDcIHAi?crv7hVphfX!b a6{Tc{CKs(Q%4(u*4y-I}%nMCDBL4*u>!CUT literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/android/values/ic_launcher_background.xml b/packages/tauri/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 00000000000..ea9c223a6cb --- /dev/null +++ b/packages/tauri/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/packages/tauri/src-tauri/icons/icon.icns b/packages/tauri/src-tauri/icons/icon.icns index 12a5bcee268851fdab744ec4a31f75d3d008e031..8a6b43f96c847749c8433d7913887c29bd57791c 100644 GIT binary patch literal 18738 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 H;THHG7wvBw literal 98451 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∓t$dRJGIadE?vc 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 Kx8Vjl{{H|h@<;Lj diff --git a/packages/tauri/src-tauri/icons/icon.ico b/packages/tauri/src-tauri/icons/icon.ico index b3636e4b22ba65db9061cd60a77b02c92022dfd6..7749a74b110ee69414f63e0fcec82dadf7c02dd3 100644 GIT binary patch literal 4669 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 LHzdl>59PlBH{j|A literal 86642 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 AKmY&$ diff --git a/packages/tauri/src-tauri/icons/icon.png b/packages/tauri/src-tauri/icons/icon.png index e1cd2619e0b5ec089cbba5ec7b03ddf2b1dfceb6..ae5fdabbe0ea0623fbeeecfb04e0ae8cded2d183 100644 GIT binary patch literal 1862 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&zE~)R&4YzZe+Uc|BbmLn>~)y>XF~*^tNO zpiB&-P?`VEf3FQzuq@}C%wiIJsLuAAoZxR(hAl<285md?7!-iI85tNj7#JLYETEAB z3=9oG7Jexbl`8-Zgjhn9=?LWxK#dSLVH0Zr%Hnq?P?rNx5wYF^Dn|rH1A_r0{&1;F z36*4=HEX8ZK=^6~5q?K_1~mwXjJ|>kXH1-uSD)e_QaxtEp?|=Dyi8?n6aj^gZ literal 14183 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

5l#v3A#xI3*z~1~fvo9Q(N^(==!|_FZ z*duZ=+M1~)8E|otX8KNZlr?qels#x_1Xq@9IIw~@9uAREJVH)Xw^}UclF6327}E42 zT)E&?U%TK?(+K7%R!`H5oX0i)4Qn5??Iw3p5J~6_u+aWehY{DSn}3V2p$bgjnAu?o)v@iC254fXeMv50$9YrpU`N?u@QIWs)T?SP|fa}(|9 zqAX+!7`cx=4)cCBg5h~pu(?@9`)aCr#oyz$ld=#RFxYCNZCZls@4v2~*e-t6PEVvV z&bbK3b3wt(Coc!ufAbXXC<**#HQ%J9k`New6iG<5RjtO4XVO?dCvwxD{kJ#tfQr(X zg^NTwF-FwAeS_{V4bfel8l`~NbfrTR2s!G>WduFWxH(t~aK4q=6rEE^$+Uox>gJO2 z{L<;6Q6nHa5#ZEM>H58not!)z(6*_=^~8}jWf*IG$AUKVWOZ4?)GfF z+BM#*wKKmLFD7E~W3U!$IVm$k_k1f&Kz6WV8@55P?r~bcg-Za-!rvW?ns&)KOGT2~ zlkAyqhQj=P$Eg3w#K~}zH@J5bo-BfHjInKSz$@?+Z)NPD4pHj^_Qxmi`UqoTy=`sV zLVxrXGuBr=QRm|}wg75yetQQK4fY3#P_~J}zEfPnb2C4Wo!E(d*(cA;b?7$g2in<( zPn)ghX}nzJPmb6(3Dpeg_GW~Hc}Lt=lgsSZz z!5QXyz7KaR;D`3Ee}d`af{H>WWZ|Io1QI3~4Ll_`g1(cRnhLK73Ro)7zPCd={1W2x zRp%Xlvv4>!<2@}$hz|!V{T}_eHx2xkLl^hQoZTCnsjCl|W_@5Fx2(+j0ogy&Y+;L- z<)G$*CiN7hOm^s!{U>1F7U=iNk{+u~dAC!eDz%=|glFW0jEZU1&o(G_c#wTxUjnG} z#cg3>jEpUi#Mlq@t?Msg_#geK^Lx@DyHWf7=AS5vVyM7YOjvUVCfcpVR<(+5!H?9- zySI6s>o3m&*zr||=wcPGyBkQV`EWJl@bH8qobjOp+sXL*)=&yX)8aAbf~tGv?a2SN zu^Ddo-z?DWk9h9Yz#5p^NU#x~wYSd?H@w@!2Gb4G)6-utEMV~~M85Br5ff(v5O1|T z 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 rmn literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/ios/AppIcon-29x29@1x.png b/packages/tauri/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..288506f6140298b577c0525f0a0f581bdfff535d GIT binary patch literal 481 zcmV<70UrK|P)Vay^bqzc#f#LPcmfYl4^dp1m0Rr%L>D57N#{#V8^jqC zO>HTC8Sw7Rp-Lw*C2n zoHD*b@mGoGbh;HPpk-MYjYe=?_pHf*=T%@sHTSY3SF6?aW~5|#uR0AT8|i<-KiA#? Xk`v8cAh)ZA00000NkvXXu0mjfTmszp literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/packages/tauri/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000000000000000000000000000000000000..013281df873220f2c4f559240b803ee0d6f0fdf6 GIT binary patch literal 595 zcmV-Z0<8UsP)FrgrWK^c@m8I(bD0cscqiOLZK0f{nD1l;fUB&uK$uu`dz z+yZOf0(m$bpwsDq<2Z0U9`Rz@lu$00VX;_{n5HQWD-DVYzn4pJM(vUr=5w$uOA7SI^Jwj# z(OkEvi%CVZUI$AO6zAev?Oiu&S$t?AGOnN2xy;8)q6l~@;bcm=^W1;#tC#DBF1Xw6 zNM!aX&JV85&%~k^lnU$Fwhb@g5g{9lyfEtG+BAxVR9v?nM%-?P2tYEsRAu*X2FRQTHxxWSkNJQB23p zbt!4c(>aQ8+wE2w6xYZU>e(mtjRrhpQeUfG&t0RSnAC@$xK?}D6>sf&y&il_CW#*G zo^Jp->hd|t7A=59`9rW~AA$|pHdfKBfsGn@YSu7r>DMhp`%P47wOW1bhN`4WxfhT@ h8I(a8ltCY4`~qIlEUuOJO*jAm002ovPDHLkV1j#^4(FrgrWK^c@m8I(bD0cscqiOLZK0f{nD1l;fUB&uK$uu`dz z+yZOf0(m$bpwsDq<2Z0U9`Rz@lu$00VX;_{n5HQWD-DVYzn4pJM(vUr=5w$uOA7SI^Jwj# z(OkEvi%CVZUI$AO6zAev?Oiu&S$t?AGOnN2xy;8)q6l~@;bcm=^W1;#tC#DBF1Xw6 zNM!aX&JV85&%~k^lnU$Fwhb@g5g{9lyfEtG+BAxVR9v?nM%-?P2tYEsRAu*X2FRQTHxxWSkNJQB23p zbt!4c(>aQ8+wE2w6xYZU>e(mtjRrhpQeUfG&t0RSnAC@$xK?}D6>sf&y&il_CW#*G zo^Jp->hd|t7A=59`9rW~AA$|pHdfKBfsGn@YSu7r>DMhp`%P47wOW1bhN`4WxfhT@ h8I(a8ltCY4`~qIlEUuOJO*jAm002ovPDHLkV1j#^4({ z=krNf-O%c*&hy)ncrjXXFC4|{k|04u6Mv*;p%GL@AopB&1AV+$? z?e%)HS}tX?*~nlpuz!mZu3NQ}$wcNiH_3#BulM)XZ}mU=uy86%`}4^-9Q(@cnsA{0 z`UV>WZruP@bvm7>;W|I{drc}vDu#dyT;QUJ;f5$ZL!?$o!i@pMn}S$QBo zy_NCRl~f)|&yLBfe?C3Qm%BSzEEX@X(<-6@p-Iyeav`Rs<(TOg zU&f+oVUp(p7q|$xzy&S>E^vX1fD2sUBH#iSxah6T&~$o`YY*1L$)CKj$NpTS(o78w z<|wOY!dB zwKb;~zdbx8b32pz8{+D#%=f=C;9jwSaJdmLaJJw!G?OhL-~tx`7r4Mhzy&UF5paPE dTm;-G^#=kj4i0^91b6@d002ovPDHLkV1i@?X;%OM literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/ios/AppIcon-40x40@1x.png b/packages/tauri/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..871f8e39d3945a15b36f21a1f4319ad50f73bd24 GIT binary patch literal 606 zcmV-k0-^nhP)-gD-He&1*?%J896#*Z8@2Rshcll?EYZ6^oF7^C@oP7eR9!~}z7sJzwdby5^1 z9v~(-91c{eROodyqDr+YzKe-DG|96&_-Y?|HBBRPy{7lcgiO;E-x<5?bV`s90%X76 zQ@h=!kLfhg5I=M}G@H$+P$&cfMhbDO)e^vALUU;z0Aa2ms8}MwSptSmbnjjX6-98d zq?xp`69L33UrwrYZwNtRx4pQek%x{tkr5_ z>`ije@;p~s+_@QZacI0Rj>W_Rgss2>m&;|K z51!V6sQ^mbC>D$K)bG=dZ*|y%A_P)@6c3(IC=M)Um;N-webI?hazNqxOu z_YVDA9s~dcAOHb~00=+;0uTWZfY}2qio*1O`@Xjgu^ikvl&|e1pw%NGj@i^>~g#bLAPQwhuC&~qrCemVop#Wv6KI=qU_D(eX zXeyQNiT5ciEQyiE01$ux1Rw$+009U<1V8`+5P;};ii_^6SvC5}3ZSecOLGBb8RLrd z*iSxtBA0Y}G(W(Y^1#be zANl{u0{Q4fpbvC>_Qb{mk_Q410T6%y1Rw$+009U<1VCkd0h2`kjcrBk@&Et;07*qo IM6N<$f<}w@RR910 literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/ios/AppIcon-40x40@2x.png b/packages/tauri/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a78ec73340f25eb25c61fd6112d9a2339d5f2abe GIT binary patch literal 570 zcmV-A0>%A_P)@6c3(IC=M)Um;N-webI?hazNqxOu z_YVDA9s~dcAOHb~00=+;0uTWZfY}2qio*1O`@Xjgu^ikvl&|e1pw%NGj@i^>~g#bLAPQwhuC&~qrCemVop#Wv6KI=qU_D(eX zXeyQNiT5ciEQyiE01$ux1Rw$+009U<1V8`+5P;};ii_^6SvC5}3ZSecOLGBb8RLrd z*iSxtBA0Y}G(W(Y^1#be zANl{u0{Q4fpbvC>_Qb{mk_Q410T6%y1Rw$+009U<1VCkd0h2`kjcrBk@&Et;07*qo IM6N<$f<}w@RR910 literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/ios/AppIcon-40x40@3x.png b/packages/tauri/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9de8403a8c0d1c1a6c5cee6fd65f52c050d3f01c GIT binary patch literal 855 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q}r7Fw1$mIEGZ*dOO!U@3w(}gS-6U zHb&*F+~*98S@W0OG{ zP{@6;W_xHVOO*FXH3tI)2>~7sHWub4#)D9*Aps?nM)>W~ttnm%qEb?)rcB z+s_o`XNOMw{WtE|MT`uPB=d@wn z>sPbBrfuews;ZWK^rxs?eQ(_R|M`v^*ss0bdPYv}$@A0QPqgCxu)j9t`*M%%js5+5 zRtIGc%ANciVl`HMN=ZlX!q31hH74 z2OL5II{Eb(cH4DVM|YIWUh~fQ+TqupoY$6~e4D4f`|Y*AvMIW^zAdknnx^%4vEreZ zDN6hG^#31itZrTZ&3bmu<7H_;nJCMr|4g`rRO9;p`-dVOrq?CK& z%>}hjc+Yre<&-^?|1IU5>$>2{=Ec6ToD)yZ*z%zGRmU@q+Fd&fcX}?2(_ImIdP{un z`g-mr+Xe5J|EZ`wdvfd9=Lx>gr+Dl<$&uy^G!|kf)ar${O|LIm1YK9md1bxgp3u)r zTR+cGtvnTRbnTW4Q?49r-X^?xMVO@Xx@pUQo6X)?)gWbYB1-GpmDkUr(gTmnI2W0% z(GpI6^(&Y8o7*La@&~7`+4U4^X(hA1Kfdi>f|twc$~m_hc6LD$C_LUVL*SrtJ)`+i Wj-r=p8XtfehQZU-&t;ucLK6UvM0QgE literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/ios/AppIcon-512@2x.png b/packages/tauri/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..348f719a1752eae0dd71e93d1da81d0a84845b72 GIT binary patch literal 7412 zcmeHLYgAKL7Ctuwl7bMeQ$-LGbq1Y^ijOKPkT{~0&ImY& zLx3fur+7|z@PaGj~@DNYf%A&w?>^`uL#xFO)bga{y>SIcaOqsMe#jfF5H z{%b6!bs}K!-hsTo(O3XDuTwJO&+T!tDhqb3$IU_PfpoO<&eDt5eU5hB+yn~*R+2TE zeer%cu9D~4vwNrj>nN+n!=27m?Rz)970jFyrezl|h8hQiRSIbaUyeADv@n=1cPeGrNSgh?CKy-RsK zUNg@nJKJr)TWN+b4N$=JpdgJn0p+R0N%&PYKGL?uch|16J-M+`sq~IbaL#c%03;l+ zC_&|OTAf#y#nXsNO3n!yO5(2gqjBz+)nX6Nt{#kM4Cl|a2m4<5^GGTlTD-qU*f1A# zTtL-ozUFc*H6rMIEu8`eWB%&W(dUivTbZCE-boQAU4r*&PURSZ*fu8R5<`9en3gZa zYYM~Uoe3vS@eqI=c-*c|OwtYv42-T5Ya+~Z%)oZ-1^~Wdu17-sk;EhYsIP?>0F2u? z2MBhl+Xtev)UenO+5_3A4t3cIaPgi609-erbA?=tN0caTL8rloPBa+NOfFIgEf� zx50-+G;bC$vP(a37O6WKqK=)Z0@si3m~{#!BxoB_Kz@=baO?zyibG)Q2|fV~j7a#5 zSKtsYrvTg#Uy!y~br zC?L95xgeEhcuULP!}JlS=>Umz9X0N_{OlULDGy*Ue=zP5DSAWAw6Q6xU@)88+U(lG z!c;w_Lzh8=9k(W+uv%2u6BzmFjO66xzK-thJh2Jk2&=lzE3@&!AbhP7f6*f?fcXu} zPhOq<_Ey|_@kxmvk^NgwM+t?(+#UAKot^JTM-A9%N1ufa;mSG(+xTauL^4P|0*>{) zR~8d6?aM^p_eMK-qp=xsrPJIN6n}oovFJS(djYfu6MEJ{qy3v4{-h1FO-U{9e?D)W zzZtzJPO&1Fnf#0`$4AmjYI526txOI~voE#1@|2=>wotNd~AS$;9IPxSw!1J4bS8T6);L1hY_M&9A6Ra zFwfHB)R0157m~7uX2o|bzt74R{hs^|IX?_qo$I@_ORl)?sW< z5AT;%W%_Io0#1IoS!uyprCQn7Qn7A$U?rKxT)uHm16~*s8EGXJu;Y^(QD(xsNXDas zS=GY<>p|LsKDJd{v*$pB{j4*@pr%H_3?lA0G3EPLoWG8VQ=QKwBqTVp+3`LNlmHsY zpAD+1=+UFw_BLuX$8A)~Iixy6whrYf8lR~|s0ke2V~l;97a069D}JtOc$6%hzX6Hw zlz!_pgqIwmc__^R55YnYBOTRWpf|Mbm&Ccth7q^`gMpj9gvN|p3T>OXi1B12nD=EA zz3KT5@q+pR2H~7^lbl_1vMTO=G*1%WX8}#YN56$*k-7$i97YcK?}O`ysAHW)648&j zA>jHU>R6v5aGj~@DH_0BPegKRI#bn8oGNgG9R15qZ~QhAZed^vBbuLi8#$iX_^s}1 z8(>428vy=RUo?phpRWjb$)rh|!tw8Rta*w+2BRw%#HOdGTgGixEeKj|EjM3f2|`<< ziv4PHnJ1YTbS+!<>n4p#v0NZ-k;%T+2HpJl9Uut?m3`4D>R%BZZ9;B0ufE_*aR7FAyO9qlbuPO8d;cG`IlQba&hP6@_m_*|za5@FNy- zk_9+d5Y~^URepDSU}>_8HE~|x?xW-k$X+}O*Nlb~MGjr_Q`h22RiYQ{e5FMEq?b}j z9F?KE$2pXN7!w2RDKb;kHlh(1h?M15D9%45SvFa7$jvna0WM~Q6d8P1PzXY$Cyv`# zx@Oy&+$rpFBabH?k5(cUsb*k@h6d@D5{smt^Z(b&35mVgq<(bC@PspbE6jNCN#l9E z!c>8eCpM{_@x&%tP(#2CaP*kTe;a&6lv9=7HJn}8f2^dp=mQ|BQM!nGXzi+~;y&W? zWU%{WK;G$pesU8nhFebY^WcsL2)0=!V~hJTGc$)mQdI+1#06Kmxk-%19fen5hgM5* zxmsOsO$Fvpw`jcy4c4tQNJJ3aVsY{d`$8p zUk)Ap4_;U%9qJ-5qn zcyR7*8yJn;Hox@w#uEs<*y7ISq;?Nf`ru(qmvC>87FZ*$>EvNNl{u1X1fI`=r_?BV zz##$MSAck3LtW+++)%Gq8gcTA=w4xzL_X?g)LmjFQ8K9@jW!$HZaus`)wFv92TU*i zm|7{--Whr-Bl_0((+V_x0Si$O+pvV{h!b~MgY&`bw-`S|KusF-uPGQ1t*?!pAaD&y zE>d@@z$f7NW|M|6Hw4@ONB@Uq0ZbT*Z=Ur;Z{NZkLvQY_Ah+Q$>Tfc0(;Wzz)Ft<> zBq9c1{rEv*k>a?1K#nI}K3P9Tjwd}%s&dHxuf}8K|5yDwR7m^@`Z?rK66%fPb^6Cc fy_)LSR0`;S-?j0<42A7`1g85Y57!&N3pn{Ndn$DJ literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/ios/AppIcon-60x60@2x.png b/packages/tauri/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9de8403a8c0d1c1a6c5cee6fd65f52c050d3f01c GIT binary patch literal 855 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q}r7Fw1$mIEGZ*dOO!U@3w(}gS-6U zHb&*F+~*98S@W0OG{ zP{@6;W_xHVOO*FXH3tI)2>~7sHWub4#)D9*Aps?nM)>W~ttnm%qEb?)rcB z+s_o`XNOMw{WtE|MT`uPB=d@wn z>sPbBrfuews;ZWK^rxs?eQ(_R|M`v^*ss0bdPYv}$@A0QPqgCxu)j9t`*M%%js5+5 zRtIGc%ANciVl`HMN=ZlX!q31hH74 z2OL5II{Eb(cH4DVM|YIWUh~fQ+TqupoY$6~e4D4f`|Y*AvMIW^zAdknnx^%4vEreZ zDN6hG^#31itZrTZ&3bmu<7H_;nJCMr|4g`rRO9;p`-dVOrq?CK& z%>}hjc+Yre<&-^?|1IU5>$>2{=Ec6ToD)yZ*z%zGRmU@q+Fd&fcX}?2(_ImIdP{un z`g-mr+Xe5J|EZ`wdvfd9=Lx>gr+Dl<$&uy^G!|kf)ar${O|LIm1YK9md1bxgp3u)r zTR+cGtvnTRbnTW4Q?49r-X^?xMVO@Xx@pUQo6X)?)gWbYB1-GpmDkUr(gTmnI2W0% z(GpI6^(&Y8o7*La@&~7`+4U4^X(hA1Kfdi>f|twc$~m_hc6LD$C_LUVL*SrtJ)`+i Wj-r=p8XtfehQZU-&t;ucLK6UvM0QgE literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/ios/AppIcon-60x60@3x.png b/packages/tauri/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..b06c67dd84b8eb08389de6ae9720a5f603ad0c82 GIT binary patch literal 1085 zcmeAS@N?(olHy`uVBq!ia0vp^TR@nD4M^IaWiw)6U}5oeaSW-r_4cl#pKzeefrs_p z(jIJ=CY^FReCdd^g!v|gO{RKZnTpi*Fx)$|;TX4sWh18|ZwX^--)A>(gN9i*CRKj@ zJ!zSq{QCW8_B6fsO~3GdIwSX@WG(>{4k;GiCbtU;8DM7b0!Jv*u^Gy2VuLb)>L5&x zU6#|PO{*#5fAH#+*M-kv(^7fb5C59`GjnTHpor^Vy%(0p?z}B~eD$2)-#9{w%I)#Z2mQ~f+Wef`HbFE1~wsrmEx z(ZrLohNo4JzkHdwX8ro>*?XpGe>wBqyw=^bc+TTZ>n1;Zq<8D8uA%4qrg^m&F7G^B zXgU39ZSKX z^}TG=(tOhY`}fDh{P$JNc_x20%$+=Wa>f4L()suI?*1pqa`x-73yassKfZj>QRMV# z!*$y}nn!N8I+j>cCwKey?frjvnx5VIbjqt_Rdo6K`|Hmy&G^C|&RYBLZM1C~OYQrq zzqIzQx?O&Gx=iNTImcd|(#b%CDcptd7B^qBf@K-`tWG$FE;L z{1QCld%r1Ee&>hcGaSGCTmz3R&N}}6*fBS;YOc$nR=tvr_jlE*&z?Q|V^Owt)!sO< z;}XCKsybs{&*u2~^rYlncQUFjWS*ROEbFA=3;X(;r=EH*T)k<*|FoBhXTYKK{HN<_ z5ss>h|GYY2iJ_ulWgHyz{3mmtkqDV+Nf+mHJ7tAc%9lQTj+x$f) zvda$_ved5sc+|D$+_^=!-==-ccw%zR1Q<}y4{OWYR|=hv-_vpR>eU%0HZRIVzeI|b zNNr{>;QDp#t+lSaS-H8o%c6hI>^C`X8K+~l zGuF~=ap0cHzq{-=~iiG3QHXeV${+x1H^8-OSs%hQ1|{QJ&|2 z`$dSjmW996nJ%+8&cBd5P))+-QL~lcMyi;6%0xSPWe8KMk6{qJ=Jfe zlpff^GfaXgm&uikr=*4${^XNE43?p2TDemi?m7Q$Wn; zw+q0&#{^jc9R^?kW~;W?5|Sd6^RAUDZ&>nyzQsbE_?$an$%j2XKGI+?NTrR$&tx*8 zPNze=dwaP;%MDn_>R)ux&Gj{1^!v27x~lb>QPAw?<^7$mE-&f4*ZcB4mo;puQX$3J zP^;Ba0UM9U+Sg(f{b}JhE&lqq?8BylUkq6RV9;UNS^?`-WZ0|#6WwI0or>&)s3Q97 z(X_E=Dl4=f%e{K%fF)VcZnx>^@Q@mf28D*eeAZ#Z;ZXa^JL7WCuxhnRtyYWHx%VHj zLN{$xR#>!(uZ5VBvJMRR2@QeyQy+6ZC4UzCv_-$D!Vg(73u{RLLxyE9FI@BGZiZQ$ zk_*w^i7_QHC1=KB$O?2^oodY>!%*~5^r7@^cP!AYpx@7&%C5CE0FC=$+&~l`X+Rk9ewPy@4m{j` zfWfn2R&Pa4(vz$&F@9er1g~T0G(UKBW&xwql!+hYv-aiF-0@iB*)tjSGS8ns zXOa#L4gHu~ZoVedVAi60r=C95=yFOZms|0RAvPxFNOx=Nk3AbN-dVQ3JHFg5WH0~s z7|A~he*XT)FFs7n$+>3Cz88zRa96Z;r`gtH?9WOaHDkZ?t91yzt@etE@j2UsugMn}2LF`}fdh&dXUD zx4xKN-TJ$I|Gs}eUafmBb*6c0>diN^52(e=Ivl^2+1kS5L{jJbvX$SC9zB{?&muOt z{^N1azu#tTi;24+a^uaLoNe09+&O2ub-I47)tGB}DLRNReZ%R~r{jM=F~%3)Aje}% z2SD)%n+y_esYf93g}x4(~7 ztb6uoV%4>`FFtIrOxxMh^1tlF?$pSq6|epW#xDu9wCw$+bS--4o;gn=b#8AzF*VNL x^81f7S{pd+6Mp1#K@uF6_y#9Lq?{#j+yB>3&A(^Ximzh;0#8>zmvv4FO#n9`*Z2Sc literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/packages/tauri/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fd49a44f352605782ebba93f37f2fe43d9ce78d7 GIT binary patch literal 976 zcmeAS@N?(olHy`uVBq!ia0vp^%R!if4M>)V`MhCZV4m*j;uunK>+M~CFX=>y0}svX z72OOUIq^SSR@J=ggP)V`)(5kF3;Vg({n6S2o%?Mqo!ufI@6zp zm65X7@BF`zT^#8j7aza)^2-(3o}Yi({Q6s`duYv7_C;A6@2=x_%eLq%t;~KB?}^*t zJ#Ia^F23&$ilm!-einU1a%Yd$lED7et5^Rhs*+kfckZk7ou1i$K7SCnudny$$&)9^ zcOT^(So-Jt-`YRB_V%7Fk}5Qt{q%dcrhCx5Q<;~yoZGdBRq*+&B|G27S@v()x^-&q z9DRNLt5&hsq$}9wPB*@N^tQH9jn?r!YW~kKf(;V7z+jauy)pgxuOpI@_bx={N=$t> z{mjRCCy(C#b?EP$-Z^>QWk0=&EiI4Dyf*dP^lO^u+I(Z)y?-Arou8YZpI)h6bL-Nx zj~_Qir3b6KN>@MgNnh66+bc0;z0%>Ar^|Nk+cGnBvN>z^7oI><`;fMoumAP8N-lb~ z@A;XZML##{y|;9P#49Lz!43pPFYf3^SLSA0p6`8o<&yOE%hy}l3Ga)VZ!tSP?%kq~ zfBwkqjnluqHty1|XOioD=lrjn-nGT@+QEa4Jm%(`dyD^k30ZqtEt2kLTxc%{!ZRQS-QX$I|D^K&c>a(>m8VFCCUCZc&kq znD+f&``o^CS*Eww6wmD5z57RHt?cBJNAAAXmOFniAwc%L70az#vb|zE`-6OzUzX@U zewF9DQ?mBiSIZxp%kUlD`Hr1ugPd#Njy3B)7hQSM&i!KxANGU~k3mTOfCu2CmOp=3 YGwivNc$d1|1?E%+Pgg&ebxsLQ0Lhrog8%>k literal 0 HcmV?d00001 diff --git a/packages/tauri/src-tauri/src/lib.rs b/packages/tauri/src-tauri/src/lib.rs index 4a277ef350f..836d575cde6 100644 --- a/packages/tauri/src-tauri/src/lib.rs +++ b/packages/tauri/src-tauri/src/lib.rs @@ -1,14 +1,269 @@ -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) +use std::{ + net::SocketAddr, + process::Command, + sync::{Arc, Mutex}, + time::{Duration, Instant}, +}; +use tauri::{App, AppHandle, Manager, RunEvent, WebviewUrl, WebviewWindow}; +use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult}; +use tauri_plugin_shell::process::{CommandChild, CommandEvent}; +use tauri_plugin_shell::ShellExt; +use tauri_plugin_updater::UpdaterExt; +use tokio::net::TcpSocket; + +#[derive(Clone)] +struct ServerState(Arc>>); + +fn get_sidecar_port() -> u16 { + option_env!("OPENCODE_PORT") + .map(|s| s.to_string()) + .or_else(|| std::env::var("OPENCODE_PORT").ok()) + .and_then(|port_str| port_str.parse().ok()) + .unwrap_or(4096) +} + +fn find_and_kill_process_on_port(port: u16) -> Result<(), Box> { + // 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() + .sidecar("opencode") + .unwrap() + .args(["serve", &format!("--port={port}")]) + .spawn() + .expect("Failed to spawn opencode"); + + tauri::async_runtime::spawn(async move { + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Stdout(line_bytes) => { + let line = String::from_utf8_lossy(&line_bytes); + print!("{line}"); + } + CommandEvent::Stderr(line_bytes) => { + let line = String::from_utf8_lossy(&line_bytes); + eprint!("{line}"); + } + _ => {} + } + } + }); + + child +} + +async fn is_server_running(port: u16) -> bool { + TcpSocket::new_v4() + .unwrap() + .connect(SocketAddr::new( + "127.0.0.1".parse().expect("Failed to parse IP"), + port, + )) + .await + .is_ok() } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - tauri::Builder::default() + let updater_enabled = option_env!("TAURI_SIGNING_PRIVATE_KEY").is_some(); + + let mut builder = tauri::Builder::default() + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![greet]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + .setup(move |app| { + let app = app.handle().clone(); + + if updater_enabled { + tauri::async_runtime::spawn(run_updater(app.clone())); + } + + tauri::async_runtime::spawn(async move { + let port = get_sidecar_port(); + let socket_connected = is_server_running(port).await; + + let should_spawn_sidecar = if socket_connected { + 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); + + let timestamp = Instant::now(); + loop { + if timestamp.elapsed() > Duration::from_secs(3) { + todo!("Handle server spawn timeout"); + } + + tokio::time::sleep(Duration::from_millis(10)).await; + + if is_server_running(port).await { + // give the server a little bit more time to warm up + tokio::time::sleep(Duration::from_millis(10)).await; + + break; + } + } + + println!("Server ready after {:?}", timestamp.elapsed()); + + Some(child) + } else { + None + }; + + let mut window_builder = + WebviewWindow::builder(&app, "main", WebviewUrl::App("/".into())) + .title("OpenCode") + .inner_size(800.0, 600.0) + .decorations(true); + + #[cfg(target_os = "macos")] + { + window_builder = window_builder.hidden_title(true); + } + + window_builder.build().expect("Failed to create window"); + + app.manage(ServerState(Arc::new(Mutex::new(child)))); + }); + + Ok(()) + }); + + if updater_enabled { + builder = builder.plugin(tauri_plugin_updater::Builder::new().build()); + } + + builder + .build(tauri::generate_context!()) + .expect("error while running tauri application") + .run(|app, event| { + if let RunEvent::Exit = event { + println!("Received Exit"); + + let _ = app + .state::() + .0 + .lock() + .expect("Failed to acquire mutex lock") + .take() + .expect("State not found") + .kill(); + + println!("Killed server"); + } + }); +} + +async fn run_updater(app: AppHandle) { + let update = match app + .updater_builder() + .version_comparator(|v, r| { + dbg!(&v, &r); + r.version > v + }) + .build() + .unwrap() + .check() + .await + { + Ok(u) => u, + Err(e) => { + dbg!(e); + app.dialog() + .message("Failed to check for updates") + .show(|_| {}); + return; + } + }; + + dbg!(update.is_some()); + + let Some(update) = update else { + return; + }; + + let Ok(update_bytes) = update.download(|_, _| {}, || {}).await else { + return; + }; + + let should_update = app + .dialog() + .message(format!( + "Version {} of OpenCode is available, would you like to install it?", + &update.version + )) + .buttons(MessageDialogButtons::YesNo) + .blocking_show(); + + if !should_update { + return; + } + + if update.install(update_bytes).is_err() { + app.dialog() + .message("Failed to install update") + .blocking_show(); + } + + let should_restart = app + .dialog() + .message("Update installed successfully, would you like to restart OpenCode?") + .buttons(MessageDialogButtons::YesNo) + .blocking_show(); + + if should_restart { + app.restart(); + } } diff --git a/packages/tauri/src-tauri/tauri.conf.json b/packages/tauri/src-tauri/tauri.conf.json index 754569c6ae6..fcb6c03de6a 100644 --- a/packages/tauri/src-tauri/tauri.conf.json +++ b/packages/tauri/src-tauri/tauri.conf.json @@ -1,8 +1,9 @@ { "$schema": "https://schema.tauri.app/config/2", - "productName": "opencode", - "version": "0.1.0", - "identifier": "ai.opencode", + "productName": "OpenCode", + "mainBinaryName": "OpenCode Desktop", + "version": "../package.json", + "identifier": "ai.opencode.desktop", "build": { "beforeDevCommand": "bun run dev", "devUrl": "http://localhost:1420", @@ -11,21 +12,22 @@ }, "app": { "withGlobalTauri": true, - "windows": [ - { - "title": "opencode", - "width": 800, - "height": 600, - "url": "https://desktop.dev.opencode.ai" - } - ], "security": { "csp": null - } + }, + "macOSPrivateApi": true }, "bundle": { "active": true, - "targets": "all", - "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"] + "targets": ["deb", "rpm", "appimage", "dmg", "app", "nsis"], + "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], + "externalBin": ["sidecars/opencode"], + "createUpdaterArtifacts": true + }, + "plugins": { + "updater": { + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDgxNjQ5OUExQjg2MEI3ODAKUldTQXQyQzRvWmxrZ2VOU09ta1IwSVpKSlQ1ZjJsRVh3cmRZcEZKUlF2MzBpTHcwWU82WWRLOXIK", + "endpoints": ["https://github.com/brendonovich/opencode/releases/latest/download/latest.json"] + } } } diff --git a/packages/tauri/src/assets/tauri.svg b/packages/tauri/src/assets/tauri.svg deleted file mode 100644 index 31b62c92804..00000000000 --- a/packages/tauri/src/assets/tauri.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/tauri/src/assets/typescript.svg b/packages/tauri/src/assets/typescript.svg deleted file mode 100644 index 30a5edd33cd..00000000000 --- a/packages/tauri/src/assets/typescript.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - diff --git a/packages/tauri/src/assets/vite.svg b/packages/tauri/src/assets/vite.svg deleted file mode 100644 index e7b8dfb1b2a..00000000000 --- a/packages/tauri/src/assets/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/tauri/src/index.ts b/packages/tauri/src/index.ts new file mode 100644 index 00000000000..b9060d6285b --- /dev/null +++ b/packages/tauri/src/index.ts @@ -0,0 +1 @@ +import "@opencode-ai/desktop" diff --git a/packages/tauri/src/main.ts b/packages/tauri/src/main.ts deleted file mode 100644 index 6b8710aec88..00000000000 --- a/packages/tauri/src/main.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { invoke } from "@tauri-apps/api/core" - -let greetInputEl: HTMLInputElement | null -let greetMsgEl: HTMLElement | null - -async function greet() { - if (greetMsgEl && greetInputEl) { - // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ - greetMsgEl.textContent = await invoke("greet", { - name: greetInputEl.value, - }) - } -} - -window.addEventListener("DOMContentLoaded", () => { - greetInputEl = document.querySelector("#greet-input") - greetMsgEl = document.querySelector("#greet-msg") - document.querySelector("#greet-form")?.addEventListener("submit", (e) => { - e.preventDefault() - greet() - }) -}) diff --git a/packages/tauri/src/styles.css b/packages/tauri/src/styles.css deleted file mode 100644 index 7011746b1ca..00000000000 --- a/packages/tauri/src/styles.css +++ /dev/null @@ -1,116 +0,0 @@ -.logo.vite:hover { - filter: drop-shadow(0 0 2em #747bff); -} - -.logo.typescript:hover { - filter: drop-shadow(0 0 2em #2d79c7); -} -:root { - font-family: Inter, Avenir, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 400; - - color: #0f0f0f; - background-color: #f6f6f6; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -.container { - margin: 0; - padding-top: 10vh; - display: flex; - flex-direction: column; - justify-content: center; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: 0.75s; -} - -.logo.tauri:hover { - filter: drop-shadow(0 0 2em #24c8db); -} - -.row { - display: flex; - justify-content: center; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} - -a:hover { - color: #535bf2; -} - -h1 { - text-align: center; -} - -input, -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - color: #0f0f0f; - background-color: #ffffff; - transition: border-color 0.25s; - box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); -} - -button { - cursor: pointer; -} - -button:hover { - border-color: #396cd8; -} -button:active { - border-color: #396cd8; - background-color: #e8e8e8; -} - -input, -button { - outline: none; -} - -#greet-input { - margin-right: 5px; -} - -@media (prefers-color-scheme: dark) { - :root { - color: #f6f6f6; - background-color: #2f2f2f; - } - - a:hover { - color: #24c8db; - } - - input, - button { - color: #ffffff; - background-color: #0f0f0f98; - } - button:active { - background-color: #0f0f0f69; - } -} diff --git a/packages/tauri/vite.config.ts b/packages/tauri/vite.config.ts index c2ac9a3fd36..ead3d8a8dc1 100644 --- a/packages/tauri/vite.config.ts +++ b/packages/tauri/vite.config.ts @@ -1,10 +1,11 @@ import { defineConfig } from "vite" +import desktopPlugin from "@opencode-ai/desktop/vite" -// @ts-expect-error process is a nodejs global const host = process.env.TAURI_DEV_HOST // https://vite.dev/config/ -export default defineConfig(async () => ({ +export default defineConfig({ + plugins: [desktopPlugin], // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // // 1. prevent Vite from obscuring rust errors @@ -26,4 +27,4 @@ export default defineConfig(async () => ({ ignored: ["**/src-tauri/**"], }, }, -})) +}) diff --git a/packages/ui/src/styles/tailwind/index.css b/packages/ui/src/styles/tailwind/index.css index c58141922aa..7a24b42acb9 100644 --- a/packages/ui/src/styles/tailwind/index.css +++ b/packages/ui/src/styles/tailwind/index.css @@ -1,6 +1,7 @@ @layer theme, base, components, utilities; + @import "tailwindcss/theme.css" layer(theme); -@import "tailwindcss/utilities.css" layer(utilities); +@import "tailwindcss/utilities.css" layer(utilities) source("../../../../"); @import "./utilities.css"; @import "../index.css"; From dfe3fb8ed3d78490d20e0e8670d444f183ebaceb Mon Sep 17 00:00:00 2001 From: Github Action Date: Fri, 5 Dec 2025 20:41:00 +0000 Subject: [PATCH 57/60] Update Nix flake.lock and hashes --- flake.lock | 6 +++--- nix/hashes.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index 1660a9b9610..bc74b636230 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1764856222, - "narHash": "sha256-yEJmtoFu4cJre1NuU4fb8q57Oux+NTbocnALtJ64aEI=", + "lastModified": 1764915887, + "narHash": "sha256-CeBCJ9BMsuzVgn8GVfuSRZ6xeau7szzG0Xn6O/OxP9M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ece6e266caf1effab32eceef0403b797b4330373", + "rev": "42e29df35be6ef54091d3a3b4e97056ce0a98ce8", "type": "github" }, "original": { diff --git a/nix/hashes.json b/nix/hashes.json index 6bc2eaec159..c22d9f8b859 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,3 +1,3 @@ { - "nodeModules": "sha256-Wrfwnmo0lpck2rbt6ttkAuDGvBvqqWJfNA8QDQxoZ6I=" + "nodeModules": "sha256-Nem37Y/1kAA2vNOxDsMwE2qfkgneIKPg85JOv8Z0oug=" } From 73258c619369fbc9dc0250a1ea19f0e7ae03d7e7 Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 5 Dec 2025 20:46:49 +0000 Subject: [PATCH 58/60] release: v1.0.134 --- bun.lock | 30 +++++++++++++------------- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++------ packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 4 ++-- packages/sdk/js/package.json | 4 ++-- packages/slack/package.json | 2 +- packages/tauri/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 18 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index 4cad56d9789..5141ec41739 100644 --- a/bun.lock +++ b/bun.lock @@ -20,7 +20,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.0.133", + "version": "1.0.134", "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.133", + "version": "1.0.134", "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.133", + "version": "1.0.134", "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.133", + "version": "1.0.134", "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.133", + "version": "1.0.134", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -166,7 +166,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.0.133", + "version": "1.0.134", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -194,7 +194,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.0.133", + "version": "1.0.134", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "22.0.0", @@ -210,7 +210,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.0.133", + "version": "1.0.134", "bin": { "opencode": "./bin/opencode", }, @@ -300,7 +300,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.0.133", + "version": "1.0.134", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -320,7 +320,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.0.133", + "version": "1.0.134", "devDependencies": { "@hey-api/openapi-ts": "0.81.0", "@tsconfig/node22": "catalog:", @@ -331,7 +331,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.0.133", + "version": "1.0.134", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -344,7 +344,7 @@ }, "packages/tauri": { "name": "@opencode-ai/tauri", - "version": "1.0.133", + "version": "1.0.134", "dependencies": { "@opencode-ai/desktop": "workspace:*", "@tauri-apps/api": "^2", @@ -364,7 +364,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.0.133", + "version": "1.0.134", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -396,7 +396,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.0.133", + "version": "1.0.134", "dependencies": { "zod": "catalog:", }, @@ -407,7 +407,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.0.133", + "version": "1.0.134", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 15f00d6f322..6190b53d633 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.0.133", + "version": "1.0.134", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", diff --git a/packages/console/core/package.json b/packages/console/core/package.json index d324589b94b..5b6a977fc84 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.0.133", + "version": "1.0.134", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 0a3149cb1df..fc8b9f29515 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.0.133", + "version": "1.0.134", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 9f55fd8f26e..36faf76ecac 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.0.133", + "version": "1.0.134", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 7d345aa7af2..78e51bdbcb6 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/desktop", - "version": "1.0.133", + "version": "1.0.134", "description": "", "type": "module", "exports": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index a007bdcd959..43e187d454d 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.0.133", + "version": "1.0.134", "private": true, "type": "module", "scripts": { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 8a603582aae..2c21242c3ff 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The AI coding agent built for the terminal" -version = "1.0.133" +version = "1.0.134" 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.133/opencode-darwin-arm64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.134/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.133/opencode-darwin-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.133/opencode-linux-arm64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-linux-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.133/opencode-linux-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-linux-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.133/opencode-windows-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index be6e1b65c00..b63d203251f 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.0.133", + "version": "1.0.134", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index a8432322fbe..309c6e88ca9 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.0.133", + "version": "1.0.134", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index b8833364d06..618b8cf020c 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.0.133", + "version": "1.0.134", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", @@ -24,4 +24,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} +} \ No newline at end of file diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index aee34752836..0243b7ecd89 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.0.133", + "version": "1.0.134", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", @@ -26,4 +26,4 @@ "publishConfig": { "directory": "dist" } -} +} \ No newline at end of file diff --git a/packages/slack/package.json b/packages/slack/package.json index 2570f0877a4..58e07adab7a 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.0.133", + "version": "1.0.134", "type": "module", "scripts": { "dev": "bun run src/index.ts", diff --git a/packages/tauri/package.json b/packages/tauri/package.json index ebeb587ec82..2e2a47403c7 100644 --- a/packages/tauri/package.json +++ b/packages/tauri/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/tauri", "private": true, - "version": "1.0.133", + "version": "1.0.134", "type": "module", "scripts": { "predev": "bun ./scripts/predev.ts", diff --git a/packages/ui/package.json b/packages/ui/package.json index a20d19f6214..5a606488db4 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.0.133", + "version": "1.0.134", "type": "module", "exports": { "./*": "./src/components/*.tsx", diff --git a/packages/util/package.json b/packages/util/package.json index 4ac022e0cbe..89cf542736c 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.0.133", + "version": "1.0.134", "private": true, "type": "module", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 5e5b178f766..6b3abd43263 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/web", "type": "module", - "version": "1.0.133", + "version": "1.0.134", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index f71048523cd..311f96e5b39 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.0.133", + "version": "1.0.134", "publisher": "sst-dev", "repository": { "type": "git", From 9e2f6898f8327878d02c0fbbfbae667ad444711e Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Fri, 5 Dec 2025 20:58:08 +0000 Subject: [PATCH 59/60] sync: record last synced tag v1.0.134 --- .github/last-synced-tag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last-synced-tag b/.github/last-synced-tag index d53b6bb1f63..14b8ed5da0a 100644 --- a/.github/last-synced-tag +++ b/.github/last-synced-tag @@ -1 +1 @@ -v1.0.133 +v1.0.134 From 4694ed0de4b002110f82a5c85d413c2d9ff7b60f Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 5 Dec 2025 20:59:23 +0000 Subject: [PATCH 60/60] chore: format code --- bun.lock | 344 ++++++++++++++++++++++++++++++++--- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- 3 files changed, 325 insertions(+), 23 deletions(-) diff --git a/bun.lock b/bun.lock index 8aa038a78bb..e244b7bad74 100644 --- a/bun.lock +++ b/bun.lock @@ -812,6 +812,8 @@ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], @@ -3214,6 +3216,8 @@ "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + "querystring": ["querystring@0.2.0", "", {}, "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], @@ -3862,8 +3866,6 @@ "@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="], - "@astrojs/markdown-remark/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=="], - "@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.9", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.5", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.13.0", "smol-toml": "^1.4.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-hX2cLC/KW74Io1zIbn92kI482j9J7LleBLGCVU9EP3BeH5MVrnFawOnqD0t/q6D1Z+ZNeQG2gNKMslCcO36wng=="], "@astrojs/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], @@ -4082,6 +4084,8 @@ "@slack/web-api/p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="], + "@solidjs/start/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "@solidjs/start/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], "@solidjs/start/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="], @@ -4116,9 +4120,11 @@ "astro/diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="], - "astro/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], + "astro/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], - "astro/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=="], + "astro/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "astro/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], "astro/unstorage": ["unstorage@1.17.3", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q=="], @@ -4158,6 +4164,8 @@ "editorconfig/minimatch": ["minimatch@9.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="], + "editorconfig/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], "esbuild-plugin-copy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], @@ -4182,6 +4190,8 @@ "gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + "gel/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "giget/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], "glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="], @@ -4194,6 +4204,8 @@ "js-beautify/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "jsonwebtoken/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="], @@ -4214,6 +4226,8 @@ "nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="], + "node-abi/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], "opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="], @@ -4278,6 +4292,8 @@ "serve-static/send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="], + "sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "sitemap/sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="], "sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="], @@ -4312,6 +4328,8 @@ "utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + "vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "vite-plugin-icons-spritesheet/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "vitest/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], @@ -4374,17 +4392,7 @@ "@actions/github/@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], - "@astrojs/markdown-remark/shiki/@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=="], - - "@astrojs/markdown-remark/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=="], - - "@astrojs/markdown-remark/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA=="], - - "@astrojs/markdown-remark/shiki/@shikijs/langs": ["@shikijs/langs@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0" } }, "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A=="], - - "@astrojs/markdown-remark/shiki/@shikijs/themes": ["@shikijs/themes@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0" } }, "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ=="], - - "@astrojs/markdown-remark/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=="], + "@astrojs/cloudflare/vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "@astrojs/mdx/@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.5", "", {}, "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA=="], @@ -4392,6 +4400,8 @@ "@astrojs/mdx/@astrojs/markdown-remark/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=="], + "@astrojs/solid-js/vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], @@ -4618,6 +4628,56 @@ "@slack/web-api/p-queue/p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="], + "@solidjs/start/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@solidjs/start/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@solidjs/start/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@solidjs/start/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@solidjs/start/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@solidjs/start/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@solidjs/start/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@solidjs/start/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@solidjs/start/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@solidjs/start/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@solidjs/start/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@solidjs/start/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@solidjs/start/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@solidjs/start/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@solidjs/start/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@solidjs/start/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@solidjs/start/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@solidjs/start/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@solidjs/start/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@solidjs/start/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@solidjs/start/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@solidjs/start/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@solidjs/start/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@solidjs/start/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@solidjs/start/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@solidjs/start/shiki/@shikijs/core": ["@shikijs/core@1.29.2", "", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="], "@solidjs/start/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "oniguruma-to-es": "^2.2.0" } }, "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A=="], @@ -4644,17 +4704,55 @@ "archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "astro/shiki/@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=="], + "astro/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "astro/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "astro/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "astro/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "astro/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "astro/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "astro/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "astro/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "astro/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "astro/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "astro/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "astro/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "astro/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "astro/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "astro/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "astro/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "astro/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "astro/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "astro/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "astro/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], - "astro/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=="], + "astro/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], - "astro/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA=="], + "astro/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], - "astro/shiki/@shikijs/langs": ["@shikijs/langs@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0" } }, "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A=="], + "astro/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], - "astro/shiki/@shikijs/themes": ["@shikijs/themes@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0" } }, "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ=="], + "astro/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], - "astro/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=="], + "astro/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], "astro/unstorage/h3": ["h3@1.15.4", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.2", "radix3": "^1.1.2", "ufo": "^1.6.1", "uncrypto": "^0.1.3" } }, "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ=="], @@ -4748,6 +4846,8 @@ "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "miniflare/sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], "opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], @@ -4796,6 +4896,58 @@ "type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "vitest/vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -4816,6 +4968,56 @@ "@actions/github/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], + "@astrojs/cloudflare/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@astrojs/cloudflare/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@astrojs/mdx/@astrojs/markdown-remark/shiki/@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=="], "@astrojs/mdx/@astrojs/markdown-remark/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=="], @@ -4828,6 +5030,56 @@ "@astrojs/mdx/@astrojs/markdown-remark/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=="], + "@astrojs/solid-js/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@astrojs/solid-js/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], @@ -4950,6 +5202,56 @@ "tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + "vitest/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "vitest/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "vitest/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "vitest/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "vitest/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "vitest/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "vitest/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "vitest/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "vitest/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "vitest/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "vitest/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "vitest/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "vitest/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "vitest/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "vitest/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "vitest/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "vitest/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "vitest/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "vitest/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "vitest/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "vitest/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "vitest/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "vitest/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "vitest/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "vitest/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="], "@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 618b8cf020c..aa871922fc3 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -24,4 +24,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} \ No newline at end of file +} diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 0243b7ecd89..86edfeb0d8e 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -26,4 +26,4 @@ "publishConfig": { "directory": "dist" } -} \ No newline at end of file +}