Merge dev into agent-loop, keeping clean loop implementation

This commit is contained in:
Dax Raad 2025-11-13 20:44:32 -05:00
commit cd61185cba
67 changed files with 1283 additions and 609 deletions

View file

@ -4,7 +4,7 @@ on:
push:
branches:
- dev
- opentui
- windows
- v0
concurrency: ${{ github.workflow }}-${{ github.ref }}

View file

@ -138,3 +138,4 @@
| 2025-11-10 | 722,288 (+8,826) | 668,225 (+7,766) | 1,390,513 (+16,592) |
| 2025-11-11 | 729,769 (+7,481) | 677,501 (+9,276) | 1,407,270 (+16,757) |
| 2025-11-12 | 740,180 (+10,411) | 686,454 (+8,953) | 1,426,634 (+19,364) |
| 2025-11-13 | 749,905 (+9,725) | 696,157 (+9,703) | 1,446,062 (+19,428) |

View file

@ -40,7 +40,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.0.62",
"version": "1.0.65",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@ -67,7 +67,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.0.62",
"version": "1.0.65",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@ -91,7 +91,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.0.62",
"version": "1.0.65",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@ -115,7 +115,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.0.62",
"version": "1.0.65",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@ -155,7 +155,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.0.62",
"version": "1.0.65",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@ -171,7 +171,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.0.62",
"version": "1.0.65",
"bin": {
"opencode": "./bin/opencode",
},
@ -249,7 +249,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.0.62",
"version": "1.0.65",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@ -269,7 +269,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.0.62",
"version": "1.0.65",
"devDependencies": {
"@hey-api/openapi-ts": "0.81.0",
"@tsconfig/node22": "catalog:",
@ -280,7 +280,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.0.62",
"version": "1.0.65",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@ -293,7 +293,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.0.62",
"version": "1.0.65",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@ -323,7 +323,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.0.62",
"version": "1.0.65",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",

View file

@ -7,7 +7,7 @@
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
"build": "./script/generate-sitemap.ts && vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
"start": "vinxi start",
"version": "1.0.62"
"version": "1.0.65"
},
"dependencies": {
"@ibm/plex": "6.4.1",

View file

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.0.62",
"version": "1.0.65",
"private": true,
"type": "module",
"dependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.0.62",
"version": "1.0.65",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.0.62",
"version": "1.0.65",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",

View file

@ -8,14 +8,12 @@
<title>OpenCode</title>
</head>
<body class="antialiased overscroll-none select-none text-12-regular">
<!-- <script> -->
<!-- ;(function () { -->
<!-- const savedTheme = localStorage.getItem("theme") || "opencode" -->
<!-- const savedDarkMode = localStorage.getItem("darkMode") !== "false" -->
<!-- document.documentElement.setAttribute("data-theme", savedTheme) -->
<!-- document.documentElement.setAttribute("data-dark", savedDarkMode.toString()) -->
<!-- })() -->
<!-- </script> -->
<script>
;(function () {
const savedTheme = localStorage.getItem("theme") || "oc-1"
document.documentElement.setAttribute("data-theme", savedTheme)
})()
</script>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="/src/index.tsx" type="module"></script>

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/desktop",
"version": "1.0.62",
"version": "1.0.65",
"description": "",
"type": "module",
"scripts": {

View file

@ -347,7 +347,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Show when={store.popoverIsOpen}>
<div
class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-[252px] min-h-10
overflow-auto no-scrollbar flex flex-col p-2 pb-0 rounded-2xl
overflow-auto no-scrollbar flex flex-col p-2 pb-0 rounded-md
border border-border-base bg-surface-raised-stronger-non-alpha shadow-md"
>
<Show when={flat().length > 0} fallback={<div class="text-text-weak px-2">No matching files</div>}>
@ -382,7 +382,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
onSubmit={handleSubmit}
classList={{
"bg-surface-raised-stronger-non-alpha border border-border-strong-base": true,
"rounded-2xl overflow-clip focus-within:border-transparent focus-within:shadow-xs-border-select": true,
"rounded-md overflow-clip focus-within:border-transparent focus-within:shadow-xs-border-select": true,
[props.class ?? ""]: !!props.class,
}}
>
@ -396,17 +396,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
onInput={handleInput}
onKeyDown={handleKeyDown}
classList={{
"w-full p-3 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
"w-full px-5 py-3 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
"[&>[data-type=file]]:text-icon-info-active": true,
}}
/>
<Show when={!session.prompt.dirty()}>
<div class="absolute top-0 left-0 p-3 text-14-regular text-text-weak pointer-events-none">
<div class="absolute top-0 left-0 px-5 py-3 text-14-regular text-text-weak pointer-events-none">
Plan and build anything
</div>
</Show>
</div>
<div class="p-3 flex items-center justify-between">
<div class="relative p-3 flex items-center justify-between">
<div class="flex items-center justify-start gap-1">
<Select
options={local.agent.list().map((agent) => agent.name)}
@ -489,7 +489,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
disabled={!session.prompt.dirty() && !session.working()}
icon={session.working() ? "stop" : "arrow-up"}
variant="primary"
class="rounded-full"
class="h-10 w-8 absolute right-2 bottom-2"
/>
</Tooltip>
</div>

View file

@ -0,0 +1,104 @@
import { useLocal } from "@/context/local"
import { useSession } from "@/context/session"
import { FileIcon } from "@/ui"
import { getDirectory, getFilename } from "@/utils"
import { Accordion, Button, Diff, DiffChanges, Icon, IconButton, Tooltip } from "@opencode-ai/ui"
import { For, Match, Show, Switch } from "solid-js"
import { StickyAccordionHeader } from "./sticky-accordion-header"
import { createStore } from "solid-js/store"
export const SessionReview = (props: { split?: boolean; class?: string; hideExpand?: boolean }) => {
const local = useLocal()
const session = useSession()
const [store, setStore] = createStore({
open: session.diffs().map((d) => d.file),
})
const handleChange = (open: string[]) => {
setStore("open", open)
}
const handleExpandOrCollapseAll = () => {
if (store.open.length > 0) {
setStore("open", [])
} else {
setStore(
"open",
session.diffs().map((d) => d.file),
)
}
}
return (
<div
classList={{
"flex flex-col gap-3 h-full overflow-y-auto no-scrollbar": true,
[props.class ?? ""]: !!props.class,
}}
>
<div class="sticky top-0 z-20 bg-background-stronger h-8 shrink-0 flex justify-between items-center self-stretch">
<div class="text-14-medium text-text-strong">Session changes</div>
<div class="flex items-center gap-x-4 pr-px">
<Button size="normal" icon="chevron-grabber-vertical" onClick={handleExpandOrCollapseAll}>
<Switch>
<Match when={store.open.length > 0}>Collapse all</Match>
<Match when={true}>Expand all</Match>
</Switch>
</Button>
<Show when={!props.hideExpand}>
<Tooltip value="Open in tab">
<IconButton
icon="expand"
variant="ghost"
onClick={() => {
local.layout.review.tab()
session.layout.setActiveTab("review")
}}
/>
</Tooltip>
</Show>
</div>
</div>
<Accordion multiple value={store.open} onChange={handleChange}>
<For each={session.diffs()}>
{(diff) => (
<Accordion.Item value={diff.file}>
<StickyAccordionHeader class="top-11 data-expanded:before:-top-11">
<Accordion.Trigger class="bg-background-stronger">
<div class="flex items-center justify-between w-full gap-5">
<div class="grow flex items-center gap-5 min-w-0">
<FileIcon node={{ path: diff.file, type: "file" }} class="shrink-0 size-4" />
<div class="flex grow min-w-0">
<Show when={diff.file.includes("/")}>
<span class="text-text-base truncate-start">{getDirectory(diff.file)}&lrm;</span>
</Show>
<span class="text-text-strong shrink-0">{getFilename(diff.file)}</span>
</div>
</div>
<div class="shrink-0 flex gap-4 items-center justify-end">
<DiffChanges changes={diff} />
<Icon name="chevron-grabber-vertical" size="small" />
</div>
</div>
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content>
<Diff
diffStyle={props.split ? "split" : "unified"}
before={{
name: diff.file!,
contents: diff.before!,
}}
after={{
name: diff.file!,
contents: diff.after!,
}}
/>
</Accordion.Content>
</Accordion.Item>
)}
</For>
</Accordion>
</div>
)
}

View file

@ -0,0 +1,17 @@
import { Accordion } from "@opencode-ai/ui"
import { ParentProps } from "solid-js"
export function StickyAccordionHeader(props: ParentProps<{ class?: string }>) {
return (
<Accordion.Header
classList={{
"sticky top-0 data-expanded:z-10": true,
"data-expanded:before:content-[''] data-expanded:before:z-[-10]": true,
"data-expanded:before:absolute data-expanded:before:inset-0 data-expanded:before:bg-background-stronger": true,
[props.class ?? ""]: !!props.class,
}}
>
{props.children}
</Accordion.Header>
)
}

View file

@ -465,11 +465,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
width: 240,
},
review: {
state: "closed" as "open" | "closed" | "tab",
state: "pane" as "pane" | "tab",
},
}),
{
name: "default-layout",
name: "_default-layout",
},
)
@ -492,11 +492,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
},
review: {
state: createMemo(() => store.review?.state ?? "closed"),
open() {
setStore("review", "state", "open")
},
close() {
setStore("review", "state", "closed")
pane() {
setStore("review", "state", "pane")
},
tab() {
setStore("review", "state", "tab")

View file

@ -10,11 +10,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
const sdk = createOpencodeClient({
baseUrl: props.url,
signal: abort.signal,
fetch: (req) => {
// @ts-ignore
req.timeout = false
return fetch(req)
},
})
const emitter = createGlobalEmitter<{

View file

@ -44,12 +44,14 @@ import {
useDragDropContext,
} from "@thisbeyond/solid-dnd"
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
import type { JSX, ParentProps } from "solid-js"
import type { JSX } from "solid-js"
import { useSync } from "@/context/sync"
import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
import { Markdown } from "@opencode-ai/ui"
import { Spinner } from "@/components/spinner"
import { useSession } from "@/context/session"
import { StickyAccordionHeader } from "@/components/sticky-accordion-header"
import { SessionReview } from "@/components/session-review"
export default function Page() {
const local = useLocal()
@ -83,6 +85,15 @@ export default function Page() {
setStore("fileSelectOpen", true)
return
}
if (event.ctrlKey && event.key.toLowerCase() === "t") {
event.preventDefault()
const currentTheme = localStorage.getItem("theme") ?? "oc-1"
const themes = ["oc-1", "oc-2-paper"]
const nextTheme = themes[(themes.indexOf(currentTheme) + 1) % themes.length]
localStorage.setItem("theme", nextTheme)
document.documentElement.setAttribute("data-theme", nextTheme)
return
}
const focused = document.activeElement === inputRef
if (focused) {
@ -216,18 +227,15 @@ export default function Page() {
// @ts-ignore
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
<div class="relative h-full">
<Tabs.Trigger value={props.tab} class="group/tab pl-3 pr-1" onClick={() => props.onTabClick(props.tab)}>
<Tabs.Trigger
value={props.tab}
closeButton={<IconButton icon="close" variant="ghost" onClick={() => props.onTabClose(props.tab)} />}
hideCloseButton
onClick={() => props.onTabClick(props.tab)}
>
<Switch>
<Match when={file()}>{(f) => <FileVisual file={f()} />}</Match>
</Switch>
<IconButton
icon="close"
class="mt-0.5 opacity-0 group-data-[selected]/tab:opacity-100
hover:bg-transparent
hover:opacity-100 group-hover/tab:opacity-100"
variant="ghost"
onClick={() => props.onTabClose(props.tab)}
/>
</Tabs.Trigger>
</div>
</div>
@ -277,38 +285,40 @@ export default function Page() {
<Tabs value={session.layout.tabs.active ?? "chat"} onChange={session.layout.openTab}>
<div class="sticky top-0 shrink-0 flex">
<Tabs.List>
<Tabs.Trigger value="chat" class="flex gap-x-4 items-center">
<div>Chat</div>
<Tooltip
value={`${new Intl.NumberFormat("en-US", {
notation: "compact",
compactDisplay: "short",
}).format(session.usage.tokens() ?? 0)} Tokens`}
class="flex items-center gap-1.5"
>
<ProgressCircle percentage={session.usage.context() ?? 0} />
<div class="text-14-regular text-text-weak text-left w-7">{session.usage.context() ?? 0}%</div>
</Tooltip>
<Tabs.Trigger value="chat">
<div class="flex gap-x-[17px] items-center">
<div>Chat</div>
<Tooltip
value={`${new Intl.NumberFormat("en-US", {
notation: "compact",
compactDisplay: "short",
}).format(session.usage.tokens() ?? 0)} Tokens`}
class="flex items-center gap-1.5"
>
<ProgressCircle percentage={session.usage.context() ?? 0} />
<div class="text-14-regular text-text-weak text-left w-7">{session.usage.context() ?? 0}%</div>
</Tooltip>
</div>
</Tabs.Trigger>
<Show when={local.layout.review.state() === "tab" && session.diffs().length}>
<Tabs.Trigger value="review" class="flex gap-3 items-center group/tab pr-1">
<Show when={session.diffs()}>
<DiffChanges changes={session.diffs()} variant="bars" />
</Show>
<div class="flex items-center gap-1.5">
<div>Review</div>
<Show when={session.info()?.summary?.files}>
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
{session.info()?.summary?.files ?? 0}
</div>
<Tabs.Trigger
value="review"
closeButton={
<IconButton icon="collapse" size="normal" variant="ghost" onClick={local.layout.review.pane} />
}
>
<div class="flex items-center gap-3">
<Show when={session.diffs()}>
<DiffChanges changes={session.diffs()} variant="bars" />
</Show>
<IconButton
icon="close"
class="mt-0.5 -ml-1 opacity-0 group-data-[selected]/tab:opacity-100
hover:bg-transparent hover:opacity-100 group-hover/tab:opacity-100"
variant="ghost"
onClick={local.layout.review.close}
/>
<div class="flex items-center gap-1.5">
<div>Review</div>
<Show when={session.info()?.summary?.files}>
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
{session.info()?.summary?.files ?? 0}
</div>
</Show>
</div>
</div>
</Tabs.Trigger>
</Show>
@ -333,24 +343,17 @@ export default function Page() {
<div
classList={{
"w-full flex-1 min-h-0": true,
grid: local.layout.review.state() !== "open",
flex: local.layout.review.state() === "open",
grid: local.layout.review.state() === "tab",
flex: local.layout.review.state() === "pane",
}}
>
<div class="relative shrink-0 px-6 py-2 flex flex-col gap-6 flex-1 min-h-0 w-full max-w-xl mx-auto">
<div class="relative shrink-0 px-6 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full max-w-xl mx-auto">
<Switch>
<Match when={session.id}>
<div class="h-8 flex shrink-0 self-stretch items-center justify-end">
<Show when={local.layout.review.state() === "closed" && session.diffs().length}>
<Button icon="layout-right" onClick={local.layout.review.open}>
Review
</Button>
</Show>
</div>
<div
classList={{
"flex-1 min-h-0 pb-20": true,
"flex items-start justify-start": local.layout.review.state() === "open",
"flex items-start justify-start": local.layout.review.state() === "pane",
}}
>
<Show when={session.messages.user().length > 1}>
@ -358,8 +361,8 @@ export default function Page() {
role="list"
classList={{
"mr-8 shrink-0 flex flex-col items-start": true,
"absolute right-full w-60 @7xl:gap-2": local.layout.review.state() !== "open",
"mt-1": local.layout.review.state() === "open",
"absolute right-full w-60 mt-3 @7xl:gap-2 @7xl:mt-1": local.layout.review.state() === "tab",
"mt-3": local.layout.review.state() === "pane",
}}
>
<For each={session.messages.user()}>
@ -379,7 +382,7 @@ export default function Page() {
<li
classList={{
"group/li flex items-center self-stretch justify-end": true,
"@7xl:justify-start": local.layout.review.state() !== "open",
"@7xl:justify-start": local.layout.review.state() === "tab",
}}
>
<Tooltip
@ -398,7 +401,7 @@ export default function Page() {
classList={{
"group/tick flex items-center justify-start h-2 w-8 -mr-3": true,
"data-[active=true]:[&>div]:bg-icon-strong-base data-[active=true]:[&>div]:w-full": true,
"@7xl:hidden": local.layout.review.state() !== "open",
"@7xl:hidden": local.layout.review.state() === "tab",
}}
>
<div class="h-px w-5 bg-icon-base group-hover/tick:w-full group-hover/tick:bg-icon-strong-base" />
@ -407,7 +410,7 @@ export default function Page() {
<button
classList={{
"hidden items-center self-stretch w-full gap-x-2 cursor-default": true,
"@7xl:flex": local.layout.review.state() !== "open",
"@7xl:flex": local.layout.review.state() === "tab",
}}
onClick={handleClick}
>
@ -477,7 +480,7 @@ export default function Page() {
class="flex flex-col items-start self-stretch gap-8 pb-20"
>
{/* Title */}
<div class="flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-20 pb-1">
<div class="flex items-center gap-2 self-stretch sticky top-0 bg-background-stronger z-20 h-8">
<div class="w-full text-14-medium text-text-strong">
<Show
when={titled()}
@ -495,9 +498,7 @@ export default function Page() {
</Show>
</div>
</div>
<div class="-mt-9">
<Message message={message} parts={parts()} />
</div>
<Message message={message} parts={parts()} />
{/* Summary */}
<Show when={completed()}>
<div class="w-full flex flex-col gap-6 items-start self-stretch">
@ -524,7 +525,7 @@ export default function Page() {
<For each={message.summary?.diffs ?? []}>
{(diff) => (
<Accordion.Item value={diff.file}>
<StickyAccordionHeader class="top-10 data-expanded:before:-top-10 ">
<StickyAccordionHeader class="top-10 data-expanded:before:-top-10">
<Accordion.Trigger>
<div class="flex items-center justify-between w-full gap-5">
<div class="grow flex items-center gap-5 min-w-0">
@ -653,127 +654,25 @@ export default function Page() {
/>
</div>
</div>
<Show when={local.layout.review.state() === "open"}>
<Show when={local.layout.review.state() === "pane" && session.diffs().length}>
<div
classList={{
"relative grow px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0 border-l border-border-weak-base": true,
"relative grow px-6 py-3 flex-1 min-h-0 border-l border-border-weak-base": true,
}}
>
<div class="h-8 w-full flex items-center justify-between shrink-0 self-stretch">
<div class="flex items-center gap-x-3">
<Tooltip value="Close">
<IconButton icon="align-right" variant="ghost" onClick={local.layout.review.close} />
</Tooltip>
<Tooltip value="Open in tab">
<IconButton
icon="expand"
variant="ghost"
onClick={() => {
local.layout.review.tab()
session.layout.setActiveTab("review")
}}
/>
</Tooltip>
</div>
</div>
<div class="text-14-medium text-text-strong">All changes</div>
<div class="h-full pb-40 overflow-y-auto no-scrollbar">
<Accordion class="w-full" multiple>
<For each={session.diffs()}>
{(diff) => (
<Accordion.Item value={diff.file} defaultOpen>
<StickyAccordionHeader>
<Accordion.Trigger>
<div class="flex items-center justify-between w-full gap-5">
<div class="grow flex items-center gap-5 min-w-0">
<FileIcon node={{ path: diff.file, type: "file" }} class="shrink-0 size-4" />
<div class="flex grow min-w-0">
<Show when={diff.file.includes("/")}>
<span class="text-text-base truncate-start">
{getDirectory(diff.file)}&lrm;
</span>
</Show>
<span class="text-text-strong shrink-0">{getFilename(diff.file)}</span>
</div>
</div>
<div class="shrink-0 flex gap-4 items-center justify-end">
<DiffChanges changes={diff} />
<Icon name="chevron-grabber-vertical" size="small" />
</div>
</div>
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content>
<Diff
before={{
name: diff.file!,
contents: diff.before!,
}}
after={{
name: diff.file!,
contents: diff.after!,
}}
/>
</Accordion.Content>
</Accordion.Item>
)}
</For>
</Accordion>
</div>
<SessionReview />
</div>
</Show>
</div>
</Tabs.Content>
<Show when={local.layout.review.state() === "tab" && session.diffs().length}>
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden mt-8">
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden">
<div
classList={{
"relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0 overflow-hidden": true,
"relative px-6 py-3 flex-1 min-h-0 overflow-hidden": true,
}}
>
<div class="text-14-medium text-text-strong shrink-0">All changes</div>
<div class="flex-1 min-h-0 pb-40 overflow-y-auto no-scrollbar">
<Accordion class="w-full" multiple>
<For each={session.diffs()}>
{(diff) => (
<Accordion.Item value={diff.file} defaultOpen>
<StickyAccordionHeader>
<Accordion.Trigger>
<div class="flex items-center justify-between w-full gap-5">
<div class="grow flex items-center gap-5 min-w-0">
<FileIcon node={{ path: diff.file, type: "file" }} class="shrink-0 size-4" />
<div class="flex grow min-w-0">
<Show when={diff.file.includes("/")}>
<span class="text-text-base truncate-start">{getDirectory(diff.file)}&lrm;</span>
</Show>
<span class="text-text-strong shrink-0">{getFilename(diff.file)}</span>
</div>
</div>
<div class="shrink-0 flex gap-4 items-center justify-end">
<DiffChanges changes={diff} />
<Icon name="chevron-grabber-vertical" size="small" />
</div>
</div>
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content>
<Diff
diffStyle="split"
before={{
name: diff.file!,
contents: diff.before!,
}}
after={{
name: diff.file!,
contents: diff.after!,
}}
/>
</Accordion.Content>
</Accordion.Item>
)}
</For>
</Accordion>
</div>
<SessionReview split hideExpand class="pb-40" />
</div>
</Tabs.Content>
</Show>
@ -828,7 +727,7 @@ export default function Page() {
</DragOverlay>
</DragDropProvider>
<Show when={session.layout.tabs.active}>
<div class="absolute inset-x-0 px-6 max-w-2xl flex flex-col justify-center items-center z-50 mx-auto bottom-8">
<div class="absolute inset-x-0 px-6 max-w-2xl flex flex-col justify-center items-center z-50 mx-auto bottom-6">
<PromptInput
ref={(el) => {
inputRef = el
@ -895,18 +794,3 @@ export default function Page() {
</div>
)
}
function StickyAccordionHeader(props: ParentProps<{ class?: string }>) {
return (
<Accordion.Header
classList={{
"sticky top-0 data-expanded:z-10": true,
"data-expanded:before:content-[''] data-expanded:before:z-[-10]": true,
"data-expanded:before:absolute data-expanded:before:inset-0 data-expanded:before:bg-background-stronger": true,
[props.class ?? ""]: !!props.class,
}}
>
{props.children}
</Accordion.Header>
)
}

View file

@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The AI coding agent built for the terminal"
version = "1.0.62"
version = "1.0.65"
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.62/opencode-darwin-arm64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.65/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.62/opencode-darwin-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.65/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.62/opencode-linux-arm64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.65/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.62/opencode-linux-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.65/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.62/opencode-windows-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.65/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.0.62",
"version": "1.0.65",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",

View file

@ -1,61 +1,84 @@
#!/bin/sh
set -e
#!/usr/bin/env node
if [ -n "$OPENCODE_BIN_PATH" ]; then
resolved="$OPENCODE_BIN_PATH"
else
# Get the real path of this script, resolving any symlinks
script_path="$0"
while [ -L "$script_path" ]; do
link_target="$(readlink "$script_path")"
case "$link_target" in
/*) script_path="$link_target" ;;
*) script_path="$(dirname "$script_path")/$link_target" ;;
esac
done
script_dir="$(dirname "$script_path")"
script_dir="$(cd "$script_dir" && pwd)"
# Map platform names
case "$(uname -s)" in
Darwin) platform="darwin" ;;
Linux) platform="linux" ;;
MINGW*|CYGWIN*|MSYS*) platform="win32" ;;
*) platform="$(uname -s | tr '[:upper:]' '[:lower:]')" ;;
esac
# Map architecture names
case "$(uname -m)" in
x86_64|amd64) arch="x64" ;;
aarch64) arch="arm64" ;;
armv7l) arch="arm" ;;
*) arch="$(uname -m)" ;;
esac
name="opencode-${platform}-${arch}"
binary="opencode"
[ "$platform" = "win32" ] && binary="opencode.exe"
# Search for the binary starting from real script location
resolved=""
current_dir="$script_dir"
while [ "$current_dir" != "/" ]; do
candidate="$current_dir/node_modules/$name/bin/$binary"
if [ -f "$candidate" ]; then
resolved="$candidate"
break
fi
current_dir="$(dirname "$current_dir")"
done
if [ -z "$resolved" ]; then
printf "It seems that your package manager failed to install the right version of the opencode CLI for your platform. You can try manually installing the \"%s\" package\n" "$name" >&2
exit 1
fi
fi
const childProcess = require("child_process")
const fs = require("fs")
const path = require("path")
const os = require("os")
# Handle SIGINT gracefully
trap '' INT
function run(target) {
const result = childProcess.spawnSync(target, process.argv.slice(2), {
stdio: "inherit",
})
if (result.error) {
console.error(result.error.message)
process.exit(1)
}
const code = typeof result.status === "number" ? result.status : 0
process.exit(code)
}
# Execute the binary with all arguments
exec "$resolved" "$@"
const envPath = process.env.OPENCODE_BIN_PATH
if (envPath) {
run(envPath)
}
const scriptPath = fs.realpathSync(__filename)
const scriptDir = path.dirname(scriptPath)
const platformMap = {
darwin: "darwin",
linux: "linux",
win32: "windows",
}
const archMap = {
x64: "x64",
arm64: "arm64",
arm: "arm",
}
let platform = platformMap[os.platform()]
if (!platform) {
platform = os.platform()
}
let arch = archMap[os.arch()]
if (!arch) {
arch = os.arch()
}
const base = "opencode-" + platform + "-" + arch
const binary = platform === "windows" ? "opencode.exe" : "opencode"
function findBinary(startDir) {
let current = startDir
for (;;) {
const modules = path.join(current, "node_modules")
if (fs.existsSync(modules)) {
const entries = fs.readdirSync(modules)
for (const entry of entries) {
if (!entry.startsWith(base)) {
continue
}
const candidate = path.join(modules, entry, "bin", binary)
if (fs.existsSync(candidate)) {
return candidate
}
}
}
const parent = path.dirname(current)
if (parent === current) {
return
}
current = parent
}
}
const resolved = findBinary(scriptDir)
if (!resolved) {
console.error(
'It seems that your package manager failed to install the right version of the opencode CLI for your platform. You can try manually installing the "' +
base +
'" package',
)
process.exit(1)
}
run(resolved)

View file

@ -1,58 +0,0 @@
@echo off
setlocal enabledelayedexpansion
if defined OPENCODE_BIN_PATH (
set "resolved=%OPENCODE_BIN_PATH%"
goto :execute
)
rem Get the directory of this script
set "script_dir=%~dp0"
set "script_dir=%script_dir:~0,-1%"
rem Detect platform and architecture
set "platform=windows"
rem Detect architecture
if "%PROCESSOR_ARCHITECTURE%"=="AMD64" (
set "arch=x64"
) else if "%PROCESSOR_ARCHITECTURE%"=="ARM64" (
set "arch=arm64"
) else if "%PROCESSOR_ARCHITECTURE%"=="x86" (
set "arch=x86"
) else (
set "arch=x64"
)
set "name=opencode-!platform!-!arch!"
set "binary=opencode.exe"
rem Search for the binary starting from script location
set "resolved="
set "current_dir=%script_dir%"
:search_loop
set "candidate=%current_dir%\node_modules\%name%\bin\%binary%"
if exist "%candidate%" (
set "resolved=%candidate%"
goto :execute
)
rem Move up one directory
for %%i in ("%current_dir%") do set "parent_dir=%%~dpi"
set "parent_dir=%parent_dir:~0,-1%"
rem Check if we've reached the root
if "%current_dir%"=="%parent_dir%" goto :not_found
set "current_dir=%parent_dir%"
goto :search_loop
:not_found
echo It seems that your package manager failed to install the right version of the opencode CLI for your platform. You can try manually installing the "%name%" package >&2
exit /b 1
:execute
rem Execute the binary with all arguments in the same console window
rem Use start /b /wait to ensure it runs in the current shell context for all shells
start /b /wait "" "%resolved%" %*
exit /b %ERRORLEVEL%

View file

@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.0.62",
"version": "1.0.65",
"name": "opencode",
"type": "module",
"private": true,

View file

@ -66,11 +66,11 @@ const allTargets: {
avx2: false,
},
{
os: "windows",
os: "win32",
arch: "x64",
},
{
os: "windows",
os: "win32",
arch: "x64",
avx2: false,
},
@ -88,7 +88,8 @@ await $`bun install --os="*" --cpu="*" @parcel/watcher@${pkg.dependencies["@parc
for (const item of targets) {
const name = [
pkg.name,
item.os,
// changing to win32 flags npm for some reason
item.os === "win32" ? "windows" : item.os,
item.arch,
item.avx2 === false ? "baseline" : undefined,
item.abi === undefined ? undefined : item.abi,
@ -115,7 +116,7 @@ for (const item of targets) {
entrypoints: ["./src/index.ts", parserWorker, workerPath],
define: {
OPENCODE_VERSION: `'${Script.version}'`,
OTUI_TREE_SITTER_WORKER_PATH: "/$bunfs/root/" + path.relative(dir, parserWorker),
OTUI_TREE_SITTER_WORKER_PATH: "/$bunfs/root/" + path.relative(dir, parserWorker).replaceAll("\\", "/"),
OPENCODE_WORKER_PATH: workerPath,
OPENCODE_CHANNEL: `'${Script.channel}'`,
},
@ -127,7 +128,7 @@ for (const item of targets) {
{
name,
version: Script.version,
os: [item.os === "windows" ? "win32" : item.os],
os: [item.os],
cpu: [item.arch],
},
null,

View file

@ -50,79 +50,66 @@ function detectPlatformAndArch() {
function findBinary() {
const { platform, arch } = detectPlatformAndArch()
const packageName = `opencode-${platform}-${arch}`
const binary = platform === "windows" ? "opencode.exe" : "opencode"
const binaryName = platform === "windows" ? "opencode.exe" : "opencode"
try {
// Use require.resolve to find the package
const packageJsonPath = require.resolve(`${packageName}/package.json`)
const packageDir = path.dirname(packageJsonPath)
const binaryPath = path.join(packageDir, "bin", binary)
const binaryPath = path.join(packageDir, "bin", binaryName)
if (!fs.existsSync(binaryPath)) {
throw new Error(`Binary not found at ${binaryPath}`)
}
return binaryPath
return { binaryPath, binaryName }
} catch (error) {
throw new Error(`Could not find package ${packageName}: ${error.message}`)
}
}
async function regenerateWindowsCmdWrappers() {
console.log("Windows + npm detected: Forcing npm to rebuild bin links")
function prepareBinDirectory(binaryName) {
const binDir = path.join(__dirname, "bin")
const targetPath = path.join(binDir, binaryName)
try {
const { execSync } = require("child_process")
const pkgPath = path.join(__dirname, "..")
// Ensure bin directory exists
if (!fs.existsSync(binDir)) {
fs.mkdirSync(binDir, { recursive: true })
}
// npm_config_global is string | undefined
// if it exists, the value is true
const isGlobal = process.env.npm_config_global === "true" || pkgPath.includes(path.join("npm", "node_modules"))
// Remove existing binary/symlink if it exists
if (fs.existsSync(targetPath)) {
fs.unlinkSync(targetPath)
}
// The npm rebuild command does 2 things - Execute lifecycle scripts and rebuild bin links
// We want to skip lifecycle scripts to avoid infinite loops, so we use --ignore-scripts
const cmd = `npm rebuild opencode-ai --ignore-scripts${isGlobal ? " -g" : ""}`
const opts = {
stdio: "inherit",
shell: true,
...(isGlobal ? {} : { cwd: path.join(pkgPath, "..", "..") }), // For local, run from project root
}
return { binDir, targetPath }
}
console.log(`Running: ${cmd}`)
execSync(cmd, opts)
console.log("Successfully rebuilt npm bin links")
} catch (error) {
console.error("Error rebuilding npm links:", error.message)
console.error("npm rebuild failed. You may need to manually run: npm rebuild opencode-ai --ignore-scripts")
function symlinkBinary(sourcePath, binaryName) {
const { targetPath } = prepareBinDirectory(binaryName)
fs.symlinkSync(sourcePath, targetPath)
console.log(`opencode binary symlinked: ${targetPath} -> ${sourcePath}`)
// Verify the file exists after operation
if (!fs.existsSync(targetPath)) {
throw new Error(`Failed to symlink binary to ${targetPath}`)
}
}
async function main() {
try {
if (os.platform() === "win32") {
// NPM eg format - npm/11.4.2 node/v24.4.1 win32 x64
// Bun eg format - bun/1.2.19 npm/? node/v24.3.0 win32 x64
if (process.env.npm_config_user_agent.startsWith("npm")) {
await regenerateWindowsCmdWrappers()
} else {
console.log("Windows detected but not npm, skipping postinstall")
}
// On Windows, the .exe is already included in the package and bin field points to it
// No postinstall setup needed
console.log("Windows detected: binary setup not needed (using packaged .exe)")
return
}
const binaryPath = findBinary()
const binScript = path.join(__dirname, "bin", "opencode")
// Remove existing bin script if it exists
if (fs.existsSync(binScript)) {
fs.unlinkSync(binScript)
}
// Create symlink to the actual binary
fs.symlinkSync(binaryPath, binScript)
console.log(`opencode binary symlinked: ${binScript} -> ${binaryPath}`)
const { binaryPath, binaryName } = findBinary()
symlinkBinary(binaryPath, binaryName)
} catch (error) {
console.error("Failed to create opencode binary symlink:", error.message)
console.error("Failed to setup opencode binary:", error.message)
process.exit(1)
}
}

View file

@ -1,44 +0,0 @@
#!/usr/bin/env node
import fs from "fs"
import path from "path"
import os from "os"
import { fileURLToPath } from "url"
const __dirname = path.dirname(fileURLToPath(import.meta.url))
function main() {
if (os.platform() !== "win32") {
console.log("Non-Windows platform detected, skipping preinstall")
return
}
console.log("Windows detected: Modifying package.json bin entry")
// Read package.json
const packageJsonPath = path.join(__dirname, "package.json")
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"))
// Modify bin to point to .cmd file on Windows
packageJson.bin = {
opencode: "./bin/opencode.cmd",
}
// Write it back
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
console.log("Updated package.json bin to use opencode.cmd")
// Now you can also remove the Unix script if you want
const unixScript = path.join(__dirname, "bin", "opencode")
if (fs.existsSync(unixScript)) {
console.log("Removing Unix shell script")
fs.unlinkSync(unixScript)
}
}
try {
main()
} catch (error) {
console.error("Preinstall script error:", error.message)
process.exit(0)
}

View file

@ -2,8 +2,9 @@
import { $ } from "bun"
import pkg from "../package.json"
import { Script } from "@opencode-ai/script"
import { fileURLToPath } from "url"
const dir = new URL("..", import.meta.url).pathname
const dir = fileURLToPath(new URL("..", import.meta.url))
process.chdir(dir)
const { binaries } = await import("./build.ts")
@ -15,8 +16,8 @@ const { binaries } = await import("./build.ts")
await $`mkdir -p ./dist/${pkg.name}`
await $`cp -r ./bin ./dist/${pkg.name}/bin`
await $`cp ./script/preinstall.mjs ./dist/${pkg.name}/preinstall.mjs`
await $`cp ./script/postinstall.mjs ./dist/${pkg.name}/postinstall.mjs`
await Bun.file(`./dist/${pkg.name}/package.json`).write(
JSON.stringify(
{
@ -25,7 +26,6 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
[pkg.name]: `./bin/${pkg.name}`,
},
scripts: {
preinstall: "bun ./preinstall.mjs || node ./preinstall.mjs",
postinstall: "bun ./postinstall.mjs || node ./postinstall.mjs",
},
version: Script.version,
@ -36,7 +36,15 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
),
)
for (const [name] of Object.entries(binaries)) {
await $`cd dist/${name} && chmod 777 -R . && bun publish --access public --tag ${Script.channel}`
try {
process.chdir(`./dist/${name}`)
if (process.platform !== "win32") {
await $`chmod 755 -R .`
}
await $`bun publish --access public --tag ${Script.channel}`
} finally {
process.chdir(dir)
}
}
await $`cd ./dist/${pkg.name} && bun publish --access public --tag ${Script.channel}`

View file

@ -1,13 +1,18 @@
import { useRenderer } from "@opentui/solid"
import { createSimpleContext } from "./helper"
import { FormatError } from "@/cli/error"
export const { use: useExit, provider: ExitProvider } = createSimpleContext({
name: "Exit",
init: (input: { onExit?: () => Promise<void> }) => {
const renderer = useRenderer()
return async () => {
return async (reason?: any) => {
renderer.destroy()
await input.onExit?.()
if (reason) {
const formatted = FormatError(reason) ?? JSON.stringify(reason)
process.stderr.write(formatted + "\n")
}
process.exit(0)
}
},

View file

@ -10,11 +10,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
const sdk = createOpencodeClient({
baseUrl: props.url,
signal: abort.signal,
fetch: (req) => {
// @ts-ignore
req.timeout = false
return fetch(req)
},
})
const emitter = createGlobalEmitter<{

View file

@ -18,6 +18,8 @@ import { useSDK } from "@tui/context/sdk"
import { Binary } from "@/util/binary"
import { createSimpleContext } from "./helper"
import type { Snapshot } from "@/snapshot"
import { useExit } from "./exit"
import { onMount } from "solid-js"
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
name: "Sync",
@ -226,29 +228,37 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
}
})
// blocking
Promise.all([
sdk.client.config.providers({ throwOnError: true }).then((x) => setStore("provider", x.data!.providers)),
sdk.client.app.agents({ throwOnError: true }).then((x) => setStore("agent", x.data ?? [])),
sdk.client.config.get({ throwOnError: true }).then((x) => setStore("config", x.data!)),
]).then(() => {
setStore("status", "partial")
// non-blocking
const exit = useExit()
onMount(() => {
// blocking
Promise.all([
sdk.client.session.list().then((x) =>
setStore(
"session",
(x.data ?? []).toSorted((a, b) => a.id.localeCompare(b.id)),
),
),
sdk.client.command.list().then((x) => setStore("command", x.data ?? [])),
sdk.client.lsp.status().then((x) => setStore("lsp", x.data!)),
sdk.client.mcp.status().then((x) => setStore("mcp", x.data!)),
sdk.client.formatter.status().then((x) => setStore("formatter", x.data!)),
sdk.client.session.status().then((x) => setStore("session_status", x.data!)),
]).then(() => {
setStore("status", "complete")
})
sdk.client.config.providers({ throwOnError: true }).then((x) => setStore("provider", x.data!.providers)),
sdk.client.app.agents({ throwOnError: true }).then((x) => setStore("agent", x.data ?? [])),
sdk.client.config.get({ throwOnError: true }).then((x) => setStore("config", x.data!)),
])
.then(() => {
setStore("status", "partial")
// non-blocking
Promise.all([
sdk.client.session.list().then((x) =>
setStore(
"session",
(x.data ?? []).toSorted((a, b) => a.id.localeCompare(b.id)),
),
),
sdk.client.command.list().then((x) => setStore("command", x.data ?? [])),
sdk.client.lsp.status().then((x) => setStore("lsp", x.data!)),
sdk.client.mcp.status().then((x) => setStore("mcp", x.data!)),
sdk.client.formatter.status().then((x) => setStore("formatter", x.data!)),
sdk.client.session.status().then((x) => setStore("session_status", x.data!)),
]).then(() => {
setStore("status", "complete")
})
})
.catch(async (e) => {
await exit(e)
})
})
const result = {

View file

@ -17,7 +17,14 @@ import { useRoute, useRouteData } from "@tui/context/route"
import { useSync } from "@tui/context/sync"
import { SplitBorder } from "@tui/component/border"
import { useTheme } from "@tui/context/theme"
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers } from "@opentui/core"
import {
BoxRenderable,
ScrollBoxRenderable,
TextAttributes,
addDefaultParsers,
MacOSScrollAccel,
type ScrollAcceleration,
} from "@opentui/core"
import { Prompt, type PromptRef } from "@tui/component/prompt"
import type { AssistantMessage, Part, ToolPart, UserMessage, TextPart, ReasoningPart } from "@opencode-ai/sdk"
import { useLocal } from "@tui/context/local"
@ -61,6 +68,16 @@ import stripAnsi from "strip-ansi"
addDefaultParsers(parsers.parsers)
class CustomSpeedScroll implements ScrollAcceleration {
constructor(private speed: number) {}
tick(_now?: number): number {
return this.speed
}
reset(): void {}
}
const context = createContext<{
width: number
conceal: () => boolean
@ -99,6 +116,17 @@ export function Session() {
const sidebarVisible = createMemo(() => sidebar() === "show" || (sidebar() === "auto" && wide()))
const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4)
const scrollAcceleration = createMemo(() => {
const tui = sync.data.config.tui
if (tui?.scroll_acceleration?.enabled) {
return new MacOSScrollAccel()
}
if (tui?.scroll_speed) {
return new CustomSpeedScroll(tui.scroll_speed)
}
return undefined
})
createEffect(async () => {
await sync.session
.sync(route.sessionID)
@ -695,6 +723,7 @@ export function Session() {
stickyScroll={true}
stickyStart="bottom"
flexGrow={1}
scrollAcceleration={scrollAcceleration()}
>
<For each={messages()}>
{(message, index) => (
@ -1229,7 +1258,7 @@ ToolRegistry.register<typeof WriteTool>({
<For each={numbers()}>{(value) => <text style={{ fg: theme.textMuted }}>{value}</text>}</For>
</box>
<box paddingLeft={1} flexGrow={1}>
<code filetype={filetype(props.input.filePath!)} syntaxStyle={syntax()} content={code()} />
<code fg={theme.text} filetype={filetype(props.input.filePath!)} syntaxStyle={syntax()} content={code()} />
</box>
</box>
<Show when={diagnostics().length}>
@ -1439,16 +1468,16 @@ ToolRegistry.register<typeof EditTool>({
<Match when={diff() && style() === "split"}>
<box paddingLeft={1} flexDirection="row" gap={2}>
<box flexGrow={1} flexBasis={0}>
<code filetype={ft()} syntaxStyle={syntax()} content={diff()!.oldContent} />
<code fg={theme.text} filetype={ft()} syntaxStyle={syntax()} content={diff()!.oldContent} />
</box>
<box flexGrow={1} flexBasis={0}>
<code filetype={ft()} syntaxStyle={syntax()} content={diff()!.newContent} />
<code fg={theme.text} filetype={ft()} syntaxStyle={syntax()} content={diff()!.newContent} />
</box>
</box>
</Match>
<Match when={code()}>
<box paddingLeft={1}>
<code filetype={ft()} syntaxStyle={syntax()} content={code()} />
<code fg={theme.text} filetype={ft()} syntaxStyle={syntax()} content={code()} />
</box>
</Match>
</Switch>

View file

@ -31,11 +31,11 @@ export function Toast() {
customBorderChars={SplitBorder.customBorderChars}
>
<Show when={current().title}>
<text attributes={TextAttributes.BOLD} marginBottom={1}>
<text attributes={TextAttributes.BOLD} marginBottom={1} fg={theme.text}>
{current().title}
</text>
</Show>
<text>{current().message}</text>
<text fg={theme.text}>{current().message}</text>
</box>
)}
</Show>

View file

@ -43,6 +43,7 @@ export const rpc = {
}
},
async shutdown() {
Log.Default.info("worker shutting down")
await Instance.disposeAll()
await server.stop(true)
},

View file

@ -437,7 +437,13 @@ export namespace Config {
})
export const TUI = z.object({
scroll_speed: z.number().min(1).optional().default(2).describe("TUI scroll speed"),
scroll_speed: z.number().min(1).optional().default(1).describe("TUI scroll speed"),
scroll_acceleration: z
.object({
enabled: z.boolean().describe("Enable scroll acceleration"),
})
.optional()
.describe("Scroll acceleration settings"),
})
export const Layout = z.enum(["auto", "stretch"]).meta({

View file

@ -41,6 +41,9 @@ export namespace Format {
extensions: [],
...item,
})
if (result.command.length === 0) continue
result.enabled = async () => true
result.name = name
formatters[name] = result

View file

@ -53,10 +53,14 @@ export const Instance = {
await State.dispose(Instance.directory)
},
async disposeAll() {
Log.Default.info("disposing all instances")
for (const [_key, value] of cache) {
await context.provide(await value, async () => {
await Instance.dispose()
})
const awaited = await value.catch(() => {})
if (awaited) {
await context.provide(await value, async () => {
await Instance.dispose()
})
}
}
cache.clear()
},

View file

@ -176,7 +176,7 @@ export namespace ProviderTransform {
}
export function maxOutputTokens(
providerID: string,
npm: string,
options: Record<string, any>,
modelLimit: number,
globalLimit: number,
@ -184,7 +184,7 @@ export namespace ProviderTransform {
const modelCap = modelLimit || globalLimit
const standardLimit = Math.min(modelCap, globalLimit)
if (providerID === "anthropic") {
if (npm === "@ai-sdk/anthropic") {
const thinking = options?.["thinking"]
const budgetTokens = typeof thinking?.["budgetTokens"] === "number" ? thinking["budgetTokens"] : 0
const enabled = thinking?.["type"] === "enabled"

View file

@ -11,6 +11,7 @@ import { Provider } from "../provider/provider"
import { Identifier } from "../id/id"
import { Permission } from "../permission"
import { Agent } from "@/agent/agent"
import { iife } from "@/util/iife"
const DEFAULT_READ_LIMIT = 2000
const MAX_LINE_LENGTH = 2000
@ -48,6 +49,19 @@ export const ReadTool = Tool.define("read", {
}
}
const block = (() => {
const whitelist = [".env.example", ".env.sample"]
if (whitelist.some((w) => filepath.endsWith(w))) return false
if (filepath.includes(".env")) return true
return false
})()
if (block) {
throw new Error(`The user has blocked you from reading ${filepath}, DO NOT make further attempts to read it`)
}
const file = Bun.file(filepath)
if (!(await file.exists())) {
const dir = path.dirname(filepath)

View file

@ -0,0 +1,98 @@
import { describe, expect, test } from "bun:test"
import { ProviderTransform } from "../../src/provider/transform"
const OUTPUT_TOKEN_MAX = 32000
describe("ProviderTransform.maxOutputTokens", () => {
test("returns 32k when modelLimit > 32k", () => {
const modelLimit = 100000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/openai", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(OUTPUT_TOKEN_MAX)
})
test("returns modelLimit when modelLimit < 32k", () => {
const modelLimit = 16000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/openai", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(16000)
})
describe("azure", () => {
test("returns 32k when modelLimit > 32k", () => {
const modelLimit = 100000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/azure", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(OUTPUT_TOKEN_MAX)
})
test("returns modelLimit when modelLimit < 32k", () => {
const modelLimit = 16000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/azure", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(16000)
})
})
describe("bedrock", () => {
test("returns 32k when modelLimit > 32k", () => {
const modelLimit = 100000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/amazon-bedrock", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(OUTPUT_TOKEN_MAX)
})
test("returns modelLimit when modelLimit < 32k", () => {
const modelLimit = 16000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/amazon-bedrock", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(16000)
})
})
describe("anthropic without thinking options", () => {
test("returns 32k when modelLimit > 32k", () => {
const modelLimit = 100000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/anthropic", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(OUTPUT_TOKEN_MAX)
})
test("returns modelLimit when modelLimit < 32k", () => {
const modelLimit = 16000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/anthropic", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(16000)
})
})
describe("anthropic with thinking options", () => {
test("returns 32k when budgetTokens + 32k <= modelLimit", () => {
const modelLimit = 100000
const options = {
thinking: {
type: "enabled",
budgetTokens: 10000,
},
}
const result = ProviderTransform.maxOutputTokens("@ai-sdk/anthropic", options, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(OUTPUT_TOKEN_MAX)
})
test("returns modelLimit - budgetTokens when budgetTokens + 32k > modelLimit", () => {
const modelLimit = 50000
const options = {
thinking: {
type: "enabled",
budgetTokens: 30000,
},
}
const result = ProviderTransform.maxOutputTokens("@ai-sdk/anthropic", options, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(20000)
})
test("returns 32k when thinking type is not enabled", () => {
const modelLimit = 100000
const options = {
thinking: {
type: "disabled",
budgetTokens: 10000,
},
}
const result = ProviderTransform.maxOutputTokens("@ai-sdk/anthropic", options, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(OUTPUT_TOKEN_MAX)
})
})
})

View file

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.0.62",
"version": "1.0.65",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",

View file

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.0.62",
"version": "1.0.65",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",

View file

@ -6,6 +6,17 @@ import { type Config } from "./gen/client/types.gen.js"
import { OpencodeClient } from "./gen/sdk.gen.js"
export function createOpencodeClient(config?: Config) {
if (!config?.fetch) {
config = {
...config,
fetch: (req) => {
// @ts-ignore
req.timeout = false
return fetch(req)
},
}
}
const client = createClient(config)
return new OpencodeClient({ client })
}

View file

@ -301,6 +301,15 @@ export type Config = {
* TUI scroll speed
*/
scroll_speed?: number
/**
* Scroll acceleration settings
*/
scroll_acceleration?: {
/**
* Enable scroll acceleration
*/
enabled: boolean
}
}
/**
* Command configuration, see https://opencode.ai/docs/commands

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.0.62",
"version": "1.0.65",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.0.62",
"version": "1.0.65",
"type": "module",
"exports": {
".": "./src/components/index.ts",

View file

@ -44,9 +44,9 @@
--surface-info-base: var(--lilac-light-3);
--surface-info-weak: var(--lilac-light-2);
--surface-info-strong: var(--lilac-light-9);
--surface-diff-unchanged-base: #FFFFFF00;
--surface-diff-skip-base: var(--smoke-light-2);
--surface-diff-hidden-base: var(--blue-light-3);
--surface-diff-unchanged-base: #FFFFFF00;
--surface-diff-hidden-weak: var(--blue-light-2);
--surface-diff-hidden-weaker: var(--blue-light-1);
--surface-diff-hidden-strong: var(--blue-light-5);
@ -95,7 +95,7 @@
--text-on-brand-weaker: var(--smoke-light-alpha-8);
--text-on-brand-strong: var(--smoke-light-alpha-12);
--button-secondary-base: #FDFCFC;
--button-secondary-base-hover: #FAF9F9;
--button-secondary-hover: #FAF9F9;
--border-base: var(--smoke-light-alpha-7);
--border-hover: var(--smoke-light-alpha-8);
--border-active: var(--smoke-light-alpha-9);
@ -190,20 +190,25 @@
--icon-diff-add-active: var(--mint-light-12);
--icon-diff-delete-base: var(--ember-light-10);
--icon-diff-delete-hover: var(--ember-light-11);
--syntax-comment: #8A8A8A;
--syntax-string: #D68C27;
--syntax-keyword: #3B7DD8;
--syntax-function: #D1383D;
--syntax-number: #3D9A57;
--syntax-operator: #D68C27;
--syntax-variable: #B0851F;
--syntax-type: #318795;
--syntax-constant: #953170;
--syntax-punctuation: #1A1A1A;
--syntax-success: var(--apple-dark-10);
--syntax-comment: var(--text-weaker);
--syntax-regexp: var(--text-base);
--syntax-string: #007663;
--syntax-keyword: var(--text-weak);
--syntax-primitive: #FB7F51;
--syntax-operator: var(--text-weak);
--syntax-variable: var(--text-strong);
--syntax-property: #EC6CC8;
--syntax-type: #738400;
--syntax-constant: #00B2B9;
--syntax-punctuation: var(--text-weaker);
--syntax-object: var(--text-strong);
--syntax-success: var(--apple-light-10);
--syntax-warning: var(--amber-light-10);
--syntax-critical: var(--ember-dark-9);
--syntax-info: var(--lilac-dark-11);
--syntax-critical: var(--ember-light-9);
--syntax-info: #0091A7;
--syntax-diff-add: var(--mint-light-11);
--syntax-diff-delete: var(--ember-light-11);
--syntax-diff-unknown: #FF0000;
--markdown-heading: #D68C27;
--markdown-text: #1A1A1A;
--markdown-link: #3B7DD8;

View file

@ -11,7 +11,7 @@ for (const line of colors.split("\n")) {
}
const output = `
/* Generated by script/colors.ts */
/* Generated by script/tailwind.ts */
/* Do not edit this file manually */
@theme {

View file

@ -24,7 +24,7 @@
[data-slot="accordion-trigger"] {
width: 100%;
display: flex;
height: 40px;
height: 32px;
padding: 8px 12px;
justify-content: space-between;
align-items: center;
@ -63,23 +63,24 @@
margin-bottom: 8px;
[data-slot="accordion-trigger"] {
border-radius: 8px 8px 0 0;
border-top-left-radius: var(--radius-md);
border-top-right-radius: var(--radius-md);
}
[data-slot="accordion-content"] {
border: 1px solid var(--border-weak-base);
border-top: none;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
border-bottom-left-radius: var(--radius-md);
border-bottom-right-radius: var(--radius-md);
}
[data-slot="accordion-item"]:has(+ &) {
&[data-closed] {
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
border-bottom-left-radius: var(--radius-md);
border-bottom-right-radius: var(--radius-md);
[data-slot="accordion-trigger"] {
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
border-bottom-left-radius: var(--radius-md);
border-bottom-right-radius: var(--radius-md);
}
}
margin-bottom: 8px;
@ -89,8 +90,8 @@
margin-top: 8px;
[data-slot="accordion-trigger"] {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
border-top-left-radius: var(--radius-md);
border-top-right-radius: var(--radius-md);
}
}
}
@ -106,8 +107,8 @@
&[data-closed] {
[data-slot="accordion-trigger"] {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
border-top-left-radius: var(--radius-md);
border-top-right-radius: var(--radius-md);
}
}
}
@ -117,8 +118,8 @@
&[data-closed] {
[data-slot="accordion-trigger"] {
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
border-bottom-left-radius: var(--radius-md);
border-bottom-right-radius: var(--radius-md);
}
}
}

View file

@ -1,11 +1,9 @@
import { Accordion as Kobalte } from "@kobalte/core/accordion"
import { createSignal, splitProps } from "solid-js"
import { splitProps } from "solid-js"
import type { ComponentProps, ParentProps } from "solid-js"
export interface AccordionProps extends ComponentProps<typeof Kobalte> {}
export interface AccordionItemProps extends ComponentProps<typeof Kobalte.Item> {
defaultOpen?: boolean
}
export interface AccordionItemProps extends ComponentProps<typeof Kobalte.Item> {}
export interface AccordionHeaderProps extends ComponentProps<typeof Kobalte.Header> {}
export interface AccordionTriggerProps extends ComponentProps<typeof Kobalte.Trigger> {}
export interface AccordionContentProps extends ComponentProps<typeof Kobalte.Content> {}
@ -25,14 +23,11 @@ function AccordionRoot(props: AccordionProps) {
}
function AccordionItem(props: AccordionItemProps) {
const [split, rest] = splitProps(props, ["class", "classList", "defaultOpen"])
const [open, setOpen] = createSignal(split.defaultOpen ?? false)
const [split, rest] = splitProps(props, ["class", "classList"])
return (
<Kobalte.Item
{...rest}
data-slot="accordion-item"
onOpenChange={setOpen}
open={open()}
classList={{
...(split.classList ?? {}),
[split.class ?? ""]: !!split.class,

View file

@ -4,7 +4,7 @@
justify-content: center;
border-style: solid;
border-width: 1px;
border-radius: 6px;
border-radius: var(--radius-sm);
text-decoration: none;
user-select: none;
cursor: default;

View file

@ -5,7 +5,7 @@
background-color: var(--surface-inset-base);
border: 1px solid var(--border-weaker-base);
transition: background-color 0.15s ease;
border-radius: 8px;
border-radius: var(--radius-md);
padding: 6px 12px;
overflow: clip;

View file

@ -25,7 +25,7 @@
padding: 2px;
aspect-ratio: 1;
flex-shrink: 0;
border-radius: 4px;
border-radius: var(--radius-sm);
border: 1px solid var(--border-weak-base);
/* background-color: var(--surface-weak); */
}

View file

@ -5,7 +5,7 @@
background-color: var(--surface-inset-base);
border: 1px solid var(--border-weaker-base);
transition: background-color 0.15s ease;
border-radius: 8px;
border-radius: var(--radius-md);
overflow: clip;
[data-slot="collapsible-trigger"] {

View file

@ -43,7 +43,7 @@
/* padding: 8px; */
padding: 8px 8px 0 8px;
border: 1px solid var(--border-base);
border-radius: 16px;
border-radius: var(--radius-md);
background: var(--surface-raised-stronger-non-alpha);
box-shadow:
0 15px 45px 0 rgba(19, 16, 16, 0.22),

View file

@ -2,7 +2,7 @@
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 6px;
border-radius: var(--radius-sm);
text-decoration: none;
user-select: none;
aspect-ratio: 1;

View file

@ -157,6 +157,7 @@ const newIcons = {
"speech-bubble": `<path d="M18.3334 10.0003C18.3334 5.57324 15.0927 2.91699 10.0001 2.91699C4.90749 2.91699 1.66675 5.57324 1.66675 10.0003C1.66675 11.1497 2.45578 13.1016 2.5771 13.3949C2.5878 13.4207 2.59839 13.4444 2.60802 13.4706C2.69194 13.6996 3.04282 14.9364 1.66675 16.7684C3.5186 17.6538 5.48526 16.1982 5.48526 16.1982C6.84592 16.9202 8.46491 17.0837 10.0001 17.0837C15.0927 17.0837 18.3334 14.4274 18.3334 10.0003Z" stroke="currentColor" stroke-linecap="square"/>`,
"align-right": `<path d="M12.292 6.04167L16.2503 9.99998L12.292 13.9583M2.91699 9.99998H15.6253M17.0837 3.75V16.25" stroke="currentColor" stroke-linecap="square"/>`,
expand: `<path d="M4.58301 10.4163V15.4163H9.58301M10.4163 4.58301H15.4163V9.58301" stroke="currentColor" stroke-linecap="square"/>`,
collapse: `<path d="M16.666 8.33398H11.666V3.33398" stroke="currentColor" stroke-linecap="square"/><path d="M8.33398 16.666V11.666H3.33398" stroke="currentColor" stroke-linecap="square"/>`,
}
export interface IconProps extends ComponentProps<"svg"> {

View file

@ -14,7 +14,7 @@
padding: 4px 12px;
text-align: left;
border-radius: 6px;
border-radius: var(--radius-md);
transition: background-color 0.2s ease-in-out;
&[data-active="true"] {

View file

@ -7,7 +7,7 @@
gap: 12px;
align-self: stretch;
border-radius: 8px;
border-radius: var(--radius-md);
background: var(--surface-base);
[data-slot="input-container"] {
@ -100,7 +100,7 @@
align-items: center;
&[data-active="true"] {
border-radius: 8px;
border-radius: var(--radius-md);
background: var(--surface-raised-base-hover);
}
}

View file

@ -21,7 +21,7 @@
[data-component="select-content"] {
min-width: 4rem;
overflow: hidden;
border-radius: 8px;
border-radius: var(--radius-md);
border-width: 1px;
border-style: solid;
border-color: var(--border-weak-base);
@ -60,7 +60,7 @@
display: flex;
align-items: center;
padding: 0 6px 0 6px;
border-radius: 6px;
border-radius: var(--radius-sm);
/* text-12-medium */
font-family: var(--font-family-sans);

View file

@ -6,7 +6,7 @@
background-color: var(--background-stronger);
overflow: clip;
[data-slot="list"] {
[data-slot="tabs-list"] {
height: 48px;
width: 100%;
position: relative;
@ -36,12 +36,12 @@
}
}
[data-slot="trigger"] {
[data-slot="tabs-trigger-wrapper"] {
position: relative;
height: 100%;
padding: 14px 24px;
display: flex;
align-items: center;
gap: 12px;
color: var(--text-base);
/* text-14-medium */
@ -58,6 +58,23 @@
border-right: 1px solid var(--border-weak-base);
background-color: var(--background-base);
[data-slot="tabs-trigger"] {
display: flex;
align-items: center;
justify-content: center;
padding: 14px 24px;
}
[data-slot="tabs-trigger-close-button"] {
display: flex;
align-items: center;
justify-content: center;
}
[data-component="icon-button"] {
margin: -0.25rem;
}
&:disabled {
pointer-events: none;
color: var(--text-weaker);
@ -66,17 +83,38 @@
outline: none;
box-shadow: 0 0 0 2px var(--border-focus);
}
&[data-selected] {
&:has([data-hidden]) {
[data-slot="tabs-trigger-close-button"] {
opacity: 0;
}
&:hover {
[data-slot="tabs-trigger-close-button"] {
opacity: 1;
}
}
}
&:has([data-selected]) {
color: var(--text-strong);
background-color: transparent;
border-bottom-color: transparent;
[data-slot="tabs-trigger-close-button"] {
opacity: 1;
}
}
&:hover:not(:disabled):not([data-selected]) {
color: var(--text-strong);
}
&:has([data-slot="tabs-trigger-close-button"]) {
padding-right: 12px;
[data-slot="tabs-trigger"] {
padding-right: 0;
}
}
}
[data-slot="content"] {
[data-slot="tabs-content"] {
overflow-y: auto;
flex: 1;

View file

@ -1,10 +1,13 @@
import { Tabs as Kobalte } from "@kobalte/core/tabs"
import { splitProps } from "solid-js"
import { Show, splitProps, type JSX } from "solid-js"
import type { ComponentProps, ParentProps } from "solid-js"
export interface TabsProps extends ComponentProps<typeof Kobalte> {}
export interface TabsListProps extends ComponentProps<typeof Kobalte.List> {}
export interface TabsTriggerProps extends ComponentProps<typeof Kobalte.Trigger> {}
export interface TabsTriggerProps extends ComponentProps<typeof Kobalte.Trigger> {
hideCloseButton?: boolean
closeButton?: JSX.Element
}
export interface TabsContentProps extends ComponentProps<typeof Kobalte.Content> {}
function TabsRoot(props: TabsProps) {
@ -26,7 +29,7 @@ function TabsList(props: TabsListProps) {
return (
<Kobalte.List
{...rest}
data-slot="list"
data-slot="tabs-list"
classList={{
...(split.classList ?? {}),
[split.class ?? ""]: !!split.class,
@ -36,18 +39,26 @@ function TabsList(props: TabsListProps) {
}
function TabsTrigger(props: ParentProps<TabsTriggerProps>) {
const [split, rest] = splitProps(props, ["class", "classList", "children"])
const [split, rest] = splitProps(props, ["class", "classList", "children", "closeButton", "hideCloseButton"])
return (
<Kobalte.Trigger
{...rest}
data-slot="trigger"
<div
data-slot="tabs-trigger-wrapper"
classList={{
...(split.classList ?? {}),
[split.class ?? ""]: !!split.class,
}}
>
{split.children}
</Kobalte.Trigger>
<Kobalte.Trigger {...rest} data-slot="tabs-trigger" class="group/tab">
{split.children}
</Kobalte.Trigger>
<Show when={split.closeButton}>
{(closeButton) => (
<div data-slot="tabs-trigger-close-button" data-hidden={split.hideCloseButton}>
{closeButton()}
</div>
)}
</Show>
</div>
)
}
@ -56,7 +67,7 @@ function TabsContent(props: ParentProps<TabsContentProps>) {
return (
<Kobalte.Content
{...rest}
data-slot="content"
data-slot="tabs-content"
classList={{
...(split.classList ?? {}),
[split.class ?? ""]: !!split.class,

View file

@ -6,7 +6,7 @@
[data-component="tooltip"] {
z-index: 1000;
max-width: 320px;
border-radius: 6px;
border-radius: var(--radius-md);
background-color: var(--surface-float-base);
color: rgba(253, 252, 252, 0.94);
padding: 2px 8px;

View file

@ -493,4 +493,52 @@
--blue-light-alpha-10: #007feff9;
--blue-light-alpha-11: #0066dbf9;
--blue-light-alpha-12: #002047f9;
--ink-dark-1: #101313;
--ink-dark-2: #181b1b;
--ink-dark-3: #212525;
--ink-dark-4: #282d2d;
--ink-dark-5: #303434;
--ink-dark-6: #393e3e;
--ink-dark-7: #464b4b;
--ink-dark-8: #5f6464;
--ink-dark-9: #6b7171;
--ink-dark-10: #797f7f;
--ink-dark-11: #b1b7b7;
--ink-dark-12: #ecf1f1;
--ink-light-1: #fcfdfd;
--ink-light-2: #f8f9f9;
--ink-light-3: #f0f1f1;
--ink-light-4: #e8e9e9;
--ink-light-5: #e0e2e2;
--ink-light-6: #d9dada;
--ink-light-7: #cdcfcf;
--ink-light-8: #bbbcbc;
--ink-light-9: #8b8e8e;
--ink-light-10: #818484;
--ink-light-11: #636565;
--ink-light-12: #1e2121;
--ink-dark-alpha-1: #38828203;
--ink-dark-alpha-2: #c6e6e60b;
--ink-dark-alpha-3: #d5eded16;
--ink-dark-alpha-4: #e1f2f21e;
--ink-dark-alpha-5: #e8f5f526;
--ink-dark-alpha-6: #e8f5f531;
--ink-dark-alpha-7: #ecf7f73f;
--ink-dark-alpha-8: #f5fafa59;
--ink-dark-alpha-9: #f4fafa67;
--ink-dark-alpha-10: #f5fbfb76;
--ink-dark-alpha-11: #f9fcfcb2;
--ink-dark-alpha-12: #fbfdfdf0;
--ink-light-alpha-1: #00555503;
--ink-light-alpha-2: #00252507;
--ink-light-alpha-3: #0011110f;
--ink-light-alpha-4: #000c0c17;
--ink-light-alpha-5: #0011111f;
--ink-light-alpha-6: #00070726;
--ink-light-alpha-7: #000b0b32;
--ink-light-alpha-8: #00040444;
--ink-light-alpha-9: #00070774;
--ink-light-alpha-10: #0004049c;
--ink-light-alpha-11: #0007077e;
--ink-light-alpha-12: #000202df;
}

View file

@ -49,9 +49,9 @@
--color-surface-info-base: var(--surface-info-base);
--color-surface-info-weak: var(--surface-info-weak);
--color-surface-info-strong: var(--surface-info-strong);
--color-surface-diff-unchanged-base: var(--surface-diff-unchanged-base);
--color-surface-diff-skip-base: var(--surface-diff-skip-base);
--color-surface-diff-hidden-base: var(--surface-diff-hidden-base);
--color-surface-diff-unchanged-base: var(--surface-diff-unchanged-base);
--color-surface-diff-hidden-weak: var(--surface-diff-hidden-weak);
--color-surface-diff-hidden-weaker: var(--surface-diff-hidden-weaker);
--color-surface-diff-hidden-strong: var(--surface-diff-hidden-strong);
@ -100,7 +100,7 @@
--color-text-on-brand-weaker: var(--text-on-brand-weaker);
--color-text-on-brand-strong: var(--text-on-brand-strong);
--color-button-secondary-base: var(--button-secondary-base);
--color-button-secondary-base-hover: var(--button-secondary-base-hover);
--color-button-secondary-hover: var(--button-secondary-hover);
--color-border-base: var(--border-base);
--color-border-hover: var(--border-hover);
--color-border-active: var(--border-active);
@ -199,21 +199,21 @@
--color-syntax-regexp: var(--syntax-regexp);
--color-syntax-string: var(--syntax-string);
--color-syntax-keyword: var(--syntax-keyword);
--color-syntax-function: var(--syntax-function);
--color-syntax-number: var(--syntax-number);
--color-syntax-primitive: var(--syntax-primitive);
--color-syntax-operator: var(--syntax-operator);
--color-syntax-variable: var(--syntax-variable);
--color-syntax-property: var(--syntax-property);
--color-syntax-parameter: var(--syntax-parameter);
--color-syntax-type: var(--syntax-type);
--color-syntax-constant: var(--syntax-constant);
--color-syntax-punctuation: var(--syntax-punctuation);
--color-syntax-namespace: var(--syntax-namespace);
--color-syntax-enum: var(--syntax-enum);
--color-syntax-object: var(--syntax-object);
--color-syntax-success: var(--syntax-success);
--color-syntax-warning: var(--syntax-warning);
--color-syntax-critical: var(--syntax-critical);
--color-syntax-info: var(--syntax-info);
--color-syntax-diff-add: var(--syntax-diff-add);
--color-syntax-diff-delete: var(--syntax-diff-delete);
--color-syntax-diff-unknown: var(--syntax-diff-unknown);
--color-markdown-heading: var(--markdown-heading);
--color-markdown-text: var(--markdown-text);
--color-markdown-link: var(--markdown-link);

View file

@ -40,14 +40,8 @@
--container-6xl: 72rem;
--container-7xl: 80rem;
--radius-xs: 0.125rem;
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--radius-xl: 0.75rem;
--radius-2xl: 1rem;
--radius-3xl: 1.5rem;
--radius-4xl: 2rem;
--shadow-xs:
0 1px 2px -1px rgba(19, 16, 16, 0.04), 0 1px 2px 0 rgba(19, 16, 16, 0.06), 0 1px 3px 0 rgba(19, 16, 16, 0.08);
@ -65,19 +59,15 @@
0 1px 2px 0 rgba(19, 16, 16, 0.08), 0 1px 3px 0 rgba(19, 16, 16, 0.12), 0 0 0 2px var(--background-weak, #f1f0f0),
0 0 0 3px var(--border-selected, rgba(0, 74, 255, 0.99));
--text-mix-blend-mode: multiply;
}
:root {
/* OC-1-Light */
--text-mix-blend-mode: multiply;
color-scheme: light;
--text-mix-blend-mode: multiply;
/* OC-1-light */
--background-base: #f8f7f7;
--background-weak: var(--smoke-light-3);
--background-strong: var(--smoke-light-1);
--background-stronger: #fcfcfc;
--surface-base: var(--smoke-light-3);
--surface-base: var(--smoke-light-alpha-2);
--base: var(--smoke-light-alpha-2);
--surface-base-hover: #0500000f;
--surface-base-active: var(--smoke-light-alpha-3);
@ -119,9 +109,9 @@
--surface-info-base: var(--lilac-light-3);
--surface-info-weak: var(--lilac-light-2);
--surface-info-strong: var(--lilac-light-9);
--surface-diff-unchanged-base: #ffffff00;
--surface-diff-skip-base: var(--smoke-light-2);
--surface-diff-hidden-base: var(--blue-light-3);
--surface-diff-unchanged-base: #ffffff00;
--surface-diff-hidden-weak: var(--blue-light-2);
--surface-diff-hidden-weaker: var(--blue-light-1);
--surface-diff-hidden-strong: var(--blue-light-5);
@ -170,7 +160,7 @@
--text-on-brand-weaker: var(--smoke-light-alpha-8);
--text-on-brand-strong: var(--smoke-light-alpha-12);
--button-secondary-base: #fdfcfc;
--button-secondary-hover: #fafafa;
--button-secondary-hover: #faf9f9;
--border-base: var(--smoke-light-alpha-7);
--border-hover: var(--smoke-light-alpha-8);
--border-active: var(--smoke-light-alpha-9);
@ -269,21 +259,21 @@
--syntax-regexp: var(--text-base);
--syntax-string: #007663;
--syntax-keyword: var(--text-weak);
--syntax-primitive: #f6916b;
--syntax-primitive: #fb7f51;
--syntax-operator: var(--text-weak);
--syntax-variable: var(--text-strong);
--syntax-property: #ed6dc8;
--syntax-type: #b2cc00;
--syntax-constant: #0092a8;
--syntax-property: #ec6cc8;
--syntax-type: #738400;
--syntax-constant: #00b2b9;
--syntax-punctuation: var(--text-weaker);
--syntax-object: var(--text-strong);
--syntax-success: var(--apple-dark-10);
--syntax-warning: var(--amber-dark-10);
--syntax-critical: var(--ember-dark-9);
--syntax-info: #0092a8;
--syntax-diff-add: var(--mint-light-alpha-8);
--syntax-diff-delete: var(--ember-light-alpha-8);
--syntax-unknown: var(--text-weak);
--syntax-success: var(--apple-light-10);
--syntax-warning: var(--amber-light-10);
--syntax-critical: var(--ember-light-9);
--syntax-info: #0091a7;
--syntax-diff-add: var(--mint-light-11);
--syntax-diff-delete: var(--ember-light-11);
--syntax-diff-unknown: #ff0000;
--markdown-heading: #d68c27;
--markdown-text: #1a1a1a;
--markdown-link: #3b7dd8;
@ -309,15 +299,15 @@
--button-ghost-hover2: var(--smoke-light-alpha-3);
@media (prefers-color-scheme: dark) {
color-scheme: dark;
--text-mix-blend-mode: plus-lighter;
/* OC-1-Dark */
color-scheme: dark;
/* OC-1-dark */
--background-base: var(--smoke-dark-1);
--background-weak: #1b1818;
--background-weak: #1c1717;
--background-strong: #151313;
--background-stronger: #191515;
--surface-base: var(--smoke-dark-3);
--surface-base: var(--smoke-dark-alpha-2);
--base: var(--smoke-dark-alpha-2);
--surface-base-hover: #e0b7b716;
--surface-base-active: var(--smoke-dark-alpha-3);
@ -359,9 +349,9 @@
--surface-info-base: var(--lilac-light-3);
--surface-info-weak: var(--lilac-light-2);
--surface-info-strong: var(--lilac-light-9);
--surface-diff-unchanged-base: var(--smoke-dark-1);
--surface-diff-skip-base: var(--smoke-dark-alpha-1);
--surface-diff-hidden-base: var(--blue-dark-2);
--surface-diff-unchanged-base: var(--smoke-dark-1);
--surface-diff-hidden-weak: var(--blue-dark-1);
--surface-diff-hidden-weaker: var(--blue-dark-3);
--surface-diff-hidden-strong: var(--blue-dark-5);
@ -417,7 +407,7 @@
--border-selected: var(--cobalt-dark-alpha-11);
--border-disabled: var(--smoke-dark-alpha-8);
--border-focus: var(--smoke-dark-alpha-9);
--border-weak-base: var(--smoke-dark-alpha-4);
--border-weak-base: var(--smoke-dark-alpha-6);
--border-strong-base: var(--smoke-dark-alpha-8);
--border-strong-hover: var(--smoke-dark-alpha-7);
--border-strong-active: var(--smoke-dark-alpha-8);
@ -519,11 +509,11 @@
--syntax-object: var(--text-strong);
--syntax-success: var(--apple-dark-10);
--syntax-warning: var(--amber-dark-10);
--syntax-critical: var(--ember-dark-9);
--syntax-info: #ff9ae2;
--syntax-diff-add: var(--mint-dark-alpha-6);
--syntax-diff-delete: var(--ember-dark-alpha-4);
--syntax-unknown: var(--text-weak);
--syntax-critical: var(--ember-dark-10);
--syntax-info: #93e9f6;
--syntax-diff-add: var(--mint-dark-11);
--syntax-diff-delete: var(--ember-dark-11);
--syntax-diff-unknown: #ff0000;
--markdown-heading: #9d7cd8;
--markdown-text: #eeeeee;
--markdown-link: #fab283;
@ -549,3 +539,478 @@
--button-ghost-hover2: var(--smoke-dark-alpha-3);
}
}
html[data-theme="oc-2-paper"] {
/* OC-2-paper */
--background-base: #f7f8f8;
--background-weak: var(--ink-light-3);
--background-strong: var(--ink-light-1);
--background-stronger: #fcfcfc;
--surface-base: var(--ink-light-alpha-2);
--base: var(--ink-light-alpha-2);
--surface-base-hover: #0005050f;
--surface-base-active: var(--ink-light-alpha-3);
--surface-base-interactive-active: var(--cobalt-light-alpha-3);
--base2: var(--ink-light-alpha-2);
--base3: var(--ink-light-alpha-2);
--surface-inset-base: var(--ink-light-alpha-2);
--surface-inset-base-hover: var(--ink-light-alpha-3);
--surface-inset-strong: #001f1f17;
--surface-inset-strong-hover: #001f1f17;
--surface-raised-base: var(--ink-light-alpha-1);
--surface-float-base: var(--ink-dark-1);
--surface-float-base-hover: var(--ink-dark-2);
--surface-raised-base-hover: var(--ink-light-alpha-2);
--surface-raised-base-active: var(--ink-light-alpha-3);
--surface-raised-strong: var(--ink-light-1);
--surface-raised-strong-hover: var(--white);
--surface-raised-stronger: var(--white);
--surface-raised-stronger-hover: var(--white);
--surface-weak: var(--ink-light-alpha-3);
--surface-weaker: var(--ink-light-alpha-4);
--surface-strong: #ffffff;
--surface-raised-stronger-non-alpha: var(--white);
--surface-brand-base: var(--yuzu-light-9);
--surface-brand-hover: var(--yuzu-light-10);
--surface-interactive-base: var(--cobalt-light-3);
--surface-interactive-hover: var(--cobalt-light-4);
--surface-interactive-weak: var(--cobalt-light-2);
--surface-interactive-weak-hover: var(--cobalt-light-3);
--surface-success-base: var(--apple-light-3);
--surface-success-weak: var(--apple-light-2);
--surface-success-strong: var(--apple-light-9);
--surface-warning-base: var(--solaris-light-3);
--surface-warning-weak: var(--solaris-light-2);
--surface-warning-strong: var(--solaris-light-9);
--surface-critical-base: var(--ember-light-3);
--surface-critical-weak: var(--ember-light-2);
--surface-critical-strong: var(--ember-light-9);
--surface-info-base: var(--lilac-light-3);
--surface-info-weak: var(--lilac-light-2);
--surface-info-strong: var(--lilac-light-9);
--surface-diff-unchanged-base: #ffffff00;
--surface-diff-skip-base: var(--ink-light-2);
--surface-diff-hidden-base: var(--blue-light-3);
--surface-diff-hidden-weak: var(--blue-light-2);
--surface-diff-hidden-weaker: var(--blue-light-1);
--surface-diff-hidden-strong: var(--blue-light-5);
--surface-diff-hidden-stronger: var(--blue-light-9);
--surface-diff-add-base: var(--mint-light-3);
--surface-diff-add-weak: var(--mint-light-2);
--surface-diff-add-weaker: var(--mint-light-1);
--surface-diff-add-strong: var(--mint-light-5);
--surface-diff-add-stronger: var(--mint-light-9);
--surface-diff-delete-base: var(--ember-light-3);
--surface-diff-delete-weak: var(--ember-light-2);
--surface-diff-delete-weaker: var(--ember-light-1);
--surface-diff-delete-strong: var(--ember-light-6);
--surface-diff-delete-stronger: var(--ember-light-9);
--text-base: var(--ink-light-11);
--input-base: var(--ink-light-1);
--input-hover: var(--ink-light-2);
--input-active: var(--cobalt-light-1);
--input-selected: var(--cobalt-light-4);
--input-focus: var(--cobalt-light-1);
--input-disabled: var(--ink-light-4);
--text-weak: var(--ink-light-9);
--text-weaker: var(--ink-light-8);
--text-strong: var(--ink-light-12);
--text-interactive-base: var(--cobalt-light-9);
--text-on-brand-base: var(--ink-light-alpha-11);
--text-on-interactive-base: var(--ink-light-1);
--text-on-interactive-weak: var(--ink-light-alpha-11);
--text-on-success-base: var(--apple-light-10);
--text-on-critical-base: var(--ember-light-10);
--text-on-critical-weak: var(--ember-light-8);
--text-on-critical-strong: var(--ember-light-12);
--text-on-warning-base: var(--ink-dark-alpha-11);
--text-on-info-base: var(--ink-dark-alpha-11);
--text-diff-add-base: var(--mint-light-11);
--text-diff-delete-base: var(--ember-light-10);
--text-diff-delete-strong: var(--ember-light-12);
--text-diff-add-strong: var(--mint-light-12);
--text-on-info-weak: var(--ink-dark-alpha-9);
--text-on-info-strong: var(--ink-dark-alpha-12);
--text-on-warning-weak: var(--ink-dark-alpha-9);
--text-on-warning-strong: var(--ink-dark-alpha-12);
--text-on-success-weak: var(--apple-light-6);
--text-on-success-strong: var(--apple-light-12);
--text-on-brand-weak: var(--ink-light-alpha-9);
--text-on-brand-weaker: var(--ink-light-alpha-8);
--text-on-brand-strong: var(--ink-light-alpha-12);
--button-secondary-base: #fcfdfd;
--button-secondary-hover: #f9fafa;
--border-base: var(--ink-light-alpha-7);
--border-hover: var(--ink-light-alpha-8);
--border-active: var(--ink-light-alpha-9);
--border-selected: var(--cobalt-light-alpha-9);
--border-disabled: var(--ink-light-alpha-8);
--border-focus: var(--ink-light-alpha-9);
--border-weak-base: var(--ink-light-alpha-5);
--border-strong-base: var(--ink-light-alpha-7);
--border-strong-hover: var(--ink-light-alpha-8);
--border-strong-active: var(--ink-light-alpha-7);
--border-strong-selected: var(--cobalt-light-alpha-6);
--border-strong-disabled: var(--ink-light-alpha-6);
--border-strong-focus: var(--ink-light-alpha-7);
--border-weak-hover: var(--ink-light-alpha-6);
--border-weak-active: var(--ink-light-alpha-7);
--border-weak-selected: var(--cobalt-light-alpha-5);
--border-weak-disabled: var(--ink-light-alpha-6);
--border-weak-focus: var(--ink-light-alpha-7);
--border-interactive-base: var(--cobalt-light-7);
--border-interactive-hover: var(--cobalt-light-8);
--border-interactive-active: var(--cobalt-light-9);
--border-interactive-selected: var(--cobalt-light-9);
--border-interactive-disabled: var(--ink-light-8);
--border-interactive-focus: var(--cobalt-light-9);
--border-success-base: var(--apple-light-6);
--border-success-hover: var(--apple-light-7);
--border-success-selected: var(--apple-light-9);
--border-warning-base: var(--solaris-light-6);
--border-warning-hover: var(--solaris-light-7);
--border-warning-selected: var(--solaris-light-9);
--border-critical-base: var(--ember-light-6);
--border-critical-hover: var(--ember-light-7);
--border-critical-selected: var(--ember-light-9);
--border-info-base: var(--lilac-light-6);
--border-info-hover: var(--lilac-light-7);
--border-info-selected: var(--lilac-light-9);
--icon-base: var(--ink-light-9);
--icon-hover: var(--ink-light-11);
--icon-active: var(--ink-light-12);
--icon-selected: var(--ink-light-12);
--icon-disabled: var(--ink-light-8);
--icon-focus: var(--ink-light-12);
--icon-invert-base: #ffffff;
--icon-weak-base: var(--ink-light-7);
--icon-weak-hover: var(--ink-light-8);
--icon-weak-active: var(--ink-light-9);
--icon-weak-selected: var(--ink-light-10);
--icon-weak-disabled: var(--ink-light-6);
--icon-weak-focus: var(--ink-light-9);
--icon-strong-base: var(--ink-light-12);
--icon-strong-hover: #131515;
--icon-strong-active: #020202;
--icon-strong-selected: #020202;
--icon-strong-disabled: var(--ink-light-8);
--icon-strong-focus: #020202;
--icon-brand-base: var(--ink-light-12);
--icon-interactive-base: var(--cobalt-light-9);
--icon-success-base: var(--apple-light-7);
--icon-success-hover: var(--apple-light-8);
--icon-success-active: var(--apple-light-11);
--icon-warning-base: var(--amber-light-7);
--icon-warning-hover: var(--amber-light-8);
--icon-warning-active: var(--amber-light-11);
--icon-critical-base: var(--ember-light-10);
--icon-critical-hover: var(--ember-light-11);
--icon-critical-active: var(--ember-light-12);
--icon-info-base: var(--lilac-light-7);
--icon-info-hover: var(--lilac-light-8);
--icon-info-active: var(--lilac-light-11);
--icon-on-brand-base: var(--ink-light-alpha-11);
--icon-on-brand-hover: var(--ink-light-alpha-12);
--icon-on-brand-selected: var(--ink-light-alpha-12);
--icon-on-interactive-base: var(--ink-light-1);
--icon-agent-plan-base: var(--purple-light-9);
--icon-agent-docs-base: var(--amber-light-9);
--icon-agent-ask-base: var(--cyan-light-9);
--icon-agent-build-base: var(--cobalt-light-9);
--icon-on-success-base: var(--apple-light-alpha-9);
--icon-on-success-hover: var(--apple-light-alpha-10);
--icon-on-success-selected: var(--apple-light-alpha-11);
--icon-on-warning-base: var(--amber-lightalpha-9);
--icon-on-warning-hover: var(--amber-lightalpha-10);
--icon-on-warning-selected: var(--amber-lightalpha-11);
--icon-on-critical-base: var(--ember-light-alpha-9);
--icon-on-critical-hover: var(--ember-light-alpha-10);
--icon-on-critical-selected: var(--ember-light-alpha-11);
--icon-on-info-base: var(--lilac-light-9);
--icon-on-info-hover: var(--lilac-light-alpha-10);
--icon-on-info-selected: var(--lilac-light-alpha-11);
--icon-diff-add-base: var(--mint-light-11);
--icon-diff-add-hover: var(--mint-light-12);
--icon-diff-add-active: var(--mint-light-12);
--icon-diff-delete-base: var(--ember-light-10);
--icon-diff-delete-hover: var(--ember-light-11);
--syntax-comment: var(--text-weaker);
--syntax-regexp: var(--text-base);
--syntax-string: #007663;
--syntax-keyword: var(--text-weak);
--syntax-primitive: #fb7f51;
--syntax-operator: var(--text-weak);
--syntax-variable: var(--text-strong);
--syntax-property: #ec6cc8;
--syntax-type: #738400;
--syntax-constant: #00b2b9;
--syntax-punctuation: var(--text-weaker);
--syntax-object: var(--text-strong);
--syntax-success: var(--apple-light-10);
--syntax-warning: var(--amber-light-10);
--syntax-critical: var(--ember-light-9);
--syntax-info: #0091a7;
--syntax-diff-add: var(--mint-light-11);
--syntax-diff-delete: var(--ember-light-11);
--syntax-diff-unknown: #ff0000;
--markdown-heading: #d68c27;
--markdown-text: #1a1a1a;
--markdown-link: #3b7dd8;
--markdown-link-text: #318795;
--markdown-code: #3d9a57;
--markdown-block-quote: #b0851f;
--markdown-emph: #b0851f;
--markdown-strong: #d68c27;
--markdown-horizontal-rule: #8a8a8a;
--markdown-list-item: #3b7dd8;
--markdown-list-enumeration: #318795;
--markdown-image: #3b7dd8;
--markdown-image-text: #318795;
--markdown-code-block: #1a1a1a;
--border-color: #ffffff;
--border-weaker-base: var(--ink-light-alpha-3);
--border-weaker-hover: var(--ink-light-alpha-4);
--border-weaker-active: var(--ink-light-alpha-6);
--border-weaker-selected: var(--cobalt-light-alpha-4);
--border-weaker-disabled: var(--ink-light-alpha-2);
--border-weaker-focus: var(--ink-light-alpha-6);
--button-ghost-hover: var(--ink-light-alpha-2);
--button-ghost-hover2: var(--ink-light-alpha-3);
@media (prefers-color-scheme: dark) {
--background-base: var(--ink-dark-1);
--background-weak: #171c1c;
--background-strong: #131515;
--background-stronger: #151919;
--surface-base: var(--ink-dark-alpha-2);
--base: var(--ink-dark-alpha-2);
--surface-base-hover: #b8e0e00f;
--surface-base-active: var(--ink-dark-alpha-3);
--surface-base-interactive-active: var(--cobalt-light-alpha-3);
--base2: var(--ink-dark-alpha-2);
--base3: var(--ink-dark-alpha-2);
--surface-inset-base: #0b0e0e7f;
--surface-inset-base-hover: #0b0e0e7f;
--surface-inset-strong: #050606cc;
--surface-inset-strong-hover: #050606cc;
--surface-raised-base: var(--ink-light-alpha-1);
--surface-float-base: var(--ink-dark-1);
--surface-float-base-hover: var(--ink-dark-2);
--surface-raised-base-hover: var(--ink-light-alpha-2);
--surface-raised-base-active: var(--ink-light-alpha-3);
--surface-raised-strong: var(--ink-light-1);
--surface-raised-strong-hover: var(--white);
--surface-raised-stronger: var(--white);
--surface-raised-stronger-hover: var(--white);
--surface-weak: var(--ink-dark-alpha-4);
--surface-weaker: var(--ink-dark-alpha-5);
--surface-strong: var(--ink-dark-alpha-7);
--surface-raised-stronger-non-alpha: var(--white);
--surface-brand-base: var(--yuzu-light-9);
--surface-brand-hover: var(--yuzu-light-10);
--surface-interactive-base: var(--cobalt-light-3);
--surface-interactive-hover: var(--cobalt-light-4);
--surface-interactive-weak: var(--cobalt-light-2);
--surface-interactive-weak-hover: var(--cobalt-light-3);
--surface-success-base: var(--apple-light-3);
--surface-success-weak: var(--apple-light-2);
--surface-success-strong: var(--apple-light-9);
--surface-warning-base: var(--solaris-light-3);
--surface-warning-weak: var(--solaris-light-2);
--surface-warning-strong: var(--solaris-light-9);
--surface-critical-base: var(--ember-light-3);
--surface-critical-weak: var(--ember-light-2);
--surface-critical-strong: var(--ember-light-9);
--surface-info-base: var(--lilac-light-3);
--surface-info-weak: var(--lilac-light-2);
--surface-info-strong: var(--lilac-light-9);
--surface-diff-unchanged-base: #ffffff00;
--surface-diff-skip-base: var(--ink-light-2);
--surface-diff-hidden-base: var(--blue-light-3);
--surface-diff-hidden-weak: var(--blue-light-2);
--surface-diff-hidden-weaker: var(--blue-light-1);
--surface-diff-hidden-strong: var(--blue-light-5);
--surface-diff-hidden-stronger: var(--blue-light-9);
--surface-diff-add-base: var(--mint-light-3);
--surface-diff-add-weak: var(--mint-light-2);
--surface-diff-add-weaker: var(--mint-light-1);
--surface-diff-add-strong: var(--mint-light-5);
--surface-diff-add-stronger: var(--mint-light-9);
--surface-diff-delete-base: var(--ember-light-3);
--surface-diff-delete-weak: var(--ember-light-2);
--surface-diff-delete-weaker: var(--ember-light-1);
--surface-diff-delete-strong: var(--ember-light-6);
--surface-diff-delete-stronger: var(--ember-light-9);
--text-base: var(--ink-light-11);
--input-base: var(--ink-light-1);
--input-hover: var(--ink-light-2);
--input-active: var(--cobalt-light-1);
--input-selected: var(--cobalt-light-4);
--input-focus: var(--cobalt-light-1);
--input-disabled: var(--ink-light-4);
--text-weak: var(--ink-light-9);
--text-weaker: var(--ink-light-8);
--text-strong: var(--ink-light-12);
--text-interactive-base: var(--cobalt-light-9);
--text-on-brand-base: var(--ink-light-alpha-11);
--text-on-interactive-base: var(--ink-light-1);
--text-on-interactive-weak: var(--ink-light-alpha-11);
--text-on-success-base: var(--apple-light-10);
--text-on-critical-base: var(--ember-light-10);
--text-on-critical-weak: var(--ember-light-8);
--text-on-critical-strong: var(--ember-light-12);
--text-on-warning-base: var(--ink-dark-alpha-11);
--text-on-info-base: var(--ink-dark-alpha-11);
--text-diff-add-base: var(--mint-light-11);
--text-diff-delete-base: var(--ember-light-10);
--text-diff-delete-strong: var(--ember-light-12);
--text-diff-add-strong: var(--mint-light-12);
--text-on-info-weak: var(--ink-dark-alpha-9);
--text-on-info-strong: var(--ink-dark-alpha-12);
--text-on-warning-weak: var(--ink-dark-alpha-9);
--text-on-warning-strong: var(--ink-dark-alpha-12);
--text-on-success-weak: var(--apple-light-6);
--text-on-success-strong: var(--apple-light-12);
--text-on-brand-weak: var(--ink-light-alpha-9);
--text-on-brand-weaker: var(--ink-light-alpha-8);
--text-on-brand-strong: var(--ink-light-alpha-12);
--button-secondary-base: #fcfdfd;
--button-secondary-hover: #f9fafa;
--border-base: var(--ink-light-alpha-7);
--border-hover: var(--ink-light-alpha-8);
--border-active: var(--ink-light-alpha-9);
--border-selected: var(--cobalt-light-alpha-9);
--border-disabled: var(--ink-light-alpha-8);
--border-focus: var(--ink-light-alpha-9);
--border-weak-base: var(--ink-light-alpha-5);
--border-strong-base: var(--ink-light-alpha-7);
--border-strong-hover: var(--ink-light-alpha-8);
--border-strong-active: var(--ink-light-alpha-7);
--border-strong-selected: var(--cobalt-light-alpha-6);
--border-strong-disabled: var(--ink-light-alpha-6);
--border-strong-focus: var(--ink-light-alpha-7);
--border-weak-hover: var(--ink-light-alpha-6);
--border-weak-active: var(--ink-light-alpha-7);
--border-weak-selected: var(--cobalt-light-alpha-5);
--border-weak-disabled: var(--ink-light-alpha-6);
--border-weak-focus: var(--ink-light-alpha-7);
--border-interactive-base: var(--cobalt-light-7);
--border-interactive-hover: var(--cobalt-light-8);
--border-interactive-active: var(--cobalt-light-9);
--border-interactive-selected: var(--cobalt-light-9);
--border-interactive-disabled: var(--ink-light-8);
--border-interactive-focus: var(--cobalt-light-9);
--border-success-base: var(--apple-light-6);
--border-success-hover: var(--apple-light-7);
--border-success-selected: var(--apple-light-9);
--border-warning-base: var(--solaris-light-6);
--border-warning-hover: var(--solaris-light-7);
--border-warning-selected: var(--solaris-light-9);
--border-critical-base: var(--ember-light-6);
--border-critical-hover: var(--ember-light-7);
--border-critical-selected: var(--ember-light-9);
--border-info-base: var(--lilac-light-6);
--border-info-hover: var(--lilac-light-7);
--border-info-selected: var(--lilac-light-9);
--icon-base: var(--ink-light-9);
--icon-hover: var(--ink-light-11);
--icon-active: var(--ink-light-12);
--icon-selected: var(--ink-light-12);
--icon-disabled: var(--ink-light-8);
--icon-focus: var(--ink-light-12);
--icon-invert-base: #ffffff;
--icon-weak-base: var(--ink-light-7);
--icon-weak-hover: var(--ink-light-8);
--icon-weak-active: var(--ink-light-9);
--icon-weak-selected: var(--ink-light-10);
--icon-weak-disabled: var(--ink-light-6);
--icon-weak-focus: var(--ink-light-9);
--icon-strong-base: var(--ink-light-12);
--icon-strong-hover: #131515;
--icon-strong-active: #020202;
--icon-strong-selected: #020202;
--icon-strong-disabled: var(--ink-light-8);
--icon-strong-focus: #020202;
--icon-brand-base: var(--ink-light-12);
--icon-interactive-base: var(--cobalt-light-9);
--icon-success-base: var(--apple-light-7);
--icon-success-hover: var(--apple-light-8);
--icon-success-active: var(--apple-light-11);
--icon-warning-base: var(--amber-light-7);
--icon-warning-hover: var(--amber-light-8);
--icon-warning-active: var(--amber-light-11);
--icon-critical-base: var(--ember-light-10);
--icon-critical-hover: var(--ember-light-11);
--icon-critical-active: var(--ember-light-12);
--icon-info-base: var(--lilac-light-7);
--icon-info-hover: var(--lilac-light-8);
--icon-info-active: var(--lilac-light-11);
--icon-on-brand-base: var(--ink-light-alpha-11);
--icon-on-brand-hover: var(--ink-light-alpha-12);
--icon-on-brand-selected: var(--ink-light-alpha-12);
--icon-on-interactive-base: var(--ink-light-1);
--icon-agent-plan-base: var(--purple-light-9);
--icon-agent-docs-base: var(--amber-light-9);
--icon-agent-ask-base: var(--cyan-light-9);
--icon-agent-build-base: var(--cobalt-light-9);
--icon-on-success-base: var(--apple-light-alpha-9);
--icon-on-success-hover: var(--apple-light-alpha-10);
--icon-on-success-selected: var(--apple-light-alpha-11);
--icon-on-warning-base: var(--amber-lightalpha-9);
--icon-on-warning-hover: var(--amber-lightalpha-10);
--icon-on-warning-selected: var(--amber-lightalpha-11);
--icon-on-critical-base: var(--ember-light-alpha-9);
--icon-on-critical-hover: var(--ember-light-alpha-10);
--icon-on-critical-selected: var(--ember-light-alpha-11);
--icon-on-info-base: var(--lilac-light-9);
--icon-on-info-hover: var(--lilac-light-alpha-10);
--icon-on-info-selected: var(--lilac-light-alpha-11);
--icon-diff-add-base: var(--mint-light-11);
--icon-diff-add-hover: var(--mint-light-12);
--icon-diff-add-active: var(--mint-light-12);
--icon-diff-delete-base: var(--ember-light-10);
--icon-diff-delete-hover: var(--ember-light-11);
--syntax-comment: var(--text-weaker);
--syntax-regexp: var(--text-base);
--syntax-string: #007663;
--syntax-keyword: var(--text-weak);
--syntax-primitive: #fb7f51;
--syntax-operator: var(--text-weak);
--syntax-variable: var(--text-strong);
--syntax-property: #ec6cc8;
--syntax-type: #738400;
--syntax-constant: #00b2b9;
--syntax-punctuation: var(--text-weaker);
--syntax-object: var(--text-strong);
--syntax-success: var(--apple-light-10);
--syntax-warning: var(--amber-light-10);
--syntax-critical: var(--ember-light-9);
--syntax-info: #0091a7;
--syntax-diff-add: var(--mint-light-11);
--syntax-diff-delete: var(--ember-light-11);
--syntax-diff-unknown: #ff0000;
--markdown-heading: #d68c27;
--markdown-text: #1a1a1a;
--markdown-link: #3b7dd8;
--markdown-link-text: #318795;
--markdown-code: #3d9a57;
--markdown-block-quote: #b0851f;
--markdown-emph: #b0851f;
--markdown-strong: #d68c27;
--markdown-horizontal-rule: #8a8a8a;
--markdown-list-item: #3b7dd8;
--markdown-list-enumeration: #318795;
--markdown-image: #3b7dd8;
--markdown-image-text: #318795;
--markdown-code-block: #1a1a1a;
--border-color: #ffffff;
--border-weaker-base: var(--ink-light-alpha-3);
--border-weaker-hover: var(--ink-light-alpha-4);
--border-weaker-active: var(--ink-light-alpha-6);
--border-weaker-selected: var(--cobalt-light-alpha-4);
--border-weaker-disabled: var(--ink-light-alpha-2);
--border-weaker-focus: var(--ink-light-alpha-6);
--button-ghost-hover: var(--ink-light-alpha-2);
--button-ghost-hover2: var(--ink-light-alpha-3);
}
}

View file

@ -17,7 +17,7 @@
::-webkit-scrollbar-thumb {
background-color: var(--theme-border-subtle);
border-radius: 6px;
border-radius: var(--radius-md);
}
* {

View file

@ -1,7 +1,7 @@
{
"name": "@opencode-ai/web",
"type": "module",
"version": "1.0.62",
"version": "1.0.65",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",

View file

@ -93,11 +93,19 @@ You can configure TUI-specific settings through the `tui` option.
{
"$schema": "https://opencode.ai/config.json",
"tui": {
"scroll_speed": 3
"scroll_speed": 3,
"scroll_acceleration": {
"enabled": true
}
}
}
```
Available options:
- `scroll_acceleration.enabled` - Enable macOS-style scroll acceleration. **Takes precedence over `scroll_speed`.**
- `scroll_speed` - Custom scroll speed multiplier (default: `1`, minimum: `1`). Ignored if `scroll_acceleration.enabled` is `true`.
[Learn more about using the TUI here](/docs/tui).
---

View file

@ -336,11 +336,15 @@ You can customize TUI behavior through your OpenCode config file.
{
"$schema": "https://opencode.ai/config.json",
"tui": {
"scroll_speed": 3
"scroll_speed": 3,
"scroll_acceleration": {
"enabled": true
}
}
}
```
### Options
- `scroll_speed` - Controls how fast the TUI scrolls when using scroll commands (default: `2`, minimum: `1`)
- `scroll_acceleration` - Enable macOS-style scroll acceleration for smooth, natural scrolling. When enabled, scroll speed increases with rapid scrolling gestures and stays precise for slower movements. **This setting takes precedence over `scroll_speed` and overrides it when enabled.**
- `scroll_speed` - Controls how fast the TUI scrolls when using scroll commands (default: `1`, minimum: `1`). **Note: This is ignored if `scroll_acceleration.enabled` is set to `true`.**

View file

@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "1.0.62",
"version": "1.0.65",
"publisher": "sst-dev",
"repository": {
"type": "git",